mirror of
https://github.com/koala73/worldmonitor.git
synced 2026-04-25 17:14:57 +02:00
* feat(supply-chain): Sprint 1 — Route Explorer wrapper RPC
Adds an internal wrapper around the vendor-only route-intelligence
compute so the upcoming Route Explorer UI can call it from a browser
PRO session instead of forcing an X-WorldMonitor-Key API gate.
Backend:
- New proto get-route-explorer-lane.proto with GetRouteExplorerLane{Request,Response}
- New handler server/worldmonitor/supply-chain/v1/get-route-explorer-lane.ts
- New static lookup tables _route-explorer-static-tables.ts:
TRANSIT_DAYS_BY_ROUTE_ID, FREIGHT_USD_BY_CARGO_TYPE,
BYPASS_CORRIDOR_GEOMETRY_BY_ID — covers all 5 land-bridge corridors
plus every sea-alternative corridor with hand-curated coordinates
- Wired into supply-chain handler.ts service dispatcher
- Cache key ROUTE_EXPLORER_LANE_KEY in cache-keys.ts (NOT in BOOTSTRAP_KEYS)
- Gateway entry: PREMIUM_RPC_PATHS + RPC_CACHE_TIER 'slow-browser'
- Premium path entry in src/shared/premium-paths.ts so browser PRO auth attaches
Response contract enriches route-intelligence with:
- primaryRouteGeometry polyline from TRADE_ROUTES (lon/lat pairs)
- fromPort/toPort coords on every bypass option so the client can call
MapContainer.setBypassRoutes directly without geometry lookups
- status: 'active' | 'proposed' | 'unavailable' derived from corridor notes
to honestly label kra_canal_future and black_sea_western_ports
- estTransitDaysRange + estFreightUsdPerTeuRange from static tables
- noModeledLane: true when origin/destination clusters share no routes
Client wrapper fetchRouteExplorerLane added to src/services/supply-chain/index.ts.
Tests: tests/route-explorer-lane.test.mts — 30-query smoke matrix
(10 country pairs × 3 HS2 codes), structural assertions only, no
hard-coded transit/cost values. Test exposes a pure computeLane()
function with an injectable status map so it does not need Redis.
Gap report (from smoke run): 12 of 30 queries fall back to a synthetic
primaryRouteId because the destination's port cluster has no shared route
with the origin (US-JP, ZA-IN, CL-CN, TR-DE × 3 HS2 each). These pairs
return noModeledLane:true; Sprint 3 will render an empty-state for them.
Plan: docs/plans/2026-04-11-001-feat-worldwide-route-explorer-plan.md
* fix(route-explorer): address PR #2980 review findings
P1: bypass warRiskTier was hard-coded to WAR_RISK_TIER_NORMAL, dropping
the live risk signal from chokepoint status. Now derived from the
statusMap via the corridor's primaryChokepointId.
P2: freight fallback in emptyResponse and client-side empty payload used
a cargo-agnostic container range for all cargo types. Removed the ranges
entirely from fallback/noModeledLane responses; they are only present
when the lane is actually modeled.
Suggestion: when noModeledLane is true, the response now returns empty
primaryRouteId, empty geometry, empty exposures, empty bypasses, and
omits transit/freight ranges. Previously it returned plausible-looking
synthetic data from the origin's first route which could mislead the UI.
Tests updated to assert the noModeledLane contract: empty fields when
the flag is set, non-empty ranges only when the lane is modeled.
* fix(route-explorer): cargo-aware route ranking + bypass waypoint risk
P1: primary route selection was order-dependent, picking whichever
shared route the origin cluster listed first. Mixed clusters like
CN/JP could return an energy lane for a container request. Now ranks
shared routes by cargo-category compatibility (container→container,
tanker→energy, bulk→bulk, roro→container) before selecting.
P1: bypass warRiskTier was copied from the primary chokepoint instead
of derived from the corridor's own waypointChokepointIds. This
overstated risk for alternatives like Cape of Good Hope whose waypoints
may have a lower risk tier. Now uses max-tier across waypoint
chokepoints, matching get-bypass-options.ts logic.
Suggestion: placeholder corridors with addedTransitDays=0 (like
gibraltar_no_bypass, cape_of_good_hope_is_bypass) are now filtered out.
Previously they could surface as active alternatives.
Regression tests added:
- CN→JP tanker: asserts energy route is selected over container route
- CN→DE with faked Suez=CRITICAL / Cape=NORMAL: asserts Cape bypass
shows NORMAL, not CRITICAL
- ES→EG: asserts zero-transit-day placeholders are excluded
* fix(route-explorer): scope exposures to primary route + narrow placeholder filter
P1: chokepointExposures and bypassOptions were computed from the full
sharedRoutes set, mixing data from energy/container corridors into a
single response. Now scoped to the cargo-ranked primaryRouteId only,
matching the proto contract that exposures are "on the primary route."
P2: the addedTransitDays === 0 filter was too broad and removed
kra_canal_future (a proposed bypass with real modeling). Narrowed to an
explicit PLACEHOLDER_CORRIDOR_IDS set (gibraltar_no_bypass,
cape_of_good_hope_is_bypass) so proposed zero-day corridors survive and
are surfaced with CORRIDOR_STATUS_PROPOSED.
Regression tests:
- chokepointExposures follow primaryRouteId (CN->JP container)
- kra_canal_future appears as CORRIDOR_STATUS_PROPOSED for Malacca routes
- placeholder filter still excludes explicit placeholders
* fix(route-explorer): address PR #2980 review comments
1. Unavailable corridors without waypoints (e.g. black_sea_western_ports)
now derive WAR_RISK_TIER_WAR_ZONE from their CORRIDOR_STATUS_UNAVAILABLE
status, instead of returning WAR_RISK_TIER_UNSPECIFIED. Corridors with
waypointChokepointIds still use max-tier across those waypoints.
2. Added fixture test with non-empty status map (suez=75/HIGH,
malacca=30/ELEVATED) so disruptionScore and warRiskTier assertions are
not trivially satisfied by the empty-map default path.
3. Documented the single-chokepoint bypass design gap in the test gap report:
bypassOptions only cover the primary chokepoint; multi-chokepoint routes
show exposure for all but bypass guidance for only the top one. Sprint 3
will decide whether to expand to top-N or add a UI hint.
1068 lines
41 KiB
YAML
1068 lines
41 KiB
YAML
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-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'
|
||
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
|
||
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
|
||
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 0–100 (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 0–100 (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 0–100; 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 (0–1). 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 (0–100).
|
||
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.
|