feat(energy): product supply shock scenario RPC (Phase 4 PR C) (#2768)

* feat(energy): ComputeEnergyShockScenario RPC + country brief shock UI (Phase 4 PR C)

Adds on-demand product supply shock scenario computation from JODI Oil, Comtrade and IEA stocks data.

* fix(tests): add runtime + intelligence-client stubs to resilience harness

ResilienceWidget imports @/services/runtime which dynamically imports
@/services/widget-store. Without stubbing runtime, esbuild bundled the
chain and failed on loadFromStorage not exported by the utils stub.

* fix(energy): dataAvailable requires Comtrade data; gate shock widget on jodiOilAvailable

- dataAvailable now requires both jodiOil and comtradeHasData, eliminating the contradiction of returning true with an "insufficient data" assessment
- Collapsed redundant !hasComtradeData branch into the unified !dataAvailable guard
- Gate renderShockScenarioWidget() behind data.jodiOilAvailable in CountryDeepDivePanel to avoid rendering a widget that will always return dataAvailable: false

* fix(energy): zero-import hasData=false; extract pure shock-compute for real unit coverage

- `totalImports === 0` in `computeGulfShare` now returns `hasData: false` so the
  handler correctly falls through to the "insufficient data" branch instead of
  treating empty Comtrade rows as usable Gulf-share data
- Extract `clamp`, `computeGulfShare`, `computeEffectiveCoverDays`, `buildAssessment`,
  `GULF_PARTNER_CODES`, and `CHOKEPOINT_EXPOSURE` into `_shock-compute.ts`
- Handler delegates pure computation to imported functions; `getGulfCrudeShare` still
  owns Redis I/O and calls `computeGulfShare(flows)` for the math
- Tests now import the real functions via `.js` ESM extension; all 24 test cases
  exercise actual production logic (was previously reimplemented inline)

* fix(energy): handle net-exporter in shock assessment; fix tautological chokepoint tests

* fix(energy): move net-exporter branch before low-Gulf-share check in buildAssessment

A net exporter with gulfCrudeShare < 0.1 (e.g. Norway) incorrectly
received "low Gulf crude dependence" instead of "net oil exporter".
Adds regression test to cover the ordering case.
This commit is contained in:
Elie Habib
2026-04-06 22:37:35 +04:00
committed by GitHub
parent 864b57f1fc
commit b9b552cfcd
13 changed files with 891 additions and 1 deletions

View File

@@ -710,6 +710,49 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/Error'
/api/intelligence/v1/compute-energy-shock:
get:
tags:
- IntelligenceService
summary: ComputeEnergyShockScenario
description: ComputeEnergyShockScenario computes on-demand product supply shock for a given country + chokepoint.
operationId: ComputeEnergyShockScenario
parameters:
- name: country_code
in: query
required: false
schema:
type: string
- name: chokepoint_id
in: query
required: false
schema:
type: string
- name: disruption_pct
in: query
required: false
schema:
type: integer
format: int32
responses:
"200":
description: Successful response
content:
application/json:
schema:
$ref: '#/components/schemas/ComputeEnergyShockScenarioResponse'
"400":
description: Validation error
content:
application/json:
schema:
$ref: '#/components/schemas/ValidationError'
default:
description: Error response
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
components:
schemas:
Error:
@@ -2127,3 +2170,54 @@ components:
type: boolean
ieaBelowObligation:
type: boolean
ComputeEnergyShockScenarioRequest:
type: object
properties:
countryCode:
type: string
chokepointId:
type: string
disruptionPct:
type: integer
format: int32
ComputeEnergyShockScenarioResponse:
type: object
properties:
countryCode:
type: string
chokepointId:
type: string
disruptionPct:
type: integer
format: int32
gulfCrudeShare:
type: number
format: double
crudeLossKbd:
type: number
format: double
products:
type: array
items:
$ref: '#/components/schemas/ProductImpact'
effectiveCoverDays:
type: integer
format: int32
assessment:
type: string
dataAvailable:
type: boolean
ProductImpact:
type: object
properties:
product:
type: string
outputLossKbd:
type: number
format: double
demandKbd:
type: number
format: double
deficitPct:
type: number
format: double