docs/scenario-engine.mdx
Scenario Engine turns WorldMonitor's live supply-chain graph into an interactive what-if tool. Instead of asking "what is the state of this lane today," you pick a named disruption scenario — a Hormuz closure, a Panama drought, a tariff shock on semiconductors — and the engine resolves the downstream impact on chokepoints, HS2 sectors, and the currently seeded reporter countries, then paints the result onto the existing map.
Scenario Engine lives inside the Supply Chain panel on the main dashboard. Each pre-built scenario template renders as a trigger button; clicking a scenario starts an async job and activates the visual overlay once results land.
You can also drive it programmatically — see Scenarios API for the /templates, /run, and /status endpoints.
Templates are defined in server/worldmonitor/supply-chain/v1/scenario-templates.ts. Each template has a type drawn from a small, curated set so scenarios are browsable by category rather than a free-form list.
The currently shipped types are:
| Type | What it models |
|---|---|
conflict | Chokepoint closure or degradation driven by an active conflict event (Taiwan Strait full closure, Suez + Bab-el-Mandeb simultaneous, Hormuz tanker blockade). |
weather | Climatic disruption — e.g. the Panama Canal 50% drought scenario. |
sanctions | Targeted trade restrictions (e.g. Russia / Baltic grain suspension). |
tariff_shock | A sudden tariff action and its cost pass-through (e.g. US tariff escalation on electronics). |
Each template declares the chokepoints it affects (IDs from the chokepoint registry), a duration in days, affected HS2 sectors, and a cost-shock multiplier. On the template-list wire shape, affectedHs2: [] means all HS2 chapters (the registry stores that sentinel as null). Run templates as-is — there are no sliders in v1. The ScenarioType union leaves room for infrastructure and pandemic categories, but no templates of those types ship today.
A completed scenario returns:
totalImpact is not a currency amount.affectedChokepointIds.join('+'), or tariff_shock when there are no physical chokepoints), duration, disruption percent, and cost-shock multiplier so clients can render the run without re-looking up the catalog. The status result does not repeat affectedHs2; read sector scope from /list-scenario-templates.The UI is state-driven, not modal — activating a scenario sets a scenarioState on every map renderer (deck.gl, globe, SVG fallback) so chokepoint colors and country choropleths reflect the disruption until you deactivate. This is coordinated by MapContainer.activateScenario at src/components/MapContainer.ts:1010, which is explicitly PRO-gated.
Scenario Engine is PRO. Free users see the trigger buttons but are blocked at activation: a scenario-engine gate-hit event is logged and the map is not repainted. The ScenarioService.RunScenario handler also enforces PRO at the edge (server/worldmonitor/scenario/v1/run-scenario.ts).
Rate limits on the API side — 10 jobs / minute / IP, with queue backpressure once the pending queue is already above 100 jobs — are documented in Scenarios API.
The workflow is inherently async — the edge function enqueues a job, a Railway worker computes the impact, and the result is polled back:
14d · +110% cost) and a tagline line such as "Simulating 14d / 100% closure / +110% cost on 1 chokepoint. Chokepoint card below shows projected score; map highlights disrupted routes." The affected chokepoints themselves are highlighted on the map and on the chokepoint cards rather than listed by name in the banner.For scripted use, see POST /api/scenario/v1/run-scenario — enqueue, then poll GET /api/scenario/v1/get-scenario-status until the response has a terminal status ("done" on success, "failed" on error). Non-terminal states are "pending" (queued) and "processing" (worker started); both can persist for several seconds. See the status lifecycle table for the full contract.
server/worldmonitor/supply-chain/v1/scenario-templates.ts. Additions require a proto-side change; not user-configurable today.scenario-queue:pending; worker results land at scenario-result:{jobId}.supply-chain:exposure:{ISO2}:{HS2}:v1. If iso2 is omitted, v1 computes only the seeded reporter set: US, CN, RU, IR, IN, and TW. Supplying iso2 scopes the job to that single country key.For physical chokepoint scenarios, each matching exposure entry contributes:
adjustedImpact = exposureScore * (disruptionPct / 100) * costShockMultiplier
For tariff-shock scenarios with no physical chokepoint closure, the worker uses
the country's cached vulnerabilityIndex as the exposure proxy:
adjustedImpact = vulnerabilityIndex * costShockMultiplier
The worker sums adjustedImpact by country, sorts descending, and returns the
top 20. impactPct is a 0-100 share against a denominator floor of 1, so the top returned country can be below 100 when every returned totalImpact is below 1:
impactPct = round(countryTotalImpact / max(maxReturnedTotalImpact, 1) * 100)