Files
worldmonitor/docs/api/SupplyChainService.openapi.yaml
Elie Habib 96fca1dc2b fix(supply-chain): popup-keyed history re-query + dataAvailable flag (#3187)
* fix(supply-chain): popup-keyed history re-query + dataAvailable flag for partial coverage

Two P1 findings on #3185 post-merge review:

1. MapPopup cross-chokepoint history contamination
   Popup's async history resolve re-queried [data-transit-chart] without a
   cpId key. User opens popup A → fetch starts for cpA; user opens popup B
   before it resolves → cpA's history mounts into cpB's chart container.
   Fix: add data-transit-chart-id keyed by cpId; re-query by it on resolve.
   Mirrors SupplyChainPanel's existing data-chart-cp-id pattern.

2. Partial portwatch coverage still looked healthy
   Previous fix emits all 13 canonical summaries (zero-state fill for
   missing IDs) and records pwCovered in seed-meta, but:
   - get-chokepoint-status still zero-filled missing chokepoints and cached
     the response as healthy — panel rendered silent empty rows.
   - api/health.js only degrades on recordCount=0, so 10/13 partial read
     as OK despite the UI hiding entire chokepoints.
   Fix:
   - proto: TransitSummary.data_available (field 12). Writer tags with
     Boolean(cpData). Status RPC passes through; defaults true for pre-fix
     payloads (absence = covered).
   - Status RPC writes seed-meta recordCount as covered count (not shape
     size), and flips response-level upstreamUnavailable on partial.
   - api/health.js: new minRecordCount field on SEED_META entries + new
     COVERAGE_PARTIAL status (warn rollup). chokepoints entry declares
     minRecordCount: 13. recordCount < 13 → COVERAGE_PARTIAL.
   - Client (panel + popup): skip stats/chart rendering when
     !dataAvailable; show "Transit data unavailable (upstream partial)"
     microcopy so users understand the gap.

5759/5759 data tests pass. Typecheck + typecheck:api clean.

* fix(supply-chain): guarantee Simulate Closure button exits Computing state

User reports "Simulate Closure does nothing beyond write Computing…" — the
button sticks at Computing forever. Two causes:

1. Scenario worker appears down (0 scenario-result:* keys in Redis in the
   last 24h of 24h-TTL). Railway-side — separate intervention needed to
   redeploy scripts/scenario-worker.mjs.

2. Client leaked the "Computing…" state on multiple exit paths:
   - signal.aborted early-return inside the poll loop never reset the
     button. Second click fired abort on first → first returned without
     resetting → button stayed "Computing…" until next render.
   - !this.content.isConnected early-return also skipped reset (less
     user-visible but same class of bug).
   - catch block swallowed AbortError without resetting.
   - POST /run had no hard timeout — a hanging edge function left the
     button in Computing indefinitely.

Fix:
- resetButton(text) helper touches the btn only if still connected;
  applied in every exit path (abort, timeout, post-success, catch).
- AbortSignal.any([caller, AbortSignal.timeout(20_000)]) on POST /run.
- console.error on failure so Simulate Closure errors surface in ops.
- Error message includes "scenario worker may be down" on loop timeout
  so operators see the right suspect.

Backend observations (for follow-up):
- Hormuz backend is healthy (/api/health chokepoints OK, 13 records,
  1 min old; live RPC has hormuz_strait.riskLevel=critical, wow=-22,
  flowEstimate present; GetChokepointHistory returns 174 entries).
  User-reported "Hormuz empty" is likely browser/CDN stale cache from
  before PR #3185; hard refresh should resolve.
- scenario-worker.mjs has zero result keys in 24h. Railway service
  needs verification/redeployment.

* fix(scenario): wrong Upstash RPUSH format silently broke every Simulate Closure

Railway scenario-worker log shows every job failing field validation since
at least 03:06Z today:

  [scenario-worker] Job failed field validation, discarding:
    ["{\"jobId\":\"scenario:1776535792087:cynxx5v4\",...

The leading [" in the payload is the smoking gun. api/scenario/v1/run.ts
was POSTing to /rpush/{key} with body `[payload]`, expecting Upstash to
unpack the array and push one string value. Upstash does NOT parse that
form — it stored the literal `["{...}"]` string as a single list value.

Worker BLMOVEs the literal string → JSON.parse → array → destructure
`{jobId, scenarioId, iso2}` on an array returns undefined for all three
→ every job discarded without writing a result. Client poll returns
`pending` for the full 60s timeout, then (on the prior client code path)
leaked the stuck "Computing…" button state indefinitely.

Fix: use the standard Upstash REST command format — POST to the base URL
with body `["RPUSH", key, value]`. Matches scripts/ais-relay.cjs upstashLpush.

After this, the scenario-queue:pending list stores the raw payload string,
BLMOVE returns the payload, JSON.parse gives the object, validation passes,
computeScenario runs, result key gets written, client poll sees `done`.

Zero result keys existed in prod Redis in the last 24h (24h TTL on
scenario-result:*) — confirms the fix addresses the production outage.
2026-04-18 23:38:33 +04:00

1241 lines
47 KiB
YAML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
openapi: 3.1.0
info:
title: SupplyChainService API
version: 1.0.0
paths:
/api/supply-chain/v1/get-shipping-rates:
get:
tags:
- SupplyChainService
summary: GetShippingRates
operationId: GetShippingRates
responses:
"200":
description: Successful response
content:
application/json:
schema:
$ref: '#/components/schemas/GetShippingRatesResponse'
"400":
description: Validation error
content:
application/json:
schema:
$ref: '#/components/schemas/ValidationError'
default:
description: Error response
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
/api/supply-chain/v1/get-chokepoint-status:
get:
tags:
- SupplyChainService
summary: GetChokepointStatus
operationId: GetChokepointStatus
responses:
"200":
description: Successful response
content:
application/json:
schema:
$ref: '#/components/schemas/GetChokepointStatusResponse'
"400":
description: Validation error
content:
application/json:
schema:
$ref: '#/components/schemas/ValidationError'
default:
description: Error response
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
/api/supply-chain/v1/get-chokepoint-history:
get:
tags:
- SupplyChainService
summary: GetChokepointHistory
description: |-
GetChokepointHistory returns transit-count history for a single chokepoint,
loaded lazily on card expand. Keeps the status RPC compact (no 180-day
history per chokepoint on every call).
operationId: GetChokepointHistory
parameters:
- name: chokepointId
in: query
required: false
schema:
type: string
responses:
"200":
description: Successful response
content:
application/json:
schema:
$ref: '#/components/schemas/GetChokepointHistoryResponse'
"400":
description: Validation error
content:
application/json:
schema:
$ref: '#/components/schemas/ValidationError'
default:
description: Error response
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
/api/supply-chain/v1/get-critical-minerals:
get:
tags:
- SupplyChainService
summary: GetCriticalMinerals
operationId: GetCriticalMinerals
responses:
"200":
description: Successful response
content:
application/json:
schema:
$ref: '#/components/schemas/GetCriticalMineralsResponse'
"400":
description: Validation error
content:
application/json:
schema:
$ref: '#/components/schemas/ValidationError'
default:
description: Error response
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
/api/supply-chain/v1/get-shipping-stress:
get:
tags:
- SupplyChainService
summary: GetShippingStress
description: GetShippingStress returns carrier market data and a composite stress index.
operationId: GetShippingStress
responses:
"200":
description: Successful response
content:
application/json:
schema:
$ref: '#/components/schemas/GetShippingStressResponse'
"400":
description: Validation error
content:
application/json:
schema:
$ref: '#/components/schemas/ValidationError'
default:
description: Error response
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
/api/supply-chain/v1/get-country-chokepoint-index:
get:
tags:
- SupplyChainService
summary: GetCountryChokepointIndex
description: GetCountryChokepointIndex returns per-chokepoint exposure scores for a country. PRO-gated.
operationId: GetCountryChokepointIndex
parameters:
- name: iso2
in: query
description: ISO 3166-1 alpha-2 country code (uppercase).
required: false
schema:
type: string
- name: hs2
in: query
description: HS2 chapter (2-digit string). Defaults to "27" (energy/mineral fuels) when absent.
required: false
schema:
type: string
responses:
"200":
description: Successful response
content:
application/json:
schema:
$ref: '#/components/schemas/GetCountryChokepointIndexResponse'
"400":
description: Validation error
content:
application/json:
schema:
$ref: '#/components/schemas/ValidationError'
default:
description: Error response
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
/api/supply-chain/v1/get-bypass-options:
get:
tags:
- SupplyChainService
summary: GetBypassOptions
description: GetBypassOptions returns ranked bypass corridors for a chokepoint. PRO-gated.
operationId: GetBypassOptions
parameters:
- name: chokepointId
in: query
required: false
schema:
type: string
- name: cargoType
in: query
description: 'container | tanker | bulk | roro (default: "container")'
required: false
schema:
type: string
- name: closurePct
in: query
description: '0-100, percent of capacity blocked (default: 100)'
required: false
schema:
type: integer
format: int32
responses:
"200":
description: Successful response
content:
application/json:
schema:
$ref: '#/components/schemas/GetBypassOptionsResponse'
"400":
description: Validation error
content:
application/json:
schema:
$ref: '#/components/schemas/ValidationError'
default:
description: Error response
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
/api/supply-chain/v1/get-country-cost-shock:
get:
tags:
- SupplyChainService
summary: GetCountryCostShock
description: GetCountryCostShock returns cost shock and war risk data for a country+chokepoint. PRO-gated.
operationId: GetCountryCostShock
parameters:
- name: iso2
in: query
required: false
schema:
type: string
- name: chokepointId
in: query
required: false
schema:
type: string
- name: hs2
in: query
description: 'HS2 chapter (default: "27")'
required: false
schema:
type: string
responses:
"200":
description: Successful response
content:
application/json:
schema:
$ref: '#/components/schemas/GetCountryCostShockResponse'
"400":
description: Validation error
content:
application/json:
schema:
$ref: '#/components/schemas/ValidationError'
default:
description: Error response
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
/api/supply-chain/v1/get-sector-dependency:
get:
tags:
- SupplyChainService
summary: GetSectorDependency
description: GetSectorDependency returns dependency flags and risk profile for a country+HS2 sector. PRO-gated.
operationId: GetSectorDependency
parameters:
- name: iso2
in: query
required: false
schema:
type: string
- name: hs2
in: query
description: HS2 chapter code, e.g. "27" (mineral fuels), "85" (electronics)
required: false
schema:
type: string
responses:
"200":
description: Successful response
content:
application/json:
schema:
$ref: '#/components/schemas/GetSectorDependencyResponse'
"400":
description: Validation error
content:
application/json:
schema:
$ref: '#/components/schemas/ValidationError'
default:
description: Error response
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
/api/supply-chain/v1/get-route-explorer-lane:
get:
tags:
- SupplyChainService
summary: GetRouteExplorerLane
description: |-
GetRouteExplorerLane returns the primary maritime route, chokepoint exposures,
bypass options with geometry, war risk, and static transit/freight estimates for
a country pair + HS2 + cargo type. PRO-gated. Wraps the route-intelligence vendor
endpoint's compute with browser-callable auth and adds fields needed by the
Route Explorer UI.
operationId: GetRouteExplorerLane
parameters:
- name: fromIso2
in: query
required: false
schema:
type: string
- name: toIso2
in: query
required: false
schema:
type: string
- name: hs2
in: query
description: HS2 chapter code, e.g. "27", "85"
required: false
schema:
type: string
- name: cargoType
in: query
description: 'One of: container, tanker, bulk, roro'
required: false
schema:
type: string
responses:
"200":
description: Successful response
content:
application/json:
schema:
$ref: '#/components/schemas/GetRouteExplorerLaneResponse'
"400":
description: Validation error
content:
application/json:
schema:
$ref: '#/components/schemas/ValidationError'
default:
description: Error response
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
/api/supply-chain/v1/get-route-impact:
get:
tags:
- SupplyChainService
summary: GetRouteImpact
operationId: GetRouteImpact
parameters:
- name: fromIso2
in: query
required: false
schema:
type: string
- name: toIso2
in: query
required: false
schema:
type: string
- name: hs2
in: query
required: false
schema:
type: string
responses:
"200":
description: Successful response
content:
application/json:
schema:
$ref: '#/components/schemas/GetRouteImpactResponse'
"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:
type: object
properties:
message:
type: string
description: Error message (e.g., 'user not found', 'database connection failed')
description: Error is returned when a handler encounters an error. It contains a simple error message that the developer can customize.
FieldViolation:
type: object
properties:
field:
type: string
description: The field path that failed validation (e.g., 'user.email' for nested fields). For header validation, this will be the header name (e.g., 'X-API-Key')
description:
type: string
description: Human-readable description of the validation violation (e.g., 'must be a valid email address', 'required field missing')
required:
- field
- description
description: FieldViolation describes a single validation error for a specific field.
ValidationError:
type: object
properties:
violations:
type: array
items:
$ref: '#/components/schemas/FieldViolation'
description: List of validation violations
required:
- violations
description: ValidationError is returned when request validation fails. It contains a list of field violations describing what went wrong.
GetShippingRatesRequest:
type: object
GetShippingRatesResponse:
type: object
properties:
indices:
type: array
items:
$ref: '#/components/schemas/ShippingIndex'
fetchedAt:
type: string
upstreamUnavailable:
type: boolean
ShippingIndex:
type: object
properties:
indexId:
type: string
name:
type: string
currentValue:
type: number
format: double
previousValue:
type: number
format: double
changePct:
type: number
format: double
unit:
type: string
history:
type: array
items:
$ref: '#/components/schemas/ShippingRatePoint'
spikeAlert:
type: boolean
ShippingRatePoint:
type: object
properties:
date:
type: string
value:
type: number
format: double
GetChokepointStatusRequest:
type: object
GetChokepointStatusResponse:
type: object
properties:
chokepoints:
type: array
items:
$ref: '#/components/schemas/ChokepointInfo'
fetchedAt:
type: string
upstreamUnavailable:
type: boolean
ChokepointInfo:
type: object
properties:
id:
type: string
name:
type: string
lat:
type: number
format: double
lon:
type: number
format: double
disruptionScore:
type: integer
format: int32
status:
type: string
activeWarnings:
type: integer
format: int32
congestionLevel:
type: string
affectedRoutes:
type: array
items:
type: string
description:
type: string
aisDisruptions:
type: integer
format: int32
directions:
type: array
items:
type: string
directionalDwt:
type: array
items:
$ref: '#/components/schemas/DirectionalDwt'
transitSummary:
$ref: '#/components/schemas/TransitSummary'
flowEstimate:
$ref: '#/components/schemas/FlowEstimate'
warRiskTier:
type: string
enum:
- WAR_RISK_TIER_UNSPECIFIED
- WAR_RISK_TIER_NORMAL
- WAR_RISK_TIER_ELEVATED
- WAR_RISK_TIER_HIGH
- WAR_RISK_TIER_CRITICAL
- WAR_RISK_TIER_WAR_ZONE
description: |-
*
War risk tier derived from Lloyd's JWC Listed Areas + OSINT threat classification.
This is a FREE field (no PRO gate) — it exposes the existing server-internal
threatLevel from ChokepointConfig, making it available to clients for badges
and bypass corridor scoring.
DirectionalDwt:
type: object
properties:
direction:
type: string
dwtThousandTonnes:
type: number
format: double
wowChangePct:
type: number
format: double
TransitSummary:
type: object
properties:
todayTotal:
type: integer
format: int32
todayTanker:
type: integer
format: int32
todayCargo:
type: integer
format: int32
todayOther:
type: integer
format: int32
wowChangePct:
type: number
format: double
history:
type: array
items:
$ref: '#/components/schemas/TransitDayCount'
riskLevel:
type: string
incidentCount7d:
type: integer
format: int32
disruptionPct:
type: number
format: double
riskSummary:
type: string
riskReportAction:
type: string
dataAvailable:
type: boolean
description: |-
False when the upstream portwatch/relay source did not return data for
this chokepoint in the current cycle — the summary fields are zero-state
fill, not a genuine "zero traffic" reading. Client should render a
"transit data unavailable" indicator and skip stat/chart rendering.
TransitDayCount:
type: object
properties:
date:
type: string
tanker:
type: integer
format: int32
cargo:
type: integer
format: int32
other:
type: integer
format: int32
total:
type: integer
format: int32
container:
type: integer
format: int32
dryBulk:
type: integer
format: int32
generalCargo:
type: integer
format: int32
roro:
type: integer
format: int32
capContainer:
type: number
format: double
capDryBulk:
type: number
format: double
capGeneralCargo:
type: number
format: double
capRoro:
type: number
format: double
capTanker:
type: number
format: double
FlowEstimate:
type: object
properties:
currentMbd:
type: number
format: double
baselineMbd:
type: number
format: double
flowRatio:
type: number
format: double
disrupted:
type: boolean
source:
type: string
hazardAlertLevel:
type: string
hazardAlertName:
type: string
GetChokepointHistoryRequest:
type: object
properties:
chokepointId:
type: string
required:
- chokepointId
description: |-
GetChokepointHistory returns the transit-count history for a single
chokepoint. Loaded lazily on card expand so the main chokepoint-status
response can stay compact (no 180-day history per chokepoint).
GetChokepointHistoryResponse:
type: object
properties:
chokepointId:
type: string
history:
type: array
items:
$ref: '#/components/schemas/TransitDayCount'
fetchedAt:
type: string
format: int64
GetCriticalMineralsRequest:
type: object
GetCriticalMineralsResponse:
type: object
properties:
minerals:
type: array
items:
$ref: '#/components/schemas/CriticalMineral'
fetchedAt:
type: string
upstreamUnavailable:
type: boolean
CriticalMineral:
type: object
properties:
mineral:
type: string
topProducers:
type: array
items:
$ref: '#/components/schemas/MineralProducer'
hhi:
type: number
format: double
riskRating:
type: string
globalProduction:
type: number
format: double
unit:
type: string
MineralProducer:
type: object
properties:
country:
type: string
countryCode:
type: string
productionTonnes:
type: number
format: double
sharePct:
type: number
format: double
GetShippingStressRequest:
type: object
GetShippingStressResponse:
type: object
properties:
carriers:
type: array
items:
$ref: '#/components/schemas/ShippingStressCarrier'
stressScore:
type: number
format: double
description: Composite stress score 0100 (higher = more disruption).
stressLevel:
type: string
description: '"low" | "moderate" | "elevated" | "critical".'
fetchedAt:
type: integer
format: int64
description: 'Warning: Values > 2^53 may lose precision in JavaScript'
upstreamUnavailable:
type: boolean
description: Set to true when upstream data source is unavailable and cached data is stale.
ShippingStressCarrier:
type: object
properties:
symbol:
type: string
description: Ticker or identifier (e.g., "BDRY", "ZIM").
name:
type: string
description: Human-readable name.
price:
type: number
format: double
description: Current price.
changePct:
type: number
format: double
description: Percentage change from previous close.
carrierType:
type: string
description: 'Carrier type: "etf" | "carrier" | "index".'
sparkline:
type: array
items:
type: number
format: double
description: 30-day price sparkline.
description: ShippingStressCarrier represents market stress data for a carrier or shipping index.
GetCountryChokepointIndexRequest:
type: object
properties:
iso2:
type: string
pattern: ^[A-Z]{2}$
description: ISO 3166-1 alpha-2 country code (uppercase).
hs2:
type: string
description: HS2 chapter (2-digit string). Defaults to "27" (energy/mineral fuels) when absent.
required:
- iso2
description: GetCountryChokepointIndexRequest specifies the country and optional HS2 chapter.
GetCountryChokepointIndexResponse:
type: object
properties:
iso2:
type: string
description: ISO 3166-1 alpha-2 country code echoed from the request.
hs2:
type: string
description: HS2 chapter used for the computation.
exposures:
type: array
items:
$ref: '#/components/schemas/ChokepointExposureEntry'
primaryChokepointId:
type: string
description: Canonical ID of the chokepoint with the highest exposure score.
vulnerabilityIndex:
type: number
format: double
description: Composite vulnerability index 0100 (weighted sum of top-3 exposures).
fetchedAt:
type: string
description: ISO timestamp of when this data was last seeded.
description: GetCountryChokepointIndexResponse returns exposure scores for all relevant chokepoints.
ChokepointExposureEntry:
type: object
properties:
chokepointId:
type: string
description: Canonical chokepoint ID from the chokepoint registry.
chokepointName:
type: string
description: Human-readable chokepoint name.
exposureScore:
type: number
format: double
description: Exposure score 0100; higher = more dependent on this chokepoint.
coastSide:
type: string
description: Which ocean/basin side the country's ports face (atlantic, pacific, indian, med, multi, landlocked).
shockSupported:
type: boolean
description: Whether the shock model is supported for this chokepoint + hs2 combination.
description: ChokepointExposureEntry holds per-chokepoint exposure data for a country.
GetBypassOptionsRequest:
type: object
properties:
chokepointId:
type: string
cargoType:
type: string
description: 'container | tanker | bulk | roro (default: "container")'
closurePct:
type: integer
format: int32
description: '0-100, percent of capacity blocked (default: 100)'
required:
- chokepointId
GetBypassOptionsResponse:
type: object
properties:
chokepointId:
type: string
cargoType:
type: string
closurePct:
type: integer
format: int32
options:
type: array
items:
$ref: '#/components/schemas/BypassOption'
fetchedAt:
type: string
primaryChokepointWarRiskTier:
type: string
enum:
- WAR_RISK_TIER_UNSPECIFIED
- WAR_RISK_TIER_NORMAL
- WAR_RISK_TIER_ELEVATED
- WAR_RISK_TIER_HIGH
- WAR_RISK_TIER_CRITICAL
- WAR_RISK_TIER_WAR_ZONE
description: |-
*
War risk tier derived from Lloyd's JWC Listed Areas + OSINT threat classification.
This is a FREE field (no PRO gate) — it exposes the existing server-internal
threatLevel from ChokepointConfig, making it available to clients for badges
and bypass corridor scoring.
BypassOption:
type: object
properties:
id:
type: string
name:
type: string
type:
type: string
addedTransitDays:
type: integer
format: int32
addedCostMultiplier:
type: number
format: double
capacityConstraintTonnage:
type: string
format: int64
suitableCargoTypes:
type: array
items:
type: string
activationThreshold:
type: string
waypointChokepointIds:
type: array
items:
type: string
liveScore:
type: number
format: double
bypassWarRiskTier:
type: string
enum:
- WAR_RISK_TIER_UNSPECIFIED
- WAR_RISK_TIER_NORMAL
- WAR_RISK_TIER_ELEVATED
- WAR_RISK_TIER_HIGH
- WAR_RISK_TIER_CRITICAL
- WAR_RISK_TIER_WAR_ZONE
description: |-
*
War risk tier derived from Lloyd's JWC Listed Areas + OSINT threat classification.
This is a FREE field (no PRO gate) — it exposes the existing server-internal
threatLevel from ChokepointConfig, making it available to clients for badges
and bypass corridor scoring.
notes:
type: string
GetCountryCostShockRequest:
type: object
properties:
iso2:
type: string
pattern: ^[A-Z]{2}$
chokepointId:
type: string
hs2:
type: string
description: 'HS2 chapter (default: "27")'
required:
- iso2
- chokepointId
GetCountryCostShockResponse:
type: object
properties:
iso2:
type: string
chokepointId:
type: string
hs2:
type: string
supplyDeficitPct:
type: number
format: double
description: Average refined-product supply deficit % under full closure (Gasoline/Diesel/Jet fuel/LPG average; HS 27 only)
coverageDays:
type: integer
format: int32
description: Energy stockpile coverage in days (IEA data, HS 27 only; 0 for non-energy sectors or net exporters)
warRiskPremiumBps:
type: integer
format: int32
description: War risk insurance premium in basis points for this chokepoint
warRiskTier:
type: string
enum:
- WAR_RISK_TIER_UNSPECIFIED
- WAR_RISK_TIER_NORMAL
- WAR_RISK_TIER_ELEVATED
- WAR_RISK_TIER_HIGH
- WAR_RISK_TIER_CRITICAL
- WAR_RISK_TIER_WAR_ZONE
description: |-
*
War risk tier derived from Lloyd's JWC Listed Areas + OSINT threat classification.
This is a FREE field (no PRO gate) — it exposes the existing server-internal
threatLevel from ChokepointConfig, making it available to clients for badges
and bypass corridor scoring.
hasEnergyModel:
type: boolean
description: Whether supply_deficit_pct and coverage_days are modelled (true) or unavailable (false)
unavailableReason:
type: string
description: Null/unavailable explanation for non-energy sectors
fetchedAt:
type: string
GetSectorDependencyRequest:
type: object
properties:
iso2:
type: string
pattern: ^[A-Z]{2}$
hs2:
type: string
description: HS2 chapter code, e.g. "27" (mineral fuels), "85" (electronics)
required:
- iso2
- hs2
GetSectorDependencyResponse:
type: object
properties:
iso2:
type: string
hs2:
type: string
hs2Label:
type: string
description: Human-readable HS2 chapter name.
flags:
type: array
items:
type: string
enum:
- DEPENDENCY_FLAG_UNSPECIFIED
- DEPENDENCY_FLAG_SINGLE_SOURCE_CRITICAL
- DEPENDENCY_FLAG_SINGLE_CORRIDOR_CRITICAL
- DEPENDENCY_FLAG_COMPOUND_RISK
- DEPENDENCY_FLAG_DIVERSIFIABLE
description: DependencyFlag classifies how a country+sector dependency can fail.
primaryExporterIso2:
type: string
description: ISO2 of the country supplying the largest share of this sector's imports.
primaryExporterShare:
type: number
format: double
description: Share of imports from the primary exporter (01). 0 = no Comtrade data available.
primaryChokepointId:
type: string
description: Chokepoint ID with the highest exposure score for this country+sector.
primaryChokepointExposure:
type: number
format: double
description: Exposure score for the primary chokepoint (0100).
hasViableBypass:
type: boolean
description: Whether at least one viable bypass corridor exists for the primary chokepoint.
fetchedAt:
type: string
GetRouteExplorerLaneRequest:
type: object
properties:
fromIso2:
type: string
pattern: ^[A-Z]{2}$
toIso2:
type: string
pattern: ^[A-Z]{2}$
hs2:
type: string
description: HS2 chapter code, e.g. "27", "85"
cargoType:
type: string
description: 'One of: container, tanker, bulk, roro'
required:
- fromIso2
- toIso2
- hs2
- cargoType
GetRouteExplorerLaneResponse:
type: object
properties:
fromIso2:
type: string
toIso2:
type: string
hs2:
type: string
cargoType:
type: string
primaryRouteId:
type: string
description: Primary trade route ID from TRADE_ROUTES config. Empty when no modeled lane.
primaryRouteGeometry:
type: array
items:
$ref: '#/components/schemas/GeoPoint'
chokepointExposures:
type: array
items:
$ref: '#/components/schemas/ChokepointExposureSummary'
bypassOptions:
type: array
items:
$ref: '#/components/schemas/BypassCorridorOption'
warRiskTier:
type: string
disruptionScore:
type: number
format: double
estTransitDaysRange:
$ref: '#/components/schemas/NumberRange'
estFreightUsdPerTeuRange:
$ref: '#/components/schemas/NumberRange'
noModeledLane:
type: boolean
description: |-
True when the wrapper fell back to the origin's first route (no shared route
between origin and destination clusters). Signals "no modeled lane" to the UI.
fetchedAt:
type: string
GeoPoint:
type: object
properties:
lon:
type: number
format: double
lat:
type: number
format: double
description: GeoPoint is a [longitude, latitude] pair.
ChokepointExposureSummary:
type: object
properties:
chokepointId:
type: string
chokepointName:
type: string
exposurePct:
type: integer
format: int32
BypassCorridorOption:
type: object
properties:
id:
type: string
name:
type: string
type:
type: string
addedTransitDays:
type: integer
format: int32
addedCostMultiplier:
type: number
format: double
warRiskTier:
type: string
status:
type: string
enum:
- CORRIDOR_STATUS_UNSPECIFIED
- CORRIDOR_STATUS_ACTIVE
- CORRIDOR_STATUS_PROPOSED
- CORRIDOR_STATUS_UNAVAILABLE
description: |-
Status of a bypass corridor for UI labeling. "active" means usable today;
"proposed" means documented but not yet built/operational; "unavailable"
means blockaded or otherwise blocked from use.
fromPort:
$ref: '#/components/schemas/GeoPoint'
toPort:
$ref: '#/components/schemas/GeoPoint'
description: |-
BypassCorridorOption is a single enriched bypass corridor for the Route Explorer UI.
Includes coordinate endpoints so the client can call MapContainer.setBypassRoutes
directly without any client-side geometry lookup.
NumberRange:
type: object
properties:
min:
type: integer
format: int32
max:
type: integer
format: int32
description: Inclusive integer range for transit days / freight USD estimates.
GetRouteImpactRequest:
type: object
properties:
fromIso2:
type: string
pattern: ^[A-Z]{2}$
toIso2:
type: string
pattern: ^[A-Z]{2}$
hs2:
type: string
required:
- fromIso2
- toIso2
- hs2
GetRouteImpactResponse:
type: object
properties:
laneValueUsd:
type: number
format: double
primaryExporterIso2:
type: string
primaryExporterShare:
type: number
format: double
topStrategicProducts:
type: array
items:
$ref: '#/components/schemas/StrategicProduct'
resilienceScore:
type: number
format: double
dependencyFlags:
type: array
items:
type: string
enum:
- DEPENDENCY_FLAG_UNSPECIFIED
- DEPENDENCY_FLAG_SINGLE_SOURCE_CRITICAL
- DEPENDENCY_FLAG_SINGLE_CORRIDOR_CRITICAL
- DEPENDENCY_FLAG_COMPOUND_RISK
- DEPENDENCY_FLAG_DIVERSIFIABLE
description: DependencyFlag classifies how a country+sector dependency can fail.
hs2InSeededUniverse:
type: boolean
comtradeSource:
type: string
fetchedAt:
type: string
StrategicProduct:
type: object
properties:
hs4:
type: string
label:
type: string
totalValueUsd:
type: number
format: double
topExporterIso2:
type: string
topExporterShare:
type: number
format: double
primaryChokepointId:
type: string