@uk-energy/elexon 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +9 -0
- package/LICENSE +21 -21
- package/README.md +17 -2
- package/dist/index.cjs +134 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +85 -2
- package/dist/index.d.ts +85 -2
- package/dist/index.js +133 -2
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# @uk-energy/elexon
|
|
2
2
|
|
|
3
|
+
## 0.3.0
|
|
4
|
+
|
|
5
|
+
- `curtailment.wastedWind(date, opts)`: Octopus's "Wasted Wind" definition from the settlement
|
|
6
|
+
bid/offer stacks — SO-flagged wind bids (MWh, £ at bid price) plus the curtailed volume
|
|
7
|
+
re-bought from the cheapest non-SO offers. Matches wastedwind.energy to the penny on the
|
|
8
|
+
recorded periods; `aggregateWastedWind()` exported.
|
|
9
|
+
- `balancing.settlementStack(bidOffer, date, sp)` (ISPSTACK) and the `SettlementStackRow` type.
|
|
10
|
+
- Fixtures for 30 Sep 2026 SP20–21; 5 new tests.
|
|
11
|
+
|
|
3
12
|
## 0.2.0
|
|
4
13
|
|
|
5
14
|
- `curtailment.forDay(date, opts)`: bid acceptances per unit per settlement period joined
|
package/LICENSE
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 Joshua Serpis
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Joshua Serpis
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -69,6 +69,21 @@ await ex.balancing.cashflows("bid", "2026-10-15", { bmUnit: ["T_AKGLW-3"] });
|
|
|
69
69
|
await ex.reference.units({ fuelType: ["WIND"], gspGroupId: ["_P", "_N"] }); // Scottish wind units
|
|
70
70
|
```
|
|
71
71
|
|
|
72
|
+
### Octopus's "Wasted Wind" figure
|
|
73
|
+
|
|
74
|
+
```ts
|
|
75
|
+
const day = await ex.curtailment.wastedWind("2026-09-30");
|
|
76
|
+
day.total; // { curtailedMwh: 124226, curtailmentCostGbp: 4040737, turnUpMwh, turnUpCostGbp: 25986249, totalCostGbp: 30026986 }
|
|
77
|
+
day.periods[19]; // SP20 (09:30 BST): 2,415.70 MWh, £72,256.84 to switch off, £506,067.61 to replace
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
[wastedwind.energy](https://wastedwind.energy)'s definition, reproduced from the settlement bid
|
|
81
|
+
and offer stacks (`balancing.settlementStack`): SO-flagged wind bids priced at their bid price,
|
|
82
|
+
plus the curtailed volume re-bought from the cheapest non-SO offers in the same period. Two
|
|
83
|
+
requests per settlement period. It is a definition, not settlement truth — see
|
|
84
|
+
[`examples/`](../../examples) for a month reproduced to 0.2% and a comparison with the settled
|
|
85
|
+
view below.
|
|
86
|
+
|
|
72
87
|
A curtailment is a **bid acceptance**: NESO accepts the unit's (negative-priced) bid to reduce
|
|
73
88
|
output, so the unit is *paid* to turn down and `cashflowGbp` is positive. `soFlaggedMwh` is the
|
|
74
89
|
share from acceptances BOALF marks `soFlag: true` — system (constraint) actions rather than energy
|
|
@@ -86,8 +101,8 @@ is returned. A response of the wrong shape throws `UkEnergyError`; an HTTP error
|
|
|
86
101
|
| --- | --- |
|
|
87
102
|
| `datasets` | `stream(code, query)`, `boalf`, `bod`, `fuelinst`, `pn(date, sp, filter)`, `pnStream`, `mels`, `mils` |
|
|
88
103
|
| `reference` | `bmUnits()`, `fuelTypes()`, `units({ fuelType?, gspGroupId?, leadParty? })` |
|
|
89
|
-
| `balancing` | `systemPrices(date, sp?)`, `marketIndex`, `acceptances(date, sp, filter)`, `bidOffers(date, sp, filter)`, `physical(dataset, date, sp, filter)`, `acceptanceVolumes(bidOffer, date, filter)`, `cashflows(bidOffer, date, filter)` |
|
|
90
|
-
| `curtailment` | `forDay(date, { bmUnit?, fuelType?, settlementPeriodFrom?, settlementPeriodTo? })` |
|
|
104
|
+
| `balancing` | `systemPrices(date, sp?)`, `marketIndex`, `acceptances(date, sp, filter)`, `bidOffers(date, sp, filter)`, `physical(dataset, date, sp, filter)`, `acceptanceVolumes(bidOffer, date, filter)`, `cashflows(bidOffer, date, filter)`, `settlementStack(bidOffer, date, sp)` |
|
|
105
|
+
| `curtailment` | `forDay(date, { bmUnit?, fuelType?, settlementPeriodFrom?, settlementPeriodTo? })`, `wastedWind(date, { settlementPeriodFrom?, settlementPeriodTo?, fuelType?, concurrency? })` |
|
|
91
106
|
|
|
92
107
|
Row types are exported: `BoalfRow`, `BodRow`, `FuelinstRow`, `PnRow`, `MelsRow`, `BmUnit`,
|
|
93
108
|
`SystemPriceRow`, `MarketIndexRow`, `AcceptanceRow`, `BidOfferRow`, `PhysicalRow`, plus the
|
package/dist/index.cjs
CHANGED
|
@@ -171,6 +171,123 @@ async function curtailmentForDay(client, settlementDate, opts = {}) {
|
|
|
171
171
|
...aggregateCurtailment(day, { from, to }, units, volumes, cashflows, boalf)
|
|
172
172
|
};
|
|
173
173
|
}
|
|
174
|
+
var DEFAULT_FUEL_TYPES2 = ["WIND"];
|
|
175
|
+
function aggregateWastedWind(settlementDate, range, units, bids, offers, fuelTypes = DEFAULT_FUEL_TYPES2) {
|
|
176
|
+
const fuelById = /* @__PURE__ */ new Map();
|
|
177
|
+
for (const u of units) if (u.elexonBmUnit) fuelById.set(u.elexonBmUnit, u.fuelType);
|
|
178
|
+
const periods = [];
|
|
179
|
+
for (let sp = range.from; sp <= range.to; sp++) {
|
|
180
|
+
const windBids = bids.filter(
|
|
181
|
+
(r) => r.settlementPeriod === sp && r.soFlag === true && r.id != null && fuelTypes.includes(fuelById.get(r.id) ?? "")
|
|
182
|
+
);
|
|
183
|
+
let volume = 0;
|
|
184
|
+
let cost = 0;
|
|
185
|
+
for (const b of windBids) {
|
|
186
|
+
const v = b.volume ?? 0;
|
|
187
|
+
volume += v;
|
|
188
|
+
cost += (b.originalPrice ?? 0) * v;
|
|
189
|
+
}
|
|
190
|
+
const need = Math.abs(volume);
|
|
191
|
+
const stack = offers.filter((r) => r.settlementPeriod === sp && !r.cadlFlag).sort(
|
|
192
|
+
(a, b) => (a.soFlag ? 1 : 0) - (b.soFlag ? 1 : 0) || (a.sequenceNumber ?? 0) - (b.sequenceNumber ?? 0)
|
|
193
|
+
);
|
|
194
|
+
let taken = 0;
|
|
195
|
+
let turnUpCost = 0;
|
|
196
|
+
let used = 0;
|
|
197
|
+
for (const o of need > 0 ? stack : []) {
|
|
198
|
+
used += 1;
|
|
199
|
+
const v = o.volume ?? 0;
|
|
200
|
+
if (taken + v > need) {
|
|
201
|
+
const fraction = Math.min(Math.max((need - taken) / v, 0), 1);
|
|
202
|
+
taken += v * fraction;
|
|
203
|
+
turnUpCost += (o.originalPrice ?? 0) * (v * fraction);
|
|
204
|
+
} else {
|
|
205
|
+
taken += v;
|
|
206
|
+
turnUpCost += (o.originalPrice ?? 0) * v;
|
|
207
|
+
}
|
|
208
|
+
if (taken >= need) break;
|
|
209
|
+
}
|
|
210
|
+
const first = windBids[0] ?? stack[0] ?? bids.find((r) => r.settlementPeriod === sp) ?? offers.find((r) => r.settlementPeriod === sp);
|
|
211
|
+
periods.push({
|
|
212
|
+
settlementDate,
|
|
213
|
+
settlementPeriod: sp,
|
|
214
|
+
startTime: first?.startTime ?? "",
|
|
215
|
+
curtailedMwh: need,
|
|
216
|
+
curtailmentCostGbp: cost,
|
|
217
|
+
turnUpMwh: taken,
|
|
218
|
+
turnUpCostGbp: turnUpCost,
|
|
219
|
+
bids: windBids.length,
|
|
220
|
+
offers: used
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
const total = {
|
|
224
|
+
curtailedMwh: 0,
|
|
225
|
+
curtailmentCostGbp: 0,
|
|
226
|
+
turnUpMwh: 0,
|
|
227
|
+
turnUpCostGbp: 0,
|
|
228
|
+
totalCostGbp: 0
|
|
229
|
+
};
|
|
230
|
+
for (const p of periods) {
|
|
231
|
+
total.curtailedMwh += p.curtailedMwh;
|
|
232
|
+
total.curtailmentCostGbp += p.curtailmentCostGbp;
|
|
233
|
+
total.turnUpMwh += p.turnUpMwh;
|
|
234
|
+
total.turnUpCostGbp += p.turnUpCostGbp;
|
|
235
|
+
}
|
|
236
|
+
total.totalCostGbp = total.curtailmentCostGbp + total.turnUpCostGbp;
|
|
237
|
+
return {
|
|
238
|
+
settlementDate,
|
|
239
|
+
settlementPeriodFrom: range.from,
|
|
240
|
+
settlementPeriodTo: range.to,
|
|
241
|
+
periods,
|
|
242
|
+
total
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
async function wastedWindForDay(client, settlementDate, opts = {}) {
|
|
246
|
+
const day = core.formatDate(settlementDate);
|
|
247
|
+
const last = core.settlementPeriodsInDay(day);
|
|
248
|
+
const from = opts.settlementPeriodFrom ?? 1;
|
|
249
|
+
const to = opts.settlementPeriodTo ?? last;
|
|
250
|
+
if (from < 1 || to > last || from > to) {
|
|
251
|
+
throw new core.UkEnergyError(`Settlement periods ${from}-${to} are outside 1-${last} for ${day}`);
|
|
252
|
+
}
|
|
253
|
+
const concurrency = Math.max(1, opts.concurrency ?? 10);
|
|
254
|
+
const units = await client.reference.bmUnits();
|
|
255
|
+
const jobs = [];
|
|
256
|
+
for (let sp = from; sp <= to; sp++) {
|
|
257
|
+
jobs.push(() => client.balancing.settlementStack("bid", day, sp));
|
|
258
|
+
jobs.push(() => client.balancing.settlementStack("offer", day, sp));
|
|
259
|
+
}
|
|
260
|
+
const results = new Array(jobs.length);
|
|
261
|
+
let next = 0;
|
|
262
|
+
let failed = false;
|
|
263
|
+
const worker = async () => {
|
|
264
|
+
while (next < jobs.length && !failed) {
|
|
265
|
+
const i = next++;
|
|
266
|
+
const job = jobs[i];
|
|
267
|
+
if (!job) continue;
|
|
268
|
+
try {
|
|
269
|
+
results[i] = await job();
|
|
270
|
+
} catch (err) {
|
|
271
|
+
failed = true;
|
|
272
|
+
throw err;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
};
|
|
276
|
+
await Promise.all(Array.from({ length: Math.min(concurrency, jobs.length) }, worker));
|
|
277
|
+
const bids = [];
|
|
278
|
+
const offers = [];
|
|
279
|
+
results.forEach((rows, i) => {
|
|
280
|
+
(i % 2 === 0 ? bids : offers).push(...rows ?? []);
|
|
281
|
+
});
|
|
282
|
+
return aggregateWastedWind(
|
|
283
|
+
day,
|
|
284
|
+
{ from, to },
|
|
285
|
+
units,
|
|
286
|
+
bids,
|
|
287
|
+
offers,
|
|
288
|
+
opts.fuelType ?? DEFAULT_FUEL_TYPES2
|
|
289
|
+
);
|
|
290
|
+
}
|
|
174
291
|
|
|
175
292
|
// src/client.ts
|
|
176
293
|
var BASE_URL = "https://data.elexon.co.uk/bmrs/api/v1";
|
|
@@ -318,6 +435,14 @@ var ElexonClient = class {
|
|
|
318
435
|
`balancing/settlement/indicative/cashflows/all/${bidOffer}/${periodPath(settlementDate, filter.settlementPeriod)}`,
|
|
319
436
|
filter.bmUnit
|
|
320
437
|
),
|
|
438
|
+
/**
|
|
439
|
+
* Settlement bid or offer stack for one period (ISPSTACK): every accepted action with
|
|
440
|
+
* `originalPrice`, `volume` (negative for bids), `soFlag`, `cadlFlag` and the sequence NESO
|
|
441
|
+
* used. This is what Octopus's Wasted Wind tracker is built on.
|
|
442
|
+
*/
|
|
443
|
+
settlementStack: async (bidOffer, settlementDate, settlementPeriod) => this.data(
|
|
444
|
+
`balancing/settlement/stack/all/${bidOffer}/${periodPath(settlementDate, settlementPeriod)}`
|
|
445
|
+
),
|
|
321
446
|
/** PN / QPN / MILS / MELS for every unit in one settlement period. */
|
|
322
447
|
physical: async (dataset, settlementDate, settlementPeriod, filter = {}) => this.data("balancing/physical/all", {
|
|
323
448
|
dataset,
|
|
@@ -331,7 +456,13 @@ var ElexonClient = class {
|
|
|
331
456
|
* Curtailment for a GB settlement day (Europe/London), per unit and per settlement period.
|
|
332
457
|
* Defaults to every WIND unit; the reference list plus three requests per 200 units.
|
|
333
458
|
*/
|
|
334
|
-
forDay: async (settlementDate, opts = {}) => curtailmentForDay(this, settlementDate, opts)
|
|
459
|
+
forDay: async (settlementDate, opts = {}) => curtailmentForDay(this, settlementDate, opts),
|
|
460
|
+
/**
|
|
461
|
+
* Octopus's "Wasted Wind" definition for a settlement day: SO-flagged wind bids from the
|
|
462
|
+
* settlement stack (MWh and £ to switch off) plus the cost of re-buying that energy from
|
|
463
|
+
* the cheapest offers in the same period (£ to replace). Two requests per settlement period.
|
|
464
|
+
*/
|
|
465
|
+
wastedWind: async (settlementDate, opts = {}) => wastedWindForDay(this, settlementDate, opts)
|
|
335
466
|
};
|
|
336
467
|
/** `{data}` endpoints with an optional unit filter, chunked so URLs stay short. */
|
|
337
468
|
async perUnitChunks(path, bmUnit) {
|
|
@@ -354,7 +485,9 @@ exports.DEFAULT_RATE_LIMIT = DEFAULT_RATE_LIMIT;
|
|
|
354
485
|
exports.ElexonClient = ElexonClient;
|
|
355
486
|
exports.MAX_UNITS_PER_REQUEST = MAX_UNITS_PER_REQUEST;
|
|
356
487
|
exports.aggregateCurtailment = aggregateCurtailment;
|
|
488
|
+
exports.aggregateWastedWind = aggregateWastedWind;
|
|
357
489
|
exports.curtailmentForDay = curtailmentForDay;
|
|
358
490
|
exports.unitId = unitId;
|
|
491
|
+
exports.wastedWindForDay = wastedWindForDay;
|
|
359
492
|
//# sourceMappingURL=index.cjs.map
|
|
360
493
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/curtailment.ts","../src/client.ts"],"names":["formatDate","settlementPeriodsInDay","UkEnergyError","formatDateTime","assertSettlementPeriod","HttpClient"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Wind curtailment from settlement data.\n *\n * A curtailment is a *bid acceptance*: NESO accepts a unit's bid to reduce output below its\n * physical notification. Three Elexon feeds describe one:\n *\n * - BOAV (`balancing.acceptanceVolumes(\"bid\", day)`): the settled volume of each acceptance in\n * each settlement period, in MWh (negative for bids). This is the quantity curtailed.\n * - EBOCF (`balancing.cashflows(\"bid\", day)`): the £ paid to the unit per period. Wind bids are\n * priced negative (the unit pays to keep generating, so is *paid* to stop), hence the\n * cashflow is positive and is what consumers fund through BSUoS.\n * - BOALF (`datasets.boalf`): the acceptance's level profile and flags. `soFlag` marks\n * actions taken for system reasons (transmission constraints: the classic \"wind farm paid\n * to switch off because the cable to England is full\"); unflagged acceptances are energy\n * balancing. `rrFlag`/`storFlag`/`deemedBoFlag` are counted, not excluded.\n *\n * `curtailmentForDay` joins the three by (unit, period) and (acceptance number) and adds the\n * unit's fuel type, GSP group and capacity from the reference list.\n *\n * Contains BMRS data © Elexon Limited copyright and database right 2026.\n */\nimport { type DateInput, formatDate, settlementPeriodsInDay, UkEnergyError } from \"@uk-energy/core\";\nimport { chunk, type ElexonClient, MAX_UNITS_PER_REQUEST, unitId } from \"./client.js\";\nimport type { AcceptanceVolumeRow, BmUnit, BoalfRow, CashflowRow } from \"./types.js\";\n\nexport interface CurtailmentOptions {\n /** Units to include. Default: every unit whose `fuelType` is in `fuelType`. */\n bmUnit?: string[];\n /** Used when `bmUnit` is omitted. Default `[\"WIND\"]`. */\n fuelType?: string[];\n /** Restrict to a range of settlement periods (1-based, inclusive). Default: the whole day. */\n settlementPeriodFrom?: number;\n settlementPeriodTo?: number;\n}\n\n/** One unit in one settlement period. */\nexport interface CurtailmentPeriod {\n settlementDate: string;\n settlementPeriod: number;\n /** UTC start of the period, from the API. */\n startTime: string;\n bmUnit: string;\n nationalGridBmUnit: string | null;\n leadPartyName: string | null;\n fuelType: string | null;\n gspGroupId: string | null;\n /** `generationCapacity` from the reference list, MW; null when unknown. */\n capacityMw: number | null;\n /** MWh curtailed in the period (positive). */\n curtailedMwh: number;\n /** Of which from SO-flagged (system / constraint) acceptances. */\n soFlaggedMwh: number;\n /**\n * £ paid to the unit for the period's bids (EBOCF); null when no cashflow row was published\n * yet. Cashflow rows for periods with no settled bid volume are not reported anywhere.\n */\n cashflowGbp: number | null;\n /** Distinct acceptances contributing to the period. */\n acceptances: number;\n /** Acceptances in the period that BOALF reported with `soFlag: true`. */\n soFlaggedAcceptances: number;\n}\n\n/** A unit's totals over the day. */\nexport interface CurtailmentUnit {\n bmUnit: string;\n nationalGridBmUnit: string | null;\n leadPartyName: string | null;\n fuelType: string | null;\n gspGroupId: string | null;\n capacityMw: number | null;\n curtailedMwh: number;\n soFlaggedMwh: number;\n cashflowGbp: number;\n /** Settlement periods with any curtailment. */\n periods: number;\n /** Distinct acceptances over the day (one acceptance can span several periods). */\n acceptances: number;\n}\n\nexport interface CurtailmentReport {\n settlementDate: string;\n settlementPeriodFrom: number;\n settlementPeriodTo: number;\n /** Units that were queried (whether or not they were curtailed). */\n unitsQueried: number;\n /** Rows ordered by settlement period then unit id; only (unit, period) pairs with volume. */\n periods: CurtailmentPeriod[];\n /** Per-unit totals, largest `curtailedMwh` first. */\n units: CurtailmentUnit[];\n total: {\n curtailedMwh: number;\n soFlaggedMwh: number;\n cashflowGbp: number;\n /** Units with any curtailment. */\n units: number;\n /** Distinct acceptances. */\n acceptances: number;\n };\n}\n\nconst DEFAULT_FUEL_TYPES = [\"WIND\"];\n\n/** Code-point order (not locale order) so TS and Python sort \"C__X\" and \"CD\" the same way. */\nfunction compareIds(a: string, b: string): number {\n return a < b ? -1 : a > b ? 1 : 0;\n}\n\nfunction num(s: string | null | undefined): number | null {\n if (s == null || s === \"\") return null;\n const n = Number(s);\n return Number.isFinite(n) ? n : null;\n}\n\n/** Pure aggregation over already-fetched rows; exported for tests and for callers with their own data. */\nexport function aggregateCurtailment(\n settlementDate: string,\n range: { from: number; to: number },\n units: readonly BmUnit[],\n volumes: readonly AcceptanceVolumeRow[],\n cashflows: readonly CashflowRow[],\n boalf: readonly BoalfRow[],\n): Omit<CurtailmentReport, \"unitsQueried\"> {\n const byId = new Map<string, BmUnit>();\n for (const u of units) {\n if (u.elexonBmUnit) byId.set(u.elexonBmUnit, u);\n if (u.nationalGridBmUnit) byId.set(u.nationalGridBmUnit, u);\n }\n const soByAcceptance = new Map<number, boolean>();\n for (const r of boalf) {\n if (r.acceptanceNumber != null) {\n soByAcceptance.set(\n r.acceptanceNumber,\n (soByAcceptance.get(r.acceptanceNumber) ?? false) || r.soFlag === true,\n );\n }\n }\n const cashByKey = new Map<string, number>();\n for (const c of cashflows) {\n const sp = c.settlementPeriod;\n if (!c.bmUnit || sp === undefined || sp < range.from || sp > range.to) continue;\n const key = `${c.bmUnit}|${sp}`;\n cashByKey.set(key, (cashByKey.get(key) ?? 0) + (c.totalCashflow ?? 0));\n }\n\n const periods = new Map<string, CurtailmentPeriod>();\n const seen = new Map<string, Set<number>>();\n for (const v of volumes) {\n const sp = v.settlementPeriod;\n const acceptance = v.acceptanceId;\n if (!v.bmUnit || sp === undefined || acceptance === undefined) continue;\n if (sp < range.from || sp > range.to) continue;\n const mwh = -(v.totalVolumeAccepted ?? 0);\n if (!(mwh > 0)) continue;\n const key = `${v.bmUnit}|${sp}`;\n let p = periods.get(key);\n let ids = seen.get(key);\n if (!p || !ids) {\n const ref = byId.get(v.bmUnit);\n p = {\n settlementDate: v.settlementDate ?? settlementDate,\n settlementPeriod: sp,\n startTime: v.startTime ?? \"\",\n bmUnit: v.bmUnit,\n nationalGridBmUnit: v.nationalGridBmUnit ?? ref?.nationalGridBmUnit ?? null,\n leadPartyName: v.leadPartyName ?? ref?.leadPartyName ?? null,\n fuelType: ref?.fuelType ?? null,\n gspGroupId: ref?.gspGroupId ?? null,\n capacityMw: num(ref?.generationCapacity),\n curtailedMwh: 0,\n soFlaggedMwh: 0,\n cashflowGbp: cashByKey.get(key) ?? null,\n acceptances: 0,\n soFlaggedAcceptances: 0,\n };\n ids = new Set();\n periods.set(key, p);\n seen.set(key, ids);\n }\n p.curtailedMwh += mwh;\n const so = soByAcceptance.get(acceptance) === true;\n if (so) p.soFlaggedMwh += mwh;\n if (!ids.has(acceptance)) {\n ids.add(acceptance);\n p.acceptances += 1;\n if (so) p.soFlaggedAcceptances += 1;\n }\n }\n\n const rows = [...periods.values()].sort(\n (a, b) => a.settlementPeriod - b.settlementPeriod || compareIds(a.bmUnit, b.bmUnit),\n );\n\n const unitTotals = new Map<string, CurtailmentUnit>();\n const unitAcceptances = new Map<string, Set<number>>();\n const allAcceptances = new Set<number>();\n const total = { curtailedMwh: 0, soFlaggedMwh: 0, cashflowGbp: 0, units: 0, acceptances: 0 };\n for (const p of rows) {\n let u = unitTotals.get(p.bmUnit);\n if (!u) {\n u = {\n bmUnit: p.bmUnit,\n nationalGridBmUnit: p.nationalGridBmUnit,\n leadPartyName: p.leadPartyName,\n fuelType: p.fuelType,\n gspGroupId: p.gspGroupId,\n capacityMw: p.capacityMw,\n curtailedMwh: 0,\n soFlaggedMwh: 0,\n cashflowGbp: 0,\n periods: 0,\n acceptances: 0,\n };\n unitTotals.set(p.bmUnit, u);\n }\n u.curtailedMwh += p.curtailedMwh;\n u.soFlaggedMwh += p.soFlaggedMwh;\n u.cashflowGbp += p.cashflowGbp ?? 0;\n u.periods += 1;\n const ids = seen.get(`${p.bmUnit}|${p.settlementPeriod}`) ?? new Set<number>();\n let acc = unitAcceptances.get(p.bmUnit);\n if (!acc) {\n acc = new Set();\n unitAcceptances.set(p.bmUnit, acc);\n }\n for (const id of ids) {\n acc.add(id);\n allAcceptances.add(id);\n }\n u.acceptances = acc.size;\n total.curtailedMwh += p.curtailedMwh;\n total.soFlaggedMwh += p.soFlaggedMwh;\n total.cashflowGbp += p.cashflowGbp ?? 0;\n }\n total.units = unitTotals.size;\n total.acceptances = allAcceptances.size;\n\n return {\n settlementDate,\n settlementPeriodFrom: range.from,\n settlementPeriodTo: range.to,\n periods: rows,\n units: [...unitTotals.values()].sort((a, b) => b.curtailedMwh - a.curtailedMwh),\n total,\n };\n}\n\nexport async function curtailmentForDay(\n client: ElexonClient,\n settlementDate: DateInput,\n opts: CurtailmentOptions = {},\n): Promise<CurtailmentReport> {\n const day = formatDate(settlementDate);\n const last = settlementPeriodsInDay(day);\n const from = opts.settlementPeriodFrom ?? 1;\n const to = opts.settlementPeriodTo ?? last;\n if (from < 1 || to > last || from > to) {\n throw new UkEnergyError(`Settlement periods ${from}-${to} are outside 1-${last} for ${day}`);\n }\n\n const units = await client.reference.bmUnits();\n let ids: string[];\n if (opts.bmUnit) {\n ids = opts.bmUnit;\n } else {\n const fuel = opts.fuelType ?? DEFAULT_FUEL_TYPES;\n ids = units\n .filter((u) => u.fuelType != null && fuel.includes(u.fuelType))\n .map(unitId)\n .filter((id) => id !== \"\");\n }\n ids = [...new Set(ids)]; // the reference list has duplicates (T_WLNYO-4 twice on 20 Oct 2026)\n if (ids.length === 0) throw new UkEnergyError(\"No BM units to query\");\n\n // BOALF is fetched for the whole day, not just from..to: an acceptance's level rows are\n // keyed by the periods they span, so one that started earlier would otherwise lose its flag.\n const [volumes, cashflows, boalfParts] = await Promise.all([\n client.balancing.acceptanceVolumes(\"bid\", day, { bmUnit: ids }),\n client.balancing.cashflows(\"bid\", day, { bmUnit: ids }),\n Promise.all(\n chunk(ids, MAX_UNITS_PER_REQUEST).map((bmUnit) =>\n client.datasets.boalf({\n from: day,\n to: day,\n settlementPeriodFrom: 1,\n settlementPeriodTo: last,\n bmUnit,\n }),\n ),\n ),\n ]);\n const boalf = boalfParts.flat();\n\n return {\n unitsQueried: ids.length,\n ...aggregateCurtailment(day, { from, to }, units, volumes, cashflows, boalf),\n };\n}\n","import {\n assertSettlementPeriod,\n type DateInput,\n formatDate,\n formatDateTime,\n HttpClient,\n type HttpClientOptions,\n type Query,\n type RateLimit,\n UkEnergyError,\n} from \"@uk-energy/core\";\nimport {\n type CurtailmentOptions,\n type CurtailmentReport,\n curtailmentForDay,\n} from \"./curtailment.js\";\nimport type {\n AcceptanceRow,\n AcceptanceVolumeRow,\n BidOffer,\n BidOfferRow,\n BmUnit,\n BoalfRow,\n BodRow,\n CashflowRow,\n DatasetQuery,\n DatasetRow,\n FuelinstRow,\n MarketIndexRow,\n MelsRow,\n PhysicalDataset,\n PhysicalRow,\n PnRow,\n StreamDataset,\n SystemPriceRow,\n} from \"./types.js\";\n\nexport const BASE_URL = \"https://data.elexon.co.uk/bmrs/api/v1\";\n\n/**\n * No limit is published and none was observed: 310 requests at ~120 req/s on 20 Oct 2026 all\n * returned 200 with no rate-limit headers (build-board d02). 10 rps / burst 20 keeps us a\n * polite order of magnitude below that; raise it per client if you need to.\n */\nexport const DEFAULT_RATE_LIMIT: RateLimit = { requestsPerSecond: 10, burst: 20 };\n\n/** Most datasets settle within minutes; a short default TTL avoids hammering on retries. */\nexport const DEFAULT_CACHE_TTL_MS = 5 * 60 * 1000;\n\nexport type ElexonClientOptions = Partial<Omit<HttpClientOptions, \"baseUrl\">> & {\n baseUrl?: string;\n};\n\n/**\n * Time window for the dataset endpoints. With `settlementPeriodFrom`/`To` set, the API treats\n * `from`/`to` as settlement *dates* (time ignored) and filters by period instead.\n */\nexport interface SettlementWindow {\n from: DateInput;\n to: DateInput;\n settlementPeriodFrom?: number;\n settlementPeriodTo?: number;\n}\n\nexport interface UnitFilter {\n /** Elexon BM unit ids, e.g. \"T_ARCHW-1\". Omit for all units. */\n bmUnit?: string[];\n}\n\n/** Client-side filter over the BM unit reference list. All given fields must match. */\nexport interface UnitsFilter {\n /** e.g. [\"WIND\"]. */\n fuelType?: string[];\n /** GSP group ids, e.g. [\"_P\", \"_N\"] (north / south Scotland). Most wind units have none. */\n gspGroupId?: string[];\n /** Case-insensitive substring of `leadPartyName`. */\n leadParty?: string;\n}\n\n/** The id to query a unit by: `elexonBmUnit`, or `nationalGridBmUnit` for units without one (48 wind units). */\nexport function unitId(u: BmUnit): string {\n return u.elexonBmUnit || u.nationalGridBmUnit || \"\";\n}\n\n/** The API accepts the whole wind fleet (287 ids, ~4.9 KB URL); chunk well below that. */\nexport const MAX_UNITS_PER_REQUEST = 200;\n\nexport function chunk<T>(items: readonly T[], size: number): T[][] {\n const out: T[][] = [];\n for (let i = 0; i < items.length; i += size) out.push(items.slice(i, i + size));\n return out;\n}\n\nexport type FuelinstQuery = (\n | { publishDateTimeFrom: DateInput; publishDateTimeTo: DateInput }\n | { settlementDateFrom: DateInput; settlementDateTo: DateInput; settlementPeriod?: number[] }\n) & { fuelType?: string[] };\n\ninterface Envelope<T> {\n data: T;\n metadata?: { datasets?: string[] };\n}\n\nfunction windowQuery(w: SettlementWindow): Query {\n const q: Query = { from: formatDateTime(w.from), to: formatDateTime(w.to) };\n if (w.settlementPeriodFrom !== undefined) {\n assertSettlementPeriod(w.settlementPeriodFrom);\n q.settlementPeriodFrom = w.settlementPeriodFrom;\n }\n if (w.settlementPeriodTo !== undefined) {\n assertSettlementPeriod(w.settlementPeriodTo);\n q.settlementPeriodTo = w.settlementPeriodTo;\n }\n return q;\n}\n\nfunction periodQuery(settlementDate: DateInput, settlementPeriod: number): Query {\n assertSettlementPeriod(settlementPeriod);\n return { settlementDate: formatDate(settlementDate), settlementPeriod };\n}\n\n/**\n * Client for Elexon Insights (BMRS). Methods return the API's own row shapes, typed from its\n * OpenAPI spec and unwrapped from the `{ data }` envelope where there is one.\n *\n * const ex = new ElexonClient();\n * const rows = await ex.datasets.boalf({ from: \"2026-10-15T00:00Z\", to: \"2026-10-15T01:00Z\" });\n * const wind = (await ex.reference.bmUnits()).filter((u) => u.fuelType === \"WIND\");\n *\n * Contains BMRS data © Elexon Limited copyright and database right 2026. Keep the attribution.\n */\nexport class ElexonClient {\n readonly http: HttpClient;\n\n constructor(opts: ElexonClientOptions = {}) {\n this.http = new HttpClient({\n baseUrl: BASE_URL,\n rateLimit: DEFAULT_RATE_LIMIT,\n cacheTtlMs: opts.cache ? DEFAULT_CACHE_TTL_MS : 0,\n ...opts,\n });\n }\n\n /** Endpoints returning a bare array. */\n private async list<T>(path: string, query?: Query): Promise<T[]> {\n const res = await this.http.get<T[]>(path, query);\n if (!Array.isArray(res)) throw new UkEnergyError(`Unexpected response shape for ${path}`);\n return res;\n }\n\n /** Endpoints returning `{ data: [...] }`. */\n private async data<T>(path: string, query?: Query): Promise<T[]> {\n const res = await this.http.get<Envelope<T[]>>(path, query);\n if (!res || !Array.isArray(res.data)) {\n throw new UkEnergyError(`Unexpected response shape for ${path}`);\n }\n return res.data;\n }\n\n /** `/datasets/{code}` — raw datasets. The `/stream` variants are used: lighter, no envelope. */\n readonly datasets = {\n /**\n * Any stream dataset (77 codes: BOALF, BOD, FUELINST, PN, MELS, WINDFOR, FREQ, …), with the\n * query parameters typed from the spec. Values are sent as given (strings in API format,\n * \"YYYY-MM-DDTHH:MMZ\" or \"YYYY-MM-DD\"); the named methods below format and validate dates.\n */\n stream: async <D extends StreamDataset>(\n dataset: D,\n query: DatasetQuery<D>,\n ): Promise<DatasetRow<D>[]> =>\n this.list<DatasetRow<D>>(`datasets/${dataset}/stream`, query as unknown as Query),\n\n /** Bid-offer acceptance levels (BOALF). Curtailment shows as `soFlag: true` bids. */\n boalf: async (w: SettlementWindow & UnitFilter): Promise<BoalfRow[]> =>\n this.list(\"datasets/BOALF/stream\", { ...windowQuery(w), bmUnit: w.bmUnit }),\n\n /** Bid-offer data (BOD): submitted bid and offer prices per pair. */\n bod: async (w: SettlementWindow & UnitFilter): Promise<BodRow[]> =>\n this.list(\"datasets/BOD/stream\", { ...windowQuery(w), bmUnit: w.bmUnit }),\n\n /** Generation by fuel type at 5-minute resolution (FUELINST). */\n fuelinst: async (q: FuelinstQuery): Promise<FuelinstRow[]> => {\n const query: Query = { fuelType: q.fuelType };\n if (\"publishDateTimeFrom\" in q) {\n query.publishDateTimeFrom = formatDateTime(q.publishDateTimeFrom);\n query.publishDateTimeTo = formatDateTime(q.publishDateTimeTo);\n } else {\n query.settlementDateFrom = formatDate(q.settlementDateFrom);\n query.settlementDateTo = formatDate(q.settlementDateTo);\n if (q.settlementPeriod) {\n for (const p of q.settlementPeriod) assertSettlementPeriod(p);\n query.settlementPeriod = q.settlementPeriod;\n }\n }\n return this.list(\"datasets/FUELINST/stream\", query);\n },\n\n /** Physical notifications for one settlement period (`/datasets/PN` takes exactly one). */\n pn: async (\n settlementDate: DateInput,\n settlementPeriod: number,\n filter: UnitFilter = {},\n ): Promise<PnRow[]> =>\n this.data(\"datasets/PN\", {\n ...periodQuery(settlementDate, settlementPeriod),\n bmUnit: filter.bmUnit,\n }),\n\n /** Physical notifications over a window (`/datasets/PN/stream`). */\n pnStream: async (w: SettlementWindow & UnitFilter): Promise<PnRow[]> =>\n this.list(\"datasets/PN/stream\", { ...windowQuery(w), bmUnit: w.bmUnit }),\n\n /** Maximum export limits (MELS). */\n mels: async (w: SettlementWindow & UnitFilter): Promise<MelsRow[]> =>\n this.list(\"datasets/MELS/stream\", { ...windowQuery(w), bmUnit: w.bmUnit }),\n\n /** Minimum import limits (MILS). */\n mils: async (w: SettlementWindow & UnitFilter): Promise<MelsRow[]> =>\n this.list(\"datasets/MILS/stream\", { ...windowQuery(w), bmUnit: w.bmUnit }),\n };\n\n /** Reference data. */\n readonly reference = {\n /** All BM units (~3,100). Capacities are strings; `fuelType` is null for many non-generators. */\n bmUnits: async (): Promise<BmUnit[]> => this.list(\"reference/bmunits/all\"),\n /** Fuel type codes used across the API. */\n fuelTypes: async (): Promise<string[]> => this.list(\"reference/fueltypes/all\"),\n /** BM units matching a filter, e.g. `{ fuelType: [\"WIND\"] }` (287 units). */\n units: async (filter: UnitsFilter = {}): Promise<BmUnit[]> => {\n const party = filter.leadParty?.toLowerCase();\n return (await this.reference.bmUnits()).filter(\n (u) =>\n (!filter.fuelType || (u.fuelType != null && filter.fuelType.includes(u.fuelType))) &&\n (!filter.gspGroupId ||\n (u.gspGroupId != null && filter.gspGroupId.includes(u.gspGroupId))) &&\n (!party || (u.leadPartyName ?? \"\").toLowerCase().includes(party)),\n );\n },\n };\n\n /** Market-wide balancing views (one settlement period per call) and settlement prices. */\n readonly balancing = {\n /** System buy/sell prices for a settlement day, or one period of it (DISEBSP). */\n systemPrices: async (\n settlementDate: DateInput,\n settlementPeriod?: number,\n ): Promise<SystemPriceRow[]> =>\n this.data(\n `balancing/settlement/system-prices/${periodPath(settlementDate, settlementPeriod)}`,\n ),\n\n /** Market index (MID) prices and volumes. */\n marketIndex: async (\n w: SettlementWindow & { dataProviders?: string[] },\n ): Promise<MarketIndexRow[]> =>\n this.data(\"balancing/pricing/market-index\", {\n ...windowQuery(w),\n dataProviders: w.dataProviders,\n }),\n\n /** Every acceptance in one settlement period. */\n acceptances: async (\n settlementDate: DateInput,\n settlementPeriod: number,\n filter: UnitFilter = {},\n ): Promise<AcceptanceRow[]> =>\n this.data(\"balancing/acceptances/all\", {\n ...periodQuery(settlementDate, settlementPeriod),\n bmUnit: filter.bmUnit,\n }),\n\n /** Every bid-offer pair in one settlement period. */\n bidOffers: async (\n settlementDate: DateInput,\n settlementPeriod: number,\n filter: UnitFilter = {},\n ): Promise<BidOfferRow[]> =>\n this.data(\"balancing/bid-offer/all\", {\n ...periodQuery(settlementDate, settlementPeriod),\n bmUnit: filter.bmUnit,\n }),\n\n /**\n * Settled acceptance volumes (BOAV) for a settlement day, or one period of it: one row per\n * acceptance per period, `totalVolumeAccepted` in MWh (negative for bids). Long unit lists\n * are split across requests.\n */\n acceptanceVolumes: async (\n bidOffer: BidOffer,\n settlementDate: DateInput,\n filter: UnitFilter & { settlementPeriod?: number } = {},\n ): Promise<AcceptanceVolumeRow[]> =>\n this.perUnitChunks(\n `balancing/settlement/acceptance/volumes/all/${bidOffer}/${periodPath(settlementDate, filter.settlementPeriod)}`,\n filter.bmUnit,\n ),\n /**\n * Indicative bid/offer cashflows (EBOCF) for a settlement day or one period: one row per unit\n * per period, `totalCashflow` in £ paid to the unit (positive for a wind bid at a negative\n * price).\n */\n cashflows: async (\n bidOffer: BidOffer,\n settlementDate: DateInput,\n filter: UnitFilter & { settlementPeriod?: number } = {},\n ): Promise<CashflowRow[]> =>\n this.perUnitChunks(\n `balancing/settlement/indicative/cashflows/all/${bidOffer}/${periodPath(settlementDate, filter.settlementPeriod)}`,\n filter.bmUnit,\n ),\n /** PN / QPN / MILS / MELS for every unit in one settlement period. */\n physical: async (\n dataset: PhysicalDataset,\n settlementDate: DateInput,\n settlementPeriod: number,\n filter: UnitFilter = {},\n ): Promise<PhysicalRow[]> =>\n this.data(\"balancing/physical/all\", {\n dataset,\n ...periodQuery(settlementDate, settlementPeriod),\n bmUnit: filter.bmUnit,\n }),\n };\n\n /** Wind curtailment: SO-flagged and energy bid acceptances per unit per period, with £. */\n readonly curtailment = {\n /**\n * Curtailment for a GB settlement day (Europe/London), per unit and per settlement period.\n * Defaults to every WIND unit; the reference list plus three requests per 200 units.\n */\n forDay: async (\n settlementDate: DateInput,\n opts: CurtailmentOptions = {},\n ): Promise<CurtailmentReport> => curtailmentForDay(this, settlementDate, opts),\n };\n\n /** `{data}` endpoints with an optional unit filter, chunked so URLs stay short. */\n private async perUnitChunks<T>(path: string, bmUnit?: string[]): Promise<T[]> {\n if (!bmUnit?.length) return this.data<T>(path);\n const parts = await Promise.all(\n chunk(bmUnit, MAX_UNITS_PER_REQUEST).map((ids) => this.data<T>(path, { bmUnit: ids })),\n );\n return parts.flat();\n }\n}\n\nfunction periodPath(settlementDate: DateInput, settlementPeriod?: number): string {\n if (settlementPeriod !== undefined) assertSettlementPeriod(settlementPeriod);\n const p = settlementPeriod === undefined ? \"\" : `/${settlementPeriod}`;\n return `${formatDate(settlementDate)}${p}`;\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/curtailment.ts","../src/wasted-wind.ts","../src/client.ts"],"names":["formatDate","settlementPeriodsInDay","UkEnergyError","DEFAULT_FUEL_TYPES","formatDateTime","assertSettlementPeriod","HttpClient"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Wind curtailment from settlement data.\n *\n * A curtailment is a *bid acceptance*: NESO accepts a unit's bid to reduce output below its\n * physical notification. Three Elexon feeds describe one:\n *\n * - BOAV (`balancing.acceptanceVolumes(\"bid\", day)`): the settled volume of each acceptance in\n * each settlement period, in MWh (negative for bids). This is the quantity curtailed.\n * - EBOCF (`balancing.cashflows(\"bid\", day)`): the £ paid to the unit per period. Wind bids are\n * priced negative (the unit pays to keep generating, so is *paid* to stop), hence the\n * cashflow is positive and is what consumers fund through BSUoS.\n * - BOALF (`datasets.boalf`): the acceptance's level profile and flags. `soFlag` marks\n * actions taken for system reasons (transmission constraints: the classic \"wind farm paid\n * to switch off because the cable to England is full\"); unflagged acceptances are energy\n * balancing. `rrFlag`/`storFlag`/`deemedBoFlag` are counted, not excluded.\n *\n * `curtailmentForDay` joins the three by (unit, period) and (acceptance number) and adds the\n * unit's fuel type, GSP group and capacity from the reference list.\n *\n * Contains BMRS data © Elexon Limited copyright and database right 2026.\n */\nimport { type DateInput, formatDate, settlementPeriodsInDay, UkEnergyError } from \"@uk-energy/core\";\nimport { chunk, type ElexonClient, MAX_UNITS_PER_REQUEST, unitId } from \"./client.js\";\nimport type { AcceptanceVolumeRow, BmUnit, BoalfRow, CashflowRow } from \"./types.js\";\n\nexport interface CurtailmentOptions {\n /** Units to include. Default: every unit whose `fuelType` is in `fuelType`. */\n bmUnit?: string[];\n /** Used when `bmUnit` is omitted. Default `[\"WIND\"]`. */\n fuelType?: string[];\n /** Restrict to a range of settlement periods (1-based, inclusive). Default: the whole day. */\n settlementPeriodFrom?: number;\n settlementPeriodTo?: number;\n}\n\n/** One unit in one settlement period. */\nexport interface CurtailmentPeriod {\n settlementDate: string;\n settlementPeriod: number;\n /** UTC start of the period, from the API. */\n startTime: string;\n bmUnit: string;\n nationalGridBmUnit: string | null;\n leadPartyName: string | null;\n fuelType: string | null;\n gspGroupId: string | null;\n /** `generationCapacity` from the reference list, MW; null when unknown. */\n capacityMw: number | null;\n /** MWh curtailed in the period (positive). */\n curtailedMwh: number;\n /** Of which from SO-flagged (system / constraint) acceptances. */\n soFlaggedMwh: number;\n /**\n * £ paid to the unit for the period's bids (EBOCF); null when no cashflow row was published\n * yet. Cashflow rows for periods with no settled bid volume are not reported anywhere.\n */\n cashflowGbp: number | null;\n /** Distinct acceptances contributing to the period. */\n acceptances: number;\n /** Acceptances in the period that BOALF reported with `soFlag: true`. */\n soFlaggedAcceptances: number;\n}\n\n/** A unit's totals over the day. */\nexport interface CurtailmentUnit {\n bmUnit: string;\n nationalGridBmUnit: string | null;\n leadPartyName: string | null;\n fuelType: string | null;\n gspGroupId: string | null;\n capacityMw: number | null;\n curtailedMwh: number;\n soFlaggedMwh: number;\n cashflowGbp: number;\n /** Settlement periods with any curtailment. */\n periods: number;\n /** Distinct acceptances over the day (one acceptance can span several periods). */\n acceptances: number;\n}\n\nexport interface CurtailmentReport {\n settlementDate: string;\n settlementPeriodFrom: number;\n settlementPeriodTo: number;\n /** Units that were queried (whether or not they were curtailed). */\n unitsQueried: number;\n /** Rows ordered by settlement period then unit id; only (unit, period) pairs with volume. */\n periods: CurtailmentPeriod[];\n /** Per-unit totals, largest `curtailedMwh` first. */\n units: CurtailmentUnit[];\n total: {\n curtailedMwh: number;\n soFlaggedMwh: number;\n cashflowGbp: number;\n /** Units with any curtailment. */\n units: number;\n /** Distinct acceptances. */\n acceptances: number;\n };\n}\n\nconst DEFAULT_FUEL_TYPES = [\"WIND\"];\n\n/** Code-point order (not locale order) so TS and Python sort \"C__X\" and \"CD\" the same way. */\nfunction compareIds(a: string, b: string): number {\n return a < b ? -1 : a > b ? 1 : 0;\n}\n\nfunction num(s: string | null | undefined): number | null {\n if (s == null || s === \"\") return null;\n const n = Number(s);\n return Number.isFinite(n) ? n : null;\n}\n\n/** Pure aggregation over already-fetched rows; exported for tests and for callers with their own data. */\nexport function aggregateCurtailment(\n settlementDate: string,\n range: { from: number; to: number },\n units: readonly BmUnit[],\n volumes: readonly AcceptanceVolumeRow[],\n cashflows: readonly CashflowRow[],\n boalf: readonly BoalfRow[],\n): Omit<CurtailmentReport, \"unitsQueried\"> {\n const byId = new Map<string, BmUnit>();\n for (const u of units) {\n if (u.elexonBmUnit) byId.set(u.elexonBmUnit, u);\n if (u.nationalGridBmUnit) byId.set(u.nationalGridBmUnit, u);\n }\n const soByAcceptance = new Map<number, boolean>();\n for (const r of boalf) {\n if (r.acceptanceNumber != null) {\n soByAcceptance.set(\n r.acceptanceNumber,\n (soByAcceptance.get(r.acceptanceNumber) ?? false) || r.soFlag === true,\n );\n }\n }\n const cashByKey = new Map<string, number>();\n for (const c of cashflows) {\n const sp = c.settlementPeriod;\n if (!c.bmUnit || sp === undefined || sp < range.from || sp > range.to) continue;\n const key = `${c.bmUnit}|${sp}`;\n cashByKey.set(key, (cashByKey.get(key) ?? 0) + (c.totalCashflow ?? 0));\n }\n\n const periods = new Map<string, CurtailmentPeriod>();\n const seen = new Map<string, Set<number>>();\n for (const v of volumes) {\n const sp = v.settlementPeriod;\n const acceptance = v.acceptanceId;\n if (!v.bmUnit || sp === undefined || acceptance === undefined) continue;\n if (sp < range.from || sp > range.to) continue;\n const mwh = -(v.totalVolumeAccepted ?? 0);\n if (!(mwh > 0)) continue;\n const key = `${v.bmUnit}|${sp}`;\n let p = periods.get(key);\n let ids = seen.get(key);\n if (!p || !ids) {\n const ref = byId.get(v.bmUnit);\n p = {\n settlementDate: v.settlementDate ?? settlementDate,\n settlementPeriod: sp,\n startTime: v.startTime ?? \"\",\n bmUnit: v.bmUnit,\n nationalGridBmUnit: v.nationalGridBmUnit ?? ref?.nationalGridBmUnit ?? null,\n leadPartyName: v.leadPartyName ?? ref?.leadPartyName ?? null,\n fuelType: ref?.fuelType ?? null,\n gspGroupId: ref?.gspGroupId ?? null,\n capacityMw: num(ref?.generationCapacity),\n curtailedMwh: 0,\n soFlaggedMwh: 0,\n cashflowGbp: cashByKey.get(key) ?? null,\n acceptances: 0,\n soFlaggedAcceptances: 0,\n };\n ids = new Set();\n periods.set(key, p);\n seen.set(key, ids);\n }\n p.curtailedMwh += mwh;\n const so = soByAcceptance.get(acceptance) === true;\n if (so) p.soFlaggedMwh += mwh;\n if (!ids.has(acceptance)) {\n ids.add(acceptance);\n p.acceptances += 1;\n if (so) p.soFlaggedAcceptances += 1;\n }\n }\n\n const rows = [...periods.values()].sort(\n (a, b) => a.settlementPeriod - b.settlementPeriod || compareIds(a.bmUnit, b.bmUnit),\n );\n\n const unitTotals = new Map<string, CurtailmentUnit>();\n const unitAcceptances = new Map<string, Set<number>>();\n const allAcceptances = new Set<number>();\n const total = { curtailedMwh: 0, soFlaggedMwh: 0, cashflowGbp: 0, units: 0, acceptances: 0 };\n for (const p of rows) {\n let u = unitTotals.get(p.bmUnit);\n if (!u) {\n u = {\n bmUnit: p.bmUnit,\n nationalGridBmUnit: p.nationalGridBmUnit,\n leadPartyName: p.leadPartyName,\n fuelType: p.fuelType,\n gspGroupId: p.gspGroupId,\n capacityMw: p.capacityMw,\n curtailedMwh: 0,\n soFlaggedMwh: 0,\n cashflowGbp: 0,\n periods: 0,\n acceptances: 0,\n };\n unitTotals.set(p.bmUnit, u);\n }\n u.curtailedMwh += p.curtailedMwh;\n u.soFlaggedMwh += p.soFlaggedMwh;\n u.cashflowGbp += p.cashflowGbp ?? 0;\n u.periods += 1;\n const ids = seen.get(`${p.bmUnit}|${p.settlementPeriod}`) ?? new Set<number>();\n let acc = unitAcceptances.get(p.bmUnit);\n if (!acc) {\n acc = new Set();\n unitAcceptances.set(p.bmUnit, acc);\n }\n for (const id of ids) {\n acc.add(id);\n allAcceptances.add(id);\n }\n u.acceptances = acc.size;\n total.curtailedMwh += p.curtailedMwh;\n total.soFlaggedMwh += p.soFlaggedMwh;\n total.cashflowGbp += p.cashflowGbp ?? 0;\n }\n total.units = unitTotals.size;\n total.acceptances = allAcceptances.size;\n\n return {\n settlementDate,\n settlementPeriodFrom: range.from,\n settlementPeriodTo: range.to,\n periods: rows,\n units: [...unitTotals.values()].sort((a, b) => b.curtailedMwh - a.curtailedMwh),\n total,\n };\n}\n\nexport async function curtailmentForDay(\n client: ElexonClient,\n settlementDate: DateInput,\n opts: CurtailmentOptions = {},\n): Promise<CurtailmentReport> {\n const day = formatDate(settlementDate);\n const last = settlementPeriodsInDay(day);\n const from = opts.settlementPeriodFrom ?? 1;\n const to = opts.settlementPeriodTo ?? last;\n if (from < 1 || to > last || from > to) {\n throw new UkEnergyError(`Settlement periods ${from}-${to} are outside 1-${last} for ${day}`);\n }\n\n const units = await client.reference.bmUnits();\n let ids: string[];\n if (opts.bmUnit) {\n ids = opts.bmUnit;\n } else {\n const fuel = opts.fuelType ?? DEFAULT_FUEL_TYPES;\n ids = units\n .filter((u) => u.fuelType != null && fuel.includes(u.fuelType))\n .map(unitId)\n .filter((id) => id !== \"\");\n }\n ids = [...new Set(ids)]; // the reference list has duplicates (T_WLNYO-4 twice on 20 Oct 2026)\n if (ids.length === 0) throw new UkEnergyError(\"No BM units to query\");\n\n // BOALF is fetched for the whole day, not just from..to: an acceptance's level rows are\n // keyed by the periods they span, so one that started earlier would otherwise lose its flag.\n const [volumes, cashflows, boalfParts] = await Promise.all([\n client.balancing.acceptanceVolumes(\"bid\", day, { bmUnit: ids }),\n client.balancing.cashflows(\"bid\", day, { bmUnit: ids }),\n Promise.all(\n chunk(ids, MAX_UNITS_PER_REQUEST).map((bmUnit) =>\n client.datasets.boalf({\n from: day,\n to: day,\n settlementPeriodFrom: 1,\n settlementPeriodTo: last,\n bmUnit,\n }),\n ),\n ),\n ]);\n const boalf = boalfParts.flat();\n\n return {\n unitsQueried: ids.length,\n ...aggregateCurtailment(day, { from, to }, units, volumes, cashflows, boalf),\n };\n}\n","/**\n * Octopus Energy's \"Wasted Wind\" figure (https://wastedwind.energy), reproduced from the same\n * Elexon data. Per settlement period:\n *\n * - **Switch-off**: rows of the settlement *bid* stack whose unit is `fuelType: \"WIND\"` in the\n * BM unit reference and which carry `soFlag: true` (system / constraint actions). Volume is\n * the sum of their `volume` (negative MWh); cost is Σ `originalPrice × volume`, which is\n * positive for the usual negative-priced wind bid (the farm is paid to stop) and negative in\n * the rare case of a positive bid price.\n * - **Replace**: the curtailed volume bought back from the settlement *offer* stack of the same\n * period, `cadlFlag` offers excluded, walking non-SO offers first then by NESO's\n * `sequenceNumber`, pro-rating the last offer. Cost is Σ `originalPrice × volume taken`.\n *\n * Totals over a day are what the tracker shows as \"£ switching off wind turbines\" and \"£ buying\n * energy elsewhere\"; its monthly summary is the sum over days. This is a *definition* rather\n * than settlement truth: it counts SO-flagged wind bids only (see `curtailment.forDay` for the\n * settled BOAV/EBOCF view of every wind bid) and estimates replacement cost from the offer\n * stack rather than attributing actual offer cashflows.\n *\n * Contains BMRS data © Elexon Limited copyright and database right 2026.\n */\nimport { type DateInput, formatDate, settlementPeriodsInDay, UkEnergyError } from \"@uk-energy/core\";\nimport type { ElexonClient } from \"./client.js\";\nimport type { BmUnit, SettlementStackRow } from \"./types.js\";\n\nexport interface WastedWindOptions {\n /** Restrict to a range of settlement periods (1-based, inclusive). Default: the whole day. */\n settlementPeriodFrom?: number;\n settlementPeriodTo?: number;\n /** Units counted as wind. Default: `fuelType === \"WIND\"` in the reference list. */\n fuelType?: string[];\n /** Stack requests in flight at once (two per period). Default 10. */\n concurrency?: number;\n}\n\nexport interface WastedWindPeriod {\n settlementDate: string;\n settlementPeriod: number;\n /** UTC start of the period, from any stack row of the period; empty when there were none. */\n startTime: string;\n /** MWh of SO-flagged wind bids (positive). */\n curtailedMwh: number;\n /** £ paid to wind units to turn down (Σ originalPrice × volume). */\n curtailmentCostGbp: number;\n /** MWh bought back from the offer stack (≤ curtailedMwh when the stack runs out). */\n turnUpMwh: number;\n /** £ to buy that energy from the offer stack. */\n turnUpCostGbp: number;\n /** Wind bid rows and offer rows that contributed. */\n bids: number;\n offers: number;\n}\n\nexport interface WastedWindReport {\n settlementDate: string;\n settlementPeriodFrom: number;\n settlementPeriodTo: number;\n periods: WastedWindPeriod[];\n total: {\n curtailedMwh: number;\n curtailmentCostGbp: number;\n turnUpMwh: number;\n turnUpCostGbp: number;\n /** curtailmentCostGbp + turnUpCostGbp: the tracker's headline. */\n totalCostGbp: number;\n };\n}\n\nconst DEFAULT_FUEL_TYPES = [\"WIND\"];\n\n/** Pure aggregation; exported for tests and for callers with their own stack rows. */\nexport function aggregateWastedWind(\n settlementDate: string,\n range: { from: number; to: number },\n units: readonly BmUnit[],\n bids: readonly SettlementStackRow[],\n offers: readonly SettlementStackRow[],\n fuelTypes: readonly string[] = DEFAULT_FUEL_TYPES,\n): WastedWindReport {\n const fuelById = new Map<string, string | null | undefined>();\n for (const u of units) if (u.elexonBmUnit) fuelById.set(u.elexonBmUnit, u.fuelType);\n\n const periods: WastedWindPeriod[] = [];\n for (let sp = range.from; sp <= range.to; sp++) {\n const windBids = bids.filter(\n (r) =>\n r.settlementPeriod === sp &&\n r.soFlag === true &&\n r.id != null &&\n fuelTypes.includes(fuelById.get(r.id) ?? \"\"),\n );\n let volume = 0;\n let cost = 0;\n for (const b of windBids) {\n const v = b.volume ?? 0;\n volume += v;\n cost += (b.originalPrice ?? 0) * v;\n }\n const need = Math.abs(volume);\n\n const stack = offers\n .filter((r) => r.settlementPeriod === sp && !r.cadlFlag)\n .sort(\n (a, b) =>\n (a.soFlag ? 1 : 0) - (b.soFlag ? 1 : 0) ||\n (a.sequenceNumber ?? 0) - (b.sequenceNumber ?? 0),\n );\n let taken = 0;\n let turnUpCost = 0;\n let used = 0;\n for (const o of need > 0 ? stack : []) {\n used += 1;\n const v = o.volume ?? 0;\n if (taken + v > need) {\n const fraction = Math.min(Math.max((need - taken) / v, 0), 1);\n taken += v * fraction;\n turnUpCost += (o.originalPrice ?? 0) * (v * fraction);\n } else {\n taken += v;\n turnUpCost += (o.originalPrice ?? 0) * v;\n }\n if (taken >= need) break;\n }\n\n const first =\n windBids[0] ??\n stack[0] ??\n bids.find((r) => r.settlementPeriod === sp) ??\n offers.find((r) => r.settlementPeriod === sp);\n periods.push({\n settlementDate,\n settlementPeriod: sp,\n startTime: first?.startTime ?? \"\",\n curtailedMwh: need,\n curtailmentCostGbp: cost,\n turnUpMwh: taken,\n turnUpCostGbp: turnUpCost,\n bids: windBids.length,\n offers: used,\n });\n }\n\n const total = {\n curtailedMwh: 0,\n curtailmentCostGbp: 0,\n turnUpMwh: 0,\n turnUpCostGbp: 0,\n totalCostGbp: 0,\n };\n for (const p of periods) {\n total.curtailedMwh += p.curtailedMwh;\n total.curtailmentCostGbp += p.curtailmentCostGbp;\n total.turnUpMwh += p.turnUpMwh;\n total.turnUpCostGbp += p.turnUpCostGbp;\n }\n total.totalCostGbp = total.curtailmentCostGbp + total.turnUpCostGbp;\n\n return {\n settlementDate,\n settlementPeriodFrom: range.from,\n settlementPeriodTo: range.to,\n periods,\n total,\n };\n}\n\nexport async function wastedWindForDay(\n client: ElexonClient,\n settlementDate: DateInput,\n opts: WastedWindOptions = {},\n): Promise<WastedWindReport> {\n const day = formatDate(settlementDate);\n const last = settlementPeriodsInDay(day);\n const from = opts.settlementPeriodFrom ?? 1;\n const to = opts.settlementPeriodTo ?? last;\n if (from < 1 || to > last || from > to) {\n throw new UkEnergyError(`Settlement periods ${from}-${to} are outside 1-${last} for ${day}`);\n }\n const concurrency = Math.max(1, opts.concurrency ?? 10);\n\n const units = await client.reference.bmUnits();\n const jobs: (() => Promise<SettlementStackRow[]>)[] = [];\n for (let sp = from; sp <= to; sp++) {\n jobs.push(() => client.balancing.settlementStack(\"bid\", day, sp));\n jobs.push(() => client.balancing.settlementStack(\"offer\", day, sp));\n }\n const results: SettlementStackRow[][] = new Array(jobs.length);\n let next = 0;\n let failed = false;\n const worker = async () => {\n while (next < jobs.length && !failed) {\n const i = next++;\n const job = jobs[i];\n if (!job) continue;\n try {\n results[i] = await job();\n } catch (err) {\n failed = true; // stop the other workers issuing requests we will not use\n throw err;\n }\n }\n };\n await Promise.all(Array.from({ length: Math.min(concurrency, jobs.length) }, worker));\n\n const bids: SettlementStackRow[] = [];\n const offers: SettlementStackRow[] = [];\n results.forEach((rows, i) => {\n (i % 2 === 0 ? bids : offers).push(...(rows ?? []));\n });\n\n return aggregateWastedWind(\n day,\n { from, to },\n units,\n bids,\n offers,\n opts.fuelType ?? DEFAULT_FUEL_TYPES,\n );\n}\n","import {\n assertSettlementPeriod,\n type DateInput,\n formatDate,\n formatDateTime,\n HttpClient,\n type HttpClientOptions,\n type Query,\n type RateLimit,\n UkEnergyError,\n} from \"@uk-energy/core\";\nimport {\n type CurtailmentOptions,\n type CurtailmentReport,\n curtailmentForDay,\n} from \"./curtailment.js\";\nimport type {\n AcceptanceRow,\n AcceptanceVolumeRow,\n BidOffer,\n BidOfferRow,\n BmUnit,\n BoalfRow,\n BodRow,\n CashflowRow,\n DatasetQuery,\n DatasetRow,\n FuelinstRow,\n MarketIndexRow,\n MelsRow,\n PhysicalDataset,\n PhysicalRow,\n PnRow,\n SettlementStackRow,\n StreamDataset,\n SystemPriceRow,\n} from \"./types.js\";\nimport { type WastedWindOptions, type WastedWindReport, wastedWindForDay } from \"./wasted-wind.js\";\n\nexport const BASE_URL = \"https://data.elexon.co.uk/bmrs/api/v1\";\n\n/**\n * No limit is published and none was observed: 310 requests at ~120 req/s on 20 Oct 2026 all\n * returned 200 with no rate-limit headers (build-board d02). 10 rps / burst 20 keeps us a\n * polite order of magnitude below that; raise it per client if you need to.\n */\nexport const DEFAULT_RATE_LIMIT: RateLimit = { requestsPerSecond: 10, burst: 20 };\n\n/** Most datasets settle within minutes; a short default TTL avoids hammering on retries. */\nexport const DEFAULT_CACHE_TTL_MS = 5 * 60 * 1000;\n\nexport type ElexonClientOptions = Partial<Omit<HttpClientOptions, \"baseUrl\">> & {\n baseUrl?: string;\n};\n\n/**\n * Time window for the dataset endpoints. With `settlementPeriodFrom`/`To` set, the API treats\n * `from`/`to` as settlement *dates* (time ignored) and filters by period instead.\n */\nexport interface SettlementWindow {\n from: DateInput;\n to: DateInput;\n settlementPeriodFrom?: number;\n settlementPeriodTo?: number;\n}\n\nexport interface UnitFilter {\n /** Elexon BM unit ids, e.g. \"T_ARCHW-1\". Omit for all units. */\n bmUnit?: string[];\n}\n\n/** Client-side filter over the BM unit reference list. All given fields must match. */\nexport interface UnitsFilter {\n /** e.g. [\"WIND\"]. */\n fuelType?: string[];\n /** GSP group ids, e.g. [\"_P\", \"_N\"] (north / south Scotland). Most wind units have none. */\n gspGroupId?: string[];\n /** Case-insensitive substring of `leadPartyName`. */\n leadParty?: string;\n}\n\n/** The id to query a unit by: `elexonBmUnit`, or `nationalGridBmUnit` for units without one (48 wind units). */\nexport function unitId(u: BmUnit): string {\n return u.elexonBmUnit || u.nationalGridBmUnit || \"\";\n}\n\n/** The API accepts the whole wind fleet (287 ids, ~4.9 KB URL); chunk well below that. */\nexport const MAX_UNITS_PER_REQUEST = 200;\n\nexport function chunk<T>(items: readonly T[], size: number): T[][] {\n const out: T[][] = [];\n for (let i = 0; i < items.length; i += size) out.push(items.slice(i, i + size));\n return out;\n}\n\nexport type FuelinstQuery = (\n | { publishDateTimeFrom: DateInput; publishDateTimeTo: DateInput }\n | { settlementDateFrom: DateInput; settlementDateTo: DateInput; settlementPeriod?: number[] }\n) & { fuelType?: string[] };\n\ninterface Envelope<T> {\n data: T;\n metadata?: { datasets?: string[] };\n}\n\nfunction windowQuery(w: SettlementWindow): Query {\n const q: Query = { from: formatDateTime(w.from), to: formatDateTime(w.to) };\n if (w.settlementPeriodFrom !== undefined) {\n assertSettlementPeriod(w.settlementPeriodFrom);\n q.settlementPeriodFrom = w.settlementPeriodFrom;\n }\n if (w.settlementPeriodTo !== undefined) {\n assertSettlementPeriod(w.settlementPeriodTo);\n q.settlementPeriodTo = w.settlementPeriodTo;\n }\n return q;\n}\n\nfunction periodQuery(settlementDate: DateInput, settlementPeriod: number): Query {\n assertSettlementPeriod(settlementPeriod);\n return { settlementDate: formatDate(settlementDate), settlementPeriod };\n}\n\n/**\n * Client for Elexon Insights (BMRS). Methods return the API's own row shapes, typed from its\n * OpenAPI spec and unwrapped from the `{ data }` envelope where there is one.\n *\n * const ex = new ElexonClient();\n * const rows = await ex.datasets.boalf({ from: \"2026-10-15T00:00Z\", to: \"2026-10-15T01:00Z\" });\n * const wind = (await ex.reference.bmUnits()).filter((u) => u.fuelType === \"WIND\");\n *\n * Contains BMRS data © Elexon Limited copyright and database right 2026. Keep the attribution.\n */\nexport class ElexonClient {\n readonly http: HttpClient;\n\n constructor(opts: ElexonClientOptions = {}) {\n this.http = new HttpClient({\n baseUrl: BASE_URL,\n rateLimit: DEFAULT_RATE_LIMIT,\n cacheTtlMs: opts.cache ? DEFAULT_CACHE_TTL_MS : 0,\n ...opts,\n });\n }\n\n /** Endpoints returning a bare array. */\n private async list<T>(path: string, query?: Query): Promise<T[]> {\n const res = await this.http.get<T[]>(path, query);\n if (!Array.isArray(res)) throw new UkEnergyError(`Unexpected response shape for ${path}`);\n return res;\n }\n\n /** Endpoints returning `{ data: [...] }`. */\n private async data<T>(path: string, query?: Query): Promise<T[]> {\n const res = await this.http.get<Envelope<T[]>>(path, query);\n if (!res || !Array.isArray(res.data)) {\n throw new UkEnergyError(`Unexpected response shape for ${path}`);\n }\n return res.data;\n }\n\n /** `/datasets/{code}` — raw datasets. The `/stream` variants are used: lighter, no envelope. */\n readonly datasets = {\n /**\n * Any stream dataset (77 codes: BOALF, BOD, FUELINST, PN, MELS, WINDFOR, FREQ, …), with the\n * query parameters typed from the spec. Values are sent as given (strings in API format,\n * \"YYYY-MM-DDTHH:MMZ\" or \"YYYY-MM-DD\"); the named methods below format and validate dates.\n */\n stream: async <D extends StreamDataset>(\n dataset: D,\n query: DatasetQuery<D>,\n ): Promise<DatasetRow<D>[]> =>\n this.list<DatasetRow<D>>(`datasets/${dataset}/stream`, query as unknown as Query),\n\n /** Bid-offer acceptance levels (BOALF). Curtailment shows as `soFlag: true` bids. */\n boalf: async (w: SettlementWindow & UnitFilter): Promise<BoalfRow[]> =>\n this.list(\"datasets/BOALF/stream\", { ...windowQuery(w), bmUnit: w.bmUnit }),\n\n /** Bid-offer data (BOD): submitted bid and offer prices per pair. */\n bod: async (w: SettlementWindow & UnitFilter): Promise<BodRow[]> =>\n this.list(\"datasets/BOD/stream\", { ...windowQuery(w), bmUnit: w.bmUnit }),\n\n /** Generation by fuel type at 5-minute resolution (FUELINST). */\n fuelinst: async (q: FuelinstQuery): Promise<FuelinstRow[]> => {\n const query: Query = { fuelType: q.fuelType };\n if (\"publishDateTimeFrom\" in q) {\n query.publishDateTimeFrom = formatDateTime(q.publishDateTimeFrom);\n query.publishDateTimeTo = formatDateTime(q.publishDateTimeTo);\n } else {\n query.settlementDateFrom = formatDate(q.settlementDateFrom);\n query.settlementDateTo = formatDate(q.settlementDateTo);\n if (q.settlementPeriod) {\n for (const p of q.settlementPeriod) assertSettlementPeriod(p);\n query.settlementPeriod = q.settlementPeriod;\n }\n }\n return this.list(\"datasets/FUELINST/stream\", query);\n },\n\n /** Physical notifications for one settlement period (`/datasets/PN` takes exactly one). */\n pn: async (\n settlementDate: DateInput,\n settlementPeriod: number,\n filter: UnitFilter = {},\n ): Promise<PnRow[]> =>\n this.data(\"datasets/PN\", {\n ...periodQuery(settlementDate, settlementPeriod),\n bmUnit: filter.bmUnit,\n }),\n\n /** Physical notifications over a window (`/datasets/PN/stream`). */\n pnStream: async (w: SettlementWindow & UnitFilter): Promise<PnRow[]> =>\n this.list(\"datasets/PN/stream\", { ...windowQuery(w), bmUnit: w.bmUnit }),\n\n /** Maximum export limits (MELS). */\n mels: async (w: SettlementWindow & UnitFilter): Promise<MelsRow[]> =>\n this.list(\"datasets/MELS/stream\", { ...windowQuery(w), bmUnit: w.bmUnit }),\n\n /** Minimum import limits (MILS). */\n mils: async (w: SettlementWindow & UnitFilter): Promise<MelsRow[]> =>\n this.list(\"datasets/MILS/stream\", { ...windowQuery(w), bmUnit: w.bmUnit }),\n };\n\n /** Reference data. */\n readonly reference = {\n /** All BM units (~3,100). Capacities are strings; `fuelType` is null for many non-generators. */\n bmUnits: async (): Promise<BmUnit[]> => this.list(\"reference/bmunits/all\"),\n /** Fuel type codes used across the API. */\n fuelTypes: async (): Promise<string[]> => this.list(\"reference/fueltypes/all\"),\n /** BM units matching a filter, e.g. `{ fuelType: [\"WIND\"] }` (287 units). */\n units: async (filter: UnitsFilter = {}): Promise<BmUnit[]> => {\n const party = filter.leadParty?.toLowerCase();\n return (await this.reference.bmUnits()).filter(\n (u) =>\n (!filter.fuelType || (u.fuelType != null && filter.fuelType.includes(u.fuelType))) &&\n (!filter.gspGroupId ||\n (u.gspGroupId != null && filter.gspGroupId.includes(u.gspGroupId))) &&\n (!party || (u.leadPartyName ?? \"\").toLowerCase().includes(party)),\n );\n },\n };\n\n /** Market-wide balancing views (one settlement period per call) and settlement prices. */\n readonly balancing = {\n /** System buy/sell prices for a settlement day, or one period of it (DISEBSP). */\n systemPrices: async (\n settlementDate: DateInput,\n settlementPeriod?: number,\n ): Promise<SystemPriceRow[]> =>\n this.data(\n `balancing/settlement/system-prices/${periodPath(settlementDate, settlementPeriod)}`,\n ),\n\n /** Market index (MID) prices and volumes. */\n marketIndex: async (\n w: SettlementWindow & { dataProviders?: string[] },\n ): Promise<MarketIndexRow[]> =>\n this.data(\"balancing/pricing/market-index\", {\n ...windowQuery(w),\n dataProviders: w.dataProviders,\n }),\n\n /** Every acceptance in one settlement period. */\n acceptances: async (\n settlementDate: DateInput,\n settlementPeriod: number,\n filter: UnitFilter = {},\n ): Promise<AcceptanceRow[]> =>\n this.data(\"balancing/acceptances/all\", {\n ...periodQuery(settlementDate, settlementPeriod),\n bmUnit: filter.bmUnit,\n }),\n\n /** Every bid-offer pair in one settlement period. */\n bidOffers: async (\n settlementDate: DateInput,\n settlementPeriod: number,\n filter: UnitFilter = {},\n ): Promise<BidOfferRow[]> =>\n this.data(\"balancing/bid-offer/all\", {\n ...periodQuery(settlementDate, settlementPeriod),\n bmUnit: filter.bmUnit,\n }),\n\n /**\n * Settled acceptance volumes (BOAV) for a settlement day, or one period of it: one row per\n * acceptance per period, `totalVolumeAccepted` in MWh (negative for bids). Long unit lists\n * are split across requests.\n */\n acceptanceVolumes: async (\n bidOffer: BidOffer,\n settlementDate: DateInput,\n filter: UnitFilter & { settlementPeriod?: number } = {},\n ): Promise<AcceptanceVolumeRow[]> =>\n this.perUnitChunks(\n `balancing/settlement/acceptance/volumes/all/${bidOffer}/${periodPath(settlementDate, filter.settlementPeriod)}`,\n filter.bmUnit,\n ),\n /**\n * Indicative bid/offer cashflows (EBOCF) for a settlement day or one period: one row per unit\n * per period, `totalCashflow` in £ paid to the unit (positive for a wind bid at a negative\n * price).\n */\n cashflows: async (\n bidOffer: BidOffer,\n settlementDate: DateInput,\n filter: UnitFilter & { settlementPeriod?: number } = {},\n ): Promise<CashflowRow[]> =>\n this.perUnitChunks(\n `balancing/settlement/indicative/cashflows/all/${bidOffer}/${periodPath(settlementDate, filter.settlementPeriod)}`,\n filter.bmUnit,\n ),\n /**\n * Settlement bid or offer stack for one period (ISPSTACK): every accepted action with\n * `originalPrice`, `volume` (negative for bids), `soFlag`, `cadlFlag` and the sequence NESO\n * used. This is what Octopus's Wasted Wind tracker is built on.\n */\n settlementStack: async (\n bidOffer: BidOffer,\n settlementDate: DateInput,\n settlementPeriod: number,\n ): Promise<SettlementStackRow[]> =>\n this.data(\n `balancing/settlement/stack/all/${bidOffer}/${periodPath(settlementDate, settlementPeriod)}`,\n ),\n /** PN / QPN / MILS / MELS for every unit in one settlement period. */\n physical: async (\n dataset: PhysicalDataset,\n settlementDate: DateInput,\n settlementPeriod: number,\n filter: UnitFilter = {},\n ): Promise<PhysicalRow[]> =>\n this.data(\"balancing/physical/all\", {\n dataset,\n ...periodQuery(settlementDate, settlementPeriod),\n bmUnit: filter.bmUnit,\n }),\n };\n\n /** Wind curtailment: SO-flagged and energy bid acceptances per unit per period, with £. */\n readonly curtailment = {\n /**\n * Curtailment for a GB settlement day (Europe/London), per unit and per settlement period.\n * Defaults to every WIND unit; the reference list plus three requests per 200 units.\n */\n forDay: async (\n settlementDate: DateInput,\n opts: CurtailmentOptions = {},\n ): Promise<CurtailmentReport> => curtailmentForDay(this, settlementDate, opts),\n /**\n * Octopus's \"Wasted Wind\" definition for a settlement day: SO-flagged wind bids from the\n * settlement stack (MWh and £ to switch off) plus the cost of re-buying that energy from\n * the cheapest offers in the same period (£ to replace). Two requests per settlement period.\n */\n wastedWind: async (\n settlementDate: DateInput,\n opts: WastedWindOptions = {},\n ): Promise<WastedWindReport> => wastedWindForDay(this, settlementDate, opts),\n };\n\n /** `{data}` endpoints with an optional unit filter, chunked so URLs stay short. */\n private async perUnitChunks<T>(path: string, bmUnit?: string[]): Promise<T[]> {\n if (!bmUnit?.length) return this.data<T>(path);\n const parts = await Promise.all(\n chunk(bmUnit, MAX_UNITS_PER_REQUEST).map((ids) => this.data<T>(path, { bmUnit: ids })),\n );\n return parts.flat();\n }\n}\n\nfunction periodPath(settlementDate: DateInput, settlementPeriod?: number): string {\n if (settlementPeriod !== undefined) assertSettlementPeriod(settlementPeriod);\n const p = settlementPeriod === undefined ? \"\" : `/${settlementPeriod}`;\n return `${formatDate(settlementDate)}${p}`;\n}\n"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -30881,6 +30881,8 @@ type PhysicalRow = S["Insights.Api.Models.Responses.Balancing.Physical.PhysicalD
|
|
|
30881
30881
|
type AcceptanceVolumeRow = S["Insights.Api.Models.Responses.Balancing.Settlement.AcceptanceVolumeResponse"];
|
|
30882
30882
|
/** Indicative bid/offer cashflows (EBOCF): one row per unit per settlement period, £. */
|
|
30883
30883
|
type CashflowRow = S["Insights.Api.Models.Responses.Balancing.Settlement.IndicativeCashflowResponse"];
|
|
30884
|
+
/** Settlement bid/offer stack (ISPSTACK): every accepted action in one period with its price, volume and flags. */
|
|
30885
|
+
type SettlementStackRow = S["Insights.Api.Models.Responses.Balancing.Settlement.SettlementStackResponse"];
|
|
30884
30886
|
type PhysicalDataset = "PN" | "QPN" | "MILS" | "MELS";
|
|
30885
30887
|
type BidOffer = "bid" | "offer";
|
|
30886
30888
|
/** RFC 9110 problem details the API returns on 4xx. Surfaces as `HttpError.body`. */
|
|
@@ -30993,6 +30995,75 @@ declare function aggregateCurtailment(settlementDate: string, range: {
|
|
|
30993
30995
|
}, units: readonly BmUnit[], volumes: readonly AcceptanceVolumeRow[], cashflows: readonly CashflowRow[], boalf: readonly BoalfRow[]): Omit<CurtailmentReport, "unitsQueried">;
|
|
30994
30996
|
declare function curtailmentForDay(client: ElexonClient, settlementDate: DateInput, opts?: CurtailmentOptions): Promise<CurtailmentReport>;
|
|
30995
30997
|
|
|
30998
|
+
/**
|
|
30999
|
+
* Octopus Energy's "Wasted Wind" figure (https://wastedwind.energy), reproduced from the same
|
|
31000
|
+
* Elexon data. Per settlement period:
|
|
31001
|
+
*
|
|
31002
|
+
* - **Switch-off**: rows of the settlement *bid* stack whose unit is `fuelType: "WIND"` in the
|
|
31003
|
+
* BM unit reference and which carry `soFlag: true` (system / constraint actions). Volume is
|
|
31004
|
+
* the sum of their `volume` (negative MWh); cost is Σ `originalPrice × volume`, which is
|
|
31005
|
+
* positive for the usual negative-priced wind bid (the farm is paid to stop) and negative in
|
|
31006
|
+
* the rare case of a positive bid price.
|
|
31007
|
+
* - **Replace**: the curtailed volume bought back from the settlement *offer* stack of the same
|
|
31008
|
+
* period, `cadlFlag` offers excluded, walking non-SO offers first then by NESO's
|
|
31009
|
+
* `sequenceNumber`, pro-rating the last offer. Cost is Σ `originalPrice × volume taken`.
|
|
31010
|
+
*
|
|
31011
|
+
* Totals over a day are what the tracker shows as "£ switching off wind turbines" and "£ buying
|
|
31012
|
+
* energy elsewhere"; its monthly summary is the sum over days. This is a *definition* rather
|
|
31013
|
+
* than settlement truth: it counts SO-flagged wind bids only (see `curtailment.forDay` for the
|
|
31014
|
+
* settled BOAV/EBOCF view of every wind bid) and estimates replacement cost from the offer
|
|
31015
|
+
* stack rather than attributing actual offer cashflows.
|
|
31016
|
+
*
|
|
31017
|
+
* Contains BMRS data © Elexon Limited copyright and database right 2026.
|
|
31018
|
+
*/
|
|
31019
|
+
|
|
31020
|
+
interface WastedWindOptions {
|
|
31021
|
+
/** Restrict to a range of settlement periods (1-based, inclusive). Default: the whole day. */
|
|
31022
|
+
settlementPeriodFrom?: number;
|
|
31023
|
+
settlementPeriodTo?: number;
|
|
31024
|
+
/** Units counted as wind. Default: `fuelType === "WIND"` in the reference list. */
|
|
31025
|
+
fuelType?: string[];
|
|
31026
|
+
/** Stack requests in flight at once (two per period). Default 10. */
|
|
31027
|
+
concurrency?: number;
|
|
31028
|
+
}
|
|
31029
|
+
interface WastedWindPeriod {
|
|
31030
|
+
settlementDate: string;
|
|
31031
|
+
settlementPeriod: number;
|
|
31032
|
+
/** UTC start of the period, from any stack row of the period; empty when there were none. */
|
|
31033
|
+
startTime: string;
|
|
31034
|
+
/** MWh of SO-flagged wind bids (positive). */
|
|
31035
|
+
curtailedMwh: number;
|
|
31036
|
+
/** £ paid to wind units to turn down (Σ originalPrice × volume). */
|
|
31037
|
+
curtailmentCostGbp: number;
|
|
31038
|
+
/** MWh bought back from the offer stack (≤ curtailedMwh when the stack runs out). */
|
|
31039
|
+
turnUpMwh: number;
|
|
31040
|
+
/** £ to buy that energy from the offer stack. */
|
|
31041
|
+
turnUpCostGbp: number;
|
|
31042
|
+
/** Wind bid rows and offer rows that contributed. */
|
|
31043
|
+
bids: number;
|
|
31044
|
+
offers: number;
|
|
31045
|
+
}
|
|
31046
|
+
interface WastedWindReport {
|
|
31047
|
+
settlementDate: string;
|
|
31048
|
+
settlementPeriodFrom: number;
|
|
31049
|
+
settlementPeriodTo: number;
|
|
31050
|
+
periods: WastedWindPeriod[];
|
|
31051
|
+
total: {
|
|
31052
|
+
curtailedMwh: number;
|
|
31053
|
+
curtailmentCostGbp: number;
|
|
31054
|
+
turnUpMwh: number;
|
|
31055
|
+
turnUpCostGbp: number;
|
|
31056
|
+
/** curtailmentCostGbp + turnUpCostGbp: the tracker's headline. */
|
|
31057
|
+
totalCostGbp: number;
|
|
31058
|
+
};
|
|
31059
|
+
}
|
|
31060
|
+
/** Pure aggregation; exported for tests and for callers with their own stack rows. */
|
|
31061
|
+
declare function aggregateWastedWind(settlementDate: string, range: {
|
|
31062
|
+
from: number;
|
|
31063
|
+
to: number;
|
|
31064
|
+
}, units: readonly BmUnit[], bids: readonly SettlementStackRow[], offers: readonly SettlementStackRow[], fuelTypes?: readonly string[]): WastedWindReport;
|
|
31065
|
+
declare function wastedWindForDay(client: ElexonClient, settlementDate: DateInput, opts?: WastedWindOptions): Promise<WastedWindReport>;
|
|
31066
|
+
|
|
30996
31067
|
declare const BASE_URL = "https://data.elexon.co.uk/bmrs/api/v1";
|
|
30997
31068
|
/**
|
|
30998
31069
|
* No limit is published and none was observed: 310 requests at ~120 req/s on 20 Oct 2026 all
|
|
@@ -31119,6 +31190,12 @@ declare class ElexonClient {
|
|
|
31119
31190
|
cashflows: (bidOffer: BidOffer, settlementDate: DateInput, filter?: UnitFilter & {
|
|
31120
31191
|
settlementPeriod?: number;
|
|
31121
31192
|
}) => Promise<CashflowRow[]>;
|
|
31193
|
+
/**
|
|
31194
|
+
* Settlement bid or offer stack for one period (ISPSTACK): every accepted action with
|
|
31195
|
+
* `originalPrice`, `volume` (negative for bids), `soFlag`, `cadlFlag` and the sequence NESO
|
|
31196
|
+
* used. This is what Octopus's Wasted Wind tracker is built on.
|
|
31197
|
+
*/
|
|
31198
|
+
settlementStack: (bidOffer: BidOffer, settlementDate: DateInput, settlementPeriod: number) => Promise<SettlementStackRow[]>;
|
|
31122
31199
|
/** PN / QPN / MILS / MELS for every unit in one settlement period. */
|
|
31123
31200
|
physical: (dataset: PhysicalDataset, settlementDate: DateInput, settlementPeriod: number, filter?: UnitFilter) => Promise<PhysicalRow[]>;
|
|
31124
31201
|
};
|
|
@@ -31129,10 +31206,16 @@ declare class ElexonClient {
|
|
|
31129
31206
|
* Defaults to every WIND unit; the reference list plus three requests per 200 units.
|
|
31130
31207
|
*/
|
|
31131
31208
|
forDay: (settlementDate: DateInput, opts?: CurtailmentOptions) => Promise<CurtailmentReport>;
|
|
31209
|
+
/**
|
|
31210
|
+
* Octopus's "Wasted Wind" definition for a settlement day: SO-flagged wind bids from the
|
|
31211
|
+
* settlement stack (MWh and £ to switch off) plus the cost of re-buying that energy from
|
|
31212
|
+
* the cheapest offers in the same period (£ to replace). Two requests per settlement period.
|
|
31213
|
+
*/
|
|
31214
|
+
wastedWind: (settlementDate: DateInput, opts?: WastedWindOptions) => Promise<WastedWindReport>;
|
|
31132
31215
|
};
|
|
31133
31216
|
/** `{data}` endpoints with an optional unit filter, chunked so URLs stay short. */
|
|
31134
31217
|
private perUnitChunks;
|
|
31135
31218
|
}
|
|
31136
31219
|
|
|
31137
|
-
export { BASE_URL, DEFAULT_CACHE_TTL_MS, DEFAULT_RATE_LIMIT, ElexonClient, MAX_UNITS_PER_REQUEST, aggregateCurtailment, curtailmentForDay, unitId };
|
|
31138
|
-
export type { AcceptanceRow, AcceptanceVolumeRow, BidOffer, BidOfferRow, BmUnit, BoalfRow, BodRow, CashflowRow, CurtailmentOptions, CurtailmentPeriod, CurtailmentReport, CurtailmentUnit, DatasetQuery, DatasetRow, ElexonClientOptions, FuelinstQuery, FuelinstRow, MarketIndexRow, MelsRow, PhysicalDataset, PhysicalRow, PnRow, ProblemDetails, SettlementWindow, StreamDataset, SystemPriceRow, UnitFilter, UnitsFilter, components, paths };
|
|
31220
|
+
export { BASE_URL, DEFAULT_CACHE_TTL_MS, DEFAULT_RATE_LIMIT, ElexonClient, MAX_UNITS_PER_REQUEST, aggregateCurtailment, aggregateWastedWind, curtailmentForDay, unitId, wastedWindForDay };
|
|
31221
|
+
export type { AcceptanceRow, AcceptanceVolumeRow, BidOffer, BidOfferRow, BmUnit, BoalfRow, BodRow, CashflowRow, CurtailmentOptions, CurtailmentPeriod, CurtailmentReport, CurtailmentUnit, DatasetQuery, DatasetRow, ElexonClientOptions, FuelinstQuery, FuelinstRow, MarketIndexRow, MelsRow, PhysicalDataset, PhysicalRow, PnRow, ProblemDetails, SettlementStackRow, SettlementWindow, StreamDataset, SystemPriceRow, UnitFilter, UnitsFilter, WastedWindOptions, WastedWindPeriod, WastedWindReport, components, paths };
|
package/dist/index.d.ts
CHANGED
|
@@ -30881,6 +30881,8 @@ type PhysicalRow = S["Insights.Api.Models.Responses.Balancing.Physical.PhysicalD
|
|
|
30881
30881
|
type AcceptanceVolumeRow = S["Insights.Api.Models.Responses.Balancing.Settlement.AcceptanceVolumeResponse"];
|
|
30882
30882
|
/** Indicative bid/offer cashflows (EBOCF): one row per unit per settlement period, £. */
|
|
30883
30883
|
type CashflowRow = S["Insights.Api.Models.Responses.Balancing.Settlement.IndicativeCashflowResponse"];
|
|
30884
|
+
/** Settlement bid/offer stack (ISPSTACK): every accepted action in one period with its price, volume and flags. */
|
|
30885
|
+
type SettlementStackRow = S["Insights.Api.Models.Responses.Balancing.Settlement.SettlementStackResponse"];
|
|
30884
30886
|
type PhysicalDataset = "PN" | "QPN" | "MILS" | "MELS";
|
|
30885
30887
|
type BidOffer = "bid" | "offer";
|
|
30886
30888
|
/** RFC 9110 problem details the API returns on 4xx. Surfaces as `HttpError.body`. */
|
|
@@ -30993,6 +30995,75 @@ declare function aggregateCurtailment(settlementDate: string, range: {
|
|
|
30993
30995
|
}, units: readonly BmUnit[], volumes: readonly AcceptanceVolumeRow[], cashflows: readonly CashflowRow[], boalf: readonly BoalfRow[]): Omit<CurtailmentReport, "unitsQueried">;
|
|
30994
30996
|
declare function curtailmentForDay(client: ElexonClient, settlementDate: DateInput, opts?: CurtailmentOptions): Promise<CurtailmentReport>;
|
|
30995
30997
|
|
|
30998
|
+
/**
|
|
30999
|
+
* Octopus Energy's "Wasted Wind" figure (https://wastedwind.energy), reproduced from the same
|
|
31000
|
+
* Elexon data. Per settlement period:
|
|
31001
|
+
*
|
|
31002
|
+
* - **Switch-off**: rows of the settlement *bid* stack whose unit is `fuelType: "WIND"` in the
|
|
31003
|
+
* BM unit reference and which carry `soFlag: true` (system / constraint actions). Volume is
|
|
31004
|
+
* the sum of their `volume` (negative MWh); cost is Σ `originalPrice × volume`, which is
|
|
31005
|
+
* positive for the usual negative-priced wind bid (the farm is paid to stop) and negative in
|
|
31006
|
+
* the rare case of a positive bid price.
|
|
31007
|
+
* - **Replace**: the curtailed volume bought back from the settlement *offer* stack of the same
|
|
31008
|
+
* period, `cadlFlag` offers excluded, walking non-SO offers first then by NESO's
|
|
31009
|
+
* `sequenceNumber`, pro-rating the last offer. Cost is Σ `originalPrice × volume taken`.
|
|
31010
|
+
*
|
|
31011
|
+
* Totals over a day are what the tracker shows as "£ switching off wind turbines" and "£ buying
|
|
31012
|
+
* energy elsewhere"; its monthly summary is the sum over days. This is a *definition* rather
|
|
31013
|
+
* than settlement truth: it counts SO-flagged wind bids only (see `curtailment.forDay` for the
|
|
31014
|
+
* settled BOAV/EBOCF view of every wind bid) and estimates replacement cost from the offer
|
|
31015
|
+
* stack rather than attributing actual offer cashflows.
|
|
31016
|
+
*
|
|
31017
|
+
* Contains BMRS data © Elexon Limited copyright and database right 2026.
|
|
31018
|
+
*/
|
|
31019
|
+
|
|
31020
|
+
interface WastedWindOptions {
|
|
31021
|
+
/** Restrict to a range of settlement periods (1-based, inclusive). Default: the whole day. */
|
|
31022
|
+
settlementPeriodFrom?: number;
|
|
31023
|
+
settlementPeriodTo?: number;
|
|
31024
|
+
/** Units counted as wind. Default: `fuelType === "WIND"` in the reference list. */
|
|
31025
|
+
fuelType?: string[];
|
|
31026
|
+
/** Stack requests in flight at once (two per period). Default 10. */
|
|
31027
|
+
concurrency?: number;
|
|
31028
|
+
}
|
|
31029
|
+
interface WastedWindPeriod {
|
|
31030
|
+
settlementDate: string;
|
|
31031
|
+
settlementPeriod: number;
|
|
31032
|
+
/** UTC start of the period, from any stack row of the period; empty when there were none. */
|
|
31033
|
+
startTime: string;
|
|
31034
|
+
/** MWh of SO-flagged wind bids (positive). */
|
|
31035
|
+
curtailedMwh: number;
|
|
31036
|
+
/** £ paid to wind units to turn down (Σ originalPrice × volume). */
|
|
31037
|
+
curtailmentCostGbp: number;
|
|
31038
|
+
/** MWh bought back from the offer stack (≤ curtailedMwh when the stack runs out). */
|
|
31039
|
+
turnUpMwh: number;
|
|
31040
|
+
/** £ to buy that energy from the offer stack. */
|
|
31041
|
+
turnUpCostGbp: number;
|
|
31042
|
+
/** Wind bid rows and offer rows that contributed. */
|
|
31043
|
+
bids: number;
|
|
31044
|
+
offers: number;
|
|
31045
|
+
}
|
|
31046
|
+
interface WastedWindReport {
|
|
31047
|
+
settlementDate: string;
|
|
31048
|
+
settlementPeriodFrom: number;
|
|
31049
|
+
settlementPeriodTo: number;
|
|
31050
|
+
periods: WastedWindPeriod[];
|
|
31051
|
+
total: {
|
|
31052
|
+
curtailedMwh: number;
|
|
31053
|
+
curtailmentCostGbp: number;
|
|
31054
|
+
turnUpMwh: number;
|
|
31055
|
+
turnUpCostGbp: number;
|
|
31056
|
+
/** curtailmentCostGbp + turnUpCostGbp: the tracker's headline. */
|
|
31057
|
+
totalCostGbp: number;
|
|
31058
|
+
};
|
|
31059
|
+
}
|
|
31060
|
+
/** Pure aggregation; exported for tests and for callers with their own stack rows. */
|
|
31061
|
+
declare function aggregateWastedWind(settlementDate: string, range: {
|
|
31062
|
+
from: number;
|
|
31063
|
+
to: number;
|
|
31064
|
+
}, units: readonly BmUnit[], bids: readonly SettlementStackRow[], offers: readonly SettlementStackRow[], fuelTypes?: readonly string[]): WastedWindReport;
|
|
31065
|
+
declare function wastedWindForDay(client: ElexonClient, settlementDate: DateInput, opts?: WastedWindOptions): Promise<WastedWindReport>;
|
|
31066
|
+
|
|
30996
31067
|
declare const BASE_URL = "https://data.elexon.co.uk/bmrs/api/v1";
|
|
30997
31068
|
/**
|
|
30998
31069
|
* No limit is published and none was observed: 310 requests at ~120 req/s on 20 Oct 2026 all
|
|
@@ -31119,6 +31190,12 @@ declare class ElexonClient {
|
|
|
31119
31190
|
cashflows: (bidOffer: BidOffer, settlementDate: DateInput, filter?: UnitFilter & {
|
|
31120
31191
|
settlementPeriod?: number;
|
|
31121
31192
|
}) => Promise<CashflowRow[]>;
|
|
31193
|
+
/**
|
|
31194
|
+
* Settlement bid or offer stack for one period (ISPSTACK): every accepted action with
|
|
31195
|
+
* `originalPrice`, `volume` (negative for bids), `soFlag`, `cadlFlag` and the sequence NESO
|
|
31196
|
+
* used. This is what Octopus's Wasted Wind tracker is built on.
|
|
31197
|
+
*/
|
|
31198
|
+
settlementStack: (bidOffer: BidOffer, settlementDate: DateInput, settlementPeriod: number) => Promise<SettlementStackRow[]>;
|
|
31122
31199
|
/** PN / QPN / MILS / MELS for every unit in one settlement period. */
|
|
31123
31200
|
physical: (dataset: PhysicalDataset, settlementDate: DateInput, settlementPeriod: number, filter?: UnitFilter) => Promise<PhysicalRow[]>;
|
|
31124
31201
|
};
|
|
@@ -31129,10 +31206,16 @@ declare class ElexonClient {
|
|
|
31129
31206
|
* Defaults to every WIND unit; the reference list plus three requests per 200 units.
|
|
31130
31207
|
*/
|
|
31131
31208
|
forDay: (settlementDate: DateInput, opts?: CurtailmentOptions) => Promise<CurtailmentReport>;
|
|
31209
|
+
/**
|
|
31210
|
+
* Octopus's "Wasted Wind" definition for a settlement day: SO-flagged wind bids from the
|
|
31211
|
+
* settlement stack (MWh and £ to switch off) plus the cost of re-buying that energy from
|
|
31212
|
+
* the cheapest offers in the same period (£ to replace). Two requests per settlement period.
|
|
31213
|
+
*/
|
|
31214
|
+
wastedWind: (settlementDate: DateInput, opts?: WastedWindOptions) => Promise<WastedWindReport>;
|
|
31132
31215
|
};
|
|
31133
31216
|
/** `{data}` endpoints with an optional unit filter, chunked so URLs stay short. */
|
|
31134
31217
|
private perUnitChunks;
|
|
31135
31218
|
}
|
|
31136
31219
|
|
|
31137
|
-
export { BASE_URL, DEFAULT_CACHE_TTL_MS, DEFAULT_RATE_LIMIT, ElexonClient, MAX_UNITS_PER_REQUEST, aggregateCurtailment, curtailmentForDay, unitId };
|
|
31138
|
-
export type { AcceptanceRow, AcceptanceVolumeRow, BidOffer, BidOfferRow, BmUnit, BoalfRow, BodRow, CashflowRow, CurtailmentOptions, CurtailmentPeriod, CurtailmentReport, CurtailmentUnit, DatasetQuery, DatasetRow, ElexonClientOptions, FuelinstQuery, FuelinstRow, MarketIndexRow, MelsRow, PhysicalDataset, PhysicalRow, PnRow, ProblemDetails, SettlementWindow, StreamDataset, SystemPriceRow, UnitFilter, UnitsFilter, components, paths };
|
|
31220
|
+
export { BASE_URL, DEFAULT_CACHE_TTL_MS, DEFAULT_RATE_LIMIT, ElexonClient, MAX_UNITS_PER_REQUEST, aggregateCurtailment, aggregateWastedWind, curtailmentForDay, unitId, wastedWindForDay };
|
|
31221
|
+
export type { AcceptanceRow, AcceptanceVolumeRow, BidOffer, BidOfferRow, BmUnit, BoalfRow, BodRow, CashflowRow, CurtailmentOptions, CurtailmentPeriod, CurtailmentReport, CurtailmentUnit, DatasetQuery, DatasetRow, ElexonClientOptions, FuelinstQuery, FuelinstRow, MarketIndexRow, MelsRow, PhysicalDataset, PhysicalRow, PnRow, ProblemDetails, SettlementStackRow, SettlementWindow, StreamDataset, SystemPriceRow, UnitFilter, UnitsFilter, WastedWindOptions, WastedWindPeriod, WastedWindReport, components, paths };
|
package/dist/index.js
CHANGED
|
@@ -169,6 +169,123 @@ async function curtailmentForDay(client, settlementDate, opts = {}) {
|
|
|
169
169
|
...aggregateCurtailment(day, { from, to }, units, volumes, cashflows, boalf)
|
|
170
170
|
};
|
|
171
171
|
}
|
|
172
|
+
var DEFAULT_FUEL_TYPES2 = ["WIND"];
|
|
173
|
+
function aggregateWastedWind(settlementDate, range, units, bids, offers, fuelTypes = DEFAULT_FUEL_TYPES2) {
|
|
174
|
+
const fuelById = /* @__PURE__ */ new Map();
|
|
175
|
+
for (const u of units) if (u.elexonBmUnit) fuelById.set(u.elexonBmUnit, u.fuelType);
|
|
176
|
+
const periods = [];
|
|
177
|
+
for (let sp = range.from; sp <= range.to; sp++) {
|
|
178
|
+
const windBids = bids.filter(
|
|
179
|
+
(r) => r.settlementPeriod === sp && r.soFlag === true && r.id != null && fuelTypes.includes(fuelById.get(r.id) ?? "")
|
|
180
|
+
);
|
|
181
|
+
let volume = 0;
|
|
182
|
+
let cost = 0;
|
|
183
|
+
for (const b of windBids) {
|
|
184
|
+
const v = b.volume ?? 0;
|
|
185
|
+
volume += v;
|
|
186
|
+
cost += (b.originalPrice ?? 0) * v;
|
|
187
|
+
}
|
|
188
|
+
const need = Math.abs(volume);
|
|
189
|
+
const stack = offers.filter((r) => r.settlementPeriod === sp && !r.cadlFlag).sort(
|
|
190
|
+
(a, b) => (a.soFlag ? 1 : 0) - (b.soFlag ? 1 : 0) || (a.sequenceNumber ?? 0) - (b.sequenceNumber ?? 0)
|
|
191
|
+
);
|
|
192
|
+
let taken = 0;
|
|
193
|
+
let turnUpCost = 0;
|
|
194
|
+
let used = 0;
|
|
195
|
+
for (const o of need > 0 ? stack : []) {
|
|
196
|
+
used += 1;
|
|
197
|
+
const v = o.volume ?? 0;
|
|
198
|
+
if (taken + v > need) {
|
|
199
|
+
const fraction = Math.min(Math.max((need - taken) / v, 0), 1);
|
|
200
|
+
taken += v * fraction;
|
|
201
|
+
turnUpCost += (o.originalPrice ?? 0) * (v * fraction);
|
|
202
|
+
} else {
|
|
203
|
+
taken += v;
|
|
204
|
+
turnUpCost += (o.originalPrice ?? 0) * v;
|
|
205
|
+
}
|
|
206
|
+
if (taken >= need) break;
|
|
207
|
+
}
|
|
208
|
+
const first = windBids[0] ?? stack[0] ?? bids.find((r) => r.settlementPeriod === sp) ?? offers.find((r) => r.settlementPeriod === sp);
|
|
209
|
+
periods.push({
|
|
210
|
+
settlementDate,
|
|
211
|
+
settlementPeriod: sp,
|
|
212
|
+
startTime: first?.startTime ?? "",
|
|
213
|
+
curtailedMwh: need,
|
|
214
|
+
curtailmentCostGbp: cost,
|
|
215
|
+
turnUpMwh: taken,
|
|
216
|
+
turnUpCostGbp: turnUpCost,
|
|
217
|
+
bids: windBids.length,
|
|
218
|
+
offers: used
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
const total = {
|
|
222
|
+
curtailedMwh: 0,
|
|
223
|
+
curtailmentCostGbp: 0,
|
|
224
|
+
turnUpMwh: 0,
|
|
225
|
+
turnUpCostGbp: 0,
|
|
226
|
+
totalCostGbp: 0
|
|
227
|
+
};
|
|
228
|
+
for (const p of periods) {
|
|
229
|
+
total.curtailedMwh += p.curtailedMwh;
|
|
230
|
+
total.curtailmentCostGbp += p.curtailmentCostGbp;
|
|
231
|
+
total.turnUpMwh += p.turnUpMwh;
|
|
232
|
+
total.turnUpCostGbp += p.turnUpCostGbp;
|
|
233
|
+
}
|
|
234
|
+
total.totalCostGbp = total.curtailmentCostGbp + total.turnUpCostGbp;
|
|
235
|
+
return {
|
|
236
|
+
settlementDate,
|
|
237
|
+
settlementPeriodFrom: range.from,
|
|
238
|
+
settlementPeriodTo: range.to,
|
|
239
|
+
periods,
|
|
240
|
+
total
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
async function wastedWindForDay(client, settlementDate, opts = {}) {
|
|
244
|
+
const day = formatDate(settlementDate);
|
|
245
|
+
const last = settlementPeriodsInDay(day);
|
|
246
|
+
const from = opts.settlementPeriodFrom ?? 1;
|
|
247
|
+
const to = opts.settlementPeriodTo ?? last;
|
|
248
|
+
if (from < 1 || to > last || from > to) {
|
|
249
|
+
throw new UkEnergyError(`Settlement periods ${from}-${to} are outside 1-${last} for ${day}`);
|
|
250
|
+
}
|
|
251
|
+
const concurrency = Math.max(1, opts.concurrency ?? 10);
|
|
252
|
+
const units = await client.reference.bmUnits();
|
|
253
|
+
const jobs = [];
|
|
254
|
+
for (let sp = from; sp <= to; sp++) {
|
|
255
|
+
jobs.push(() => client.balancing.settlementStack("bid", day, sp));
|
|
256
|
+
jobs.push(() => client.balancing.settlementStack("offer", day, sp));
|
|
257
|
+
}
|
|
258
|
+
const results = new Array(jobs.length);
|
|
259
|
+
let next = 0;
|
|
260
|
+
let failed = false;
|
|
261
|
+
const worker = async () => {
|
|
262
|
+
while (next < jobs.length && !failed) {
|
|
263
|
+
const i = next++;
|
|
264
|
+
const job = jobs[i];
|
|
265
|
+
if (!job) continue;
|
|
266
|
+
try {
|
|
267
|
+
results[i] = await job();
|
|
268
|
+
} catch (err) {
|
|
269
|
+
failed = true;
|
|
270
|
+
throw err;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
};
|
|
274
|
+
await Promise.all(Array.from({ length: Math.min(concurrency, jobs.length) }, worker));
|
|
275
|
+
const bids = [];
|
|
276
|
+
const offers = [];
|
|
277
|
+
results.forEach((rows, i) => {
|
|
278
|
+
(i % 2 === 0 ? bids : offers).push(...rows ?? []);
|
|
279
|
+
});
|
|
280
|
+
return aggregateWastedWind(
|
|
281
|
+
day,
|
|
282
|
+
{ from, to },
|
|
283
|
+
units,
|
|
284
|
+
bids,
|
|
285
|
+
offers,
|
|
286
|
+
opts.fuelType ?? DEFAULT_FUEL_TYPES2
|
|
287
|
+
);
|
|
288
|
+
}
|
|
172
289
|
|
|
173
290
|
// src/client.ts
|
|
174
291
|
var BASE_URL = "https://data.elexon.co.uk/bmrs/api/v1";
|
|
@@ -316,6 +433,14 @@ var ElexonClient = class {
|
|
|
316
433
|
`balancing/settlement/indicative/cashflows/all/${bidOffer}/${periodPath(settlementDate, filter.settlementPeriod)}`,
|
|
317
434
|
filter.bmUnit
|
|
318
435
|
),
|
|
436
|
+
/**
|
|
437
|
+
* Settlement bid or offer stack for one period (ISPSTACK): every accepted action with
|
|
438
|
+
* `originalPrice`, `volume` (negative for bids), `soFlag`, `cadlFlag` and the sequence NESO
|
|
439
|
+
* used. This is what Octopus's Wasted Wind tracker is built on.
|
|
440
|
+
*/
|
|
441
|
+
settlementStack: async (bidOffer, settlementDate, settlementPeriod) => this.data(
|
|
442
|
+
`balancing/settlement/stack/all/${bidOffer}/${periodPath(settlementDate, settlementPeriod)}`
|
|
443
|
+
),
|
|
319
444
|
/** PN / QPN / MILS / MELS for every unit in one settlement period. */
|
|
320
445
|
physical: async (dataset, settlementDate, settlementPeriod, filter = {}) => this.data("balancing/physical/all", {
|
|
321
446
|
dataset,
|
|
@@ -329,7 +454,13 @@ var ElexonClient = class {
|
|
|
329
454
|
* Curtailment for a GB settlement day (Europe/London), per unit and per settlement period.
|
|
330
455
|
* Defaults to every WIND unit; the reference list plus three requests per 200 units.
|
|
331
456
|
*/
|
|
332
|
-
forDay: async (settlementDate, opts = {}) => curtailmentForDay(this, settlementDate, opts)
|
|
457
|
+
forDay: async (settlementDate, opts = {}) => curtailmentForDay(this, settlementDate, opts),
|
|
458
|
+
/**
|
|
459
|
+
* Octopus's "Wasted Wind" definition for a settlement day: SO-flagged wind bids from the
|
|
460
|
+
* settlement stack (MWh and £ to switch off) plus the cost of re-buying that energy from
|
|
461
|
+
* the cheapest offers in the same period (£ to replace). Two requests per settlement period.
|
|
462
|
+
*/
|
|
463
|
+
wastedWind: async (settlementDate, opts = {}) => wastedWindForDay(this, settlementDate, opts)
|
|
333
464
|
};
|
|
334
465
|
/** `{data}` endpoints with an optional unit filter, chunked so URLs stay short. */
|
|
335
466
|
async perUnitChunks(path, bmUnit) {
|
|
@@ -346,6 +477,6 @@ function periodPath(settlementDate, settlementPeriod) {
|
|
|
346
477
|
return `${formatDate(settlementDate)}${p}`;
|
|
347
478
|
}
|
|
348
479
|
|
|
349
|
-
export { BASE_URL, DEFAULT_CACHE_TTL_MS, DEFAULT_RATE_LIMIT, ElexonClient, MAX_UNITS_PER_REQUEST, aggregateCurtailment, curtailmentForDay, unitId };
|
|
480
|
+
export { BASE_URL, DEFAULT_CACHE_TTL_MS, DEFAULT_RATE_LIMIT, ElexonClient, MAX_UNITS_PER_REQUEST, aggregateCurtailment, aggregateWastedWind, curtailmentForDay, unitId, wastedWindForDay };
|
|
350
481
|
//# sourceMappingURL=index.js.map
|
|
351
482
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/curtailment.ts","../src/client.ts"],"names":["formatDate","UkEnergyError"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Wind curtailment from settlement data.\n *\n * A curtailment is a *bid acceptance*: NESO accepts a unit's bid to reduce output below its\n * physical notification. Three Elexon feeds describe one:\n *\n * - BOAV (`balancing.acceptanceVolumes(\"bid\", day)`): the settled volume of each acceptance in\n * each settlement period, in MWh (negative for bids). This is the quantity curtailed.\n * - EBOCF (`balancing.cashflows(\"bid\", day)`): the £ paid to the unit per period. Wind bids are\n * priced negative (the unit pays to keep generating, so is *paid* to stop), hence the\n * cashflow is positive and is what consumers fund through BSUoS.\n * - BOALF (`datasets.boalf`): the acceptance's level profile and flags. `soFlag` marks\n * actions taken for system reasons (transmission constraints: the classic \"wind farm paid\n * to switch off because the cable to England is full\"); unflagged acceptances are energy\n * balancing. `rrFlag`/`storFlag`/`deemedBoFlag` are counted, not excluded.\n *\n * `curtailmentForDay` joins the three by (unit, period) and (acceptance number) and adds the\n * unit's fuel type, GSP group and capacity from the reference list.\n *\n * Contains BMRS data © Elexon Limited copyright and database right 2026.\n */\nimport { type DateInput, formatDate, settlementPeriodsInDay, UkEnergyError } from \"@uk-energy/core\";\nimport { chunk, type ElexonClient, MAX_UNITS_PER_REQUEST, unitId } from \"./client.js\";\nimport type { AcceptanceVolumeRow, BmUnit, BoalfRow, CashflowRow } from \"./types.js\";\n\nexport interface CurtailmentOptions {\n /** Units to include. Default: every unit whose `fuelType` is in `fuelType`. */\n bmUnit?: string[];\n /** Used when `bmUnit` is omitted. Default `[\"WIND\"]`. */\n fuelType?: string[];\n /** Restrict to a range of settlement periods (1-based, inclusive). Default: the whole day. */\n settlementPeriodFrom?: number;\n settlementPeriodTo?: number;\n}\n\n/** One unit in one settlement period. */\nexport interface CurtailmentPeriod {\n settlementDate: string;\n settlementPeriod: number;\n /** UTC start of the period, from the API. */\n startTime: string;\n bmUnit: string;\n nationalGridBmUnit: string | null;\n leadPartyName: string | null;\n fuelType: string | null;\n gspGroupId: string | null;\n /** `generationCapacity` from the reference list, MW; null when unknown. */\n capacityMw: number | null;\n /** MWh curtailed in the period (positive). */\n curtailedMwh: number;\n /** Of which from SO-flagged (system / constraint) acceptances. */\n soFlaggedMwh: number;\n /**\n * £ paid to the unit for the period's bids (EBOCF); null when no cashflow row was published\n * yet. Cashflow rows for periods with no settled bid volume are not reported anywhere.\n */\n cashflowGbp: number | null;\n /** Distinct acceptances contributing to the period. */\n acceptances: number;\n /** Acceptances in the period that BOALF reported with `soFlag: true`. */\n soFlaggedAcceptances: number;\n}\n\n/** A unit's totals over the day. */\nexport interface CurtailmentUnit {\n bmUnit: string;\n nationalGridBmUnit: string | null;\n leadPartyName: string | null;\n fuelType: string | null;\n gspGroupId: string | null;\n capacityMw: number | null;\n curtailedMwh: number;\n soFlaggedMwh: number;\n cashflowGbp: number;\n /** Settlement periods with any curtailment. */\n periods: number;\n /** Distinct acceptances over the day (one acceptance can span several periods). */\n acceptances: number;\n}\n\nexport interface CurtailmentReport {\n settlementDate: string;\n settlementPeriodFrom: number;\n settlementPeriodTo: number;\n /** Units that were queried (whether or not they were curtailed). */\n unitsQueried: number;\n /** Rows ordered by settlement period then unit id; only (unit, period) pairs with volume. */\n periods: CurtailmentPeriod[];\n /** Per-unit totals, largest `curtailedMwh` first. */\n units: CurtailmentUnit[];\n total: {\n curtailedMwh: number;\n soFlaggedMwh: number;\n cashflowGbp: number;\n /** Units with any curtailment. */\n units: number;\n /** Distinct acceptances. */\n acceptances: number;\n };\n}\n\nconst DEFAULT_FUEL_TYPES = [\"WIND\"];\n\n/** Code-point order (not locale order) so TS and Python sort \"C__X\" and \"CD\" the same way. */\nfunction compareIds(a: string, b: string): number {\n return a < b ? -1 : a > b ? 1 : 0;\n}\n\nfunction num(s: string | null | undefined): number | null {\n if (s == null || s === \"\") return null;\n const n = Number(s);\n return Number.isFinite(n) ? n : null;\n}\n\n/** Pure aggregation over already-fetched rows; exported for tests and for callers with their own data. */\nexport function aggregateCurtailment(\n settlementDate: string,\n range: { from: number; to: number },\n units: readonly BmUnit[],\n volumes: readonly AcceptanceVolumeRow[],\n cashflows: readonly CashflowRow[],\n boalf: readonly BoalfRow[],\n): Omit<CurtailmentReport, \"unitsQueried\"> {\n const byId = new Map<string, BmUnit>();\n for (const u of units) {\n if (u.elexonBmUnit) byId.set(u.elexonBmUnit, u);\n if (u.nationalGridBmUnit) byId.set(u.nationalGridBmUnit, u);\n }\n const soByAcceptance = new Map<number, boolean>();\n for (const r of boalf) {\n if (r.acceptanceNumber != null) {\n soByAcceptance.set(\n r.acceptanceNumber,\n (soByAcceptance.get(r.acceptanceNumber) ?? false) || r.soFlag === true,\n );\n }\n }\n const cashByKey = new Map<string, number>();\n for (const c of cashflows) {\n const sp = c.settlementPeriod;\n if (!c.bmUnit || sp === undefined || sp < range.from || sp > range.to) continue;\n const key = `${c.bmUnit}|${sp}`;\n cashByKey.set(key, (cashByKey.get(key) ?? 0) + (c.totalCashflow ?? 0));\n }\n\n const periods = new Map<string, CurtailmentPeriod>();\n const seen = new Map<string, Set<number>>();\n for (const v of volumes) {\n const sp = v.settlementPeriod;\n const acceptance = v.acceptanceId;\n if (!v.bmUnit || sp === undefined || acceptance === undefined) continue;\n if (sp < range.from || sp > range.to) continue;\n const mwh = -(v.totalVolumeAccepted ?? 0);\n if (!(mwh > 0)) continue;\n const key = `${v.bmUnit}|${sp}`;\n let p = periods.get(key);\n let ids = seen.get(key);\n if (!p || !ids) {\n const ref = byId.get(v.bmUnit);\n p = {\n settlementDate: v.settlementDate ?? settlementDate,\n settlementPeriod: sp,\n startTime: v.startTime ?? \"\",\n bmUnit: v.bmUnit,\n nationalGridBmUnit: v.nationalGridBmUnit ?? ref?.nationalGridBmUnit ?? null,\n leadPartyName: v.leadPartyName ?? ref?.leadPartyName ?? null,\n fuelType: ref?.fuelType ?? null,\n gspGroupId: ref?.gspGroupId ?? null,\n capacityMw: num(ref?.generationCapacity),\n curtailedMwh: 0,\n soFlaggedMwh: 0,\n cashflowGbp: cashByKey.get(key) ?? null,\n acceptances: 0,\n soFlaggedAcceptances: 0,\n };\n ids = new Set();\n periods.set(key, p);\n seen.set(key, ids);\n }\n p.curtailedMwh += mwh;\n const so = soByAcceptance.get(acceptance) === true;\n if (so) p.soFlaggedMwh += mwh;\n if (!ids.has(acceptance)) {\n ids.add(acceptance);\n p.acceptances += 1;\n if (so) p.soFlaggedAcceptances += 1;\n }\n }\n\n const rows = [...periods.values()].sort(\n (a, b) => a.settlementPeriod - b.settlementPeriod || compareIds(a.bmUnit, b.bmUnit),\n );\n\n const unitTotals = new Map<string, CurtailmentUnit>();\n const unitAcceptances = new Map<string, Set<number>>();\n const allAcceptances = new Set<number>();\n const total = { curtailedMwh: 0, soFlaggedMwh: 0, cashflowGbp: 0, units: 0, acceptances: 0 };\n for (const p of rows) {\n let u = unitTotals.get(p.bmUnit);\n if (!u) {\n u = {\n bmUnit: p.bmUnit,\n nationalGridBmUnit: p.nationalGridBmUnit,\n leadPartyName: p.leadPartyName,\n fuelType: p.fuelType,\n gspGroupId: p.gspGroupId,\n capacityMw: p.capacityMw,\n curtailedMwh: 0,\n soFlaggedMwh: 0,\n cashflowGbp: 0,\n periods: 0,\n acceptances: 0,\n };\n unitTotals.set(p.bmUnit, u);\n }\n u.curtailedMwh += p.curtailedMwh;\n u.soFlaggedMwh += p.soFlaggedMwh;\n u.cashflowGbp += p.cashflowGbp ?? 0;\n u.periods += 1;\n const ids = seen.get(`${p.bmUnit}|${p.settlementPeriod}`) ?? new Set<number>();\n let acc = unitAcceptances.get(p.bmUnit);\n if (!acc) {\n acc = new Set();\n unitAcceptances.set(p.bmUnit, acc);\n }\n for (const id of ids) {\n acc.add(id);\n allAcceptances.add(id);\n }\n u.acceptances = acc.size;\n total.curtailedMwh += p.curtailedMwh;\n total.soFlaggedMwh += p.soFlaggedMwh;\n total.cashflowGbp += p.cashflowGbp ?? 0;\n }\n total.units = unitTotals.size;\n total.acceptances = allAcceptances.size;\n\n return {\n settlementDate,\n settlementPeriodFrom: range.from,\n settlementPeriodTo: range.to,\n periods: rows,\n units: [...unitTotals.values()].sort((a, b) => b.curtailedMwh - a.curtailedMwh),\n total,\n };\n}\n\nexport async function curtailmentForDay(\n client: ElexonClient,\n settlementDate: DateInput,\n opts: CurtailmentOptions = {},\n): Promise<CurtailmentReport> {\n const day = formatDate(settlementDate);\n const last = settlementPeriodsInDay(day);\n const from = opts.settlementPeriodFrom ?? 1;\n const to = opts.settlementPeriodTo ?? last;\n if (from < 1 || to > last || from > to) {\n throw new UkEnergyError(`Settlement periods ${from}-${to} are outside 1-${last} for ${day}`);\n }\n\n const units = await client.reference.bmUnits();\n let ids: string[];\n if (opts.bmUnit) {\n ids = opts.bmUnit;\n } else {\n const fuel = opts.fuelType ?? DEFAULT_FUEL_TYPES;\n ids = units\n .filter((u) => u.fuelType != null && fuel.includes(u.fuelType))\n .map(unitId)\n .filter((id) => id !== \"\");\n }\n ids = [...new Set(ids)]; // the reference list has duplicates (T_WLNYO-4 twice on 20 Oct 2026)\n if (ids.length === 0) throw new UkEnergyError(\"No BM units to query\");\n\n // BOALF is fetched for the whole day, not just from..to: an acceptance's level rows are\n // keyed by the periods they span, so one that started earlier would otherwise lose its flag.\n const [volumes, cashflows, boalfParts] = await Promise.all([\n client.balancing.acceptanceVolumes(\"bid\", day, { bmUnit: ids }),\n client.balancing.cashflows(\"bid\", day, { bmUnit: ids }),\n Promise.all(\n chunk(ids, MAX_UNITS_PER_REQUEST).map((bmUnit) =>\n client.datasets.boalf({\n from: day,\n to: day,\n settlementPeriodFrom: 1,\n settlementPeriodTo: last,\n bmUnit,\n }),\n ),\n ),\n ]);\n const boalf = boalfParts.flat();\n\n return {\n unitsQueried: ids.length,\n ...aggregateCurtailment(day, { from, to }, units, volumes, cashflows, boalf),\n };\n}\n","import {\n assertSettlementPeriod,\n type DateInput,\n formatDate,\n formatDateTime,\n HttpClient,\n type HttpClientOptions,\n type Query,\n type RateLimit,\n UkEnergyError,\n} from \"@uk-energy/core\";\nimport {\n type CurtailmentOptions,\n type CurtailmentReport,\n curtailmentForDay,\n} from \"./curtailment.js\";\nimport type {\n AcceptanceRow,\n AcceptanceVolumeRow,\n BidOffer,\n BidOfferRow,\n BmUnit,\n BoalfRow,\n BodRow,\n CashflowRow,\n DatasetQuery,\n DatasetRow,\n FuelinstRow,\n MarketIndexRow,\n MelsRow,\n PhysicalDataset,\n PhysicalRow,\n PnRow,\n StreamDataset,\n SystemPriceRow,\n} from \"./types.js\";\n\nexport const BASE_URL = \"https://data.elexon.co.uk/bmrs/api/v1\";\n\n/**\n * No limit is published and none was observed: 310 requests at ~120 req/s on 20 Oct 2026 all\n * returned 200 with no rate-limit headers (build-board d02). 10 rps / burst 20 keeps us a\n * polite order of magnitude below that; raise it per client if you need to.\n */\nexport const DEFAULT_RATE_LIMIT: RateLimit = { requestsPerSecond: 10, burst: 20 };\n\n/** Most datasets settle within minutes; a short default TTL avoids hammering on retries. */\nexport const DEFAULT_CACHE_TTL_MS = 5 * 60 * 1000;\n\nexport type ElexonClientOptions = Partial<Omit<HttpClientOptions, \"baseUrl\">> & {\n baseUrl?: string;\n};\n\n/**\n * Time window for the dataset endpoints. With `settlementPeriodFrom`/`To` set, the API treats\n * `from`/`to` as settlement *dates* (time ignored) and filters by period instead.\n */\nexport interface SettlementWindow {\n from: DateInput;\n to: DateInput;\n settlementPeriodFrom?: number;\n settlementPeriodTo?: number;\n}\n\nexport interface UnitFilter {\n /** Elexon BM unit ids, e.g. \"T_ARCHW-1\". Omit for all units. */\n bmUnit?: string[];\n}\n\n/** Client-side filter over the BM unit reference list. All given fields must match. */\nexport interface UnitsFilter {\n /** e.g. [\"WIND\"]. */\n fuelType?: string[];\n /** GSP group ids, e.g. [\"_P\", \"_N\"] (north / south Scotland). Most wind units have none. */\n gspGroupId?: string[];\n /** Case-insensitive substring of `leadPartyName`. */\n leadParty?: string;\n}\n\n/** The id to query a unit by: `elexonBmUnit`, or `nationalGridBmUnit` for units without one (48 wind units). */\nexport function unitId(u: BmUnit): string {\n return u.elexonBmUnit || u.nationalGridBmUnit || \"\";\n}\n\n/** The API accepts the whole wind fleet (287 ids, ~4.9 KB URL); chunk well below that. */\nexport const MAX_UNITS_PER_REQUEST = 200;\n\nexport function chunk<T>(items: readonly T[], size: number): T[][] {\n const out: T[][] = [];\n for (let i = 0; i < items.length; i += size) out.push(items.slice(i, i + size));\n return out;\n}\n\nexport type FuelinstQuery = (\n | { publishDateTimeFrom: DateInput; publishDateTimeTo: DateInput }\n | { settlementDateFrom: DateInput; settlementDateTo: DateInput; settlementPeriod?: number[] }\n) & { fuelType?: string[] };\n\ninterface Envelope<T> {\n data: T;\n metadata?: { datasets?: string[] };\n}\n\nfunction windowQuery(w: SettlementWindow): Query {\n const q: Query = { from: formatDateTime(w.from), to: formatDateTime(w.to) };\n if (w.settlementPeriodFrom !== undefined) {\n assertSettlementPeriod(w.settlementPeriodFrom);\n q.settlementPeriodFrom = w.settlementPeriodFrom;\n }\n if (w.settlementPeriodTo !== undefined) {\n assertSettlementPeriod(w.settlementPeriodTo);\n q.settlementPeriodTo = w.settlementPeriodTo;\n }\n return q;\n}\n\nfunction periodQuery(settlementDate: DateInput, settlementPeriod: number): Query {\n assertSettlementPeriod(settlementPeriod);\n return { settlementDate: formatDate(settlementDate), settlementPeriod };\n}\n\n/**\n * Client for Elexon Insights (BMRS). Methods return the API's own row shapes, typed from its\n * OpenAPI spec and unwrapped from the `{ data }` envelope where there is one.\n *\n * const ex = new ElexonClient();\n * const rows = await ex.datasets.boalf({ from: \"2026-10-15T00:00Z\", to: \"2026-10-15T01:00Z\" });\n * const wind = (await ex.reference.bmUnits()).filter((u) => u.fuelType === \"WIND\");\n *\n * Contains BMRS data © Elexon Limited copyright and database right 2026. Keep the attribution.\n */\nexport class ElexonClient {\n readonly http: HttpClient;\n\n constructor(opts: ElexonClientOptions = {}) {\n this.http = new HttpClient({\n baseUrl: BASE_URL,\n rateLimit: DEFAULT_RATE_LIMIT,\n cacheTtlMs: opts.cache ? DEFAULT_CACHE_TTL_MS : 0,\n ...opts,\n });\n }\n\n /** Endpoints returning a bare array. */\n private async list<T>(path: string, query?: Query): Promise<T[]> {\n const res = await this.http.get<T[]>(path, query);\n if (!Array.isArray(res)) throw new UkEnergyError(`Unexpected response shape for ${path}`);\n return res;\n }\n\n /** Endpoints returning `{ data: [...] }`. */\n private async data<T>(path: string, query?: Query): Promise<T[]> {\n const res = await this.http.get<Envelope<T[]>>(path, query);\n if (!res || !Array.isArray(res.data)) {\n throw new UkEnergyError(`Unexpected response shape for ${path}`);\n }\n return res.data;\n }\n\n /** `/datasets/{code}` — raw datasets. The `/stream` variants are used: lighter, no envelope. */\n readonly datasets = {\n /**\n * Any stream dataset (77 codes: BOALF, BOD, FUELINST, PN, MELS, WINDFOR, FREQ, …), with the\n * query parameters typed from the spec. Values are sent as given (strings in API format,\n * \"YYYY-MM-DDTHH:MMZ\" or \"YYYY-MM-DD\"); the named methods below format and validate dates.\n */\n stream: async <D extends StreamDataset>(\n dataset: D,\n query: DatasetQuery<D>,\n ): Promise<DatasetRow<D>[]> =>\n this.list<DatasetRow<D>>(`datasets/${dataset}/stream`, query as unknown as Query),\n\n /** Bid-offer acceptance levels (BOALF). Curtailment shows as `soFlag: true` bids. */\n boalf: async (w: SettlementWindow & UnitFilter): Promise<BoalfRow[]> =>\n this.list(\"datasets/BOALF/stream\", { ...windowQuery(w), bmUnit: w.bmUnit }),\n\n /** Bid-offer data (BOD): submitted bid and offer prices per pair. */\n bod: async (w: SettlementWindow & UnitFilter): Promise<BodRow[]> =>\n this.list(\"datasets/BOD/stream\", { ...windowQuery(w), bmUnit: w.bmUnit }),\n\n /** Generation by fuel type at 5-minute resolution (FUELINST). */\n fuelinst: async (q: FuelinstQuery): Promise<FuelinstRow[]> => {\n const query: Query = { fuelType: q.fuelType };\n if (\"publishDateTimeFrom\" in q) {\n query.publishDateTimeFrom = formatDateTime(q.publishDateTimeFrom);\n query.publishDateTimeTo = formatDateTime(q.publishDateTimeTo);\n } else {\n query.settlementDateFrom = formatDate(q.settlementDateFrom);\n query.settlementDateTo = formatDate(q.settlementDateTo);\n if (q.settlementPeriod) {\n for (const p of q.settlementPeriod) assertSettlementPeriod(p);\n query.settlementPeriod = q.settlementPeriod;\n }\n }\n return this.list(\"datasets/FUELINST/stream\", query);\n },\n\n /** Physical notifications for one settlement period (`/datasets/PN` takes exactly one). */\n pn: async (\n settlementDate: DateInput,\n settlementPeriod: number,\n filter: UnitFilter = {},\n ): Promise<PnRow[]> =>\n this.data(\"datasets/PN\", {\n ...periodQuery(settlementDate, settlementPeriod),\n bmUnit: filter.bmUnit,\n }),\n\n /** Physical notifications over a window (`/datasets/PN/stream`). */\n pnStream: async (w: SettlementWindow & UnitFilter): Promise<PnRow[]> =>\n this.list(\"datasets/PN/stream\", { ...windowQuery(w), bmUnit: w.bmUnit }),\n\n /** Maximum export limits (MELS). */\n mels: async (w: SettlementWindow & UnitFilter): Promise<MelsRow[]> =>\n this.list(\"datasets/MELS/stream\", { ...windowQuery(w), bmUnit: w.bmUnit }),\n\n /** Minimum import limits (MILS). */\n mils: async (w: SettlementWindow & UnitFilter): Promise<MelsRow[]> =>\n this.list(\"datasets/MILS/stream\", { ...windowQuery(w), bmUnit: w.bmUnit }),\n };\n\n /** Reference data. */\n readonly reference = {\n /** All BM units (~3,100). Capacities are strings; `fuelType` is null for many non-generators. */\n bmUnits: async (): Promise<BmUnit[]> => this.list(\"reference/bmunits/all\"),\n /** Fuel type codes used across the API. */\n fuelTypes: async (): Promise<string[]> => this.list(\"reference/fueltypes/all\"),\n /** BM units matching a filter, e.g. `{ fuelType: [\"WIND\"] }` (287 units). */\n units: async (filter: UnitsFilter = {}): Promise<BmUnit[]> => {\n const party = filter.leadParty?.toLowerCase();\n return (await this.reference.bmUnits()).filter(\n (u) =>\n (!filter.fuelType || (u.fuelType != null && filter.fuelType.includes(u.fuelType))) &&\n (!filter.gspGroupId ||\n (u.gspGroupId != null && filter.gspGroupId.includes(u.gspGroupId))) &&\n (!party || (u.leadPartyName ?? \"\").toLowerCase().includes(party)),\n );\n },\n };\n\n /** Market-wide balancing views (one settlement period per call) and settlement prices. */\n readonly balancing = {\n /** System buy/sell prices for a settlement day, or one period of it (DISEBSP). */\n systemPrices: async (\n settlementDate: DateInput,\n settlementPeriod?: number,\n ): Promise<SystemPriceRow[]> =>\n this.data(\n `balancing/settlement/system-prices/${periodPath(settlementDate, settlementPeriod)}`,\n ),\n\n /** Market index (MID) prices and volumes. */\n marketIndex: async (\n w: SettlementWindow & { dataProviders?: string[] },\n ): Promise<MarketIndexRow[]> =>\n this.data(\"balancing/pricing/market-index\", {\n ...windowQuery(w),\n dataProviders: w.dataProviders,\n }),\n\n /** Every acceptance in one settlement period. */\n acceptances: async (\n settlementDate: DateInput,\n settlementPeriod: number,\n filter: UnitFilter = {},\n ): Promise<AcceptanceRow[]> =>\n this.data(\"balancing/acceptances/all\", {\n ...periodQuery(settlementDate, settlementPeriod),\n bmUnit: filter.bmUnit,\n }),\n\n /** Every bid-offer pair in one settlement period. */\n bidOffers: async (\n settlementDate: DateInput,\n settlementPeriod: number,\n filter: UnitFilter = {},\n ): Promise<BidOfferRow[]> =>\n this.data(\"balancing/bid-offer/all\", {\n ...periodQuery(settlementDate, settlementPeriod),\n bmUnit: filter.bmUnit,\n }),\n\n /**\n * Settled acceptance volumes (BOAV) for a settlement day, or one period of it: one row per\n * acceptance per period, `totalVolumeAccepted` in MWh (negative for bids). Long unit lists\n * are split across requests.\n */\n acceptanceVolumes: async (\n bidOffer: BidOffer,\n settlementDate: DateInput,\n filter: UnitFilter & { settlementPeriod?: number } = {},\n ): Promise<AcceptanceVolumeRow[]> =>\n this.perUnitChunks(\n `balancing/settlement/acceptance/volumes/all/${bidOffer}/${periodPath(settlementDate, filter.settlementPeriod)}`,\n filter.bmUnit,\n ),\n /**\n * Indicative bid/offer cashflows (EBOCF) for a settlement day or one period: one row per unit\n * per period, `totalCashflow` in £ paid to the unit (positive for a wind bid at a negative\n * price).\n */\n cashflows: async (\n bidOffer: BidOffer,\n settlementDate: DateInput,\n filter: UnitFilter & { settlementPeriod?: number } = {},\n ): Promise<CashflowRow[]> =>\n this.perUnitChunks(\n `balancing/settlement/indicative/cashflows/all/${bidOffer}/${periodPath(settlementDate, filter.settlementPeriod)}`,\n filter.bmUnit,\n ),\n /** PN / QPN / MILS / MELS for every unit in one settlement period. */\n physical: async (\n dataset: PhysicalDataset,\n settlementDate: DateInput,\n settlementPeriod: number,\n filter: UnitFilter = {},\n ): Promise<PhysicalRow[]> =>\n this.data(\"balancing/physical/all\", {\n dataset,\n ...periodQuery(settlementDate, settlementPeriod),\n bmUnit: filter.bmUnit,\n }),\n };\n\n /** Wind curtailment: SO-flagged and energy bid acceptances per unit per period, with £. */\n readonly curtailment = {\n /**\n * Curtailment for a GB settlement day (Europe/London), per unit and per settlement period.\n * Defaults to every WIND unit; the reference list plus three requests per 200 units.\n */\n forDay: async (\n settlementDate: DateInput,\n opts: CurtailmentOptions = {},\n ): Promise<CurtailmentReport> => curtailmentForDay(this, settlementDate, opts),\n };\n\n /** `{data}` endpoints with an optional unit filter, chunked so URLs stay short. */\n private async perUnitChunks<T>(path: string, bmUnit?: string[]): Promise<T[]> {\n if (!bmUnit?.length) return this.data<T>(path);\n const parts = await Promise.all(\n chunk(bmUnit, MAX_UNITS_PER_REQUEST).map((ids) => this.data<T>(path, { bmUnit: ids })),\n );\n return parts.flat();\n }\n}\n\nfunction periodPath(settlementDate: DateInput, settlementPeriod?: number): string {\n if (settlementPeriod !== undefined) assertSettlementPeriod(settlementPeriod);\n const p = settlementPeriod === undefined ? \"\" : `/${settlementPeriod}`;\n return `${formatDate(settlementDate)}${p}`;\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/curtailment.ts","../src/wasted-wind.ts","../src/client.ts"],"names":["DEFAULT_FUEL_TYPES","formatDate","settlementPeriodsInDay","UkEnergyError"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Wind curtailment from settlement data.\n *\n * A curtailment is a *bid acceptance*: NESO accepts a unit's bid to reduce output below its\n * physical notification. Three Elexon feeds describe one:\n *\n * - BOAV (`balancing.acceptanceVolumes(\"bid\", day)`): the settled volume of each acceptance in\n * each settlement period, in MWh (negative for bids). This is the quantity curtailed.\n * - EBOCF (`balancing.cashflows(\"bid\", day)`): the £ paid to the unit per period. Wind bids are\n * priced negative (the unit pays to keep generating, so is *paid* to stop), hence the\n * cashflow is positive and is what consumers fund through BSUoS.\n * - BOALF (`datasets.boalf`): the acceptance's level profile and flags. `soFlag` marks\n * actions taken for system reasons (transmission constraints: the classic \"wind farm paid\n * to switch off because the cable to England is full\"); unflagged acceptances are energy\n * balancing. `rrFlag`/`storFlag`/`deemedBoFlag` are counted, not excluded.\n *\n * `curtailmentForDay` joins the three by (unit, period) and (acceptance number) and adds the\n * unit's fuel type, GSP group and capacity from the reference list.\n *\n * Contains BMRS data © Elexon Limited copyright and database right 2026.\n */\nimport { type DateInput, formatDate, settlementPeriodsInDay, UkEnergyError } from \"@uk-energy/core\";\nimport { chunk, type ElexonClient, MAX_UNITS_PER_REQUEST, unitId } from \"./client.js\";\nimport type { AcceptanceVolumeRow, BmUnit, BoalfRow, CashflowRow } from \"./types.js\";\n\nexport interface CurtailmentOptions {\n /** Units to include. Default: every unit whose `fuelType` is in `fuelType`. */\n bmUnit?: string[];\n /** Used when `bmUnit` is omitted. Default `[\"WIND\"]`. */\n fuelType?: string[];\n /** Restrict to a range of settlement periods (1-based, inclusive). Default: the whole day. */\n settlementPeriodFrom?: number;\n settlementPeriodTo?: number;\n}\n\n/** One unit in one settlement period. */\nexport interface CurtailmentPeriod {\n settlementDate: string;\n settlementPeriod: number;\n /** UTC start of the period, from the API. */\n startTime: string;\n bmUnit: string;\n nationalGridBmUnit: string | null;\n leadPartyName: string | null;\n fuelType: string | null;\n gspGroupId: string | null;\n /** `generationCapacity` from the reference list, MW; null when unknown. */\n capacityMw: number | null;\n /** MWh curtailed in the period (positive). */\n curtailedMwh: number;\n /** Of which from SO-flagged (system / constraint) acceptances. */\n soFlaggedMwh: number;\n /**\n * £ paid to the unit for the period's bids (EBOCF); null when no cashflow row was published\n * yet. Cashflow rows for periods with no settled bid volume are not reported anywhere.\n */\n cashflowGbp: number | null;\n /** Distinct acceptances contributing to the period. */\n acceptances: number;\n /** Acceptances in the period that BOALF reported with `soFlag: true`. */\n soFlaggedAcceptances: number;\n}\n\n/** A unit's totals over the day. */\nexport interface CurtailmentUnit {\n bmUnit: string;\n nationalGridBmUnit: string | null;\n leadPartyName: string | null;\n fuelType: string | null;\n gspGroupId: string | null;\n capacityMw: number | null;\n curtailedMwh: number;\n soFlaggedMwh: number;\n cashflowGbp: number;\n /** Settlement periods with any curtailment. */\n periods: number;\n /** Distinct acceptances over the day (one acceptance can span several periods). */\n acceptances: number;\n}\n\nexport interface CurtailmentReport {\n settlementDate: string;\n settlementPeriodFrom: number;\n settlementPeriodTo: number;\n /** Units that were queried (whether or not they were curtailed). */\n unitsQueried: number;\n /** Rows ordered by settlement period then unit id; only (unit, period) pairs with volume. */\n periods: CurtailmentPeriod[];\n /** Per-unit totals, largest `curtailedMwh` first. */\n units: CurtailmentUnit[];\n total: {\n curtailedMwh: number;\n soFlaggedMwh: number;\n cashflowGbp: number;\n /** Units with any curtailment. */\n units: number;\n /** Distinct acceptances. */\n acceptances: number;\n };\n}\n\nconst DEFAULT_FUEL_TYPES = [\"WIND\"];\n\n/** Code-point order (not locale order) so TS and Python sort \"C__X\" and \"CD\" the same way. */\nfunction compareIds(a: string, b: string): number {\n return a < b ? -1 : a > b ? 1 : 0;\n}\n\nfunction num(s: string | null | undefined): number | null {\n if (s == null || s === \"\") return null;\n const n = Number(s);\n return Number.isFinite(n) ? n : null;\n}\n\n/** Pure aggregation over already-fetched rows; exported for tests and for callers with their own data. */\nexport function aggregateCurtailment(\n settlementDate: string,\n range: { from: number; to: number },\n units: readonly BmUnit[],\n volumes: readonly AcceptanceVolumeRow[],\n cashflows: readonly CashflowRow[],\n boalf: readonly BoalfRow[],\n): Omit<CurtailmentReport, \"unitsQueried\"> {\n const byId = new Map<string, BmUnit>();\n for (const u of units) {\n if (u.elexonBmUnit) byId.set(u.elexonBmUnit, u);\n if (u.nationalGridBmUnit) byId.set(u.nationalGridBmUnit, u);\n }\n const soByAcceptance = new Map<number, boolean>();\n for (const r of boalf) {\n if (r.acceptanceNumber != null) {\n soByAcceptance.set(\n r.acceptanceNumber,\n (soByAcceptance.get(r.acceptanceNumber) ?? false) || r.soFlag === true,\n );\n }\n }\n const cashByKey = new Map<string, number>();\n for (const c of cashflows) {\n const sp = c.settlementPeriod;\n if (!c.bmUnit || sp === undefined || sp < range.from || sp > range.to) continue;\n const key = `${c.bmUnit}|${sp}`;\n cashByKey.set(key, (cashByKey.get(key) ?? 0) + (c.totalCashflow ?? 0));\n }\n\n const periods = new Map<string, CurtailmentPeriod>();\n const seen = new Map<string, Set<number>>();\n for (const v of volumes) {\n const sp = v.settlementPeriod;\n const acceptance = v.acceptanceId;\n if (!v.bmUnit || sp === undefined || acceptance === undefined) continue;\n if (sp < range.from || sp > range.to) continue;\n const mwh = -(v.totalVolumeAccepted ?? 0);\n if (!(mwh > 0)) continue;\n const key = `${v.bmUnit}|${sp}`;\n let p = periods.get(key);\n let ids = seen.get(key);\n if (!p || !ids) {\n const ref = byId.get(v.bmUnit);\n p = {\n settlementDate: v.settlementDate ?? settlementDate,\n settlementPeriod: sp,\n startTime: v.startTime ?? \"\",\n bmUnit: v.bmUnit,\n nationalGridBmUnit: v.nationalGridBmUnit ?? ref?.nationalGridBmUnit ?? null,\n leadPartyName: v.leadPartyName ?? ref?.leadPartyName ?? null,\n fuelType: ref?.fuelType ?? null,\n gspGroupId: ref?.gspGroupId ?? null,\n capacityMw: num(ref?.generationCapacity),\n curtailedMwh: 0,\n soFlaggedMwh: 0,\n cashflowGbp: cashByKey.get(key) ?? null,\n acceptances: 0,\n soFlaggedAcceptances: 0,\n };\n ids = new Set();\n periods.set(key, p);\n seen.set(key, ids);\n }\n p.curtailedMwh += mwh;\n const so = soByAcceptance.get(acceptance) === true;\n if (so) p.soFlaggedMwh += mwh;\n if (!ids.has(acceptance)) {\n ids.add(acceptance);\n p.acceptances += 1;\n if (so) p.soFlaggedAcceptances += 1;\n }\n }\n\n const rows = [...periods.values()].sort(\n (a, b) => a.settlementPeriod - b.settlementPeriod || compareIds(a.bmUnit, b.bmUnit),\n );\n\n const unitTotals = new Map<string, CurtailmentUnit>();\n const unitAcceptances = new Map<string, Set<number>>();\n const allAcceptances = new Set<number>();\n const total = { curtailedMwh: 0, soFlaggedMwh: 0, cashflowGbp: 0, units: 0, acceptances: 0 };\n for (const p of rows) {\n let u = unitTotals.get(p.bmUnit);\n if (!u) {\n u = {\n bmUnit: p.bmUnit,\n nationalGridBmUnit: p.nationalGridBmUnit,\n leadPartyName: p.leadPartyName,\n fuelType: p.fuelType,\n gspGroupId: p.gspGroupId,\n capacityMw: p.capacityMw,\n curtailedMwh: 0,\n soFlaggedMwh: 0,\n cashflowGbp: 0,\n periods: 0,\n acceptances: 0,\n };\n unitTotals.set(p.bmUnit, u);\n }\n u.curtailedMwh += p.curtailedMwh;\n u.soFlaggedMwh += p.soFlaggedMwh;\n u.cashflowGbp += p.cashflowGbp ?? 0;\n u.periods += 1;\n const ids = seen.get(`${p.bmUnit}|${p.settlementPeriod}`) ?? new Set<number>();\n let acc = unitAcceptances.get(p.bmUnit);\n if (!acc) {\n acc = new Set();\n unitAcceptances.set(p.bmUnit, acc);\n }\n for (const id of ids) {\n acc.add(id);\n allAcceptances.add(id);\n }\n u.acceptances = acc.size;\n total.curtailedMwh += p.curtailedMwh;\n total.soFlaggedMwh += p.soFlaggedMwh;\n total.cashflowGbp += p.cashflowGbp ?? 0;\n }\n total.units = unitTotals.size;\n total.acceptances = allAcceptances.size;\n\n return {\n settlementDate,\n settlementPeriodFrom: range.from,\n settlementPeriodTo: range.to,\n periods: rows,\n units: [...unitTotals.values()].sort((a, b) => b.curtailedMwh - a.curtailedMwh),\n total,\n };\n}\n\nexport async function curtailmentForDay(\n client: ElexonClient,\n settlementDate: DateInput,\n opts: CurtailmentOptions = {},\n): Promise<CurtailmentReport> {\n const day = formatDate(settlementDate);\n const last = settlementPeriodsInDay(day);\n const from = opts.settlementPeriodFrom ?? 1;\n const to = opts.settlementPeriodTo ?? last;\n if (from < 1 || to > last || from > to) {\n throw new UkEnergyError(`Settlement periods ${from}-${to} are outside 1-${last} for ${day}`);\n }\n\n const units = await client.reference.bmUnits();\n let ids: string[];\n if (opts.bmUnit) {\n ids = opts.bmUnit;\n } else {\n const fuel = opts.fuelType ?? DEFAULT_FUEL_TYPES;\n ids = units\n .filter((u) => u.fuelType != null && fuel.includes(u.fuelType))\n .map(unitId)\n .filter((id) => id !== \"\");\n }\n ids = [...new Set(ids)]; // the reference list has duplicates (T_WLNYO-4 twice on 20 Oct 2026)\n if (ids.length === 0) throw new UkEnergyError(\"No BM units to query\");\n\n // BOALF is fetched for the whole day, not just from..to: an acceptance's level rows are\n // keyed by the periods they span, so one that started earlier would otherwise lose its flag.\n const [volumes, cashflows, boalfParts] = await Promise.all([\n client.balancing.acceptanceVolumes(\"bid\", day, { bmUnit: ids }),\n client.balancing.cashflows(\"bid\", day, { bmUnit: ids }),\n Promise.all(\n chunk(ids, MAX_UNITS_PER_REQUEST).map((bmUnit) =>\n client.datasets.boalf({\n from: day,\n to: day,\n settlementPeriodFrom: 1,\n settlementPeriodTo: last,\n bmUnit,\n }),\n ),\n ),\n ]);\n const boalf = boalfParts.flat();\n\n return {\n unitsQueried: ids.length,\n ...aggregateCurtailment(day, { from, to }, units, volumes, cashflows, boalf),\n };\n}\n","/**\n * Octopus Energy's \"Wasted Wind\" figure (https://wastedwind.energy), reproduced from the same\n * Elexon data. Per settlement period:\n *\n * - **Switch-off**: rows of the settlement *bid* stack whose unit is `fuelType: \"WIND\"` in the\n * BM unit reference and which carry `soFlag: true` (system / constraint actions). Volume is\n * the sum of their `volume` (negative MWh); cost is Σ `originalPrice × volume`, which is\n * positive for the usual negative-priced wind bid (the farm is paid to stop) and negative in\n * the rare case of a positive bid price.\n * - **Replace**: the curtailed volume bought back from the settlement *offer* stack of the same\n * period, `cadlFlag` offers excluded, walking non-SO offers first then by NESO's\n * `sequenceNumber`, pro-rating the last offer. Cost is Σ `originalPrice × volume taken`.\n *\n * Totals over a day are what the tracker shows as \"£ switching off wind turbines\" and \"£ buying\n * energy elsewhere\"; its monthly summary is the sum over days. This is a *definition* rather\n * than settlement truth: it counts SO-flagged wind bids only (see `curtailment.forDay` for the\n * settled BOAV/EBOCF view of every wind bid) and estimates replacement cost from the offer\n * stack rather than attributing actual offer cashflows.\n *\n * Contains BMRS data © Elexon Limited copyright and database right 2026.\n */\nimport { type DateInput, formatDate, settlementPeriodsInDay, UkEnergyError } from \"@uk-energy/core\";\nimport type { ElexonClient } from \"./client.js\";\nimport type { BmUnit, SettlementStackRow } from \"./types.js\";\n\nexport interface WastedWindOptions {\n /** Restrict to a range of settlement periods (1-based, inclusive). Default: the whole day. */\n settlementPeriodFrom?: number;\n settlementPeriodTo?: number;\n /** Units counted as wind. Default: `fuelType === \"WIND\"` in the reference list. */\n fuelType?: string[];\n /** Stack requests in flight at once (two per period). Default 10. */\n concurrency?: number;\n}\n\nexport interface WastedWindPeriod {\n settlementDate: string;\n settlementPeriod: number;\n /** UTC start of the period, from any stack row of the period; empty when there were none. */\n startTime: string;\n /** MWh of SO-flagged wind bids (positive). */\n curtailedMwh: number;\n /** £ paid to wind units to turn down (Σ originalPrice × volume). */\n curtailmentCostGbp: number;\n /** MWh bought back from the offer stack (≤ curtailedMwh when the stack runs out). */\n turnUpMwh: number;\n /** £ to buy that energy from the offer stack. */\n turnUpCostGbp: number;\n /** Wind bid rows and offer rows that contributed. */\n bids: number;\n offers: number;\n}\n\nexport interface WastedWindReport {\n settlementDate: string;\n settlementPeriodFrom: number;\n settlementPeriodTo: number;\n periods: WastedWindPeriod[];\n total: {\n curtailedMwh: number;\n curtailmentCostGbp: number;\n turnUpMwh: number;\n turnUpCostGbp: number;\n /** curtailmentCostGbp + turnUpCostGbp: the tracker's headline. */\n totalCostGbp: number;\n };\n}\n\nconst DEFAULT_FUEL_TYPES = [\"WIND\"];\n\n/** Pure aggregation; exported for tests and for callers with their own stack rows. */\nexport function aggregateWastedWind(\n settlementDate: string,\n range: { from: number; to: number },\n units: readonly BmUnit[],\n bids: readonly SettlementStackRow[],\n offers: readonly SettlementStackRow[],\n fuelTypes: readonly string[] = DEFAULT_FUEL_TYPES,\n): WastedWindReport {\n const fuelById = new Map<string, string | null | undefined>();\n for (const u of units) if (u.elexonBmUnit) fuelById.set(u.elexonBmUnit, u.fuelType);\n\n const periods: WastedWindPeriod[] = [];\n for (let sp = range.from; sp <= range.to; sp++) {\n const windBids = bids.filter(\n (r) =>\n r.settlementPeriod === sp &&\n r.soFlag === true &&\n r.id != null &&\n fuelTypes.includes(fuelById.get(r.id) ?? \"\"),\n );\n let volume = 0;\n let cost = 0;\n for (const b of windBids) {\n const v = b.volume ?? 0;\n volume += v;\n cost += (b.originalPrice ?? 0) * v;\n }\n const need = Math.abs(volume);\n\n const stack = offers\n .filter((r) => r.settlementPeriod === sp && !r.cadlFlag)\n .sort(\n (a, b) =>\n (a.soFlag ? 1 : 0) - (b.soFlag ? 1 : 0) ||\n (a.sequenceNumber ?? 0) - (b.sequenceNumber ?? 0),\n );\n let taken = 0;\n let turnUpCost = 0;\n let used = 0;\n for (const o of need > 0 ? stack : []) {\n used += 1;\n const v = o.volume ?? 0;\n if (taken + v > need) {\n const fraction = Math.min(Math.max((need - taken) / v, 0), 1);\n taken += v * fraction;\n turnUpCost += (o.originalPrice ?? 0) * (v * fraction);\n } else {\n taken += v;\n turnUpCost += (o.originalPrice ?? 0) * v;\n }\n if (taken >= need) break;\n }\n\n const first =\n windBids[0] ??\n stack[0] ??\n bids.find((r) => r.settlementPeriod === sp) ??\n offers.find((r) => r.settlementPeriod === sp);\n periods.push({\n settlementDate,\n settlementPeriod: sp,\n startTime: first?.startTime ?? \"\",\n curtailedMwh: need,\n curtailmentCostGbp: cost,\n turnUpMwh: taken,\n turnUpCostGbp: turnUpCost,\n bids: windBids.length,\n offers: used,\n });\n }\n\n const total = {\n curtailedMwh: 0,\n curtailmentCostGbp: 0,\n turnUpMwh: 0,\n turnUpCostGbp: 0,\n totalCostGbp: 0,\n };\n for (const p of periods) {\n total.curtailedMwh += p.curtailedMwh;\n total.curtailmentCostGbp += p.curtailmentCostGbp;\n total.turnUpMwh += p.turnUpMwh;\n total.turnUpCostGbp += p.turnUpCostGbp;\n }\n total.totalCostGbp = total.curtailmentCostGbp + total.turnUpCostGbp;\n\n return {\n settlementDate,\n settlementPeriodFrom: range.from,\n settlementPeriodTo: range.to,\n periods,\n total,\n };\n}\n\nexport async function wastedWindForDay(\n client: ElexonClient,\n settlementDate: DateInput,\n opts: WastedWindOptions = {},\n): Promise<WastedWindReport> {\n const day = formatDate(settlementDate);\n const last = settlementPeriodsInDay(day);\n const from = opts.settlementPeriodFrom ?? 1;\n const to = opts.settlementPeriodTo ?? last;\n if (from < 1 || to > last || from > to) {\n throw new UkEnergyError(`Settlement periods ${from}-${to} are outside 1-${last} for ${day}`);\n }\n const concurrency = Math.max(1, opts.concurrency ?? 10);\n\n const units = await client.reference.bmUnits();\n const jobs: (() => Promise<SettlementStackRow[]>)[] = [];\n for (let sp = from; sp <= to; sp++) {\n jobs.push(() => client.balancing.settlementStack(\"bid\", day, sp));\n jobs.push(() => client.balancing.settlementStack(\"offer\", day, sp));\n }\n const results: SettlementStackRow[][] = new Array(jobs.length);\n let next = 0;\n let failed = false;\n const worker = async () => {\n while (next < jobs.length && !failed) {\n const i = next++;\n const job = jobs[i];\n if (!job) continue;\n try {\n results[i] = await job();\n } catch (err) {\n failed = true; // stop the other workers issuing requests we will not use\n throw err;\n }\n }\n };\n await Promise.all(Array.from({ length: Math.min(concurrency, jobs.length) }, worker));\n\n const bids: SettlementStackRow[] = [];\n const offers: SettlementStackRow[] = [];\n results.forEach((rows, i) => {\n (i % 2 === 0 ? bids : offers).push(...(rows ?? []));\n });\n\n return aggregateWastedWind(\n day,\n { from, to },\n units,\n bids,\n offers,\n opts.fuelType ?? DEFAULT_FUEL_TYPES,\n );\n}\n","import {\n assertSettlementPeriod,\n type DateInput,\n formatDate,\n formatDateTime,\n HttpClient,\n type HttpClientOptions,\n type Query,\n type RateLimit,\n UkEnergyError,\n} from \"@uk-energy/core\";\nimport {\n type CurtailmentOptions,\n type CurtailmentReport,\n curtailmentForDay,\n} from \"./curtailment.js\";\nimport type {\n AcceptanceRow,\n AcceptanceVolumeRow,\n BidOffer,\n BidOfferRow,\n BmUnit,\n BoalfRow,\n BodRow,\n CashflowRow,\n DatasetQuery,\n DatasetRow,\n FuelinstRow,\n MarketIndexRow,\n MelsRow,\n PhysicalDataset,\n PhysicalRow,\n PnRow,\n SettlementStackRow,\n StreamDataset,\n SystemPriceRow,\n} from \"./types.js\";\nimport { type WastedWindOptions, type WastedWindReport, wastedWindForDay } from \"./wasted-wind.js\";\n\nexport const BASE_URL = \"https://data.elexon.co.uk/bmrs/api/v1\";\n\n/**\n * No limit is published and none was observed: 310 requests at ~120 req/s on 20 Oct 2026 all\n * returned 200 with no rate-limit headers (build-board d02). 10 rps / burst 20 keeps us a\n * polite order of magnitude below that; raise it per client if you need to.\n */\nexport const DEFAULT_RATE_LIMIT: RateLimit = { requestsPerSecond: 10, burst: 20 };\n\n/** Most datasets settle within minutes; a short default TTL avoids hammering on retries. */\nexport const DEFAULT_CACHE_TTL_MS = 5 * 60 * 1000;\n\nexport type ElexonClientOptions = Partial<Omit<HttpClientOptions, \"baseUrl\">> & {\n baseUrl?: string;\n};\n\n/**\n * Time window for the dataset endpoints. With `settlementPeriodFrom`/`To` set, the API treats\n * `from`/`to` as settlement *dates* (time ignored) and filters by period instead.\n */\nexport interface SettlementWindow {\n from: DateInput;\n to: DateInput;\n settlementPeriodFrom?: number;\n settlementPeriodTo?: number;\n}\n\nexport interface UnitFilter {\n /** Elexon BM unit ids, e.g. \"T_ARCHW-1\". Omit for all units. */\n bmUnit?: string[];\n}\n\n/** Client-side filter over the BM unit reference list. All given fields must match. */\nexport interface UnitsFilter {\n /** e.g. [\"WIND\"]. */\n fuelType?: string[];\n /** GSP group ids, e.g. [\"_P\", \"_N\"] (north / south Scotland). Most wind units have none. */\n gspGroupId?: string[];\n /** Case-insensitive substring of `leadPartyName`. */\n leadParty?: string;\n}\n\n/** The id to query a unit by: `elexonBmUnit`, or `nationalGridBmUnit` for units without one (48 wind units). */\nexport function unitId(u: BmUnit): string {\n return u.elexonBmUnit || u.nationalGridBmUnit || \"\";\n}\n\n/** The API accepts the whole wind fleet (287 ids, ~4.9 KB URL); chunk well below that. */\nexport const MAX_UNITS_PER_REQUEST = 200;\n\nexport function chunk<T>(items: readonly T[], size: number): T[][] {\n const out: T[][] = [];\n for (let i = 0; i < items.length; i += size) out.push(items.slice(i, i + size));\n return out;\n}\n\nexport type FuelinstQuery = (\n | { publishDateTimeFrom: DateInput; publishDateTimeTo: DateInput }\n | { settlementDateFrom: DateInput; settlementDateTo: DateInput; settlementPeriod?: number[] }\n) & { fuelType?: string[] };\n\ninterface Envelope<T> {\n data: T;\n metadata?: { datasets?: string[] };\n}\n\nfunction windowQuery(w: SettlementWindow): Query {\n const q: Query = { from: formatDateTime(w.from), to: formatDateTime(w.to) };\n if (w.settlementPeriodFrom !== undefined) {\n assertSettlementPeriod(w.settlementPeriodFrom);\n q.settlementPeriodFrom = w.settlementPeriodFrom;\n }\n if (w.settlementPeriodTo !== undefined) {\n assertSettlementPeriod(w.settlementPeriodTo);\n q.settlementPeriodTo = w.settlementPeriodTo;\n }\n return q;\n}\n\nfunction periodQuery(settlementDate: DateInput, settlementPeriod: number): Query {\n assertSettlementPeriod(settlementPeriod);\n return { settlementDate: formatDate(settlementDate), settlementPeriod };\n}\n\n/**\n * Client for Elexon Insights (BMRS). Methods return the API's own row shapes, typed from its\n * OpenAPI spec and unwrapped from the `{ data }` envelope where there is one.\n *\n * const ex = new ElexonClient();\n * const rows = await ex.datasets.boalf({ from: \"2026-10-15T00:00Z\", to: \"2026-10-15T01:00Z\" });\n * const wind = (await ex.reference.bmUnits()).filter((u) => u.fuelType === \"WIND\");\n *\n * Contains BMRS data © Elexon Limited copyright and database right 2026. Keep the attribution.\n */\nexport class ElexonClient {\n readonly http: HttpClient;\n\n constructor(opts: ElexonClientOptions = {}) {\n this.http = new HttpClient({\n baseUrl: BASE_URL,\n rateLimit: DEFAULT_RATE_LIMIT,\n cacheTtlMs: opts.cache ? DEFAULT_CACHE_TTL_MS : 0,\n ...opts,\n });\n }\n\n /** Endpoints returning a bare array. */\n private async list<T>(path: string, query?: Query): Promise<T[]> {\n const res = await this.http.get<T[]>(path, query);\n if (!Array.isArray(res)) throw new UkEnergyError(`Unexpected response shape for ${path}`);\n return res;\n }\n\n /** Endpoints returning `{ data: [...] }`. */\n private async data<T>(path: string, query?: Query): Promise<T[]> {\n const res = await this.http.get<Envelope<T[]>>(path, query);\n if (!res || !Array.isArray(res.data)) {\n throw new UkEnergyError(`Unexpected response shape for ${path}`);\n }\n return res.data;\n }\n\n /** `/datasets/{code}` — raw datasets. The `/stream` variants are used: lighter, no envelope. */\n readonly datasets = {\n /**\n * Any stream dataset (77 codes: BOALF, BOD, FUELINST, PN, MELS, WINDFOR, FREQ, …), with the\n * query parameters typed from the spec. Values are sent as given (strings in API format,\n * \"YYYY-MM-DDTHH:MMZ\" or \"YYYY-MM-DD\"); the named methods below format and validate dates.\n */\n stream: async <D extends StreamDataset>(\n dataset: D,\n query: DatasetQuery<D>,\n ): Promise<DatasetRow<D>[]> =>\n this.list<DatasetRow<D>>(`datasets/${dataset}/stream`, query as unknown as Query),\n\n /** Bid-offer acceptance levels (BOALF). Curtailment shows as `soFlag: true` bids. */\n boalf: async (w: SettlementWindow & UnitFilter): Promise<BoalfRow[]> =>\n this.list(\"datasets/BOALF/stream\", { ...windowQuery(w), bmUnit: w.bmUnit }),\n\n /** Bid-offer data (BOD): submitted bid and offer prices per pair. */\n bod: async (w: SettlementWindow & UnitFilter): Promise<BodRow[]> =>\n this.list(\"datasets/BOD/stream\", { ...windowQuery(w), bmUnit: w.bmUnit }),\n\n /** Generation by fuel type at 5-minute resolution (FUELINST). */\n fuelinst: async (q: FuelinstQuery): Promise<FuelinstRow[]> => {\n const query: Query = { fuelType: q.fuelType };\n if (\"publishDateTimeFrom\" in q) {\n query.publishDateTimeFrom = formatDateTime(q.publishDateTimeFrom);\n query.publishDateTimeTo = formatDateTime(q.publishDateTimeTo);\n } else {\n query.settlementDateFrom = formatDate(q.settlementDateFrom);\n query.settlementDateTo = formatDate(q.settlementDateTo);\n if (q.settlementPeriod) {\n for (const p of q.settlementPeriod) assertSettlementPeriod(p);\n query.settlementPeriod = q.settlementPeriod;\n }\n }\n return this.list(\"datasets/FUELINST/stream\", query);\n },\n\n /** Physical notifications for one settlement period (`/datasets/PN` takes exactly one). */\n pn: async (\n settlementDate: DateInput,\n settlementPeriod: number,\n filter: UnitFilter = {},\n ): Promise<PnRow[]> =>\n this.data(\"datasets/PN\", {\n ...periodQuery(settlementDate, settlementPeriod),\n bmUnit: filter.bmUnit,\n }),\n\n /** Physical notifications over a window (`/datasets/PN/stream`). */\n pnStream: async (w: SettlementWindow & UnitFilter): Promise<PnRow[]> =>\n this.list(\"datasets/PN/stream\", { ...windowQuery(w), bmUnit: w.bmUnit }),\n\n /** Maximum export limits (MELS). */\n mels: async (w: SettlementWindow & UnitFilter): Promise<MelsRow[]> =>\n this.list(\"datasets/MELS/stream\", { ...windowQuery(w), bmUnit: w.bmUnit }),\n\n /** Minimum import limits (MILS). */\n mils: async (w: SettlementWindow & UnitFilter): Promise<MelsRow[]> =>\n this.list(\"datasets/MILS/stream\", { ...windowQuery(w), bmUnit: w.bmUnit }),\n };\n\n /** Reference data. */\n readonly reference = {\n /** All BM units (~3,100). Capacities are strings; `fuelType` is null for many non-generators. */\n bmUnits: async (): Promise<BmUnit[]> => this.list(\"reference/bmunits/all\"),\n /** Fuel type codes used across the API. */\n fuelTypes: async (): Promise<string[]> => this.list(\"reference/fueltypes/all\"),\n /** BM units matching a filter, e.g. `{ fuelType: [\"WIND\"] }` (287 units). */\n units: async (filter: UnitsFilter = {}): Promise<BmUnit[]> => {\n const party = filter.leadParty?.toLowerCase();\n return (await this.reference.bmUnits()).filter(\n (u) =>\n (!filter.fuelType || (u.fuelType != null && filter.fuelType.includes(u.fuelType))) &&\n (!filter.gspGroupId ||\n (u.gspGroupId != null && filter.gspGroupId.includes(u.gspGroupId))) &&\n (!party || (u.leadPartyName ?? \"\").toLowerCase().includes(party)),\n );\n },\n };\n\n /** Market-wide balancing views (one settlement period per call) and settlement prices. */\n readonly balancing = {\n /** System buy/sell prices for a settlement day, or one period of it (DISEBSP). */\n systemPrices: async (\n settlementDate: DateInput,\n settlementPeriod?: number,\n ): Promise<SystemPriceRow[]> =>\n this.data(\n `balancing/settlement/system-prices/${periodPath(settlementDate, settlementPeriod)}`,\n ),\n\n /** Market index (MID) prices and volumes. */\n marketIndex: async (\n w: SettlementWindow & { dataProviders?: string[] },\n ): Promise<MarketIndexRow[]> =>\n this.data(\"balancing/pricing/market-index\", {\n ...windowQuery(w),\n dataProviders: w.dataProviders,\n }),\n\n /** Every acceptance in one settlement period. */\n acceptances: async (\n settlementDate: DateInput,\n settlementPeriod: number,\n filter: UnitFilter = {},\n ): Promise<AcceptanceRow[]> =>\n this.data(\"balancing/acceptances/all\", {\n ...periodQuery(settlementDate, settlementPeriod),\n bmUnit: filter.bmUnit,\n }),\n\n /** Every bid-offer pair in one settlement period. */\n bidOffers: async (\n settlementDate: DateInput,\n settlementPeriod: number,\n filter: UnitFilter = {},\n ): Promise<BidOfferRow[]> =>\n this.data(\"balancing/bid-offer/all\", {\n ...periodQuery(settlementDate, settlementPeriod),\n bmUnit: filter.bmUnit,\n }),\n\n /**\n * Settled acceptance volumes (BOAV) for a settlement day, or one period of it: one row per\n * acceptance per period, `totalVolumeAccepted` in MWh (negative for bids). Long unit lists\n * are split across requests.\n */\n acceptanceVolumes: async (\n bidOffer: BidOffer,\n settlementDate: DateInput,\n filter: UnitFilter & { settlementPeriod?: number } = {},\n ): Promise<AcceptanceVolumeRow[]> =>\n this.perUnitChunks(\n `balancing/settlement/acceptance/volumes/all/${bidOffer}/${periodPath(settlementDate, filter.settlementPeriod)}`,\n filter.bmUnit,\n ),\n /**\n * Indicative bid/offer cashflows (EBOCF) for a settlement day or one period: one row per unit\n * per period, `totalCashflow` in £ paid to the unit (positive for a wind bid at a negative\n * price).\n */\n cashflows: async (\n bidOffer: BidOffer,\n settlementDate: DateInput,\n filter: UnitFilter & { settlementPeriod?: number } = {},\n ): Promise<CashflowRow[]> =>\n this.perUnitChunks(\n `balancing/settlement/indicative/cashflows/all/${bidOffer}/${periodPath(settlementDate, filter.settlementPeriod)}`,\n filter.bmUnit,\n ),\n /**\n * Settlement bid or offer stack for one period (ISPSTACK): every accepted action with\n * `originalPrice`, `volume` (negative for bids), `soFlag`, `cadlFlag` and the sequence NESO\n * used. This is what Octopus's Wasted Wind tracker is built on.\n */\n settlementStack: async (\n bidOffer: BidOffer,\n settlementDate: DateInput,\n settlementPeriod: number,\n ): Promise<SettlementStackRow[]> =>\n this.data(\n `balancing/settlement/stack/all/${bidOffer}/${periodPath(settlementDate, settlementPeriod)}`,\n ),\n /** PN / QPN / MILS / MELS for every unit in one settlement period. */\n physical: async (\n dataset: PhysicalDataset,\n settlementDate: DateInput,\n settlementPeriod: number,\n filter: UnitFilter = {},\n ): Promise<PhysicalRow[]> =>\n this.data(\"balancing/physical/all\", {\n dataset,\n ...periodQuery(settlementDate, settlementPeriod),\n bmUnit: filter.bmUnit,\n }),\n };\n\n /** Wind curtailment: SO-flagged and energy bid acceptances per unit per period, with £. */\n readonly curtailment = {\n /**\n * Curtailment for a GB settlement day (Europe/London), per unit and per settlement period.\n * Defaults to every WIND unit; the reference list plus three requests per 200 units.\n */\n forDay: async (\n settlementDate: DateInput,\n opts: CurtailmentOptions = {},\n ): Promise<CurtailmentReport> => curtailmentForDay(this, settlementDate, opts),\n /**\n * Octopus's \"Wasted Wind\" definition for a settlement day: SO-flagged wind bids from the\n * settlement stack (MWh and £ to switch off) plus the cost of re-buying that energy from\n * the cheapest offers in the same period (£ to replace). Two requests per settlement period.\n */\n wastedWind: async (\n settlementDate: DateInput,\n opts: WastedWindOptions = {},\n ): Promise<WastedWindReport> => wastedWindForDay(this, settlementDate, opts),\n };\n\n /** `{data}` endpoints with an optional unit filter, chunked so URLs stay short. */\n private async perUnitChunks<T>(path: string, bmUnit?: string[]): Promise<T[]> {\n if (!bmUnit?.length) return this.data<T>(path);\n const parts = await Promise.all(\n chunk(bmUnit, MAX_UNITS_PER_REQUEST).map((ids) => this.data<T>(path, { bmUnit: ids })),\n );\n return parts.flat();\n }\n}\n\nfunction periodPath(settlementDate: DateInput, settlementPeriod?: number): string {\n if (settlementPeriod !== undefined) assertSettlementPeriod(settlementPeriod);\n const p = settlementPeriod === undefined ? \"\" : `/${settlementPeriod}`;\n return `${formatDate(settlementDate)}${p}`;\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uk-energy/elexon",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Typed client for Elexon Insights (BMRS): bid-offer acceptances, bid-offer data, physical notifications, generation by fuel, BMU reference, system prices. Part of uk-energy-sdk.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"sdk"
|
|
45
45
|
],
|
|
46
46
|
"dependencies": {
|
|
47
|
-
"@uk-energy/core": "^0.
|
|
47
|
+
"@uk-energy/core": "^0.3.0"
|
|
48
48
|
},
|
|
49
49
|
"publishConfig": {
|
|
50
50
|
"access": "public"
|