mirror of
https://github.com/koala73/worldmonitor.git
synced 2026-04-25 17:14:57 +02:00
feat(supply-chain): Global Shipping Intelligence — Sprint 0 + Sprint 1 (#2870)
* feat(supply-chain): Sprint 0 — chokepoint registry, HS2 sectors, war_risk_tier
- src/config/chokepoint-registry.ts: single source of truth for all 13
canonical chokepoints with displayName, relayName, portwatchName,
corridorRiskName, baselineId, shockModelSupported, routeIds, lat/lon
- src/config/hs2-sectors.ts: static dictionary for all 99 HS2 chapters
with category, shockModelSupported (true only for HS27), cargoType
- server/worldmonitor/supply-chain/v1/_chokepoint-ids.ts: migrated to
derive CANONICAL_CHOKEPOINTS from chokepoint-registry; no data duplication
- src/config/geo.ts + src/types/index.ts: added chokepointId field to
StrategicWaterway interface and all 13 STRATEGIC_WATERWAYS entries
- src/components/MapPopup.ts: switched chokepoint matching from fragile
name.toLowerCase() to direct chokepointId === id comparison
- server/worldmonitor/intelligence/v1/_shock-compute.ts: migrated from old
IDs (hormuz/malacca/babelm) to canonical IDs (hormuz_strait/malacca_strait/
bab_el_mandeb); same for CHOKEPOINT_LNG_EXPOSURE
- proto/worldmonitor/supply_chain/v1/supply_chain_data.proto: added
WarRiskTier enum + war_risk_tier field (field 16) on ChokepointInfo
- get-chokepoint-status.ts: populates warRiskTier from ChokepointConfig.threatLevel
via new threatLevelToWarRiskTier() helper (FREE field, no PRO gate)
* feat(supply-chain): Sprint 1 — country chokepoint exposure index + sector ring
S1.1: scripts/shared/country-port-clusters.json
~130 country → {nearestRouteIds, coastSide} mappings derived from trade route
waypoints; covers all 6 seeded Comtrade reporters plus major trading nations.
S1.2: scripts/seed-hs2-chokepoint-exposure.mjs
Daily cron seeder. Pure computation — reads country-port-clusters.json,
scores each country against CHOKEPOINT_REGISTRY route overlap, writes
supply-chain:exposure:{iso2}:{hs2}:v1 keys + seed-meta (24h TTL).
S1.3: RPC get-country-chokepoint-index (PRO-gated, request-varying)
- proto: GetCountryChokepointIndexRequest/Response + ChokepointExposureEntry
- handler: isCallerPremium gate; cachedFetchJson 24h; on-demand for any iso2
- cache-keys.ts: CHOKEPOINT_EXPOSURE_KEY(iso2, hs2) constant
- health.js: chokepointExposure SEED_META entry (48h threshold)
- gateway.ts: slow-browser cache tier
- service client: fetchCountryChokepointIndex() exported
S1.4: Chokepoint popup HS2 sector ring chart (PRO-gated)
Static trade-sector breakdown (IEA/UNCTAD estimates) per 9 major chokepoints.
SVG donut ring + legend shown for PRO users; blurred lockout + gate-hit
analytics for free users. Wired into renderWaterwayPopup().
🤖 Generated with Claude Sonnet 4.6 via Claude Code (https://claude.com/claude-code) + Compound Engineering v2.49.0
Co-Authored-By: Claude Sonnet 4.6 (200K context) <noreply@anthropic.com>
* fix(tests): update energy-shock-v2 tests to use canonical chokepoint IDs
CHOKEPOINT_EXPOSURE and CHOKEPOINT_LNG_EXPOSURE keys were migrated from
short IDs (hormuz, malacca, babelm) to canonical registry IDs
(hormuz_strait, malacca_strait, bab_el_mandeb) in Sprint 0.
Test fixtures were not updated at the time; fix them now.
* fix(tests): update energy-shock-seed chokepoint ID to canonical form
VALID_CHOKEPOINTS changed to canonical IDs in Sprint 0; the seed test
that checks valid IDs was not updated alongside it.
* fix(cache-keys): reword JSDoc comment to avoid confusing bootstrap test regex
The comment "NOT in BOOTSTRAP_CACHE_KEYS" caused the bootstrap.test.mjs
regex to match the comment rather than the actual export declaration,
resulting in 0 entries found. Rephrase to "excluded from bootstrap".
* fix(supply-chain): address P1 review findings for chokepoint exposure index
- Add get-country-chokepoint-index to PREMIUM_RPC_PATHS (CDN bypass)
- Validate iso2/hs2 params before Redis key construction (cache injection)
- Fix seeder TTL to 172800s (2× interval) and extend TTL on skipped lock
- Fix CHOKEPOINT_EXPOSURE_SEED_META_KEY to match seeder write key
- Render placeholder sectors behind blur gate (DOM data leakage)
- Document get-country-chokepoint-index in widget agent system prompts
* fix(lint): resolve Biome CI failures
- Add biome.json overrides to silence noVar in HTML inline scripts,
disable linting for public/ vendor/build artifacts and pro-test/
- Remove duplicate NG and MW keys from country-port-clusters.json
- Use import attributes (with) instead of deprecated assert syntax
* fix(build): drop JSON import attribute — esbuild rejects `with` syntax
---------
Co-authored-by: Claude Sonnet 4.6 (200K context) <noreply@anthropic.com>
This commit is contained in:
@@ -104,6 +104,45 @@ paths:
|
||||
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'
|
||||
components:
|
||||
schemas:
|
||||
Error:
|
||||
@@ -239,6 +278,21 @@ components:
|
||||
$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:
|
||||
@@ -443,3 +497,60 @@ components:
|
||||
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.
|
||||
|
||||
Reference in New Issue
Block a user