mirror of
https://github.com/koala73/worldmonitor.git
synced 2026-04-25 17:14:57 +02:00
* feat(supply-chain): energy flow estimates per chokepoint (mb/d card row) - Add FlowEstimate proto message + ChokepointInfo field 15; regenerate stubs - Add baselineId mapping to _chokepoint-ids.ts (7 of 13 chokepoints) - Add relayId to seed-chokepoint-baselines.mjs CHOKEPOINTS entries - New seed-chokepoint-flows.mjs: reads portwatch + baselines, computes 7d tanker avg vs 90d baseline, outputs flow_ratio and current_mbd; prefers DWT (capTanker) when available; flags disruption if last 3 days each below 0.85 threshold; writes energy:chokepoint-flows:v1 (TTL 3d) - get-chokepoint-status.ts: parallel-reads flows key, attaches flowEstimate - SupplyChainPanel: compact card gains mb/d row (red <85%, amber <95%) - 19 new unit tests for flow computation and seeder contract * fix(chokepoint-flows): base useDwt on 90d baseline window, not recent 7 days Zero recent capTanker is the disruption signal, not a reason to fall back to vessel counts. Switching metrics during peak disruption caused the seeder to report a higher (less accurate) flow estimate exactly when oil-flow collapse is most acute. useDwt is now locked to whether the baseline window has DWT data -- stable across disruption events. Adds regression test covering DWT-collapse scenario. * fix(chokepoint-flows): require majority DWT coverage in baseline before activating DWT mode capBaselineSum > 0 would activate DWT on a single non-zero day during partial data roll-out, pulling down the baseline average via zero-filled gaps. Now requires >= ceil(prev90.length / 2) days with DWT data. ArcGIS data is all-or-nothing per chokepoint in practice, so this guard catches edge cases without affecting normal operation.
46 lines
1.8 KiB
JavaScript
46 lines
1.8 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
import { loadEnvFile, runSeed } from './_seed-utils.mjs';
|
|
|
|
loadEnvFile(import.meta.url);
|
|
|
|
export const CANONICAL_KEY = 'energy:chokepoint-baselines:v1';
|
|
export const CHOKEPOINT_TTL_SECONDS = 34_560_000;
|
|
|
|
export const CHOKEPOINTS = [
|
|
{ id: 'hormuz', relayId: 'hormuz_strait', name: 'Strait of Hormuz', mbd: 21.0, lat: 26.6, lon: 56.3 },
|
|
{ id: 'malacca', relayId: 'malacca_strait', name: 'Strait of Malacca', mbd: 17.2, lat: 1.3, lon: 103.8 },
|
|
{ id: 'suez', relayId: 'suez', name: 'Suez Canal / SUMED', mbd: 7.6, lat: 30.7, lon: 32.3 },
|
|
{ id: 'babelm', relayId: 'bab_el_mandeb', name: 'Bab el-Mandeb', mbd: 6.2, lat: 12.6, lon: 43.4 },
|
|
{ id: 'danish', relayId: 'dover_strait', name: 'Danish Straits', mbd: 3.0, lat: 57.5, lon: 10.5 },
|
|
{ id: 'turkish', relayId: 'bosphorus', name: 'Turkish Straits', mbd: 2.9, lat: 41.1, lon: 29.0 },
|
|
{ id: 'panama', relayId: 'panama', name: 'Panama Canal', mbd: 0.9, lat: 9.1, lon: -79.7 },
|
|
];
|
|
|
|
export function buildPayload() {
|
|
return {
|
|
source: 'EIA World Oil Transit Chokepoints',
|
|
referenceYear: 2023,
|
|
updatedAt: new Date().toISOString(),
|
|
chokepoints: CHOKEPOINTS,
|
|
};
|
|
}
|
|
|
|
export function validateFn(data) {
|
|
return Array.isArray(data?.chokepoints) && data.chokepoints.length === 7;
|
|
}
|
|
|
|
const isMain = process.argv[1]?.endsWith('seed-chokepoint-baselines.mjs');
|
|
if (isMain) {
|
|
runSeed('energy', 'chokepoint-baselines', CANONICAL_KEY, buildPayload, {
|
|
validateFn,
|
|
ttlSeconds: CHOKEPOINT_TTL_SECONDS,
|
|
sourceVersion: 'eia-chokepoint-baselines-v1',
|
|
recordCount: (data) => data?.chokepoints?.length || 0,
|
|
}).catch((err) => {
|
|
const cause = err.cause ? ` (cause: ${err.cause.message || err.cause.code || err.cause})` : '';
|
|
console.error('FATAL:', (err.message || err) + cause);
|
|
process.exit(1);
|
|
});
|
|
}
|