Files
worldmonitor/tests
Elie Habib d96259048d feat(energy): canonical energy spine — V5-1 (#2798)
* feat(energy): canonical energy spine seeder + handler read-through (V5-1)

- Add scripts/seed-energy-spine.mjs: daily seeder that assembles one
  energy:spine:v1:<ISO2> key per country from 6 domain keys (OWID mix,
  JODI oil, JODI gas, IEA stocks, ENTSO-E electricity, GIE gas storage)
- TTL 172800s (48h), count-drop guard at 80%, schema sentinel for OWID mix,
  lock pattern + Redis pipeline batch write mirroring seed-owid-energy-mix.mjs
- Update get-country-energy-profile.ts to read from spine first; fall back
  to existing 6-key Promise.allSettled join on spine miss
- Update chat-analyst-context.ts buildProductSupply/buildGasFlows/buildOilStocksCover
  to prefer spine key; fall through to direct domain key reads on miss
- Update get-country-intel-brief.ts to read energy mix from spine.mix + sources.mixYear
  before falling back to energy:mix:v1: direct key
- Add ENERGY_SPINE_KEY_PREFIX and ENERGY_SPINE_COUNTRIES_KEY to cache-keys.ts
- Add energySpineCountries to api/health.js STANDALONE_KEYS and SEED_META
- Add Railway cron comment (0 6 * * *) in ais-relay.cjs
- Add tests/energy-spine-seed.test.mjs: 26 tests covering spine build logic,
  IEA anomaly guard, JODI oil fallback, schema sentinel, count-drop math

* fix(energy): add cache-keys module replacement in redis-caching test

The new ENERGY_SPINE_KEY_PREFIX import in get-country-intel-brief.ts
was not patched in the importPatchedTsModule call used by redis-caching
tests. Add cache-keys to the replacement map to resolve the module.

* fix(energy): add missing fields to spine entry and read-through

- buildOilFields: add product importsKbd (gasoline/diesel/jet/lpg) and belowObligation
- buildMixFields: add windShare, solarShare, hydroShare
- buildGasStorageFields: new helper storing fillPct, fillPctChange1d, trend
- buildSpineEntry: add gasStorage section using new helper
- EnergySpine interface: extend oil/mix/gasStorage to match seeder output
- buildResponseFromSpine: read all new fields instead of hard-coding 0/false

* fix(energy): exclude electricity/gas-storage from spine; add seed-health entry

Spine-first path was returning stale gas-storage and electricity data for
up to 8h after seeding (spine runs 06:00 UTC, gas storage updates 10:30 UTC,
electricity updates 14:00 UTC).

Fix: handler now reads gas-storage and electricity directly in parallel with
the spine read (3-key allSettled). Fallback path drops from 6 to 4 keys since
gas-storage and electricity are already fetched in the hot path.

Also registers energy:spine in api/seed-health.js (daily cron, maxStaleMin
inferred as 2×intervalMin = 2880 min).

Seeder (seed-energy-spine.mjs) and its tests updated to reflect the narrowed
spine schema — electricity and gasStorage fields removed from buildSpineEntry.

* fix(energy): address Greptile P2 review findings

- chat-analyst-context: return undefined when gas imports are 0 rather
  than falling back to totalDemandTj with an "imports" label — avoids
  mislabeling domestic gas demand as imports for net exporters (RU, QA, etc.)
- seed-energy-spine: add status:'ok' to success-path seed-meta write so
  all seed-meta records have a consistent status field regardless of path
2026-04-07 23:40:25 +04:00
..