Skip to content

feat(energy-atlas): EnergyRiskOverviewPanel — executive overview tile (parity PR 2, plan U5-U6)#3398

Merged
koala73 merged 3 commits into
mainfrom
feat/energy-parity-pr2-risk-overview
Apr 25, 2026
Merged

feat(energy-atlas): EnergyRiskOverviewPanel — executive overview tile (parity PR 2, plan U5-U6)#3398
koala73 merged 3 commits into
mainfrom
feat/energy-parity-pr2-risk-overview

Conversation

@koala73

@koala73 koala73 commented Apr 25, 2026

Copy link
Copy Markdown
Owner

Summary

Second of three sequential parity-push PRs. Lands the consolidated executive overview surface — the "first fold" tile peer reference energy-intel sites use to anchor the energy variant. Six tiles in a single panel with degraded-mode rendering; failed sources show '—' and don't block the others.

Plan: `docs/plans/2026-04-25-003-feat-energy-parity-pushup-plan.md` PR 2 (U5–U6).

Tiles

  1. Strait of Hormuz — status enum `closed` / `disrupted` / `restricted` / `open` (matches `src/services/hormuz-tracker.ts:20` exactly; explicit reject of the wrong-cased `normal/reduced/critical` triplet in tests)
  2. EU Gas — fill % (red <30, amber 30–49, green ≥50)
  3. Brent — price + 1-day delta. Importer-leaning inversion: oil up = red, oil down = green
  4. Active disruptions — count of events with `endAt === null` (green 0, amber 1–4, red 5+)
  5. Updated — youngest `fetchedAt` rendered as "X min ago"
  6. Hormuz crisis — Day-N counter from a configurable pinned start (default `2026-02-23`)

Degraded-mode contract

`Promise.allSettled`, never `Promise.all`. One slow or failing source CANNOT freeze the panel; failed tiles render `—` with `data-degraded="true"` for QA inspection. This is the single most important behavior — guards against the recurrence of the #3386 panel-stuck-on-Loading bug.

Wiring (5 sites per skill panel-stuck-loading-means-missing-primetask)

  • `src/components/index.ts` — barrel export
  • `src/app/panel-layout.ts` — `createPanel('energy-risk-overview', ...)`
  • `src/config/panels.ts` — priority-1 in `ENERGY_PANELS` (top-of-grid on energy variant), priority-2 in `FULL_PANELS` (CMD+K-discoverable, default disabled), added to `PANEL_CATEGORY_MAP` marketsFinance
  • `src/App.ts` — `primeTask('energy-risk-overview', ...)` in the existing kickoff table
  • `src/config/commands.ts` — `panel:energy-risk-overview` with keywords for "risk overview", "executive overview", "hormuz status", "crisis day"

What's NOT in scope

  • No new RPCs. Every metric is already exposed via existing Connect-RPC handlers / bootstrap-cache keys (preserves agent-native parity — agents can answer the same questions through the same surfaces).
  • No editorial "Issue #N" weekly counter — replaced with the live data-freshness tile.

Test plan

  • `npm run typecheck` clean.
  • 24 unit tests pass on the panel's pure helpers (Hormuz color enum incl. wrong-triplet rejection, EU gas thresholds, Brent inversion, active-disruption color bands, freshness formatting, crisis-day counter NaN/future/today/5-days, degraded-mode all-fail still produces 6 tiles with 4 marked degraded).
  • Manual visual check on `https://energy.worldmonitor.app\` post-deploy: panel renders at top of grid with 6 tiles populated; one tile failing (e.g. by toggling network) leaves the others rendering normally.

Codex review

Approved through 8 rounds against `origin/main @ 0500733` as part of plan review.

Sequence

… (PR 2, plan U5-U6)

Lands the consolidated "first fold" surface that peer reference energy-intel
sites use as their executive overview. Six tiles in a single panel:
1. Strait of Hormuz status (closed/disrupted/restricted/open)
2. EU gas storage fill % (red <30, amber 30-49, green ≥50)
3. Brent crude price + 1-day change (importer-leaning: up=red, down=green)
4. Active disruption count (filtered to endAt === null)
5. Data freshness ("X min ago" from youngest fetchedAt)
6. Hormuz crisis day counter (default 2026-02-23 start, env-overridable)

Per docs/plans/2026-04-25-003-feat-energy-parity-pushup-plan.md PR 2.

U5 — Component (src/components/EnergyRiskOverviewPanel.ts):
- Composes 5 existing services via Promise.allSettled — never .all. One slow
  or failing source CANNOT freeze the panel; failed tiles render '—' and
  carry data-degraded="true" for QA inspection. Single most important
  behavior — guards against the recurrence of the #3386 panel-stuck bug.
- Uses the actual Hormuz status enum 'closed'|'disrupted'|'restricted'|'open'
  (NOT 'normal'/'reduced'/'critical' — that triplet was a misread in earlier
  drafts). Test suite explicitly rejects the wrong triplet via the gray
  sentinel fallback.
- Brent color inverted from a default market panel: oil price UP = red
  (bad for energy importers, the dominant Atlas reader); DOWN = green.
- Crisis-day counter sourced from VITE_HORMUZ_CRISIS_START_DATE env
  (default 2026-02-23). NaN/future-dated values handled with explicit
  '—' / 'pending' sentinels so the tile never renders 'Day NaN'.
- 60s setInterval re-renders the freshness tile only — no new RPCs fire
  on tick. setInterval cleared in destroy() so panel teardown is clean.
- Tests: 24 in tests/energy-risk-overview-panel.test.mts cover Hormuz
  color enum (including the wrong-triplet rejection), EU gas thresholds,
  Brent inversion, active-disruption color bands, freshness label
  formatting, crisis-day counter (today/5-days/NaN/future), and the
  degraded-mode contract (all-fail still renders 6 tiles with 4 marked
  data-degraded).

U6 — Wiring (5 sites per skill panel-stuck-loading-means-missing-primetask):
- src/components/index.ts: barrel export
- src/app/panel-layout.ts: import + createPanel('energy-risk-overview', ...)
- src/config/panels.ts: priority-1 entry in ENERGY_PANELS (top-of-grid),
  priority-2 entry in FULL_PANELS (CMD+K-discoverable, default disabled),
  panelKey added to PANEL_CATEGORY_MAP marketsFinance category
- src/App.ts: import type + primeTask kickoff (between energy-disruptions
  and climate-news in the existing ordering convention)
- src/config/commands.ts: panel:energy-risk-overview command with keywords
  for 'risk overview', 'executive overview', 'hormuz status', 'crisis day'

No new RPCs (preserves agent-native parity — every metric the panel shows
is already exposed via existing Connect-RPC handlers and bootstrap-cache
keys; agents can answer the same questions through the same surface).

Tests: typecheck clean; 24 unit tests pass on the panel's pure helpers.
Manual visual QA pending PR merge + deploy.

Plan section §M effort estimate: ~1.5d. Codex-approved through 8 review
rounds against origin/main @ 0500733.
@vercel

vercel Bot commented Apr 25, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
worldmonitor Ready Ready Preview, Comment Apr 25, 2026 1:42pm

Request Review

@greptile-apps

greptile-apps Bot commented Apr 25, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR lands the EnergyRiskOverviewPanel — a six-tile executive overview composing four existing data sources (Hormuz tracker, EU gas storage, Brent crude, active disruptions) with two computed tiles (freshness, crisis-day counter). The degraded-mode contract (Promise.allSettled, per-tile data-degraded markers) is correctly implemented and well-tested. All five wiring points (barrel export, panel-layout, panels config, App.ts primeTask, commands) are present and consistent.

Confidence Score: 4/5

Safe to merge; findings are all P2 style/efficiency suggestions with no correctness impact

All three findings are P2: an unnecessary full-history RPC payload, CSS re-injection on the freshness tick, and a panel name inconsistency between config entries. None affect runtime correctness or the degraded-mode contract.

src/components/EnergyRiskOverviewPanel.ts (ongoingOnly flag and style injection), src/config/panels.ts (name inconsistency)

Important Files Changed

Filename Overview
src/components/EnergyRiskOverviewPanel.ts New 290-line panel composing 4 data sources via Promise.allSettled with correct degraded-mode rendering; two P2 issues: ongoingOnly: false causes unnecessary full-history payload, and the CSS <style> block is re-injected on every 60 s freshness tick
src/config/panels.ts Panel registered in FULL_PANELS (disabled, priority-2) and ENERGY_PANELS (enabled, priority-1); minor name inconsistency between the two entries ("Energy Risk Overview" vs "Global Energy Risk Overview")
tests/energy-risk-overview-panel.test.mts 24 unit tests covering all color-threshold helpers, freshness label, crisis-day counter edge cases (NaN/future/today/+N days), and degraded-mode shape contract; degraded-mode tests use a stub rather than exercising the real Promise.allSettled path
src/App.ts Adds primeTask('energy-risk-overview', ...) in the existing kickoff table, consistent with the pattern used by adjacent panels
src/app/panel-layout.ts Adds createPanel('energy-risk-overview', () => new EnergyRiskOverviewPanel()) between energy-disruptions and polymarket, correct wiring
src/config/commands.ts Adds CMD+K entry with comprehensive keywords (risk overview, hormuz status, crisis day, etc.), well-placed alphabetically among energy panel commands
src/components/index.ts Named export added adjacent to the other energy panel exports; consistent with barrel pattern

Sequence Diagram

sequenceDiagram
    participant App as App.ts
    participant PL as PanelLayoutManager
    participant P as EnergyRiskOverviewPanel
    participant H as fetchHormuzTracker
    participant G as getEuGasStorageData
    participant B as fetchCommodityQuotes
    participant S as SupplyChainServiceClient

    App->>PL: primeTask('energy-risk-overview')
    PL->>P: panel.fetchData()
    P->>P: Promise.allSettled([...])
    par Concurrent fetches
        P->>H: fetchHormuzTracker()
        P->>G: getEuGasStorageData()
        P->>B: fetchCommodityQuotes(BRENT_META)
        P->>S: listEnergyDisruptions({ongoingOnly:false})
    end
    H-->>P: HormuzTrackerData | null
    G-->>P: GetEuGasStorageResponse
    B-->>P: ListCommodityQuotesResponse
    S-->>P: DisruptionsResponse
    Note over P: Each settled result → TileState (fulfilled/rejected)
    P->>P: render() — 6 tiles (4 data + freshness + crisis-day)
    P->>P: setInterval(render, 60s) — freshness tick only
Loading

Reviews (1): Last reviewed commit: "feat(energy-atlas): EnergyRiskOverviewPa..." | Re-trigger Greptile

fetchHormuzTracker(),
getEuGasStorageData(),
fetchCommodityQuotes(BRENT_META),
supplyChain.listEnergyDisruptions({ assetId: '', assetType: '', ongoingOnly: false }),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 ongoingOnly: false fetches all historical disruptions client-side

Passing ongoingOnly: false causes the RPC to return every disruption in the database (historical + active), then filter(e => !e.endAt) counts ongoing ones client-side. Since the proto field exists for this purpose, passing ongoingOnly: true would let the server do the filtering and avoid transferring the full historical log on every panel refresh.

Suggested change
supplyChain.listEnergyDisruptions({ assetId: '', assetType: '', ongoingOnly: false }),
supplyChain.listEnergyDisruptions({ assetId: '', assetType: '', ongoingOnly: true }),

Comment on lines +155 to +168
private render(): void {
const html = `
<div class="ero-grid">
${this.renderHormuzTile()}
${this.renderEuGasTile()}
${this.renderBrentTile()}
${this.renderActiveDisruptionsTile()}
${this.renderFreshnessTile()}
${this.renderCrisisDayTile()}
</div>
<style>${RISK_OVERVIEW_CSS}</style>
`;
this.setContent(html);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 <style> block re-injected on every 60 s freshness tick

render() is called by setInterval every minute and always includes <style>${RISK_OVERVIEW_CSS}</style> in the setContent payload. If setContent replaces innerHTML, the style tag is torn out and re-inserted each tick, which can cause a brief FOUC as well as unnecessary style recalculation. Moving the CSS injection to the constructor (or to a one-time injectPanelStyle helper like other panels use) would keep the hot render path style-free.

Comment thread src/config/panels.ts Outdated
'storage-facility-map': { name: 'Strategic Storage Atlas', enabled: true, priority: 2 },
'fuel-shortages': { name: 'Global Fuel Shortage Registry', enabled: true, priority: 2 },
'energy-disruptions': { name: 'Energy Disruptions Log', enabled: true, priority: 2 },
'energy-risk-overview': { name: 'Energy Risk Overview', enabled: false, priority: 2 },

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Panel name inconsistency between FULL_PANELS and ENERGY_PANELS

FULL_PANELS registers the panel as "Energy Risk Overview", while ENERGY_PANELS (line 931) and commands.ts both use "Global Energy Risk Overview". A user browsing the panel picker in the full variant would see the shorter name, while the energy variant and CMD+K show the longer one. Aligning them keeps the UI consistent.

Suggested change
'energy-risk-overview': { name: 'Energy Risk Overview', enabled: false, priority: 2 },
'energy-risk-overview': { name: 'Global Energy Risk Overview', enabled: false, priority: 2 },

…nt test (PR2 review)

P2 — tests duplicated helper logic instead of testing the real panel
(energy-risk-overview-panel.test.mts:10):
- The original tests pinned color/threshold helpers but didn't import
  the panel's actual state-building logic, so the panel could ship
  with a broken Promise.allSettled wiring while the tests stayed green.

Refactor:
- Extract the state-building logic into a NEW Vite-free module:
  src/components/_energy-risk-overview-state.ts. Exports
  buildOverviewState(hormuz, euGas, brent, disruptions, now) and a
  countDegradedTiles() helper for tests.
- The panel now imports and calls buildOverviewState() directly inside
  fetchData(); no logic duplication. The Hormuz tile renderer narrows
  status with an explicit cast at use site.
- Why a new module: the panel transitively imports `import.meta.glob`
  via the i18n service, which doesn't resolve under node:test even
  with tsx loader. Extracting the testable logic into a
  Vite-dependency-free module is the cleanest way to exercise the
  production code from tests, per skill panel-stuck-loading-means-
  missing-primetask's emphasis on "test the actual production logic,
  not a copy-paste of it".

Tests added (11 real-component cases via the new module):
- All four sources fulfilled → 0 degraded.
- All four sources rejected → 4 degraded, no throw, no cascade.
- Mixed (1 fulfilled, 3 rejected) → only one tile populated.
- euGas with `unavailable: true` sentinel → degraded.
- euGas with fillPct=0 → degraded (treats as no-data, not "0% red").
- brent empty data array → degraded.
- brent first-quote price=null → degraded.
- disruptions upstreamUnavailable=true → degraded.
- disruptions ongoing filter: counts only endAt-falsy events.
- Malformed hormuz response (missing status field) → degraded sentinel.
- One rejected source MUST NOT cascade to fulfilled siblings (the
  core degraded-mode contract — pinned explicitly).

Total: 35 tests in this file (was 24; +11 real-component cases).
typecheck clean.
… panel name parity (PR2 review)

Three Greptile P2 findings on PR #3398:

- listEnergyDisruptions called with ongoingOnly:true so the server filters
  the historical 52-event payload server-side. The state builder still
  re-filters as defense-in-depth.
- RISK_OVERVIEW_CSS injected once into <head> via injectRiskOverviewStylesOnce
  instead of being emitted into setContent on every render. The 60s freshness
  setInterval was tearing out and re-inserting the style tag every minute.
- FULL_PANELS entry renamed from "Energy Risk Overview" to
  "Global Energy Risk Overview" to match ENERGY_PANELS and the CMD+K command.
@koala73
koala73 merged commit 0bca368 into main Apr 25, 2026
10 checks passed
@koala73
koala73 deleted the feat/energy-parity-pr2-risk-overview branch April 25, 2026 13:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant