Who it is for
- Supply-chain and commodity desks stress-testing routing assumptions against a named event.
- Risk and policy teams translating a geopolitical or environmental scenario into concrete country exposure.
- Leadership building talking-tracks around “if X happens, what breaks first?”
Opening the engine
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.
Scenario templates
Templates are defined inserver/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:
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 seeded HS2 chapters (the registry stores that sentinel as null). Country selection and integer disruptionPct overrides from 0 through 100 are supported for physical scenarios. Omission preserves the template severity. Tariff scenarios reject severity overrides. Duration is descriptive only and does not change the score. The ScenarioType union leaves room for infrastructure and pandemic categories, but no templates of those types ship today.
What you get back
A completed scenario returns:- Affected chokepoints — which ones go red on the map.
- Impact ranking — the top affected seeded reporter countries by ISO-2, ordered by the worker’s relative weighted impact score.
totalImpactis not a currency amount. - Template echo — the worker-derived template key (
affectedChokepointIds.join('+'), ortariff_shockwhen 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 also carriesscenarioId, the selected country inscopedIso2, the computation time incomputedAt, the effective severity intemplate.disruptionPct, and the evaluated country/sector scope incoverage. - A summary card injected into the Supply Chain panel that stays visible until you deactivate the scenario.
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.
Tier & gating
Scenario Engine is PRO. Free users see the trigger buttons but are blocked at activation: ascenario-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.
Run it yourself
The workflow is inherently async — the edge function enqueues a job, a Railway worker computes the impact, and the result is polled back:- Open the Supply Chain panel.
- Click a scenario trigger button (the template name).
- The button disables while the job runs (typically 5-30 s).
- When the result lands, the map repaints and a scenario banner appears. It shows the selected country scope, effective closure severity, descriptive duration, cost multiplier, raw score units and relative rankings. The coverage summary distinguishes missing, malformed, unseeded and evaluated records, including geographic fallback and valid zero impacts. Expand the country/sector evidence to inspect each record or download the captured scenario JSON. Duration does not change the calculation.
- Click the × dismiss control on the banner (aria-label: “Dismiss scenario”) to clear the scenario state — the map repaints to its baseline and the panel re-renders without the projected score and red-border callouts.
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.
Data behind Scenario Engine
- Scenario templates —
server/worldmonitor/supply-chain/v1/scenario-templates.ts. The catalog is predefined; supported physical severity and country controls are run inputs. - Job queue — Redis list
scenario-queue:pending; worker results land atscenario-result:{jobId}. - Chokepoint registry — the same registry that backs live chokepoint status and Route Explorer, ensuring scenario results visually align with the rest of the product.
- Trade / impact data — HS2 exposure cache entries read from
supply-chain:exposure:{ISO2}:{HS2}:v1. Ifiso2is omitted, v1 computes only the bounded country/sector manifest in existing seed metadata. Supplyingiso2scopes the job to that single country key.
Impact Math
For physical chokepoint scenarios, each matching exposure entry contributes:vulnerabilityIndex as the exposure proxy:
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:
Related workflows
- Route Explorer — run a specific lane against today’s state.
- Scenarios API — the underlying HTTP contract.
- Supply Chain — the broader service that backs the Supply Chain panel.
Scenario coverage and export
A missing, old or invalid seed manifest produces unknown coverage. The worker does not fall back to a guessed country list or scan Redis. It reads exposure keys in batches (seeEXPOSURE_BATCH_SIZE in scripts/scenario-worker.mjs) and stops at a wall-clock budget, reporting whatever it could not read as missing rather than as zero impact. A complete result means complete within the bounded seed scope, not global trade coverage.
coverage.records identifies each requested country and HS2 chapter as evaluated, missing, malformed, incomplete_routes, or not_seeded. incomplete_routes means the cached record was well-formed but carried no entry for every chokepoint the scenario disrupts — seeder/template registry drift, not corrupt cache data. There is one record per requested country/chapter pair, so len(countryIds) x len(hs2Codes) entries. Evaluated records retain flow_weighted or country_route_fallback basis and raw impact, including valid zero. Partial totals exclude unavailable records and must not be read as low exposure. Each entry in topImpactCountries carries evaluatedRecords, requestedRecords and partialEvidence; when partialEvidence is true, totalImpact is a LOWER BOUND over the evidence that was available, and the panel marks it as such. Flow-weighted scores use recorded trade shares with modeled routes; fallback scores use geographic routes. Cache production dates are not trade observation dates; an unknown observation date remains unknown.
The panel allows country selection and physical closure severity (0–100%). Duration remains descriptive metadata. Download scenario JSON preserves the captured inputs, raw score units, coverage records and dates. Raw impact is not currency or lost trade. Relative impactPct rankings need not double when severity doubles because the denominator also changes.