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(',');
}