Files
worldmonitor/tests/gscpi-shape-extraction.test.mjs
Elie Habib c072edc89f fix(economy): GSCPI shape mismatch with ais-relay payload (#3072)
* fix(economy): GSCPI shape mismatch with ais-relay payload

`seed-economy.mjs` was reporting `[StressIndex] GSCPI not in Redis yet
(ais-relay lag or first run) — excluding` even when GSCPI was current
in Redis. The Stress Index then computed on 5/6 components instead
of 6/6 every run.

Root cause: shape mismatch.
- ais-relay.cjs (`seedGscpi()`) writes the FRED-compatible payload
  `{ series: { series_id, title, units, frequency, observations: [...] } }`
- seed-economy.mjs `fetchGscpiFromRedis()` was reading the legacy
  flat shape `{ observations: [...] }` (top-level), so
  `Array.isArray(parsed.observations)` was always false → null returned
  → "not in Redis yet" log, even though 343 monthly observations were
  sitting in `economic:fred:v1:GSCPI:0`

Fix: extract the parsing into `extractGscpiObservations()` which checks
both shapes (`parsed.series.observations` first, then top-level
`parsed.observations` for back-compat). The "not in Redis yet" message
will now correctly fire only when the relay is genuinely behind.

Verified against live Redis: returns `{ observations: 343 entries,
latest 2026-03-01 = 0.68 }` instead of null.

Tests added in tests/gscpi-shape-extraction.test.mjs (3 cases:
ais-relay shape, legacy flat shape, malformed payload).

* style(economy): single @type cast in extractGscpiObservations

PR #3072 review (P2): cast `parsed` to `any` once into a local instead
of repeating the inline `/** @type {any} */` annotation on every access.
Same behavior, less visual noise.
2026-04-13 22:03:27 +04:00

48 lines
1.8 KiB
JavaScript

import { describe, it } from 'node:test';
import assert from 'node:assert/strict';
import { extractGscpiObservations } from '../scripts/seed-economy.mjs';
describe('extractGscpiObservations', () => {
it('reads the ais-relay FRED-compatible shape (observations under .series)', () => {
// This is the actual shape ais-relay.cjs writes — see seedGscpi() in that file.
const parsed = {
series: {
series_id: 'GSCPI',
title: 'Global Supply Chain Pressure Index',
units: 'Standard Deviations',
frequency: 'Monthly',
observations: [
{ date: '2026-02-01', value: 0.42 },
{ date: '2026-03-01', value: 0.68 },
],
},
};
const result = extractGscpiObservations(parsed);
assert.ok(result, 'should return non-null');
assert.equal(result.observations.length, 2);
assert.equal(result.observations[1].value, 0.68);
});
it('reads the legacy flat shape (top-level observations) for back-compat', () => {
// Earlier ais-relay versions stored this shape — keep working if any
// long-lived Redis key still has it.
const parsed = {
observations: [
{ date: '2026-03-01', value: 0.68 },
],
};
const result = extractGscpiObservations(parsed);
assert.ok(result, 'should return non-null');
assert.equal(result.observations.length, 1);
});
it('returns null when neither shape is present', () => {
assert.equal(extractGscpiObservations(null), null);
assert.equal(extractGscpiObservations({}), null);
assert.equal(extractGscpiObservations({ series: {} }), null);
assert.equal(extractGscpiObservations({ observations: 'not-an-array' }), null);
assert.equal(extractGscpiObservations({ series: { observations: 'nope' } }), null);
});
});