@uk-energy/elexon 0.0.0-stage → 0.2.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/dist/index.js ADDED
@@ -0,0 +1,351 @@
1
+ import { formatDate, settlementPeriodsInDay, UkEnergyError, HttpClient, formatDateTime, assertSettlementPeriod } from '@uk-energy/core';
2
+
3
+ // src/client.ts
4
+ var DEFAULT_FUEL_TYPES = ["WIND"];
5
+ function compareIds(a, b) {
6
+ return a < b ? -1 : a > b ? 1 : 0;
7
+ }
8
+ function num(s) {
9
+ if (s == null || s === "") return null;
10
+ const n = Number(s);
11
+ return Number.isFinite(n) ? n : null;
12
+ }
13
+ function aggregateCurtailment(settlementDate, range, units, volumes, cashflows, boalf) {
14
+ const byId = /* @__PURE__ */ new Map();
15
+ for (const u of units) {
16
+ if (u.elexonBmUnit) byId.set(u.elexonBmUnit, u);
17
+ if (u.nationalGridBmUnit) byId.set(u.nationalGridBmUnit, u);
18
+ }
19
+ const soByAcceptance = /* @__PURE__ */ new Map();
20
+ for (const r of boalf) {
21
+ if (r.acceptanceNumber != null) {
22
+ soByAcceptance.set(
23
+ r.acceptanceNumber,
24
+ (soByAcceptance.get(r.acceptanceNumber) ?? false) || r.soFlag === true
25
+ );
26
+ }
27
+ }
28
+ const cashByKey = /* @__PURE__ */ new Map();
29
+ for (const c of cashflows) {
30
+ const sp = c.settlementPeriod;
31
+ if (!c.bmUnit || sp === void 0 || sp < range.from || sp > range.to) continue;
32
+ const key = `${c.bmUnit}|${sp}`;
33
+ cashByKey.set(key, (cashByKey.get(key) ?? 0) + (c.totalCashflow ?? 0));
34
+ }
35
+ const periods = /* @__PURE__ */ new Map();
36
+ const seen = /* @__PURE__ */ new Map();
37
+ for (const v of volumes) {
38
+ const sp = v.settlementPeriod;
39
+ const acceptance = v.acceptanceId;
40
+ if (!v.bmUnit || sp === void 0 || acceptance === void 0) continue;
41
+ if (sp < range.from || sp > range.to) continue;
42
+ const mwh = -(v.totalVolumeAccepted ?? 0);
43
+ if (!(mwh > 0)) continue;
44
+ const key = `${v.bmUnit}|${sp}`;
45
+ let p = periods.get(key);
46
+ let ids = seen.get(key);
47
+ if (!p || !ids) {
48
+ const ref = byId.get(v.bmUnit);
49
+ p = {
50
+ settlementDate: v.settlementDate ?? settlementDate,
51
+ settlementPeriod: sp,
52
+ startTime: v.startTime ?? "",
53
+ bmUnit: v.bmUnit,
54
+ nationalGridBmUnit: v.nationalGridBmUnit ?? ref?.nationalGridBmUnit ?? null,
55
+ leadPartyName: v.leadPartyName ?? ref?.leadPartyName ?? null,
56
+ fuelType: ref?.fuelType ?? null,
57
+ gspGroupId: ref?.gspGroupId ?? null,
58
+ capacityMw: num(ref?.generationCapacity),
59
+ curtailedMwh: 0,
60
+ soFlaggedMwh: 0,
61
+ cashflowGbp: cashByKey.get(key) ?? null,
62
+ acceptances: 0,
63
+ soFlaggedAcceptances: 0
64
+ };
65
+ ids = /* @__PURE__ */ new Set();
66
+ periods.set(key, p);
67
+ seen.set(key, ids);
68
+ }
69
+ p.curtailedMwh += mwh;
70
+ const so = soByAcceptance.get(acceptance) === true;
71
+ if (so) p.soFlaggedMwh += mwh;
72
+ if (!ids.has(acceptance)) {
73
+ ids.add(acceptance);
74
+ p.acceptances += 1;
75
+ if (so) p.soFlaggedAcceptances += 1;
76
+ }
77
+ }
78
+ const rows = [...periods.values()].sort(
79
+ (a, b) => a.settlementPeriod - b.settlementPeriod || compareIds(a.bmUnit, b.bmUnit)
80
+ );
81
+ const unitTotals = /* @__PURE__ */ new Map();
82
+ const unitAcceptances = /* @__PURE__ */ new Map();
83
+ const allAcceptances = /* @__PURE__ */ new Set();
84
+ const total = { curtailedMwh: 0, soFlaggedMwh: 0, cashflowGbp: 0, units: 0, acceptances: 0 };
85
+ for (const p of rows) {
86
+ let u = unitTotals.get(p.bmUnit);
87
+ if (!u) {
88
+ u = {
89
+ bmUnit: p.bmUnit,
90
+ nationalGridBmUnit: p.nationalGridBmUnit,
91
+ leadPartyName: p.leadPartyName,
92
+ fuelType: p.fuelType,
93
+ gspGroupId: p.gspGroupId,
94
+ capacityMw: p.capacityMw,
95
+ curtailedMwh: 0,
96
+ soFlaggedMwh: 0,
97
+ cashflowGbp: 0,
98
+ periods: 0,
99
+ acceptances: 0
100
+ };
101
+ unitTotals.set(p.bmUnit, u);
102
+ }
103
+ u.curtailedMwh += p.curtailedMwh;
104
+ u.soFlaggedMwh += p.soFlaggedMwh;
105
+ u.cashflowGbp += p.cashflowGbp ?? 0;
106
+ u.periods += 1;
107
+ const ids = seen.get(`${p.bmUnit}|${p.settlementPeriod}`) ?? /* @__PURE__ */ new Set();
108
+ let acc = unitAcceptances.get(p.bmUnit);
109
+ if (!acc) {
110
+ acc = /* @__PURE__ */ new Set();
111
+ unitAcceptances.set(p.bmUnit, acc);
112
+ }
113
+ for (const id of ids) {
114
+ acc.add(id);
115
+ allAcceptances.add(id);
116
+ }
117
+ u.acceptances = acc.size;
118
+ total.curtailedMwh += p.curtailedMwh;
119
+ total.soFlaggedMwh += p.soFlaggedMwh;
120
+ total.cashflowGbp += p.cashflowGbp ?? 0;
121
+ }
122
+ total.units = unitTotals.size;
123
+ total.acceptances = allAcceptances.size;
124
+ return {
125
+ settlementDate,
126
+ settlementPeriodFrom: range.from,
127
+ settlementPeriodTo: range.to,
128
+ periods: rows,
129
+ units: [...unitTotals.values()].sort((a, b) => b.curtailedMwh - a.curtailedMwh),
130
+ total
131
+ };
132
+ }
133
+ async function curtailmentForDay(client, settlementDate, opts = {}) {
134
+ const day = formatDate(settlementDate);
135
+ const last = settlementPeriodsInDay(day);
136
+ const from = opts.settlementPeriodFrom ?? 1;
137
+ const to = opts.settlementPeriodTo ?? last;
138
+ if (from < 1 || to > last || from > to) {
139
+ throw new UkEnergyError(`Settlement periods ${from}-${to} are outside 1-${last} for ${day}`);
140
+ }
141
+ const units = await client.reference.bmUnits();
142
+ let ids;
143
+ if (opts.bmUnit) {
144
+ ids = opts.bmUnit;
145
+ } else {
146
+ const fuel = opts.fuelType ?? DEFAULT_FUEL_TYPES;
147
+ ids = units.filter((u) => u.fuelType != null && fuel.includes(u.fuelType)).map(unitId).filter((id) => id !== "");
148
+ }
149
+ ids = [...new Set(ids)];
150
+ if (ids.length === 0) throw new UkEnergyError("No BM units to query");
151
+ const [volumes, cashflows, boalfParts] = await Promise.all([
152
+ client.balancing.acceptanceVolumes("bid", day, { bmUnit: ids }),
153
+ client.balancing.cashflows("bid", day, { bmUnit: ids }),
154
+ Promise.all(
155
+ chunk(ids, MAX_UNITS_PER_REQUEST).map(
156
+ (bmUnit) => client.datasets.boalf({
157
+ from: day,
158
+ to: day,
159
+ settlementPeriodFrom: 1,
160
+ settlementPeriodTo: last,
161
+ bmUnit
162
+ })
163
+ )
164
+ )
165
+ ]);
166
+ const boalf = boalfParts.flat();
167
+ return {
168
+ unitsQueried: ids.length,
169
+ ...aggregateCurtailment(day, { from, to }, units, volumes, cashflows, boalf)
170
+ };
171
+ }
172
+
173
+ // src/client.ts
174
+ var BASE_URL = "https://data.elexon.co.uk/bmrs/api/v1";
175
+ var DEFAULT_RATE_LIMIT = { requestsPerSecond: 10, burst: 20 };
176
+ var DEFAULT_CACHE_TTL_MS = 5 * 60 * 1e3;
177
+ function unitId(u) {
178
+ return u.elexonBmUnit || u.nationalGridBmUnit || "";
179
+ }
180
+ var MAX_UNITS_PER_REQUEST = 200;
181
+ function chunk(items, size) {
182
+ const out = [];
183
+ for (let i = 0; i < items.length; i += size) out.push(items.slice(i, i + size));
184
+ return out;
185
+ }
186
+ function windowQuery(w) {
187
+ const q = { from: formatDateTime(w.from), to: formatDateTime(w.to) };
188
+ if (w.settlementPeriodFrom !== void 0) {
189
+ assertSettlementPeriod(w.settlementPeriodFrom);
190
+ q.settlementPeriodFrom = w.settlementPeriodFrom;
191
+ }
192
+ if (w.settlementPeriodTo !== void 0) {
193
+ assertSettlementPeriod(w.settlementPeriodTo);
194
+ q.settlementPeriodTo = w.settlementPeriodTo;
195
+ }
196
+ return q;
197
+ }
198
+ function periodQuery(settlementDate, settlementPeriod) {
199
+ assertSettlementPeriod(settlementPeriod);
200
+ return { settlementDate: formatDate(settlementDate), settlementPeriod };
201
+ }
202
+ var ElexonClient = class {
203
+ http;
204
+ constructor(opts = {}) {
205
+ this.http = new HttpClient({
206
+ baseUrl: BASE_URL,
207
+ rateLimit: DEFAULT_RATE_LIMIT,
208
+ cacheTtlMs: opts.cache ? DEFAULT_CACHE_TTL_MS : 0,
209
+ ...opts
210
+ });
211
+ }
212
+ /** Endpoints returning a bare array. */
213
+ async list(path, query) {
214
+ const res = await this.http.get(path, query);
215
+ if (!Array.isArray(res)) throw new UkEnergyError(`Unexpected response shape for ${path}`);
216
+ return res;
217
+ }
218
+ /** Endpoints returning `{ data: [...] }`. */
219
+ async data(path, query) {
220
+ const res = await this.http.get(path, query);
221
+ if (!res || !Array.isArray(res.data)) {
222
+ throw new UkEnergyError(`Unexpected response shape for ${path}`);
223
+ }
224
+ return res.data;
225
+ }
226
+ /** `/datasets/{code}` — raw datasets. The `/stream` variants are used: lighter, no envelope. */
227
+ datasets = {
228
+ /**
229
+ * Any stream dataset (77 codes: BOALF, BOD, FUELINST, PN, MELS, WINDFOR, FREQ, …), with the
230
+ * query parameters typed from the spec. Values are sent as given (strings in API format,
231
+ * "YYYY-MM-DDTHH:MMZ" or "YYYY-MM-DD"); the named methods below format and validate dates.
232
+ */
233
+ stream: async (dataset, query) => this.list(`datasets/${dataset}/stream`, query),
234
+ /** Bid-offer acceptance levels (BOALF). Curtailment shows as `soFlag: true` bids. */
235
+ boalf: async (w) => this.list("datasets/BOALF/stream", { ...windowQuery(w), bmUnit: w.bmUnit }),
236
+ /** Bid-offer data (BOD): submitted bid and offer prices per pair. */
237
+ bod: async (w) => this.list("datasets/BOD/stream", { ...windowQuery(w), bmUnit: w.bmUnit }),
238
+ /** Generation by fuel type at 5-minute resolution (FUELINST). */
239
+ fuelinst: async (q) => {
240
+ const query = { fuelType: q.fuelType };
241
+ if ("publishDateTimeFrom" in q) {
242
+ query.publishDateTimeFrom = formatDateTime(q.publishDateTimeFrom);
243
+ query.publishDateTimeTo = formatDateTime(q.publishDateTimeTo);
244
+ } else {
245
+ query.settlementDateFrom = formatDate(q.settlementDateFrom);
246
+ query.settlementDateTo = formatDate(q.settlementDateTo);
247
+ if (q.settlementPeriod) {
248
+ for (const p of q.settlementPeriod) assertSettlementPeriod(p);
249
+ query.settlementPeriod = q.settlementPeriod;
250
+ }
251
+ }
252
+ return this.list("datasets/FUELINST/stream", query);
253
+ },
254
+ /** Physical notifications for one settlement period (`/datasets/PN` takes exactly one). */
255
+ pn: async (settlementDate, settlementPeriod, filter = {}) => this.data("datasets/PN", {
256
+ ...periodQuery(settlementDate, settlementPeriod),
257
+ bmUnit: filter.bmUnit
258
+ }),
259
+ /** Physical notifications over a window (`/datasets/PN/stream`). */
260
+ pnStream: async (w) => this.list("datasets/PN/stream", { ...windowQuery(w), bmUnit: w.bmUnit }),
261
+ /** Maximum export limits (MELS). */
262
+ mels: async (w) => this.list("datasets/MELS/stream", { ...windowQuery(w), bmUnit: w.bmUnit }),
263
+ /** Minimum import limits (MILS). */
264
+ mils: async (w) => this.list("datasets/MILS/stream", { ...windowQuery(w), bmUnit: w.bmUnit })
265
+ };
266
+ /** Reference data. */
267
+ reference = {
268
+ /** All BM units (~3,100). Capacities are strings; `fuelType` is null for many non-generators. */
269
+ bmUnits: async () => this.list("reference/bmunits/all"),
270
+ /** Fuel type codes used across the API. */
271
+ fuelTypes: async () => this.list("reference/fueltypes/all"),
272
+ /** BM units matching a filter, e.g. `{ fuelType: ["WIND"] }` (287 units). */
273
+ units: async (filter = {}) => {
274
+ const party = filter.leadParty?.toLowerCase();
275
+ return (await this.reference.bmUnits()).filter(
276
+ (u) => (!filter.fuelType || u.fuelType != null && filter.fuelType.includes(u.fuelType)) && (!filter.gspGroupId || u.gspGroupId != null && filter.gspGroupId.includes(u.gspGroupId)) && (!party || (u.leadPartyName ?? "").toLowerCase().includes(party))
277
+ );
278
+ }
279
+ };
280
+ /** Market-wide balancing views (one settlement period per call) and settlement prices. */
281
+ balancing = {
282
+ /** System buy/sell prices for a settlement day, or one period of it (DISEBSP). */
283
+ systemPrices: async (settlementDate, settlementPeriod) => this.data(
284
+ `balancing/settlement/system-prices/${periodPath(settlementDate, settlementPeriod)}`
285
+ ),
286
+ /** Market index (MID) prices and volumes. */
287
+ marketIndex: async (w) => this.data("balancing/pricing/market-index", {
288
+ ...windowQuery(w),
289
+ dataProviders: w.dataProviders
290
+ }),
291
+ /** Every acceptance in one settlement period. */
292
+ acceptances: async (settlementDate, settlementPeriod, filter = {}) => this.data("balancing/acceptances/all", {
293
+ ...periodQuery(settlementDate, settlementPeriod),
294
+ bmUnit: filter.bmUnit
295
+ }),
296
+ /** Every bid-offer pair in one settlement period. */
297
+ bidOffers: async (settlementDate, settlementPeriod, filter = {}) => this.data("balancing/bid-offer/all", {
298
+ ...periodQuery(settlementDate, settlementPeriod),
299
+ bmUnit: filter.bmUnit
300
+ }),
301
+ /**
302
+ * Settled acceptance volumes (BOAV) for a settlement day, or one period of it: one row per
303
+ * acceptance per period, `totalVolumeAccepted` in MWh (negative for bids). Long unit lists
304
+ * are split across requests.
305
+ */
306
+ acceptanceVolumes: async (bidOffer, settlementDate, filter = {}) => this.perUnitChunks(
307
+ `balancing/settlement/acceptance/volumes/all/${bidOffer}/${periodPath(settlementDate, filter.settlementPeriod)}`,
308
+ filter.bmUnit
309
+ ),
310
+ /**
311
+ * Indicative bid/offer cashflows (EBOCF) for a settlement day or one period: one row per unit
312
+ * per period, `totalCashflow` in £ paid to the unit (positive for a wind bid at a negative
313
+ * price).
314
+ */
315
+ cashflows: async (bidOffer, settlementDate, filter = {}) => this.perUnitChunks(
316
+ `balancing/settlement/indicative/cashflows/all/${bidOffer}/${periodPath(settlementDate, filter.settlementPeriod)}`,
317
+ filter.bmUnit
318
+ ),
319
+ /** PN / QPN / MILS / MELS for every unit in one settlement period. */
320
+ physical: async (dataset, settlementDate, settlementPeriod, filter = {}) => this.data("balancing/physical/all", {
321
+ dataset,
322
+ ...periodQuery(settlementDate, settlementPeriod),
323
+ bmUnit: filter.bmUnit
324
+ })
325
+ };
326
+ /** Wind curtailment: SO-flagged and energy bid acceptances per unit per period, with £. */
327
+ curtailment = {
328
+ /**
329
+ * Curtailment for a GB settlement day (Europe/London), per unit and per settlement period.
330
+ * Defaults to every WIND unit; the reference list plus three requests per 200 units.
331
+ */
332
+ forDay: async (settlementDate, opts = {}) => curtailmentForDay(this, settlementDate, opts)
333
+ };
334
+ /** `{data}` endpoints with an optional unit filter, chunked so URLs stay short. */
335
+ async perUnitChunks(path, bmUnit) {
336
+ if (!bmUnit?.length) return this.data(path);
337
+ const parts = await Promise.all(
338
+ chunk(bmUnit, MAX_UNITS_PER_REQUEST).map((ids) => this.data(path, { bmUnit: ids }))
339
+ );
340
+ return parts.flat();
341
+ }
342
+ };
343
+ function periodPath(settlementDate, settlementPeriod) {
344
+ if (settlementPeriod !== void 0) assertSettlementPeriod(settlementPeriod);
345
+ const p = settlementPeriod === void 0 ? "" : `/${settlementPeriod}`;
346
+ return `${formatDate(settlementDate)}${p}`;
347
+ }
348
+
349
+ export { BASE_URL, DEFAULT_CACHE_TTL_MS, DEFAULT_RATE_LIMIT, ElexonClient, MAX_UNITS_PER_REQUEST, aggregateCurtailment, curtailmentForDay, unitId };
350
+ //# sourceMappingURL=index.js.map
351
+ //# sourceMappingURL=index.js.map
@@ -0,0 +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"]}
package/package.json CHANGED
@@ -1,6 +1,60 @@
1
1
  {
2
2
  "name": "@uk-energy/elexon",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.2.0",
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
+ "license": "MIT",
6
+ "type": "module",
7
+ "sideEffects": false,
8
+ "main": "./dist/index.cjs",
9
+ "module": "./dist/index.js",
10
+ "types": "./dist/index.d.ts",
11
+ "exports": {
12
+ ".": {
13
+ "import": {
14
+ "types": "./dist/index.d.ts",
15
+ "default": "./dist/index.js"
16
+ },
17
+ "require": {
18
+ "types": "./dist/index.d.cts",
19
+ "default": "./dist/index.cjs"
20
+ }
21
+ }
22
+ },
23
+ "files": [
24
+ "dist",
25
+ "README.md",
26
+ "CHANGELOG.md"
27
+ ],
28
+ "engines": {
29
+ "node": ">=20"
30
+ },
31
+ "repository": {
32
+ "type": "git",
33
+ "url": "https://github.com/Styrle/Wind-farm-sdk",
34
+ "directory": "packages/elexon"
35
+ },
36
+ "keywords": [
37
+ "elexon",
38
+ "bmrs",
39
+ "insights",
40
+ "balancing-mechanism",
41
+ "boalf",
42
+ "uk",
43
+ "energy",
44
+ "sdk"
45
+ ],
46
+ "dependencies": {
47
+ "@uk-energy/core": "^0.2.0"
48
+ },
49
+ "publishConfig": {
50
+ "access": "public"
51
+ },
52
+ "scripts": {
53
+ "build": "tsup",
54
+ "typecheck": "tsc -p tsconfig.json --noEmit",
55
+ "test": "vitest run",
56
+ "test:live": "vitest run",
57
+ "test:watch": "vitest",
58
+ "generate": "openapi-typescript openapi/insights-v1.json -o src/generated/insights.ts --alphabetize --export-type"
59
+ }
6
60
  }