mirror of
https://github.com/koala73/worldmonitor.git
synced 2026-04-26 01:24:59 +02:00
* 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.
1241 lines
47 KiB
YAML
1241 lines
47 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-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 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.
|
||
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
|