feat(energy): shock model v2 — live flow ratios, coverage, limitations (V5-3) (#2810)

* feat(energy): shock model v2 — live flow ratios, coverage, limitations (V5-3)

Replace static CHOKEPOINT_EXPOSURE multipliers with live PortWatch flow
ratios from energy:chokepoint-flows:v1. Add proto fields 11-19: per-source
coverage flags (jodi_oil_coverage, comtrade_coverage, iea_stocks_coverage,
portwatch_coverage), coverage_level, limitations[], degraded,
chokepoint_confidence, live_flow_ratio. Expand ISO2_TO_COMTRADE from 6 to
150+ countries via _comtrade-reporters.ts. Partial-coverage path proxies
Gulf share at 40%. Unsupported countries return structured metadata instead
of opaque string. data_available (field 9) preserved for backward compat.

* fix(energy): correct chokepoint-flows key shape in shock handler (V5-3)

energy:chokepoint-flows:v1 is a flat object keyed by canonicalId
(hormuz_strait, bab_el_mandeb, suez, malacca_strait), not an object
with a chokepoints[] array. The wrong shape caused degraded=true and
liveFlowRatio=null for every request, silently falling back to static
CHOKEPOINT_EXPOSURE multipliers even when live PortWatch data was
available.

Fixes: ChokepointEntry interface, typecast, cpEntry lookup, degraded check.

* fix(energy): 4 review fixes for shock v2 handler (V5-3)

1. Cache key v1→v2: response shape changed (fields 11-19 added), old
   v1 cache entries would be served without new coverage fields.
2. IEA unknown vs zero: when ieaStocksCoverage=false, assessment now
   shows "IEA cover: unknown" instead of "0 days" to avoid conflating
   missing data with real zero stock.
3. liveFlowRatio 0.0 truthiness: changed `if (liveFlowRatio)` to
   `if (liveFlowRatio != null)` — a blocked chokepoint (ratio=0.0)
   is now shown, not hidden as "no live data".
4. Badge cleared on request start: coverageBadge is now reset before
   each shock compute request so a failed request doesn't leave the
   previous result's badge visible.

* fix(energy): 3 remaining review fixes for shock v2 (V5-3)

1. Flow column: gate on portwatchCoverage (bool) not liveFlowRatio!=null —
   proto double defaults to 0, so null-check was always true and degraded
   responses showed a misleading "Flow: 0%" column.
2. Degraded cache TTL: 5min when degraded=true, 1h when live data present —
   limits how long stale degraded state persists after PortWatch recovers.
3. IEA anomaly cover days: zero daysOfCover when ieaStocksCoverage=false
   so anomalous IEA data no longer contributes to effectiveCoverDays;
   panel IEA cover row also gated on ieaStocksCoverage.

* fix(energy): netExporter from anomalous IEA + zero-days panel display (V5-3)

1. netExporter: gated on ieaStocksCoverage — anomalous IEA rows no
   longer drive the net-exporter assessment branch when coverage=false.
2. Panel IEA cover: removed effectiveCoverDays>0 guard so a real zero
   (reserves exhausted under scenario) renders as "0 days" instead of
   being silently hidden as if there were no IEA data.

* fix(energy): handle net-exporter sentinel (-1) in IEA cover panel row

* fix(energy): NaN guard on flowRatio; optional live_flow_ratio; coverageLevel includes IEA+degraded

* fix(energy): narrow Gulf-share proxy to Comtrade-only; NaN guard computeGulfShare; EMPTY liveFlowRatio undefined

* fix(energy): tighten ieaStocksCoverage null guard; cache key varies by degraded state

* fix(energy): harden IEA/PortWatch input validation; reduce shock cache TTL

* fix(energy): add null narrowing for daysOfCover to satisfy strict TS
This commit is contained in:
Elie Habib
2026-04-08 12:26:21 +04:00
committed by GitHub
parent b8924eb90f
commit 80b24d8686
11 changed files with 975 additions and 27 deletions

View File

@@ -2239,6 +2239,28 @@ components:
type: string
dataAvailable:
type: boolean
jodiOilCoverage:
type: boolean
description: v2 fields
comtradeCoverage:
type: boolean
ieaStocksCoverage:
type: boolean
portwatchCoverage:
type: boolean
coverageLevel:
type: string
limitations:
type: array
items:
type: string
degraded:
type: boolean
chokepointConfidence:
type: string
liveFlowRatio:
type: number
format: double
ProductImpact:
type: object
properties: