From ec4a7f9e9df0b3d909997b642a97043e148f56d7 Mon Sep 17 00:00:00 2001 From: Elie Habib Date: Tue, 7 Apr 2026 22:24:17 +0400 Subject: [PATCH] fix(electricity): correct EIA-930 respondent codes for NYISO and SPP (#2799) * fix(electricity): correct EIA-930 respondent codes for NYISO and SPP NYISO and SPP returned empty data because the EIA API uses abbreviated BA e-tag IDs (NYIS, SWPP), not public-facing acronyms. * fix(electricity): separate EIA respondent codes from stable region IDs EIA-930 uses abbreviated BA e-tag IDs (NYIS, SWPP) as API respondent codes, not the public-facing acronyms (NYISO, SPP). Added a separate respondent field for the API query while keeping region stable for Redis keys and downstream consumers. * test(electricity): add EIA respondent code mapping coverage Export EIA_REGIONS and add tests verifying the region-to-respondent mapping so a future refactor cannot silently regress the codes. --- scripts/seed-electricity-prices.mjs | 16 ++++++------ tests/electricity-prices-seed.test.mjs | 36 ++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 8 deletions(-) diff --git a/scripts/seed-electricity-prices.mjs b/scripts/seed-electricity-prices.mjs index d8d63f6ab..707cc40a3 100644 --- a/scripts/seed-electricity-prices.mjs +++ b/scripts/seed-electricity-prices.mjs @@ -35,13 +35,13 @@ const ENTSO_E_REGIONS = [ { region: 'SE', eic: '10Y1001A1001A46L', name: 'Sweden (Stockholm)' }, // SE3 bidding zone ]; -const EIA_REGIONS = [ - { region: 'CISO', name: 'California' }, - { region: 'MISO', name: 'Midwest' }, - { region: 'PJM', name: 'Mid-Atlantic' }, - { region: 'NYISO', name: 'New York' }, - { region: 'ERCO', name: 'Texas (ERCOT)' }, - { region: 'SPP', name: 'Southwest' }, +export const EIA_REGIONS = [ + { region: 'CISO', respondent: 'CISO', name: 'California' }, + { region: 'MISO', respondent: 'MISO', name: 'Midwest' }, + { region: 'PJM', respondent: 'PJM', name: 'Mid-Atlantic' }, + { region: 'NYISO', respondent: 'NYIS', name: 'New York' }, + { region: 'ERCO', respondent: 'ERCO', name: 'Texas (ERCOT)' }, + { region: 'SPP', respondent: 'SWPP', name: 'Southwest' }, ]; // ── Date helpers ───────────────────────────────────────────────────────────── @@ -179,7 +179,7 @@ async function fetchEiaRegion(region, apiKey, today) { const dateStr = isoDate(today); const params = new URLSearchParams({ 'data[]': 'value', - 'facets[respondent][]': region.region, + 'facets[respondent][]': region.respondent, start: isoDate(new Date(Date.now() - 2 * 24 * 3600 * 1000)), end: dateStr, 'sort[0][column]': 'period', diff --git a/tests/electricity-prices-seed.test.mjs b/tests/electricity-prices-seed.test.mjs index 9f2fcc5de..512ac412f 100644 --- a/tests/electricity-prices-seed.test.mjs +++ b/tests/electricity-prices-seed.test.mjs @@ -4,6 +4,7 @@ import assert from 'node:assert/strict'; import { parseEntsoEPrice, buildElectricityIndex, + EIA_REGIONS, ELECTRICITY_INDEX_KEY, ELECTRICITY_KEY_PREFIX, ELECTRICITY_TTL_SECONDS, @@ -127,3 +128,38 @@ describe('exported key constants', () => { ); }); }); + +// ── EIA region/respondent mapping ──────────────────────────────────────────── + +describe('EIA_REGIONS respondent codes', () => { + const EXPECTED = { + CISO: 'CISO', + MISO: 'MISO', + PJM: 'PJM', + NYISO: 'NYIS', + ERCO: 'ERCO', + SPP: 'SWPP', + }; + + it('every entry has distinct region and respondent fields', () => { + for (const entry of EIA_REGIONS) { + assert.ok(typeof entry.region === 'string' && entry.region.length > 0, `missing region`); + assert.ok(typeof entry.respondent === 'string' && entry.respondent.length > 0, `missing respondent for ${entry.region}`); + } + }); + + it('maps public region IDs to correct EIA respondent codes', () => { + for (const [region, respondent] of Object.entries(EXPECTED)) { + const entry = EIA_REGIONS.find((r) => r.region === region); + assert.ok(entry, `missing EIA_REGIONS entry for ${region}`); + assert.equal(entry.respondent, respondent, `${region} should use respondent ${respondent}, got ${entry.respondent}`); + } + }); + + it('covers all expected regions', () => { + const regions = EIA_REGIONS.map((r) => r.region); + for (const expected of Object.keys(EXPECTED)) { + assert.ok(regions.includes(expected), `EIA_REGIONS missing ${expected}`); + } + }); +});