worldenergydata field-hub drill-down pages — grammar + build playbook
Use this for ANY page work in the wed field-hub family: lifecycle posters, well timelines,
insight front-doors, phase-norm/benchmark pages, atlas/browse, asset drill-downs.
Source epics: wed#754 (fractal IA), wed#764 (engineering↔asset circle), wed#774 (insight family).
Live site: https://vamseeachanta.github.io/worldenergydata/
IA grammar (non-negotiable contract)
- Fractal spine: the same "stage-gate timeline + you-are-here + deep-dive" grammar recurs at
every zoom level — Region ▸ Play ▸ Field ▸ Well ▸ Stage. New pages reuse the grammar, never
invent a new one per page.
- Both-altitudes insight contract (#754 comment issuecomment-4881675172): every life-cycle
insight ships at TWO altitudes — (a) a standalone indexable front-door page (traffic magnet,
own URL, specific title) AND (b) a per-well/per-field stage deep-dive card. Same data, two zooms.
- Unique live links: every capability/insight gets its OWN purpose-built page — N distinct
pages = N front doors. Never fold into a shared "dashboards" section.
- Comprehensiveness bar: thesis + a number ("4.4× vessel shortfall, $375M–$1B/yr"), not
descriptive stats.
- Drill-down CTAs: poster/hub pages gain deep-dive links only when the target exists
(generator globs for the artifact — e.g.
wells/<id>_*_well.html — never hardcode).
Honesty rules (these have killed 2 pitched theses — load-bearing)
- Validate numbers BEFORE building. Scratchpad probe / curated spec first; delegate the build
only with acceptance targets. Verify the built artifact independently — not the agent's report.
- Cite-or-null: for web-sourced facts, 1 agent per item, refuse to borrow values across fields
(em-dash the cell instead). Curate the spec yourself before build.
- Honest density badges: RICH / SAMPLE / ROADMAP per data reality (freshness-scorecard).
Thin populations → explicit "insufficient (n=X)" badge, never a fabricated norm.
- Like-for-like bases: never compare native V30 rig-days against calendar-day proxies; tag
every metric with its basis and compute deltas only within one basis (wed#848 rule).
- Curated over raw: play-level D&C stats come from the curated FDAS_V30 xlsx (drop the blank +
TOTALS trailer rows — filter on LEASE_NAME notna), NOT raw WAR extraction (wed#846: raw overshoots
published figures).
Key data sources (verified 2026-07-06)
| Source |
What it gives |
docs/modules/bsee/analysis/production/FDAS_V30/drilling_and_completion_days.xlsx |
LT play population, native rig-days (SPUD/TD dates, DRILLING_DAYS, COMPLETION_DAYS, TVD, mud wt) |
reports/lower_tertiary/lt_well_benchmark_lower_tertiary_2010_latest.csv |
56 wells, field-crosswalked: api12, days, first_oil, cum_oil, uptime, interventions, decline |
data/modules/bsee/current/wells/well_data.csv |
57k GoM boreholes: SPUD/TD dates (calendar proxy only), BOTM_FLD_NAME_CD, BOREHOLE_STAT_CD (PA/TA), TVD |
reports/lower_tertiary/lifecycle/_facts.json |
list of 10 field dicts (id, play, gates, reservoir, provenance) |
config/fields.yml |
#755 canonical crosswalk id ⇄ name ⇄ BSEE block/lease ⇄ surfaces flags |
Join trick: benchmark api12 ↔ well_data API_WELL_NUMBER → discover a field's
BOTM_FLD_NAME_CD codes → pull the field's full well population.
Build pipeline conventions
- Generator scripts live in
scripts/lower_tertiary/ (or scripts/field_atlas/ etc.); each new
publish family needs a build_* domain entry in scripts/build_pages.py AND (if new paths)
config/repo_structure.yml allowlist rows.
- build_pages renames on publish (underscore→hyphen, path flatten) → author cross-page hrefs
for the PUBLIC layout, then link-check the published tree.
- Templates:
reports/lower_tertiary/lifecycle/lifecycle_template.html (field posters, stage
cards + engtrack lane) and lifecycle/wells/well_lifecycle_template.html (dual-scale well
timeline: rig-day construction band + calendar-year production band).
- Additive lanes that share a template/
__init__.py → ONE integration PR (squash-merge breaks
stacked PRs). After merge, GREP the unique markup on origin/main — bundled commits get lost in
merge races (#767 lost in #788; re-landed #803).
wed CI gotchas (every PR)
- Lint = THREE steps on
src/ tests/: mirror ALL of uvx black@25.9.0, uvx isort@6.0.1,
uvx flake8@7.3.0 (flake8 runs on src/ only — modules under packages/*/src/ are NOT
flake8-gated; put lint-gated modules in src/worldenergydata/). black-only is not enough.
Re-verify pins against .github/workflows/ci.yml before pushing (versions drift).
- "Validate PR Title": conventional type required (
polish: FAILS) and subject after
type(scope): ≤ 80 chars.
- Tests via
./.venv/bin/python -m pytest (plain python3 is sandbox-blocked; uv broken in wed).
- Branch protection: 11 checks + up-to-date; main churns →
gh pr merge <N> --auto --squash --delete-branch + self-healing gh pr update-branch on BEHIND.
- Agent can verify but NOT self-merge; hand the human the exact merge command.
- NEVER blind
git stash pop (pre-existing stale stashes corrupt worktrees); git stash list
first. Single-lane worktree off fresh origin/main for template-touching work.
Related
- Plan precedent:
docs/plans/2026-07-06-issue-wed-848-phase-norm-layer.md (phase-norm layer)
- Open family: wed#848 (norms), wed#849 (per-well economics), wed#850 (nav spine), #756/#757 (hub/econ)
- Memory:
project_wed_field_hub_ia_epic, feedback_unique_live_links_traffic_credibility
1---2name: wed-field-hub-drilldown-pages3description: Build worldenergydata drill-down pages (field hub, well timelines, insight/norm pages) with the proven validate-first playbook, the fractal IA grammar, honesty rules, and the exact wed CI gotchas.4---56# worldenergydata field-hub drill-down pages — grammar + build playbook78Use this for ANY page work in the wed field-hub family: lifecycle posters, well timelines,9insight front-doors, phase-norm/benchmark pages, atlas/browse, asset drill-downs.10Source epics: wed#754 (fractal IA), wed#764 (engineering↔asset circle), wed#774 (insight family).11Live site: https://vamseeachanta.github.io/worldenergydata/1213## IA grammar (non-negotiable contract)14151. **Fractal spine:** the same "stage-gate timeline + you-are-here + deep-dive" grammar recurs at16 every zoom level — Region ▸ Play ▸ Field ▸ Well ▸ Stage. New pages reuse the grammar, never17 invent a new one per page.182. **Both-altitudes insight contract** (#754 comment issuecomment-4881675172): every life-cycle19 insight ships at TWO altitudes — (a) a standalone indexable front-door page (traffic magnet,20 own URL, specific title) AND (b) a per-well/per-field stage deep-dive card. Same data, two zooms.213. **Unique live links:** every capability/insight gets its OWN purpose-built page — N distinct22 pages = N front doors. Never fold into a shared "dashboards" section.234. **Comprehensiveness bar:** thesis + a number ("4.4× vessel shortfall, $375M–$1B/yr"), not24 descriptive stats.255. **Drill-down CTAs:** poster/hub pages gain deep-dive links only when the target exists26 (generator globs for the artifact — e.g. `wells/<id>_*_well.html` — never hardcode).2728## Honesty rules (these have killed 2 pitched theses — load-bearing)2930- **Validate numbers BEFORE building.** Scratchpad probe / curated spec first; delegate the build31 only with acceptance targets. Verify the built artifact independently — not the agent's report.32- **Cite-or-null:** for web-sourced facts, 1 agent per item, refuse to borrow values across fields33 (em-dash the cell instead). Curate the spec yourself before build.34- **Honest density badges:** RICH / SAMPLE / ROADMAP per data reality (freshness-scorecard).35 Thin populations → explicit "insufficient (n=X)" badge, never a fabricated norm.36- **Like-for-like bases:** never compare native V30 rig-days against calendar-day proxies; tag37 every metric with its basis and compute deltas only within one basis (wed#848 rule).38- **Curated over raw:** play-level D&C stats come from the curated FDAS_V30 xlsx (drop the blank +39 TOTALS trailer rows — filter on LEASE_NAME notna), NOT raw WAR extraction (wed#846: raw overshoots40 published figures).4142## Key data sources (verified 2026-07-06)4344| Source | What it gives |45|---|---|46| `docs/modules/bsee/analysis/production/FDAS_V30/drilling_and_completion_days.xlsx` | LT play population, native rig-days (SPUD/TD dates, DRILLING_DAYS, COMPLETION_DAYS, TVD, mud wt) |47| `reports/lower_tertiary/lt_well_benchmark_lower_tertiary_2010_latest.csv` | 56 wells, field-crosswalked: api12, days, first_oil, cum_oil, uptime, interventions, decline |48| `data/modules/bsee/current/wells/well_data.csv` | 57k GoM boreholes: SPUD/TD dates (calendar proxy only), BOTM_FLD_NAME_CD, BOREHOLE_STAT_CD (PA/TA), TVD |49| `reports/lower_tertiary/lifecycle/_facts.json` | list of 10 field dicts (id, play, gates, reservoir, provenance) |50| `config/fields.yml` | #755 canonical crosswalk id ⇄ name ⇄ BSEE block/lease ⇄ surfaces flags |5152Join trick: benchmark `api12` ↔ well_data `API_WELL_NUMBER` → discover a field's53`BOTM_FLD_NAME_CD` codes → pull the field's full well population.5455## Build pipeline conventions5657- Generator scripts live in `scripts/lower_tertiary/` (or `scripts/field_atlas/` etc.); each new58 publish family needs a `build_*` domain entry in `scripts/build_pages.py` AND (if new paths)59 `config/repo_structure.yml` allowlist rows.60- **build_pages renames on publish** (underscore→hyphen, path flatten) → author cross-page hrefs61 for the PUBLIC layout, then link-check the published tree.62- Templates: `reports/lower_tertiary/lifecycle/lifecycle_template.html` (field posters, stage63 cards + engtrack lane) and `lifecycle/wells/well_lifecycle_template.html` (dual-scale well64 timeline: rig-day construction band + calendar-year production band).65- Additive lanes that share a template/`__init__.py` → ONE integration PR (squash-merge breaks66 stacked PRs). After merge, GREP the unique markup on origin/main — bundled commits get lost in67 merge races (#767 lost in #788; re-landed #803).6869## wed CI gotchas (every PR)70711. **Lint = THREE steps** on `src/ tests/`: mirror ALL of `uvx black@25.9.0`, `uvx isort@6.0.1`,72 `uvx flake8@7.3.0` (flake8 runs on `src/` only — modules under `packages/*/src/` are NOT73 flake8-gated; put lint-gated modules in `src/worldenergydata/`). black-only is not enough.74 Re-verify pins against `.github/workflows/ci.yml` before pushing (versions drift).752. "Validate PR Title": conventional type required (`polish:` FAILS) and subject after76 `type(scope):` ≤ 80 chars.773. Tests via `./.venv/bin/python -m pytest` (plain python3 is sandbox-blocked; uv broken in wed).784. Branch protection: 11 checks + up-to-date; main churns → `gh pr merge <N> --auto --squash79 --delete-branch` + self-healing `gh pr update-branch` on BEHIND.805. Agent can verify but NOT self-merge; hand the human the exact merge command.816. NEVER blind `git stash pop` (pre-existing stale stashes corrupt worktrees); `git stash list`82 first. Single-lane worktree off fresh origin/main for template-touching work.8384## Related85- Plan precedent: `docs/plans/2026-07-06-issue-wed-848-phase-norm-layer.md` (phase-norm layer)86- Open family: wed#848 (norms), wed#849 (per-well economics), wed#850 (nav spine), #756/#757 (hub/econ)87- Memory: `project_wed_field_hub_ia_epic`, `feedback_unique_live_links_traffic_credibility`