File size: 3,072 Bytes
20f83d9 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 | // Shared UN Comtrade annual-period helpers. Single source of truth imported by:
// - scripts/seed-trade-flows.mjs (strategic commodity flows)
// - scripts/seed-comtrade-bilateral-hs4.mjs (bulk per-country seeder)
// - server/worldmonitor/supply-chain/v1/_bilateral-hs4-lazy.ts (on-demand fallback)
//
// Before PR #5641's review these three carried three byte-identical copies of
// recentPeriod(). The copies mattered: seed-trade-flows.mjs already had a
// year-boundary fallback the other two silently lacked, so a fix landing in
// one did not reach the others.
//
// IMPORTANT — DO NOT MOVE THIS FILE OUT OF scripts/. Railway nixpacks services
// build with `root_dir=scripts` and package only `scripts/` into `/app/`, so a
// relative import that escapes `scripts/` resolves to a path that does not
// exist in the container and crashes the worker on startup with
// ERR_MODULE_NOT_FOUND. See scripts/_simulation-queue-constants.mjs (#3811
// incident / #3818 hotfix) and the regression test
// tests/scripts-railway-nixpacks-no-escape-import.test.mts. Vercel-side TS
// handlers are fine: esbuild inlines this module's contents at build time.
//
// Runtime constraint: Web-Platform APIs only (must run on Vercel Edge + Node).
/**
* Newest annual period that is reliably final across reporters.
*
* Comtrade annual data lags, and it lags unevenly: the fastest reporters are a
* full year ahead of the slowest. `lag = 2` is the repo-wide default because
* (y-2) is old enough that the major reporters have all filed.
*/
export function recentPeriod(now = new Date(), lag = 2) {
return String(now.getUTCFullYear() - lag);
}
/**
* Sequential fallback periods, freshest first, for endpoints that accept only
* ONE period per request.
*
* Needed because (y-2) rolls forward the instant the UTC year turns, to a year
* the slower reporters have not filed yet; without a fallback a seed goes empty
* every January until they catch up.
*/
export function candidatePeriods(now = new Date()) {
return [recentPeriod(now, 2), recentPeriod(now, 3)];
}
/**
* Comma-joined multi-year window for endpoints that accept a period LIST.
*
* Costs the same single request as one period but still lands a row for a
* reporter that files late — the failure mode documented in
* scripts/seed-recovery-import-hhi.mjs (UAE, Oman, Bahrain publish 1-2y behind
* the G7). Consumers must resolve one row per (product, partner) afterwards,
* newest year first, or a multi-year response silently mixes years.
*
* ONLY the authenticated `data/v1/get` route accepts a list. The public
* `public/v1/preview` route returns HTTP 400 for a comma-separated period —
* verified by live probe 2026-07-26: `period=2024` -> 200 (31 rows),
* `period=2024,2023` -> 400. Use recentPeriod()/candidatePeriods() there.
*/
export function periodWindow(now = new Date(), { from = 2, to = 5 } = {}) {
const years = [];
for (let lag = from; lag <= to; lag++) years.push(recentPeriod(now, lag));
return years.join(',');
}
|