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.
This commit is contained in:
Elie Habib
2026-04-07 22:24:17 +04:00
committed by GitHub
parent 2f80d192ab
commit ec4a7f9e9d
2 changed files with 44 additions and 8 deletions

View File

@@ -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',

View File

@@ -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}`);
}
});
});