@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 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
@@ -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.2.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.2.0"
47
+ "@uk-energy/core": "^0.3.0"
48
48
  },
49
49
  "publishConfig": {
50
50
  "access": "public"