@uk-energy/neso 0.1.0 → 0.1.1

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,10 @@
1
1
  # @uk-energy/neso
2
2
 
3
+ ## 0.1.1
4
+
5
+ - The CKAN layer now lives in `@uk-energy/core` (`CkanClient`); `neso.ckan.*`, the helpers and
6
+ the `Ckan*` / `Datastore*` types are re-exported here, so nothing changes for callers.
7
+
3
8
  ## 0.1.0
4
9
 
5
10
  - `NesoClient` for the NESO Data Portal (CKAN 2.8): `ckan.*` (`packageShow`, `packageSearch`,
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/dist/index.cjs CHANGED
@@ -30,12 +30,10 @@ function financialYearInName(name) {
30
30
  function resourceForFinancialYear(resources, startYear) {
31
31
  return resources.find((r) => r.datastore_active && financialYearInName(r.name) === startYear);
32
32
  }
33
-
34
- // src/client.ts
35
33
  var BASE_URL = "https://api.neso.energy/api/3/action";
36
34
  var DEFAULT_RATE_LIMIT = { requestsPerSecond: 5, burst: 10 };
37
35
  var DEFAULT_CACHE_TTL_MS = 10 * 60 * 1e3;
38
- var MAX_PAGE_SIZE = 32e3;
36
+ var MAX_PAGE_SIZE = core.CKAN_MAX_PAGE_SIZE;
39
37
  var PACKAGES = {
40
38
  thermalConstraintCosts: "thermal-constraint-costs",
41
39
  constraintBreakdown: "constraint-breakdown",
@@ -45,45 +43,6 @@ var PACKAGES = {
45
43
  dailyBalancingCosts: "daily-balancing-costs-balancing-services-use-of-system",
46
44
  demandFlexibility: "demand-flexibility"
47
45
  };
48
- var DECIMAL = /^-?\d+(\.\d+)?([eE][-+]?\d+)?$/;
49
- var NUMERIC_TYPES = /* @__PURE__ */ new Set([
50
- "numeric",
51
- "int4",
52
- "int8",
53
- "float8",
54
- "float4",
55
- "int",
56
- "double precision"
57
- ]);
58
- function normaliseRows(records, fields, extraNumeric = []) {
59
- const numeric = new Set(extraNumeric);
60
- for (const f of fields) if (NUMERIC_TYPES.has(f.type)) numeric.add(f.id);
61
- return records.map((r) => {
62
- const out = {};
63
- for (const [k, v] of Object.entries(r)) {
64
- if (k === "_full_text") continue;
65
- if (numeric.has(k) && typeof v === "string" && DECIMAL.test(v.trim())) {
66
- const n = Number(v);
67
- out[k] = Number.isFinite(n) ? n : v;
68
- } else {
69
- out[k] = v;
70
- }
71
- }
72
- return out;
73
- });
74
- }
75
- function ident(name) {
76
- return `"${name.replace(/"/g, '""')}"`;
77
- }
78
- function literal(value) {
79
- return typeof value === "number" ? String(value) : `'${value.replace(/'/g, "''")}'`;
80
- }
81
- function rangeSql(resourceId, column, from, to, orderBy, opts = {}) {
82
- const upper = opts.toExclusive ? "<" : "<=";
83
- const extra = opts.where ? ` AND ${opts.where}` : "";
84
- const order = orderBy.length ? ` ORDER BY ${orderBy.map(ident).join(", ")}` : "";
85
- return `SELECT * FROM ${ident(resourceId)} WHERE ${ident(column)} >= ${literal(from)} AND ${ident(column)} ${upper} ${literal(to)}${extra}${order}`;
86
- }
87
46
  function localRange(from, to) {
88
47
  const f = core.londonDate(from);
89
48
  const t = core.londonDate(to);
@@ -97,8 +56,8 @@ function nextDay(date) {
97
56
  }
98
57
  var NesoClient = class {
99
58
  http;
100
- packageCache = /* @__PURE__ */ new Map();
101
- packageTtlMs;
59
+ /** Raw CKAN API (`@uk-energy/core` `CkanClient`): `packageShow` (memoised), `datastoreSearch`, `datastoreAll`, `sql`… */
60
+ ckan;
102
61
  constructor(opts = {}) {
103
62
  this.http = new core.HttpClient({
104
63
  baseUrl: BASE_URL,
@@ -106,91 +65,19 @@ var NesoClient = class {
106
65
  cacheTtlMs: opts.cache ? DEFAULT_CACHE_TTL_MS : 0,
107
66
  ...opts
108
67
  });
109
- this.packageTtlMs = opts.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS;
68
+ this.ckan = new core.CkanClient(this.http, {
69
+ packageTtlMs: opts.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS,
70
+ maxPageSize: MAX_PAGE_SIZE
71
+ });
110
72
  }
111
73
  /** Any CKAN action; unwraps `{ success, result }` and throws `UkEnergyError` on `success: false`. */
112
74
  async action(name, query) {
113
- const res = await this.http.get(name, query);
114
- if (!res || typeof res !== "object" || !("success" in res)) {
115
- throw new core.UkEnergyError(`Unexpected response shape for ${name}`);
116
- }
117
- if (!res.success) {
118
- throw new core.UkEnergyError(`${name}: ${res.error?.message ?? JSON.stringify(res.error)}`);
119
- }
120
- return res.result;
75
+ return this.ckan.action(name, query);
121
76
  }
122
- /** Raw CKAN API. */
123
- ckan = {
124
- /** Dataset metadata and its resources (memoised for `cacheTtlMs`, default 10 minutes). */
125
- packageShow: async (id) => {
126
- const hit = this.packageCache.get(id);
127
- if (hit && Date.now() - hit.at < this.packageTtlMs) return hit.value;
128
- const value = this.action("package_show", { id }).then((pkg) => {
129
- if (!pkg || !Array.isArray(pkg.resources)) {
130
- throw new core.UkEnergyError(`package_show: unexpected shape for ${id}`);
131
- }
132
- return pkg;
133
- });
134
- this.packageCache.set(id, { at: Date.now(), value });
135
- value.catch(() => this.packageCache.delete(id));
136
- return value;
137
- },
138
- /** Search datasets by free text. */
139
- packageSearch: async (q, rows = 20) => this.action("package_search", { q, rows }),
140
- resourceShow: async (id) => this.action("resource_show", { id }),
141
- /** One page of a datastore resource (up to 32,000 rows). Rows are normalised (see `normaliseRows`). */
142
- datastoreSearch: async (resourceId, opts = {}) => {
143
- const query = {
144
- resource_id: resourceId,
145
- limit: opts.limit ?? MAX_PAGE_SIZE,
146
- sort: opts.sort ?? "_id"
147
- };
148
- if (opts.offset !== void 0) query.offset = opts.offset;
149
- if (opts.filters) query.filters = JSON.stringify(opts.filters);
150
- if (opts.fields) query.fields = opts.fields.join(",");
151
- if (opts.q) query.q = opts.q;
152
- const res = await this.action("datastore_search", query);
153
- if (!Array.isArray(res?.records)) {
154
- throw new core.UkEnergyError("Unexpected response shape for datastore_search");
155
- }
156
- return { ...res, records: normaliseRows(res.records, res.fields ?? []) };
157
- },
158
- /** Every row of a datastore resource, paging by `limit` (default 32,000). */
159
- datastoreAll: async (resourceId, opts = {}) => {
160
- const pageSize = Math.min(opts.limit ?? MAX_PAGE_SIZE, MAX_PAGE_SIZE);
161
- const out = [];
162
- for await (const page of core.pages({
163
- fetchPage: (offset) => this.ckan.datastoreSearch(resourceId, {
164
- ...opts,
165
- limit: pageSize,
166
- offset: offset ?? 0
167
- }),
168
- items: (p) => p.records,
169
- nextCursor: core.offsetCursor(pageSize, (p) => p.records.length)
170
- })) {
171
- out.push(...page.records);
172
- }
173
- return out;
174
- },
175
- /**
176
- * `datastore_search_sql`: read-only PostgreSQL over the datastore. Resource ids are the
177
- * table names (quote them). Numeric columns come back as strings from this endpoint; they
178
- * are converted using the `fields` types, and `_full_text` is dropped.
179
- */
180
- sql: async (sql, extraNumeric = []) => {
181
- const res = await this.action("datastore_search_sql", {
182
- sql
183
- });
184
- if (!Array.isArray(res?.records)) {
185
- throw new core.UkEnergyError("Unexpected response shape for datastore_search_sql");
186
- }
187
- return normaliseRows(res.records, res.fields ?? [], extraNumeric);
188
- }
189
- };
190
77
  /** Resolving resources inside datasets. */
191
78
  resources = {
192
79
  /** All datastore-backed resources of a dataset. */
193
- datastore: async (packageId) => (await this.ckan.packageShow(packageId)).resources.filter((r) => r.datastore_active),
80
+ datastore: async (packageId) => this.ckan.datastoreResources(packageId),
194
81
  /** The resource for a GB financial year (`2025` = April 2025 – March 2026), by name. */
195
82
  forFinancialYear: async (packageId, startYear) => {
196
83
  const pkg = await this.ckan.packageShow(packageId);
@@ -203,14 +90,7 @@ var NesoClient = class {
203
90
  return r;
204
91
  },
205
92
  /** The datastore resource whose name matches exactly (or the regex), e.g. "DFS Utilisation Report". */
206
- byName: async (packageId, name) => {
207
- const list = await this.resources.datastore(packageId);
208
- const r = list.find(
209
- (x) => typeof name === "string" ? x.name.trim() === name.trim() : name.test(x.name)
210
- );
211
- if (!r) throw new core.UkEnergyError(`${packageId}: no datastore resource named ${String(name)}`);
212
- return r;
213
- }
93
+ byName: async (packageId, name) => this.ckan.resourceByName(packageId, name)
214
94
  };
215
95
  /** Rows of a per-financial-year dataset for an inclusive date range, across year boundaries. */
216
96
  async byFinancialYear(packageId, column, range, orderBy, opts = {}) {
@@ -218,7 +98,7 @@ var NesoClient = class {
218
98
  const out = [];
219
99
  for (const fy of financialYearsBetween(from, to)) {
220
100
  const resource = await this.resources.forFinancialYear(packageId, fy);
221
- const sql = rangeSql(resource.id, column, from, to, orderBy, { where: opts.where });
101
+ const sql = core.rangeSql(resource.id, column, from, to, orderBy, { where: opts.where });
222
102
  out.push(...await this.ckan.sql(sql, opts.extraNumeric));
223
103
  }
224
104
  return out;
@@ -235,7 +115,7 @@ var NesoClient = class {
235
115
  "Settlement Date",
236
116
  { from, to },
237
117
  ["Settlement Date", "Constraint Group"],
238
- constraintGroup ? { where: `${ident("Constraint Group")} = ${literal(constraintGroup)}` } : {}
118
+ constraintGroup ? { where: `${core.ident("Constraint Group")} = ${core.literal(constraintGroup)}` } : {}
239
119
  ),
240
120
  /**
241
121
  * Daily constraint cost (£) and volume (MWh) by type: thermal, voltage, inertia, largest
@@ -262,7 +142,7 @@ var NesoClient = class {
262
142
  dayAheadFlows: async (from, to, constraintGroup) => {
263
143
  const [f, t] = localRange(from, to);
264
144
  const r = await this.resources.byName(PACKAGES.dayAheadConstraintFlows, /Flows and Limits/);
265
- const sql = rangeSql(
145
+ const sql = core.rangeSql(
266
146
  r.id,
267
147
  "Date_ Time GMT_BST",
268
148
  `${f}T00:00:00`,
@@ -270,7 +150,7 @@ var NesoClient = class {
270
150
  ["Constraint Group", "Date_ Time GMT_BST"],
271
151
  {
272
152
  toExclusive: true,
273
- where: constraintGroup ? `${ident("Constraint Group")} = ${literal(constraintGroup)}` : void 0
153
+ where: constraintGroup ? `${core.ident("Constraint Group")} = ${core.literal(constraintGroup)}` : void 0
274
154
  }
275
155
  );
276
156
  return this.ckan.sql(sql);
@@ -303,10 +183,26 @@ var NesoClient = class {
303
183
  async dfsRange(name, from, to, orderBy) {
304
184
  const [f, t] = localRange(from, to);
305
185
  const r = await this.resources.byName(PACKAGES.demandFlexibility, name);
306
- return this.ckan.sql(rangeSql(r.id, "Delivery Date", f, t, orderBy));
186
+ return this.ckan.sql(core.rangeSql(r.id, "Delivery Date", f, t, orderBy));
307
187
  }
308
188
  };
309
189
 
190
+ Object.defineProperty(exports, "ident", {
191
+ enumerable: true,
192
+ get: function () { return core.ident; }
193
+ });
194
+ Object.defineProperty(exports, "literal", {
195
+ enumerable: true,
196
+ get: function () { return core.literal; }
197
+ });
198
+ Object.defineProperty(exports, "normaliseRows", {
199
+ enumerable: true,
200
+ get: function () { return core.normaliseRows; }
201
+ });
202
+ Object.defineProperty(exports, "rangeSql", {
203
+ enumerable: true,
204
+ get: function () { return core.rangeSql; }
205
+ });
310
206
  exports.BASE_URL = BASE_URL;
311
207
  exports.DEFAULT_CACHE_TTL_MS = DEFAULT_CACHE_TTL_MS;
312
208
  exports.DEFAULT_RATE_LIMIT = DEFAULT_RATE_LIMIT;
@@ -316,10 +212,6 @@ exports.PACKAGES = PACKAGES;
316
212
  exports.financialYearInName = financialYearInName;
317
213
  exports.financialYearOf = financialYearOf;
318
214
  exports.financialYearsBetween = financialYearsBetween;
319
- exports.ident = ident;
320
- exports.literal = literal;
321
- exports.normaliseRows = normaliseRows;
322
- exports.rangeSql = rangeSql;
323
215
  exports.resourceForFinancialYear = resourceForFinancialYear;
324
216
  //# sourceMappingURL=index.cjs.map
325
217
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/resources.ts","../src/client.ts"],"names":["londonDate","UkEnergyError","HttpClient","pages","offsetCursor"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Financial-year arithmetic and resource-name matching for datasets split one resource per\n * GB financial year (1 April – 31 March): \"Thermal Constraint Costs Data 25-26\",\n * \"Constraint Breakdown 2025-2026\", \"Daily Balancing Costs 2025-2026\".\n */\nimport type { CkanResource } from \"./types.js\";\n\n/** Financial year a date (\"YYYY-MM-DD\") falls in, as its starting calendar year. */\nexport function financialYearOf(date: string): number {\n const year = Number(date.slice(0, 4));\n const month = Number(date.slice(5, 7));\n return month >= 4 ? year : year - 1;\n}\n\n/** Starting years of every financial year touched by an inclusive date range. */\nexport function financialYearsBetween(from: string, to: string): number[] {\n const first = financialYearOf(from);\n const last = financialYearOf(to);\n const out: number[] = [];\n for (let y = first; y <= last; y++) out.push(y);\n return out;\n}\n\nconst YEAR_SPAN = /(?<!\\d)(\\d{4}|\\d{2})\\s*[-/–]\\s*(\\d{4}|\\d{2})(?!\\d)/g;\n\n/** The financial year a resource name refers to (\"25-26\" / \"2025-2026\" → 2025), or undefined. */\nexport function financialYearInName(name: string): number | undefined {\n for (const m of name.matchAll(YEAR_SPAN)) {\n if (!m[1] || !m[2]) continue;\n const start = m[1].length === 2 ? 2000 + Number(m[1]) : Number(m[1]);\n const end = m[2].length === 2 ? 2000 + Number(m[2]) : Number(m[2]);\n if (end === start + 1) return start;\n }\n return undefined;\n}\n\n/** The datastore resource of a dataset for one financial year, if any. */\nexport function resourceForFinancialYear(\n resources: readonly CkanResource[],\n startYear: number,\n): CkanResource | undefined {\n return resources.find((r) => r.datastore_active && financialYearInName(r.name) === startYear);\n}\n","/**\n * Client for the NESO Data Portal (https://www.neso.energy/data-portal), a CKAN 2.8 instance at\n * https://api.neso.energy/api/3/action/. No API key. Datasets (\"packages\") hold \"resources\";\n * tabular resources are loaded into a Postgres datastore that `datastore_search` and\n * `datastore_search_sql` query. Many datasets split one resource per GB financial year\n * (April–March), so the helpers resolve the right resource(s) for a date range.\n *\n * const neso = new NesoClient();\n * const costs = await neso.constraints.thermalCosts(\"2025-04-01\", \"2025-04-07\"); // £/day per boundary\n * const dfs = await neso.dfs.summary(\"2026-10-20\", \"2026-10-22\");\n * const rows = await neso.ckan.sql(`SELECT * FROM \"407f3e9d-…\" WHERE \"Settlement Date\" = '2025-04-01'`);\n *\n * Licence: National Energy SO Open Data Licence v1.0 (OGL v3 based): copy, adapt, use\n * commercially, with the attribution \"Supported by National Energy SO Open Data\".\n */\nimport {\n type DateInput,\n HttpClient,\n type HttpClientOptions,\n londonDate,\n offsetCursor,\n pages,\n type Query,\n type RateLimit,\n UkEnergyError,\n} from \"@uk-energy/core\";\nimport { financialYearsBetween, resourceForFinancialYear } from \"./resources.js\";\nimport type {\n CkanPackage,\n CkanPackageSearch,\n CkanResource,\n CkanResponse,\n ConstraintBreakdownRow,\n ConstraintForecastRow,\n ConstraintLimitRow,\n DailyBalancingCostRow,\n DatastoreField,\n DatastoreRow,\n DatastoreSearchOptions,\n DatastoreSearchResult,\n DatastoreSqlResult,\n DayAheadFlowRow,\n DfsRequirementRow,\n DfsSummaryRow,\n DfsUtilisationRow,\n ThermalConstraintCostRow,\n} from \"./types.js\";\n\nexport const BASE_URL = \"https://api.neso.energy/api/3/action\";\n\n/**\n * No limit is published; the licence only reserves the right to throttle \"excessive use\".\n * 5 rps / burst 10 is comfortably polite for a shared public CKAN.\n */\nexport const DEFAULT_RATE_LIMIT: RateLimit = { requestsPerSecond: 5, burst: 10 };\n\n/** Resources update daily or weekly; package metadata is memoised for this long. */\nexport const DEFAULT_CACHE_TTL_MS = 10 * 60 * 1000;\n\n/** The portal returns up to this many rows per `datastore_search` page. */\nexport const MAX_PAGE_SIZE = 32_000;\n\n/** Dataset (package) names on the portal that the helpers use. */\nexport const PACKAGES = {\n thermalConstraintCosts: \"thermal-constraint-costs\",\n constraintBreakdown: \"constraint-breakdown\",\n constraintCostForecast24m: \"24-months-ahead-constraint-cost-forecast\",\n constraintLimits24m: \"24-months-ahead-constraint-limits\",\n dayAheadConstraintFlows: \"day-ahead-constraint-flows-and-limits\",\n dailyBalancingCosts: \"daily-balancing-costs-balancing-services-use-of-system\",\n demandFlexibility: \"demand-flexibility\",\n} as const;\n\nexport interface NesoClientOptions extends Omit<HttpClientOptions, \"baseUrl\"> {\n baseUrl?: string;\n}\n\n/** Inclusive date range; strings are \"YYYY-MM-DD\", `Date`s are GB-local calendar days. */\ninterface DateRange {\n from: DateInput;\n to: DateInput;\n}\n\nconst DECIMAL = /^-?\\d+(\\.\\d+)?([eE][-+]?\\d+)?$/;\n\nconst NUMERIC_TYPES = new Set([\n \"numeric\",\n \"int4\",\n \"int8\",\n \"float8\",\n \"float4\",\n \"int\",\n \"double precision\",\n]);\n\n/**\n * The SQL endpoint returns `numeric` columns as strings (\"252\"); `datastore_search` returns\n * numbers. Make rows consistent: numeric-typed columns become numbers, `_full_text` goes.\n */\nexport function normaliseRows<Row extends object>(\n records: readonly DatastoreRow[],\n fields: readonly DatastoreField[],\n extraNumeric: readonly string[] = [],\n): Row[] {\n const numeric = new Set(extraNumeric);\n for (const f of fields) if (NUMERIC_TYPES.has(f.type)) numeric.add(f.id);\n return records.map((r) => {\n const out: DatastoreRow = {};\n for (const [k, v] of Object.entries(r)) {\n if (k === \"_full_text\") continue;\n if (numeric.has(k) && typeof v === \"string\" && DECIMAL.test(v.trim())) {\n const n = Number(v);\n out[k] = Number.isFinite(n) ? n : v;\n } else {\n out[k] = v;\n }\n }\n return out as Row;\n });\n}\n\n/** Double-quote an identifier for the SQL endpoint. */\nexport function ident(name: string): string {\n return `\"${name.replace(/\"/g, '\"\"')}\"`;\n}\n\n/** Single-quote a literal for the SQL endpoint. */\nexport function literal(value: string | number): string {\n return typeof value === \"number\" ? String(value) : `'${value.replace(/'/g, \"''\")}'`;\n}\n\n/**\n * `SELECT * FROM \"resource\" WHERE \"col\" >= 'from' AND \"col\" <= 'to' ORDER BY …` — the shape\n * every date-range helper uses. `toExclusive` switches the upper bound to `<` for timestamps.\n */\nexport function rangeSql(\n resourceId: string,\n column: string,\n from: string,\n to: string,\n orderBy: readonly string[],\n opts: { toExclusive?: boolean; where?: string | undefined } = {},\n): string {\n const upper = opts.toExclusive ? \"<\" : \"<=\";\n const extra = opts.where ? ` AND ${opts.where}` : \"\";\n const order = orderBy.length ? ` ORDER BY ${orderBy.map(ident).join(\", \")}` : \"\";\n return `SELECT * FROM ${ident(resourceId)} WHERE ${ident(column)} >= ${literal(from)} AND ${ident(column)} ${upper} ${literal(to)}${extra}${order}`;\n}\n\n/** Inclusive GB-local date range: `Date`s are read as Europe/London calendar days. */\nfunction localRange(from: DateInput, to: DateInput): [string, string] {\n const f = londonDate(from);\n const t = londonDate(to);\n if (f > t) throw new UkEnergyError(`from (${f}) is after to (${t})`);\n return [f, t];\n}\n\nfunction nextDay(date: string): string {\n const d = new Date(`${date}T00:00:00Z`);\n d.setUTCDate(d.getUTCDate() + 1);\n return d.toISOString().slice(0, 10);\n}\n\nexport class NesoClient {\n readonly http: HttpClient;\n private readonly packageCache = new Map<string, { at: number; value: Promise<CkanPackage> }>();\n private readonly packageTtlMs: number;\n\n constructor(opts: NesoClientOptions = {}) {\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 this.packageTtlMs = opts.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS;\n }\n\n /** Any CKAN action; unwraps `{ success, result }` and throws `UkEnergyError` on `success: false`. */\n async action<T>(name: string, query?: Query): Promise<T> {\n const res = await this.http.get<CkanResponse<T>>(name, query);\n if (!res || typeof res !== \"object\" || !(\"success\" in res)) {\n throw new UkEnergyError(`Unexpected response shape for ${name}`);\n }\n if (!res.success) {\n throw new UkEnergyError(`${name}: ${res.error?.message ?? JSON.stringify(res.error)}`);\n }\n return res.result;\n }\n\n /** Raw CKAN API. */\n readonly ckan = {\n /** Dataset metadata and its resources (memoised for `cacheTtlMs`, default 10 minutes). */\n packageShow: async (id: string): Promise<CkanPackage> => {\n const hit = this.packageCache.get(id);\n if (hit && Date.now() - hit.at < this.packageTtlMs) return hit.value;\n const value = this.action<CkanPackage>(\"package_show\", { id }).then((pkg) => {\n if (!pkg || !Array.isArray(pkg.resources)) {\n throw new UkEnergyError(`package_show: unexpected shape for ${id}`);\n }\n return pkg;\n });\n this.packageCache.set(id, { at: Date.now(), value });\n value.catch(() => this.packageCache.delete(id));\n return value;\n },\n /** Search datasets by free text. */\n packageSearch: async (q: string, rows = 20): Promise<CkanPackageSearch> =>\n this.action(\"package_search\", { q, rows }),\n resourceShow: async (id: string): Promise<CkanResource> => this.action(\"resource_show\", { id }),\n /** One page of a datastore resource (up to 32,000 rows). Rows are normalised (see `normaliseRows`). */\n datastoreSearch: async <Row extends object = DatastoreRow>(\n resourceId: string,\n opts: DatastoreSearchOptions = {},\n ): Promise<DatastoreSearchResult<Row>> => {\n const query: Query = {\n resource_id: resourceId,\n limit: opts.limit ?? MAX_PAGE_SIZE,\n sort: opts.sort ?? \"_id\",\n };\n if (opts.offset !== undefined) query.offset = opts.offset;\n if (opts.filters) query.filters = JSON.stringify(opts.filters);\n if (opts.fields) query.fields = opts.fields.join(\",\");\n if (opts.q) query.q = opts.q;\n const res = await this.action<DatastoreSearchResult<DatastoreRow>>(\"datastore_search\", query);\n if (!Array.isArray(res?.records)) {\n throw new UkEnergyError(\"Unexpected response shape for datastore_search\");\n }\n return { ...res, records: normaliseRows<Row>(res.records, res.fields ?? []) };\n },\n /** Every row of a datastore resource, paging by `limit` (default 32,000). */\n datastoreAll: async <Row extends object = DatastoreRow>(\n resourceId: string,\n opts: Omit<DatastoreSearchOptions, \"offset\"> = {},\n ): Promise<Row[]> => {\n const pageSize = Math.min(opts.limit ?? MAX_PAGE_SIZE, MAX_PAGE_SIZE);\n const out: Row[] = [];\n for await (const page of pages<DatastoreSearchResult<Row>, Row, number>({\n fetchPage: (offset) =>\n this.ckan.datastoreSearch<Row>(resourceId, {\n ...opts,\n limit: pageSize,\n offset: offset ?? 0,\n }),\n items: (p) => p.records,\n nextCursor: offsetCursor(pageSize, (p) => p.records.length),\n })) {\n out.push(...page.records);\n }\n return out;\n },\n /**\n * `datastore_search_sql`: read-only PostgreSQL over the datastore. Resource ids are the\n * table names (quote them). Numeric columns come back as strings from this endpoint; they\n * are converted using the `fields` types, and `_full_text` is dropped.\n */\n sql: async <Row extends object = DatastoreRow>(\n sql: string,\n extraNumeric: readonly string[] = [],\n ): Promise<Row[]> => {\n const res = await this.action<DatastoreSqlResult<DatastoreRow>>(\"datastore_search_sql\", {\n sql,\n });\n if (!Array.isArray(res?.records)) {\n throw new UkEnergyError(\"Unexpected response shape for datastore_search_sql\");\n }\n return normaliseRows<Row>(res.records, res.fields ?? [], extraNumeric);\n },\n };\n\n /** Resolving resources inside datasets. */\n readonly resources = {\n /** All datastore-backed resources of a dataset. */\n datastore: async (packageId: string): Promise<CkanResource[]> =>\n (await this.ckan.packageShow(packageId)).resources.filter((r) => r.datastore_active),\n /** The resource for a GB financial year (`2025` = April 2025 – March 2026), by name. */\n forFinancialYear: async (packageId: string, startYear: number): Promise<CkanResource> => {\n const pkg = await this.ckan.packageShow(packageId);\n const r = resourceForFinancialYear(pkg.resources, startYear);\n if (!r) {\n throw new UkEnergyError(\n `${packageId}: no datastore resource for financial year ${startYear}/${(startYear + 1) % 100}`,\n );\n }\n return r;\n },\n /** The datastore resource whose name matches exactly (or the regex), e.g. \"DFS Utilisation Report\". */\n byName: async (packageId: string, name: string | RegExp): Promise<CkanResource> => {\n const list = await this.resources.datastore(packageId);\n const r = list.find((x) =>\n typeof name === \"string\" ? x.name.trim() === name.trim() : name.test(x.name),\n );\n if (!r) throw new UkEnergyError(`${packageId}: no datastore resource named ${String(name)}`);\n return r;\n },\n };\n\n /** Rows of a per-financial-year dataset for an inclusive date range, across year boundaries. */\n private async byFinancialYear<Row extends object>(\n packageId: string,\n column: string,\n range: DateRange,\n orderBy: readonly string[],\n opts: { where?: string | undefined; extraNumeric?: readonly string[] } = {},\n ): Promise<Row[]> {\n const [from, to] = localRange(range.from, range.to);\n const out: Row[] = [];\n for (const fy of financialYearsBetween(from, to)) {\n const resource = await this.resources.forFinancialYear(packageId, fy);\n const sql = rangeSql(resource.id, column, from, to, orderBy, { where: opts.where });\n out.push(...(await this.ckan.sql<Row>(sql, opts.extraNumeric)));\n }\n return out;\n }\n\n /** Transmission constraint costs and forecasts. */\n readonly constraints = {\n /**\n * Thermal constraint cost per boundary per day (£), `thermal-constraint-costs`. One row per\n * constraint group per settlement date, e.g. SCOTEX, SSHARN, SEIMP (Scottish export\n * boundaries), ESTEX, SWALEX.\n */\n thermalCosts: async (\n from: DateInput,\n to: DateInput,\n constraintGroup?: string,\n ): Promise<ThermalConstraintCostRow[]> =>\n this.byFinancialYear(\n PACKAGES.thermalConstraintCosts,\n \"Settlement Date\",\n { from, to },\n [\"Settlement Date\", \"Constraint Group\"],\n constraintGroup\n ? { where: `${ident(\"Constraint Group\")} = ${literal(constraintGroup)}` }\n : {},\n ),\n /**\n * Daily constraint cost (£) and volume (MWh) by type: thermal, voltage, inertia, largest\n * loss (`constraint-breakdown`). Thermal volume is negative (energy turned down).\n */\n breakdown: async (from: DateInput, to: DateInput): Promise<ConstraintBreakdownRow[]> =>\n this.byFinancialYear(PACKAGES.constraintBreakdown, \"Date\", { from, to }, [\"Date\"], {\n // \"Thermal constraints cost\" is typed text in some years\n extraNumeric: [\"Thermal constraints cost\"],\n }),\n /** NESO's 24-month-ahead constraint cost forecast, £m per month (24 rows). */\n forecast24m: async (): Promise<ConstraintForecastRow[]> => {\n const r = await this.resources.byName(PACKAGES.constraintCostForecast24m, /Forecast/);\n return this.ckan.datastoreAll<ConstraintForecastRow>(r.id);\n },\n /** 24-month-ahead boundary limits (MW) per week; boundary columns vary by publication. */\n limits24m: async (): Promise<ConstraintLimitRow[]> => {\n const r = await this.resources.byName(PACKAGES.constraintLimits24m, /limits/i);\n return this.ckan.datastoreAll<ConstraintLimitRow>(r.id);\n },\n /**\n * Day-ahead forecast flow vs limit per boundary, half-hourly, for a range of local dates\n * (`day-ahead-constraint-flows-and-limits`, ~750k rows, so always filtered by SQL).\n */\n dayAheadFlows: async (\n from: DateInput,\n to: DateInput,\n constraintGroup?: string,\n ): Promise<DayAheadFlowRow[]> => {\n const [f, t] = localRange(from, to);\n const r = await this.resources.byName(PACKAGES.dayAheadConstraintFlows, /Flows and Limits/);\n const sql = rangeSql(\n r.id,\n \"Date_ Time GMT_BST\",\n `${f}T00:00:00`,\n `${nextDay(t)}T00:00:00`,\n [\"Constraint Group\", \"Date_ Time GMT_BST\"],\n {\n toExclusive: true,\n where: constraintGroup\n ? `${ident(\"Constraint Group\")} = ${literal(constraintGroup)}`\n : undefined,\n },\n );\n return this.ckan.sql<DayAheadFlowRow>(sql);\n },\n /** Daily balancing (BSUoS) costs per settlement period by category, incl. `Constraints` (£). */\n dailyBalancingCosts: async (from: DateInput, to: DateInput): Promise<DailyBalancingCostRow[]> =>\n this.byFinancialYear(PACKAGES.dailyBalancingCosts, \"SETT_DATE\", { from, to }, [\n \"SETT_DATE\",\n \"SETT_PERIOD\",\n ]),\n };\n\n /** Demand Flexibility Service (current season; archives are separate resources). */\n readonly dfs = {\n /** Published requirements per event window, by delivery date. */\n requirements: async (from: DateInput, to: DateInput): Promise<DfsRequirementRow[]> =>\n this.dfsRange<DfsRequirementRow>(\"DFS Service Requirement\", from, to, [\n \"Event ID\",\n \"From_UTC\",\n ]),\n /** Accepted/rejected bids per participant unit per event window. */\n utilisation: async (from: DateInput, to: DateInput): Promise<DfsUtilisationRow[]> =>\n this.dfsRange<DfsUtilisationRow>(\"DFS Utilisation Report\", from, to, [\n \"Event ID\",\n \"From_UTC\",\n \"DFS Unit ID\",\n ]),\n /** Procured MW, accepted cost and settled volume/cost per event window. */\n summary: async (from: DateInput, to: DateInput): Promise<DfsSummaryRow[]> =>\n this.dfsRange<DfsSummaryRow>(\"DFS Utilisation Report Summary\", from, to, [\n \"Event ID\",\n \"From_UTC\",\n ]),\n };\n\n private async dfsRange<Row extends object>(\n name: string,\n from: DateInput,\n to: DateInput,\n orderBy: readonly string[],\n ): Promise<Row[]> {\n const [f, t] = localRange(from, to);\n const r = await this.resources.byName(PACKAGES.demandFlexibility, name);\n return this.ckan.sql<Row>(rangeSql(r.id, \"Delivery Date\", f, t, orderBy));\n }\n}\n"]}
1
+ {"version":3,"sources":["../src/resources.ts","../src/client.ts"],"names":["CKAN_MAX_PAGE_SIZE","londonDate","UkEnergyError","HttpClient","CkanClient","rangeSql","ident","literal"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Financial-year arithmetic and resource-name matching for datasets split one resource per\n * GB financial year (1 April – 31 March): \"Thermal Constraint Costs Data 25-26\",\n * \"Constraint Breakdown 2025-2026\", \"Daily Balancing Costs 2025-2026\".\n */\nimport type { CkanResource } from \"./types.js\";\n\n/** Financial year a date (\"YYYY-MM-DD\") falls in, as its starting calendar year. */\nexport function financialYearOf(date: string): number {\n const year = Number(date.slice(0, 4));\n const month = Number(date.slice(5, 7));\n return month >= 4 ? year : year - 1;\n}\n\n/** Starting years of every financial year touched by an inclusive date range. */\nexport function financialYearsBetween(from: string, to: string): number[] {\n const first = financialYearOf(from);\n const last = financialYearOf(to);\n const out: number[] = [];\n for (let y = first; y <= last; y++) out.push(y);\n return out;\n}\n\nconst YEAR_SPAN = /(?<!\\d)(\\d{4}|\\d{2})\\s*[-/–]\\s*(\\d{4}|\\d{2})(?!\\d)/g;\n\n/** The financial year a resource name refers to (\"25-26\" / \"2025-2026\" → 2025), or undefined. */\nexport function financialYearInName(name: string): number | undefined {\n for (const m of name.matchAll(YEAR_SPAN)) {\n if (!m[1] || !m[2]) continue;\n const start = m[1].length === 2 ? 2000 + Number(m[1]) : Number(m[1]);\n const end = m[2].length === 2 ? 2000 + Number(m[2]) : Number(m[2]);\n if (end === start + 1) return start;\n }\n return undefined;\n}\n\n/** The datastore resource of a dataset for one financial year, if any. */\nexport function resourceForFinancialYear(\n resources: readonly CkanResource[],\n startYear: number,\n): CkanResource | undefined {\n return resources.find((r) => r.datastore_active && financialYearInName(r.name) === startYear);\n}\n","/**\n * Client for the NESO Data Portal (https://www.neso.energy/data-portal), a CKAN 2.8 instance at\n * https://api.neso.energy/api/3/action/. No API key. Datasets (\"packages\") hold \"resources\";\n * tabular resources are loaded into a Postgres datastore that `datastore_search` and\n * `datastore_search_sql` query. Many datasets split one resource per GB financial year\n * (April–March), so the helpers resolve the right resource(s) for a date range.\n *\n * const neso = new NesoClient();\n * const costs = await neso.constraints.thermalCosts(\"2025-04-01\", \"2025-04-07\"); // £/day per boundary\n * const dfs = await neso.dfs.summary(\"2026-10-20\", \"2026-10-22\");\n * const rows = await neso.ckan.sql(`SELECT * FROM \"407f3e9d-…\" WHERE \"Settlement Date\" = '2025-04-01'`);\n *\n * Licence: National Energy SO Open Data Licence v1.0 (OGL v3 based): copy, adapt, use\n * commercially, with the attribution \"Supported by National Energy SO Open Data\".\n */\nimport {\n CKAN_MAX_PAGE_SIZE,\n CkanClient,\n type CkanResource,\n type DateInput,\n HttpClient,\n type HttpClientOptions,\n ident,\n literal,\n londonDate,\n type Query,\n type RateLimit,\n rangeSql,\n UkEnergyError,\n} from \"@uk-energy/core\";\nimport { financialYearsBetween, resourceForFinancialYear } from \"./resources.js\";\nimport type {\n ConstraintBreakdownRow,\n ConstraintForecastRow,\n ConstraintLimitRow,\n DailyBalancingCostRow,\n DayAheadFlowRow,\n DfsRequirementRow,\n DfsSummaryRow,\n DfsUtilisationRow,\n ThermalConstraintCostRow,\n} from \"./types.js\";\n\nexport const BASE_URL = \"https://api.neso.energy/api/3/action\";\n\n/**\n * No limit is published; the licence only reserves the right to throttle \"excessive use\".\n * 5 rps / burst 10 is comfortably polite for a shared public CKAN.\n */\nexport const DEFAULT_RATE_LIMIT: RateLimit = { requestsPerSecond: 5, burst: 10 };\n\n/** Resources update daily or weekly; package metadata is memoised for this long. */\nexport const DEFAULT_CACHE_TTL_MS = 10 * 60 * 1000;\n\n/** The portal returns up to this many rows per `datastore_search` page. */\nexport const MAX_PAGE_SIZE = CKAN_MAX_PAGE_SIZE;\n\n/** Dataset (package) names on the portal that the helpers use. */\nexport const PACKAGES = {\n thermalConstraintCosts: \"thermal-constraint-costs\",\n constraintBreakdown: \"constraint-breakdown\",\n constraintCostForecast24m: \"24-months-ahead-constraint-cost-forecast\",\n constraintLimits24m: \"24-months-ahead-constraint-limits\",\n dayAheadConstraintFlows: \"day-ahead-constraint-flows-and-limits\",\n dailyBalancingCosts: \"daily-balancing-costs-balancing-services-use-of-system\",\n demandFlexibility: \"demand-flexibility\",\n} as const;\n\nexport interface NesoClientOptions extends Omit<HttpClientOptions, \"baseUrl\"> {\n baseUrl?: string;\n}\n\n/** Inclusive date range; strings are \"YYYY-MM-DD\", `Date`s are GB-local calendar days. */\ninterface DateRange {\n from: DateInput;\n to: DateInput;\n}\n\nexport { ident, literal, normaliseRows, rangeSql } from \"@uk-energy/core\";\n\n/** Inclusive GB-local date range: `Date`s are read as Europe/London calendar days. */\nfunction localRange(from: DateInput, to: DateInput): [string, string] {\n const f = londonDate(from);\n const t = londonDate(to);\n if (f > t) throw new UkEnergyError(`from (${f}) is after to (${t})`);\n return [f, t];\n}\n\nfunction nextDay(date: string): string {\n const d = new Date(`${date}T00:00:00Z`);\n d.setUTCDate(d.getUTCDate() + 1);\n return d.toISOString().slice(0, 10);\n}\n\nexport class NesoClient {\n readonly http: HttpClient;\n /** Raw CKAN API (`@uk-energy/core` `CkanClient`): `packageShow` (memoised), `datastoreSearch`, `datastoreAll`, `sql`… */\n readonly ckan: CkanClient;\n\n constructor(opts: NesoClientOptions = {}) {\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 this.ckan = new CkanClient(this.http, {\n packageTtlMs: opts.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS,\n maxPageSize: MAX_PAGE_SIZE,\n });\n }\n\n /** Any CKAN action; unwraps `{ success, result }` and throws `UkEnergyError` on `success: false`. */\n async action<T>(name: string, query?: Query): Promise<T> {\n return this.ckan.action<T>(name, query);\n }\n\n /** Resolving resources inside datasets. */\n readonly resources = {\n /** All datastore-backed resources of a dataset. */\n datastore: async (packageId: string): Promise<CkanResource[]> =>\n this.ckan.datastoreResources(packageId),\n /** The resource for a GB financial year (`2025` = April 2025 – March 2026), by name. */\n forFinancialYear: async (packageId: string, startYear: number): Promise<CkanResource> => {\n const pkg = await this.ckan.packageShow(packageId);\n const r = resourceForFinancialYear(pkg.resources, startYear);\n if (!r) {\n throw new UkEnergyError(\n `${packageId}: no datastore resource for financial year ${startYear}/${(startYear + 1) % 100}`,\n );\n }\n return r;\n },\n /** The datastore resource whose name matches exactly (or the regex), e.g. \"DFS Utilisation Report\". */\n byName: async (packageId: string, name: string | RegExp): Promise<CkanResource> =>\n this.ckan.resourceByName(packageId, name),\n };\n\n /** Rows of a per-financial-year dataset for an inclusive date range, across year boundaries. */\n private async byFinancialYear<Row extends object>(\n packageId: string,\n column: string,\n range: DateRange,\n orderBy: readonly string[],\n opts: { where?: string | undefined; extraNumeric?: readonly string[] } = {},\n ): Promise<Row[]> {\n const [from, to] = localRange(range.from, range.to);\n const out: Row[] = [];\n for (const fy of financialYearsBetween(from, to)) {\n const resource = await this.resources.forFinancialYear(packageId, fy);\n const sql = rangeSql(resource.id, column, from, to, orderBy, { where: opts.where });\n out.push(...(await this.ckan.sql<Row>(sql, opts.extraNumeric)));\n }\n return out;\n }\n\n /** Transmission constraint costs and forecasts. */\n readonly constraints = {\n /**\n * Thermal constraint cost per boundary per day (£), `thermal-constraint-costs`. One row per\n * constraint group per settlement date, e.g. SCOTEX, SSHARN, SEIMP (Scottish export\n * boundaries), ESTEX, SWALEX.\n */\n thermalCosts: async (\n from: DateInput,\n to: DateInput,\n constraintGroup?: string,\n ): Promise<ThermalConstraintCostRow[]> =>\n this.byFinancialYear(\n PACKAGES.thermalConstraintCosts,\n \"Settlement Date\",\n { from, to },\n [\"Settlement Date\", \"Constraint Group\"],\n constraintGroup\n ? { where: `${ident(\"Constraint Group\")} = ${literal(constraintGroup)}` }\n : {},\n ),\n /**\n * Daily constraint cost (£) and volume (MWh) by type: thermal, voltage, inertia, largest\n * loss (`constraint-breakdown`). Thermal volume is negative (energy turned down).\n */\n breakdown: async (from: DateInput, to: DateInput): Promise<ConstraintBreakdownRow[]> =>\n this.byFinancialYear(PACKAGES.constraintBreakdown, \"Date\", { from, to }, [\"Date\"], {\n // \"Thermal constraints cost\" is typed text in some years\n extraNumeric: [\"Thermal constraints cost\"],\n }),\n /** NESO's 24-month-ahead constraint cost forecast, £m per month (24 rows). */\n forecast24m: async (): Promise<ConstraintForecastRow[]> => {\n const r = await this.resources.byName(PACKAGES.constraintCostForecast24m, /Forecast/);\n return this.ckan.datastoreAll<ConstraintForecastRow>(r.id);\n },\n /** 24-month-ahead boundary limits (MW) per week; boundary columns vary by publication. */\n limits24m: async (): Promise<ConstraintLimitRow[]> => {\n const r = await this.resources.byName(PACKAGES.constraintLimits24m, /limits/i);\n return this.ckan.datastoreAll<ConstraintLimitRow>(r.id);\n },\n /**\n * Day-ahead forecast flow vs limit per boundary, half-hourly, for a range of local dates\n * (`day-ahead-constraint-flows-and-limits`, ~750k rows, so always filtered by SQL).\n */\n dayAheadFlows: async (\n from: DateInput,\n to: DateInput,\n constraintGroup?: string,\n ): Promise<DayAheadFlowRow[]> => {\n const [f, t] = localRange(from, to);\n const r = await this.resources.byName(PACKAGES.dayAheadConstraintFlows, /Flows and Limits/);\n const sql = rangeSql(\n r.id,\n \"Date_ Time GMT_BST\",\n `${f}T00:00:00`,\n `${nextDay(t)}T00:00:00`,\n [\"Constraint Group\", \"Date_ Time GMT_BST\"],\n {\n toExclusive: true,\n where: constraintGroup\n ? `${ident(\"Constraint Group\")} = ${literal(constraintGroup)}`\n : undefined,\n },\n );\n return this.ckan.sql<DayAheadFlowRow>(sql);\n },\n /** Daily balancing (BSUoS) costs per settlement period by category, incl. `Constraints` (£). */\n dailyBalancingCosts: async (from: DateInput, to: DateInput): Promise<DailyBalancingCostRow[]> =>\n this.byFinancialYear(PACKAGES.dailyBalancingCosts, \"SETT_DATE\", { from, to }, [\n \"SETT_DATE\",\n \"SETT_PERIOD\",\n ]),\n };\n\n /** Demand Flexibility Service (current season; archives are separate resources). */\n readonly dfs = {\n /** Published requirements per event window, by delivery date. */\n requirements: async (from: DateInput, to: DateInput): Promise<DfsRequirementRow[]> =>\n this.dfsRange<DfsRequirementRow>(\"DFS Service Requirement\", from, to, [\n \"Event ID\",\n \"From_UTC\",\n ]),\n /** Accepted/rejected bids per participant unit per event window. */\n utilisation: async (from: DateInput, to: DateInput): Promise<DfsUtilisationRow[]> =>\n this.dfsRange<DfsUtilisationRow>(\"DFS Utilisation Report\", from, to, [\n \"Event ID\",\n \"From_UTC\",\n \"DFS Unit ID\",\n ]),\n /** Procured MW, accepted cost and settled volume/cost per event window. */\n summary: async (from: DateInput, to: DateInput): Promise<DfsSummaryRow[]> =>\n this.dfsRange<DfsSummaryRow>(\"DFS Utilisation Report Summary\", from, to, [\n \"Event ID\",\n \"From_UTC\",\n ]),\n };\n\n private async dfsRange<Row extends object>(\n name: string,\n from: DateInput,\n to: DateInput,\n orderBy: readonly string[],\n ): Promise<Row[]> {\n const [f, t] = localRange(from, to);\n const r = await this.resources.byName(PACKAGES.demandFlexibility, name);\n return this.ckan.sql<Row>(rangeSql(r.id, \"Delivery Date\", f, t, orderBy));\n }\n}\n"]}
package/dist/index.d.cts CHANGED
@@ -1,4 +1,5 @@
1
- import { RateLimit, HttpClient, HttpClientOptions, Query, DateInput } from '@uk-energy/core';
1
+ import { RateLimit, HttpClient, CkanClient, HttpClientOptions, Query, CkanResource, DateInput } from '@uk-energy/core';
2
+ export { CkanFailure, CkanPackage, CkanPackageSearch, CkanResource, CkanResponse, CkanSuccess, DatastoreField, DatastoreRow, DatastoreSearchOptions, DatastoreSearchResult, DatastoreSqlResult, ident, literal, normaliseRows, rangeSql } from '@uk-energy/core';
2
3
 
3
4
  /**
4
5
  * Shapes of the NESO Data Portal (CKAN 2.8) API and of the datasets this package wraps.
@@ -8,103 +9,7 @@ import { RateLimit, HttpClient, HttpClientOptions, Query, DateInput } from '@uk-
8
9
  *
9
10
  * Supported by National Energy SO Open Data.
10
11
  */
11
- interface CkanSuccess<T> {
12
- help: string;
13
- success: true;
14
- result: T;
15
- }
16
- interface CkanFailure {
17
- help: string;
18
- success: false;
19
- error: {
20
- message?: string;
21
- __type?: string;
22
- [field: string]: unknown;
23
- };
24
- }
25
- type CkanResponse<T> = CkanSuccess<T> | CkanFailure;
26
- interface CkanResource {
27
- id: string;
28
- package_id: string;
29
- name: string;
30
- description: string;
31
- format: string;
32
- url: string;
33
- /** True when the resource is loaded into the datastore (CSV/XLSX pushed to Postgres). */
34
- datastore_active: boolean;
35
- last_modified: string | null;
36
- created: string;
37
- size: number | null;
38
- position: number;
39
- state: string;
40
- mimetype: string | null;
41
- }
42
- interface CkanPackage {
43
- id: string;
44
- name: string;
45
- title: string;
46
- notes: string;
47
- license_id: string;
48
- license_title: string;
49
- license_url?: string;
50
- metadata_created: string;
51
- metadata_modified: string;
52
- num_resources: number;
53
- organization: {
54
- name: string;
55
- title: string;
56
- } | null;
57
- resources: CkanResource[];
58
- tags: {
59
- name: string;
60
- display_name: string;
61
- }[];
62
- extras: {
63
- key: string;
64
- value: string;
65
- }[];
66
- }
67
- interface CkanPackageSearch {
68
- count: number;
69
- results: CkanPackage[];
70
- }
71
- /** Datastore column: `int4`, `numeric`, `text`, `date`, `timestamp`, `tsvector`… */
72
- interface DatastoreField {
73
- id: string;
74
- type: string;
75
- }
76
- interface DatastoreSearchResult<Row = DatastoreRow> {
77
- resource_id: string;
78
- fields: DatastoreField[];
79
- records: Row[];
80
- total: number;
81
- limit?: number;
82
- offset?: number;
83
- _links?: {
84
- start?: string;
85
- next?: string;
86
- prev?: string;
87
- };
88
- }
89
- interface DatastoreSqlResult<Row = DatastoreRow> {
90
- sql: string;
91
- fields: DatastoreField[];
92
- records: Row[];
93
- }
94
- type DatastoreRow = Record<string, string | number | boolean | null>;
95
- interface DatastoreSearchOptions {
96
- /** Rows per request; the portal returns everything up to 32,000 in one page. */
97
- limit?: number;
98
- offset?: number;
99
- /** e.g. `"_id"` or `"Settlement Date desc"`. */
100
- sort?: string;
101
- /** Equality filters, `{ "Constraint Group": "ESTEX" }`. */
102
- filters?: Record<string, string | number | boolean | (string | number)[]>;
103
- /** Columns to return. */
104
- fields?: string[];
105
- /** Full-text query. */
106
- q?: string;
107
- }
12
+
108
13
  /** `thermal-constraint-costs`, one row per boundary per day. */
109
14
  interface ThermalConstraintCostRow {
110
15
  _id: number;
@@ -256,48 +161,14 @@ declare const PACKAGES: {
256
161
  interface NesoClientOptions extends Omit<HttpClientOptions, "baseUrl"> {
257
162
  baseUrl?: string;
258
163
  }
259
- /**
260
- * The SQL endpoint returns `numeric` columns as strings ("252"); `datastore_search` returns
261
- * numbers. Make rows consistent: numeric-typed columns become numbers, `_full_text` goes.
262
- */
263
- declare function normaliseRows<Row extends object>(records: readonly DatastoreRow[], fields: readonly DatastoreField[], extraNumeric?: readonly string[]): Row[];
264
- /** Double-quote an identifier for the SQL endpoint. */
265
- declare function ident(name: string): string;
266
- /** Single-quote a literal for the SQL endpoint. */
267
- declare function literal(value: string | number): string;
268
- /**
269
- * `SELECT * FROM "resource" WHERE "col" >= 'from' AND "col" <= 'to' ORDER BY …` — the shape
270
- * every date-range helper uses. `toExclusive` switches the upper bound to `<` for timestamps.
271
- */
272
- declare function rangeSql(resourceId: string, column: string, from: string, to: string, orderBy: readonly string[], opts?: {
273
- toExclusive?: boolean;
274
- where?: string | undefined;
275
- }): string;
164
+
276
165
  declare class NesoClient {
277
166
  readonly http: HttpClient;
278
- private readonly packageCache;
279
- private readonly packageTtlMs;
167
+ /** Raw CKAN API (`@uk-energy/core` `CkanClient`): `packageShow` (memoised), `datastoreSearch`, `datastoreAll`, `sql`… */
168
+ readonly ckan: CkanClient;
280
169
  constructor(opts?: NesoClientOptions);
281
170
  /** Any CKAN action; unwraps `{ success, result }` and throws `UkEnergyError` on `success: false`. */
282
171
  action<T>(name: string, query?: Query): Promise<T>;
283
- /** Raw CKAN API. */
284
- readonly ckan: {
285
- /** Dataset metadata and its resources (memoised for `cacheTtlMs`, default 10 minutes). */
286
- packageShow: (id: string) => Promise<CkanPackage>;
287
- /** Search datasets by free text. */
288
- packageSearch: (q: string, rows?: number) => Promise<CkanPackageSearch>;
289
- resourceShow: (id: string) => Promise<CkanResource>;
290
- /** One page of a datastore resource (up to 32,000 rows). Rows are normalised (see `normaliseRows`). */
291
- datastoreSearch: <Row extends object = DatastoreRow>(resourceId: string, opts?: DatastoreSearchOptions) => Promise<DatastoreSearchResult<Row>>;
292
- /** Every row of a datastore resource, paging by `limit` (default 32,000). */
293
- datastoreAll: <Row extends object = DatastoreRow>(resourceId: string, opts?: Omit<DatastoreSearchOptions, "offset">) => Promise<Row[]>;
294
- /**
295
- * `datastore_search_sql`: read-only PostgreSQL over the datastore. Resource ids are the
296
- * table names (quote them). Numeric columns come back as strings from this endpoint; they
297
- * are converted using the `fields` types, and `_full_text` is dropped.
298
- */
299
- sql: <Row extends object = DatastoreRow>(sql: string, extraNumeric?: readonly string[]) => Promise<Row[]>;
300
- };
301
172
  /** Resolving resources inside datasets. */
302
173
  readonly resources: {
303
174
  /** All datastore-backed resources of a dataset. */
@@ -361,5 +232,5 @@ declare function financialYearInName(name: string): number | undefined;
361
232
  /** The datastore resource of a dataset for one financial year, if any. */
362
233
  declare function resourceForFinancialYear(resources: readonly CkanResource[], startYear: number): CkanResource | undefined;
363
234
 
364
- export { BASE_URL, DEFAULT_CACHE_TTL_MS, DEFAULT_RATE_LIMIT, MAX_PAGE_SIZE, NesoClient, PACKAGES, financialYearInName, financialYearOf, financialYearsBetween, ident, literal, normaliseRows, rangeSql, resourceForFinancialYear };
365
- export type { CkanFailure, CkanPackage, CkanPackageSearch, CkanResource, CkanResponse, CkanSuccess, ConstraintBreakdownRow, ConstraintForecastRow, ConstraintLimitRow, DailyBalancingCostRow, DatastoreField, DatastoreRow, DatastoreSearchOptions, DatastoreSearchResult, DatastoreSqlResult, DayAheadFlowRow, DfsRequirementRow, DfsSummaryRow, DfsUtilisationRow, NesoClientOptions, ThermalConstraintCostRow };
235
+ export { BASE_URL, DEFAULT_CACHE_TTL_MS, DEFAULT_RATE_LIMIT, MAX_PAGE_SIZE, NesoClient, PACKAGES, financialYearInName, financialYearOf, financialYearsBetween, resourceForFinancialYear };
236
+ export type { ConstraintBreakdownRow, ConstraintForecastRow, ConstraintLimitRow, DailyBalancingCostRow, DayAheadFlowRow, DfsRequirementRow, DfsSummaryRow, DfsUtilisationRow, NesoClientOptions, ThermalConstraintCostRow };
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
- import { RateLimit, HttpClient, HttpClientOptions, Query, DateInput } from '@uk-energy/core';
1
+ import { RateLimit, HttpClient, CkanClient, HttpClientOptions, Query, CkanResource, DateInput } from '@uk-energy/core';
2
+ export { CkanFailure, CkanPackage, CkanPackageSearch, CkanResource, CkanResponse, CkanSuccess, DatastoreField, DatastoreRow, DatastoreSearchOptions, DatastoreSearchResult, DatastoreSqlResult, ident, literal, normaliseRows, rangeSql } from '@uk-energy/core';
2
3
 
3
4
  /**
4
5
  * Shapes of the NESO Data Portal (CKAN 2.8) API and of the datasets this package wraps.
@@ -8,103 +9,7 @@ import { RateLimit, HttpClient, HttpClientOptions, Query, DateInput } from '@uk-
8
9
  *
9
10
  * Supported by National Energy SO Open Data.
10
11
  */
11
- interface CkanSuccess<T> {
12
- help: string;
13
- success: true;
14
- result: T;
15
- }
16
- interface CkanFailure {
17
- help: string;
18
- success: false;
19
- error: {
20
- message?: string;
21
- __type?: string;
22
- [field: string]: unknown;
23
- };
24
- }
25
- type CkanResponse<T> = CkanSuccess<T> | CkanFailure;
26
- interface CkanResource {
27
- id: string;
28
- package_id: string;
29
- name: string;
30
- description: string;
31
- format: string;
32
- url: string;
33
- /** True when the resource is loaded into the datastore (CSV/XLSX pushed to Postgres). */
34
- datastore_active: boolean;
35
- last_modified: string | null;
36
- created: string;
37
- size: number | null;
38
- position: number;
39
- state: string;
40
- mimetype: string | null;
41
- }
42
- interface CkanPackage {
43
- id: string;
44
- name: string;
45
- title: string;
46
- notes: string;
47
- license_id: string;
48
- license_title: string;
49
- license_url?: string;
50
- metadata_created: string;
51
- metadata_modified: string;
52
- num_resources: number;
53
- organization: {
54
- name: string;
55
- title: string;
56
- } | null;
57
- resources: CkanResource[];
58
- tags: {
59
- name: string;
60
- display_name: string;
61
- }[];
62
- extras: {
63
- key: string;
64
- value: string;
65
- }[];
66
- }
67
- interface CkanPackageSearch {
68
- count: number;
69
- results: CkanPackage[];
70
- }
71
- /** Datastore column: `int4`, `numeric`, `text`, `date`, `timestamp`, `tsvector`… */
72
- interface DatastoreField {
73
- id: string;
74
- type: string;
75
- }
76
- interface DatastoreSearchResult<Row = DatastoreRow> {
77
- resource_id: string;
78
- fields: DatastoreField[];
79
- records: Row[];
80
- total: number;
81
- limit?: number;
82
- offset?: number;
83
- _links?: {
84
- start?: string;
85
- next?: string;
86
- prev?: string;
87
- };
88
- }
89
- interface DatastoreSqlResult<Row = DatastoreRow> {
90
- sql: string;
91
- fields: DatastoreField[];
92
- records: Row[];
93
- }
94
- type DatastoreRow = Record<string, string | number | boolean | null>;
95
- interface DatastoreSearchOptions {
96
- /** Rows per request; the portal returns everything up to 32,000 in one page. */
97
- limit?: number;
98
- offset?: number;
99
- /** e.g. `"_id"` or `"Settlement Date desc"`. */
100
- sort?: string;
101
- /** Equality filters, `{ "Constraint Group": "ESTEX" }`. */
102
- filters?: Record<string, string | number | boolean | (string | number)[]>;
103
- /** Columns to return. */
104
- fields?: string[];
105
- /** Full-text query. */
106
- q?: string;
107
- }
12
+
108
13
  /** `thermal-constraint-costs`, one row per boundary per day. */
109
14
  interface ThermalConstraintCostRow {
110
15
  _id: number;
@@ -256,48 +161,14 @@ declare const PACKAGES: {
256
161
  interface NesoClientOptions extends Omit<HttpClientOptions, "baseUrl"> {
257
162
  baseUrl?: string;
258
163
  }
259
- /**
260
- * The SQL endpoint returns `numeric` columns as strings ("252"); `datastore_search` returns
261
- * numbers. Make rows consistent: numeric-typed columns become numbers, `_full_text` goes.
262
- */
263
- declare function normaliseRows<Row extends object>(records: readonly DatastoreRow[], fields: readonly DatastoreField[], extraNumeric?: readonly string[]): Row[];
264
- /** Double-quote an identifier for the SQL endpoint. */
265
- declare function ident(name: string): string;
266
- /** Single-quote a literal for the SQL endpoint. */
267
- declare function literal(value: string | number): string;
268
- /**
269
- * `SELECT * FROM "resource" WHERE "col" >= 'from' AND "col" <= 'to' ORDER BY …` — the shape
270
- * every date-range helper uses. `toExclusive` switches the upper bound to `<` for timestamps.
271
- */
272
- declare function rangeSql(resourceId: string, column: string, from: string, to: string, orderBy: readonly string[], opts?: {
273
- toExclusive?: boolean;
274
- where?: string | undefined;
275
- }): string;
164
+
276
165
  declare class NesoClient {
277
166
  readonly http: HttpClient;
278
- private readonly packageCache;
279
- private readonly packageTtlMs;
167
+ /** Raw CKAN API (`@uk-energy/core` `CkanClient`): `packageShow` (memoised), `datastoreSearch`, `datastoreAll`, `sql`… */
168
+ readonly ckan: CkanClient;
280
169
  constructor(opts?: NesoClientOptions);
281
170
  /** Any CKAN action; unwraps `{ success, result }` and throws `UkEnergyError` on `success: false`. */
282
171
  action<T>(name: string, query?: Query): Promise<T>;
283
- /** Raw CKAN API. */
284
- readonly ckan: {
285
- /** Dataset metadata and its resources (memoised for `cacheTtlMs`, default 10 minutes). */
286
- packageShow: (id: string) => Promise<CkanPackage>;
287
- /** Search datasets by free text. */
288
- packageSearch: (q: string, rows?: number) => Promise<CkanPackageSearch>;
289
- resourceShow: (id: string) => Promise<CkanResource>;
290
- /** One page of a datastore resource (up to 32,000 rows). Rows are normalised (see `normaliseRows`). */
291
- datastoreSearch: <Row extends object = DatastoreRow>(resourceId: string, opts?: DatastoreSearchOptions) => Promise<DatastoreSearchResult<Row>>;
292
- /** Every row of a datastore resource, paging by `limit` (default 32,000). */
293
- datastoreAll: <Row extends object = DatastoreRow>(resourceId: string, opts?: Omit<DatastoreSearchOptions, "offset">) => Promise<Row[]>;
294
- /**
295
- * `datastore_search_sql`: read-only PostgreSQL over the datastore. Resource ids are the
296
- * table names (quote them). Numeric columns come back as strings from this endpoint; they
297
- * are converted using the `fields` types, and `_full_text` is dropped.
298
- */
299
- sql: <Row extends object = DatastoreRow>(sql: string, extraNumeric?: readonly string[]) => Promise<Row[]>;
300
- };
301
172
  /** Resolving resources inside datasets. */
302
173
  readonly resources: {
303
174
  /** All datastore-backed resources of a dataset. */
@@ -361,5 +232,5 @@ declare function financialYearInName(name: string): number | undefined;
361
232
  /** The datastore resource of a dataset for one financial year, if any. */
362
233
  declare function resourceForFinancialYear(resources: readonly CkanResource[], startYear: number): CkanResource | undefined;
363
234
 
364
- export { BASE_URL, DEFAULT_CACHE_TTL_MS, DEFAULT_RATE_LIMIT, MAX_PAGE_SIZE, NesoClient, PACKAGES, financialYearInName, financialYearOf, financialYearsBetween, ident, literal, normaliseRows, rangeSql, resourceForFinancialYear };
365
- export type { CkanFailure, CkanPackage, CkanPackageSearch, CkanResource, CkanResponse, CkanSuccess, ConstraintBreakdownRow, ConstraintForecastRow, ConstraintLimitRow, DailyBalancingCostRow, DatastoreField, DatastoreRow, DatastoreSearchOptions, DatastoreSearchResult, DatastoreSqlResult, DayAheadFlowRow, DfsRequirementRow, DfsSummaryRow, DfsUtilisationRow, NesoClientOptions, ThermalConstraintCostRow };
235
+ export { BASE_URL, DEFAULT_CACHE_TTL_MS, DEFAULT_RATE_LIMIT, MAX_PAGE_SIZE, NesoClient, PACKAGES, financialYearInName, financialYearOf, financialYearsBetween, resourceForFinancialYear };
236
+ export type { ConstraintBreakdownRow, ConstraintForecastRow, ConstraintLimitRow, DailyBalancingCostRow, DayAheadFlowRow, DfsRequirementRow, DfsSummaryRow, DfsUtilisationRow, NesoClientOptions, ThermalConstraintCostRow };
package/dist/index.js CHANGED
@@ -1,4 +1,5 @@
1
- import { HttpClient, UkEnergyError, pages, offsetCursor, londonDate } from '@uk-energy/core';
1
+ import { CKAN_MAX_PAGE_SIZE, HttpClient, CkanClient, UkEnergyError, rangeSql, ident, literal, londonDate } from '@uk-energy/core';
2
+ export { ident, literal, normaliseRows, rangeSql } from '@uk-energy/core';
2
3
 
3
4
  // src/client.ts
4
5
 
@@ -28,12 +29,10 @@ function financialYearInName(name) {
28
29
  function resourceForFinancialYear(resources, startYear) {
29
30
  return resources.find((r) => r.datastore_active && financialYearInName(r.name) === startYear);
30
31
  }
31
-
32
- // src/client.ts
33
32
  var BASE_URL = "https://api.neso.energy/api/3/action";
34
33
  var DEFAULT_RATE_LIMIT = { requestsPerSecond: 5, burst: 10 };
35
34
  var DEFAULT_CACHE_TTL_MS = 10 * 60 * 1e3;
36
- var MAX_PAGE_SIZE = 32e3;
35
+ var MAX_PAGE_SIZE = CKAN_MAX_PAGE_SIZE;
37
36
  var PACKAGES = {
38
37
  thermalConstraintCosts: "thermal-constraint-costs",
39
38
  constraintBreakdown: "constraint-breakdown",
@@ -43,45 +42,6 @@ var PACKAGES = {
43
42
  dailyBalancingCosts: "daily-balancing-costs-balancing-services-use-of-system",
44
43
  demandFlexibility: "demand-flexibility"
45
44
  };
46
- var DECIMAL = /^-?\d+(\.\d+)?([eE][-+]?\d+)?$/;
47
- var NUMERIC_TYPES = /* @__PURE__ */ new Set([
48
- "numeric",
49
- "int4",
50
- "int8",
51
- "float8",
52
- "float4",
53
- "int",
54
- "double precision"
55
- ]);
56
- function normaliseRows(records, fields, extraNumeric = []) {
57
- const numeric = new Set(extraNumeric);
58
- for (const f of fields) if (NUMERIC_TYPES.has(f.type)) numeric.add(f.id);
59
- return records.map((r) => {
60
- const out = {};
61
- for (const [k, v] of Object.entries(r)) {
62
- if (k === "_full_text") continue;
63
- if (numeric.has(k) && typeof v === "string" && DECIMAL.test(v.trim())) {
64
- const n = Number(v);
65
- out[k] = Number.isFinite(n) ? n : v;
66
- } else {
67
- out[k] = v;
68
- }
69
- }
70
- return out;
71
- });
72
- }
73
- function ident(name) {
74
- return `"${name.replace(/"/g, '""')}"`;
75
- }
76
- function literal(value) {
77
- return typeof value === "number" ? String(value) : `'${value.replace(/'/g, "''")}'`;
78
- }
79
- function rangeSql(resourceId, column, from, to, orderBy, opts = {}) {
80
- const upper = opts.toExclusive ? "<" : "<=";
81
- const extra = opts.where ? ` AND ${opts.where}` : "";
82
- const order = orderBy.length ? ` ORDER BY ${orderBy.map(ident).join(", ")}` : "";
83
- return `SELECT * FROM ${ident(resourceId)} WHERE ${ident(column)} >= ${literal(from)} AND ${ident(column)} ${upper} ${literal(to)}${extra}${order}`;
84
- }
85
45
  function localRange(from, to) {
86
46
  const f = londonDate(from);
87
47
  const t = londonDate(to);
@@ -95,8 +55,8 @@ function nextDay(date) {
95
55
  }
96
56
  var NesoClient = class {
97
57
  http;
98
- packageCache = /* @__PURE__ */ new Map();
99
- packageTtlMs;
58
+ /** Raw CKAN API (`@uk-energy/core` `CkanClient`): `packageShow` (memoised), `datastoreSearch`, `datastoreAll`, `sql`… */
59
+ ckan;
100
60
  constructor(opts = {}) {
101
61
  this.http = new HttpClient({
102
62
  baseUrl: BASE_URL,
@@ -104,91 +64,19 @@ var NesoClient = class {
104
64
  cacheTtlMs: opts.cache ? DEFAULT_CACHE_TTL_MS : 0,
105
65
  ...opts
106
66
  });
107
- this.packageTtlMs = opts.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS;
67
+ this.ckan = new CkanClient(this.http, {
68
+ packageTtlMs: opts.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS,
69
+ maxPageSize: MAX_PAGE_SIZE
70
+ });
108
71
  }
109
72
  /** Any CKAN action; unwraps `{ success, result }` and throws `UkEnergyError` on `success: false`. */
110
73
  async action(name, query) {
111
- const res = await this.http.get(name, query);
112
- if (!res || typeof res !== "object" || !("success" in res)) {
113
- throw new UkEnergyError(`Unexpected response shape for ${name}`);
114
- }
115
- if (!res.success) {
116
- throw new UkEnergyError(`${name}: ${res.error?.message ?? JSON.stringify(res.error)}`);
117
- }
118
- return res.result;
74
+ return this.ckan.action(name, query);
119
75
  }
120
- /** Raw CKAN API. */
121
- ckan = {
122
- /** Dataset metadata and its resources (memoised for `cacheTtlMs`, default 10 minutes). */
123
- packageShow: async (id) => {
124
- const hit = this.packageCache.get(id);
125
- if (hit && Date.now() - hit.at < this.packageTtlMs) return hit.value;
126
- const value = this.action("package_show", { id }).then((pkg) => {
127
- if (!pkg || !Array.isArray(pkg.resources)) {
128
- throw new UkEnergyError(`package_show: unexpected shape for ${id}`);
129
- }
130
- return pkg;
131
- });
132
- this.packageCache.set(id, { at: Date.now(), value });
133
- value.catch(() => this.packageCache.delete(id));
134
- return value;
135
- },
136
- /** Search datasets by free text. */
137
- packageSearch: async (q, rows = 20) => this.action("package_search", { q, rows }),
138
- resourceShow: async (id) => this.action("resource_show", { id }),
139
- /** One page of a datastore resource (up to 32,000 rows). Rows are normalised (see `normaliseRows`). */
140
- datastoreSearch: async (resourceId, opts = {}) => {
141
- const query = {
142
- resource_id: resourceId,
143
- limit: opts.limit ?? MAX_PAGE_SIZE,
144
- sort: opts.sort ?? "_id"
145
- };
146
- if (opts.offset !== void 0) query.offset = opts.offset;
147
- if (opts.filters) query.filters = JSON.stringify(opts.filters);
148
- if (opts.fields) query.fields = opts.fields.join(",");
149
- if (opts.q) query.q = opts.q;
150
- const res = await this.action("datastore_search", query);
151
- if (!Array.isArray(res?.records)) {
152
- throw new UkEnergyError("Unexpected response shape for datastore_search");
153
- }
154
- return { ...res, records: normaliseRows(res.records, res.fields ?? []) };
155
- },
156
- /** Every row of a datastore resource, paging by `limit` (default 32,000). */
157
- datastoreAll: async (resourceId, opts = {}) => {
158
- const pageSize = Math.min(opts.limit ?? MAX_PAGE_SIZE, MAX_PAGE_SIZE);
159
- const out = [];
160
- for await (const page of pages({
161
- fetchPage: (offset) => this.ckan.datastoreSearch(resourceId, {
162
- ...opts,
163
- limit: pageSize,
164
- offset: offset ?? 0
165
- }),
166
- items: (p) => p.records,
167
- nextCursor: offsetCursor(pageSize, (p) => p.records.length)
168
- })) {
169
- out.push(...page.records);
170
- }
171
- return out;
172
- },
173
- /**
174
- * `datastore_search_sql`: read-only PostgreSQL over the datastore. Resource ids are the
175
- * table names (quote them). Numeric columns come back as strings from this endpoint; they
176
- * are converted using the `fields` types, and `_full_text` is dropped.
177
- */
178
- sql: async (sql, extraNumeric = []) => {
179
- const res = await this.action("datastore_search_sql", {
180
- sql
181
- });
182
- if (!Array.isArray(res?.records)) {
183
- throw new UkEnergyError("Unexpected response shape for datastore_search_sql");
184
- }
185
- return normaliseRows(res.records, res.fields ?? [], extraNumeric);
186
- }
187
- };
188
76
  /** Resolving resources inside datasets. */
189
77
  resources = {
190
78
  /** All datastore-backed resources of a dataset. */
191
- datastore: async (packageId) => (await this.ckan.packageShow(packageId)).resources.filter((r) => r.datastore_active),
79
+ datastore: async (packageId) => this.ckan.datastoreResources(packageId),
192
80
  /** The resource for a GB financial year (`2025` = April 2025 – March 2026), by name. */
193
81
  forFinancialYear: async (packageId, startYear) => {
194
82
  const pkg = await this.ckan.packageShow(packageId);
@@ -201,14 +89,7 @@ var NesoClient = class {
201
89
  return r;
202
90
  },
203
91
  /** The datastore resource whose name matches exactly (or the regex), e.g. "DFS Utilisation Report". */
204
- byName: async (packageId, name) => {
205
- const list = await this.resources.datastore(packageId);
206
- const r = list.find(
207
- (x) => typeof name === "string" ? x.name.trim() === name.trim() : name.test(x.name)
208
- );
209
- if (!r) throw new UkEnergyError(`${packageId}: no datastore resource named ${String(name)}`);
210
- return r;
211
- }
92
+ byName: async (packageId, name) => this.ckan.resourceByName(packageId, name)
212
93
  };
213
94
  /** Rows of a per-financial-year dataset for an inclusive date range, across year boundaries. */
214
95
  async byFinancialYear(packageId, column, range, orderBy, opts = {}) {
@@ -305,6 +186,6 @@ var NesoClient = class {
305
186
  }
306
187
  };
307
188
 
308
- export { BASE_URL, DEFAULT_CACHE_TTL_MS, DEFAULT_RATE_LIMIT, MAX_PAGE_SIZE, NesoClient, PACKAGES, financialYearInName, financialYearOf, financialYearsBetween, ident, literal, normaliseRows, rangeSql, resourceForFinancialYear };
189
+ export { BASE_URL, DEFAULT_CACHE_TTL_MS, DEFAULT_RATE_LIMIT, MAX_PAGE_SIZE, NesoClient, PACKAGES, financialYearInName, financialYearOf, financialYearsBetween, resourceForFinancialYear };
309
190
  //# sourceMappingURL=index.js.map
310
191
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/resources.ts","../src/client.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Financial-year arithmetic and resource-name matching for datasets split one resource per\n * GB financial year (1 April – 31 March): \"Thermal Constraint Costs Data 25-26\",\n * \"Constraint Breakdown 2025-2026\", \"Daily Balancing Costs 2025-2026\".\n */\nimport type { CkanResource } from \"./types.js\";\n\n/** Financial year a date (\"YYYY-MM-DD\") falls in, as its starting calendar year. */\nexport function financialYearOf(date: string): number {\n const year = Number(date.slice(0, 4));\n const month = Number(date.slice(5, 7));\n return month >= 4 ? year : year - 1;\n}\n\n/** Starting years of every financial year touched by an inclusive date range. */\nexport function financialYearsBetween(from: string, to: string): number[] {\n const first = financialYearOf(from);\n const last = financialYearOf(to);\n const out: number[] = [];\n for (let y = first; y <= last; y++) out.push(y);\n return out;\n}\n\nconst YEAR_SPAN = /(?<!\\d)(\\d{4}|\\d{2})\\s*[-/–]\\s*(\\d{4}|\\d{2})(?!\\d)/g;\n\n/** The financial year a resource name refers to (\"25-26\" / \"2025-2026\" → 2025), or undefined. */\nexport function financialYearInName(name: string): number | undefined {\n for (const m of name.matchAll(YEAR_SPAN)) {\n if (!m[1] || !m[2]) continue;\n const start = m[1].length === 2 ? 2000 + Number(m[1]) : Number(m[1]);\n const end = m[2].length === 2 ? 2000 + Number(m[2]) : Number(m[2]);\n if (end === start + 1) return start;\n }\n return undefined;\n}\n\n/** The datastore resource of a dataset for one financial year, if any. */\nexport function resourceForFinancialYear(\n resources: readonly CkanResource[],\n startYear: number,\n): CkanResource | undefined {\n return resources.find((r) => r.datastore_active && financialYearInName(r.name) === startYear);\n}\n","/**\n * Client for the NESO Data Portal (https://www.neso.energy/data-portal), a CKAN 2.8 instance at\n * https://api.neso.energy/api/3/action/. No API key. Datasets (\"packages\") hold \"resources\";\n * tabular resources are loaded into a Postgres datastore that `datastore_search` and\n * `datastore_search_sql` query. Many datasets split one resource per GB financial year\n * (April–March), so the helpers resolve the right resource(s) for a date range.\n *\n * const neso = new NesoClient();\n * const costs = await neso.constraints.thermalCosts(\"2025-04-01\", \"2025-04-07\"); // £/day per boundary\n * const dfs = await neso.dfs.summary(\"2026-10-20\", \"2026-10-22\");\n * const rows = await neso.ckan.sql(`SELECT * FROM \"407f3e9d-…\" WHERE \"Settlement Date\" = '2025-04-01'`);\n *\n * Licence: National Energy SO Open Data Licence v1.0 (OGL v3 based): copy, adapt, use\n * commercially, with the attribution \"Supported by National Energy SO Open Data\".\n */\nimport {\n type DateInput,\n HttpClient,\n type HttpClientOptions,\n londonDate,\n offsetCursor,\n pages,\n type Query,\n type RateLimit,\n UkEnergyError,\n} from \"@uk-energy/core\";\nimport { financialYearsBetween, resourceForFinancialYear } from \"./resources.js\";\nimport type {\n CkanPackage,\n CkanPackageSearch,\n CkanResource,\n CkanResponse,\n ConstraintBreakdownRow,\n ConstraintForecastRow,\n ConstraintLimitRow,\n DailyBalancingCostRow,\n DatastoreField,\n DatastoreRow,\n DatastoreSearchOptions,\n DatastoreSearchResult,\n DatastoreSqlResult,\n DayAheadFlowRow,\n DfsRequirementRow,\n DfsSummaryRow,\n DfsUtilisationRow,\n ThermalConstraintCostRow,\n} from \"./types.js\";\n\nexport const BASE_URL = \"https://api.neso.energy/api/3/action\";\n\n/**\n * No limit is published; the licence only reserves the right to throttle \"excessive use\".\n * 5 rps / burst 10 is comfortably polite for a shared public CKAN.\n */\nexport const DEFAULT_RATE_LIMIT: RateLimit = { requestsPerSecond: 5, burst: 10 };\n\n/** Resources update daily or weekly; package metadata is memoised for this long. */\nexport const DEFAULT_CACHE_TTL_MS = 10 * 60 * 1000;\n\n/** The portal returns up to this many rows per `datastore_search` page. */\nexport const MAX_PAGE_SIZE = 32_000;\n\n/** Dataset (package) names on the portal that the helpers use. */\nexport const PACKAGES = {\n thermalConstraintCosts: \"thermal-constraint-costs\",\n constraintBreakdown: \"constraint-breakdown\",\n constraintCostForecast24m: \"24-months-ahead-constraint-cost-forecast\",\n constraintLimits24m: \"24-months-ahead-constraint-limits\",\n dayAheadConstraintFlows: \"day-ahead-constraint-flows-and-limits\",\n dailyBalancingCosts: \"daily-balancing-costs-balancing-services-use-of-system\",\n demandFlexibility: \"demand-flexibility\",\n} as const;\n\nexport interface NesoClientOptions extends Omit<HttpClientOptions, \"baseUrl\"> {\n baseUrl?: string;\n}\n\n/** Inclusive date range; strings are \"YYYY-MM-DD\", `Date`s are GB-local calendar days. */\ninterface DateRange {\n from: DateInput;\n to: DateInput;\n}\n\nconst DECIMAL = /^-?\\d+(\\.\\d+)?([eE][-+]?\\d+)?$/;\n\nconst NUMERIC_TYPES = new Set([\n \"numeric\",\n \"int4\",\n \"int8\",\n \"float8\",\n \"float4\",\n \"int\",\n \"double precision\",\n]);\n\n/**\n * The SQL endpoint returns `numeric` columns as strings (\"252\"); `datastore_search` returns\n * numbers. Make rows consistent: numeric-typed columns become numbers, `_full_text` goes.\n */\nexport function normaliseRows<Row extends object>(\n records: readonly DatastoreRow[],\n fields: readonly DatastoreField[],\n extraNumeric: readonly string[] = [],\n): Row[] {\n const numeric = new Set(extraNumeric);\n for (const f of fields) if (NUMERIC_TYPES.has(f.type)) numeric.add(f.id);\n return records.map((r) => {\n const out: DatastoreRow = {};\n for (const [k, v] of Object.entries(r)) {\n if (k === \"_full_text\") continue;\n if (numeric.has(k) && typeof v === \"string\" && DECIMAL.test(v.trim())) {\n const n = Number(v);\n out[k] = Number.isFinite(n) ? n : v;\n } else {\n out[k] = v;\n }\n }\n return out as Row;\n });\n}\n\n/** Double-quote an identifier for the SQL endpoint. */\nexport function ident(name: string): string {\n return `\"${name.replace(/\"/g, '\"\"')}\"`;\n}\n\n/** Single-quote a literal for the SQL endpoint. */\nexport function literal(value: string | number): string {\n return typeof value === \"number\" ? String(value) : `'${value.replace(/'/g, \"''\")}'`;\n}\n\n/**\n * `SELECT * FROM \"resource\" WHERE \"col\" >= 'from' AND \"col\" <= 'to' ORDER BY …` — the shape\n * every date-range helper uses. `toExclusive` switches the upper bound to `<` for timestamps.\n */\nexport function rangeSql(\n resourceId: string,\n column: string,\n from: string,\n to: string,\n orderBy: readonly string[],\n opts: { toExclusive?: boolean; where?: string | undefined } = {},\n): string {\n const upper = opts.toExclusive ? \"<\" : \"<=\";\n const extra = opts.where ? ` AND ${opts.where}` : \"\";\n const order = orderBy.length ? ` ORDER BY ${orderBy.map(ident).join(\", \")}` : \"\";\n return `SELECT * FROM ${ident(resourceId)} WHERE ${ident(column)} >= ${literal(from)} AND ${ident(column)} ${upper} ${literal(to)}${extra}${order}`;\n}\n\n/** Inclusive GB-local date range: `Date`s are read as Europe/London calendar days. */\nfunction localRange(from: DateInput, to: DateInput): [string, string] {\n const f = londonDate(from);\n const t = londonDate(to);\n if (f > t) throw new UkEnergyError(`from (${f}) is after to (${t})`);\n return [f, t];\n}\n\nfunction nextDay(date: string): string {\n const d = new Date(`${date}T00:00:00Z`);\n d.setUTCDate(d.getUTCDate() + 1);\n return d.toISOString().slice(0, 10);\n}\n\nexport class NesoClient {\n readonly http: HttpClient;\n private readonly packageCache = new Map<string, { at: number; value: Promise<CkanPackage> }>();\n private readonly packageTtlMs: number;\n\n constructor(opts: NesoClientOptions = {}) {\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 this.packageTtlMs = opts.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS;\n }\n\n /** Any CKAN action; unwraps `{ success, result }` and throws `UkEnergyError` on `success: false`. */\n async action<T>(name: string, query?: Query): Promise<T> {\n const res = await this.http.get<CkanResponse<T>>(name, query);\n if (!res || typeof res !== \"object\" || !(\"success\" in res)) {\n throw new UkEnergyError(`Unexpected response shape for ${name}`);\n }\n if (!res.success) {\n throw new UkEnergyError(`${name}: ${res.error?.message ?? JSON.stringify(res.error)}`);\n }\n return res.result;\n }\n\n /** Raw CKAN API. */\n readonly ckan = {\n /** Dataset metadata and its resources (memoised for `cacheTtlMs`, default 10 minutes). */\n packageShow: async (id: string): Promise<CkanPackage> => {\n const hit = this.packageCache.get(id);\n if (hit && Date.now() - hit.at < this.packageTtlMs) return hit.value;\n const value = this.action<CkanPackage>(\"package_show\", { id }).then((pkg) => {\n if (!pkg || !Array.isArray(pkg.resources)) {\n throw new UkEnergyError(`package_show: unexpected shape for ${id}`);\n }\n return pkg;\n });\n this.packageCache.set(id, { at: Date.now(), value });\n value.catch(() => this.packageCache.delete(id));\n return value;\n },\n /** Search datasets by free text. */\n packageSearch: async (q: string, rows = 20): Promise<CkanPackageSearch> =>\n this.action(\"package_search\", { q, rows }),\n resourceShow: async (id: string): Promise<CkanResource> => this.action(\"resource_show\", { id }),\n /** One page of a datastore resource (up to 32,000 rows). Rows are normalised (see `normaliseRows`). */\n datastoreSearch: async <Row extends object = DatastoreRow>(\n resourceId: string,\n opts: DatastoreSearchOptions = {},\n ): Promise<DatastoreSearchResult<Row>> => {\n const query: Query = {\n resource_id: resourceId,\n limit: opts.limit ?? MAX_PAGE_SIZE,\n sort: opts.sort ?? \"_id\",\n };\n if (opts.offset !== undefined) query.offset = opts.offset;\n if (opts.filters) query.filters = JSON.stringify(opts.filters);\n if (opts.fields) query.fields = opts.fields.join(\",\");\n if (opts.q) query.q = opts.q;\n const res = await this.action<DatastoreSearchResult<DatastoreRow>>(\"datastore_search\", query);\n if (!Array.isArray(res?.records)) {\n throw new UkEnergyError(\"Unexpected response shape for datastore_search\");\n }\n return { ...res, records: normaliseRows<Row>(res.records, res.fields ?? []) };\n },\n /** Every row of a datastore resource, paging by `limit` (default 32,000). */\n datastoreAll: async <Row extends object = DatastoreRow>(\n resourceId: string,\n opts: Omit<DatastoreSearchOptions, \"offset\"> = {},\n ): Promise<Row[]> => {\n const pageSize = Math.min(opts.limit ?? MAX_PAGE_SIZE, MAX_PAGE_SIZE);\n const out: Row[] = [];\n for await (const page of pages<DatastoreSearchResult<Row>, Row, number>({\n fetchPage: (offset) =>\n this.ckan.datastoreSearch<Row>(resourceId, {\n ...opts,\n limit: pageSize,\n offset: offset ?? 0,\n }),\n items: (p) => p.records,\n nextCursor: offsetCursor(pageSize, (p) => p.records.length),\n })) {\n out.push(...page.records);\n }\n return out;\n },\n /**\n * `datastore_search_sql`: read-only PostgreSQL over the datastore. Resource ids are the\n * table names (quote them). Numeric columns come back as strings from this endpoint; they\n * are converted using the `fields` types, and `_full_text` is dropped.\n */\n sql: async <Row extends object = DatastoreRow>(\n sql: string,\n extraNumeric: readonly string[] = [],\n ): Promise<Row[]> => {\n const res = await this.action<DatastoreSqlResult<DatastoreRow>>(\"datastore_search_sql\", {\n sql,\n });\n if (!Array.isArray(res?.records)) {\n throw new UkEnergyError(\"Unexpected response shape for datastore_search_sql\");\n }\n return normaliseRows<Row>(res.records, res.fields ?? [], extraNumeric);\n },\n };\n\n /** Resolving resources inside datasets. */\n readonly resources = {\n /** All datastore-backed resources of a dataset. */\n datastore: async (packageId: string): Promise<CkanResource[]> =>\n (await this.ckan.packageShow(packageId)).resources.filter((r) => r.datastore_active),\n /** The resource for a GB financial year (`2025` = April 2025 – March 2026), by name. */\n forFinancialYear: async (packageId: string, startYear: number): Promise<CkanResource> => {\n const pkg = await this.ckan.packageShow(packageId);\n const r = resourceForFinancialYear(pkg.resources, startYear);\n if (!r) {\n throw new UkEnergyError(\n `${packageId}: no datastore resource for financial year ${startYear}/${(startYear + 1) % 100}`,\n );\n }\n return r;\n },\n /** The datastore resource whose name matches exactly (or the regex), e.g. \"DFS Utilisation Report\". */\n byName: async (packageId: string, name: string | RegExp): Promise<CkanResource> => {\n const list = await this.resources.datastore(packageId);\n const r = list.find((x) =>\n typeof name === \"string\" ? x.name.trim() === name.trim() : name.test(x.name),\n );\n if (!r) throw new UkEnergyError(`${packageId}: no datastore resource named ${String(name)}`);\n return r;\n },\n };\n\n /** Rows of a per-financial-year dataset for an inclusive date range, across year boundaries. */\n private async byFinancialYear<Row extends object>(\n packageId: string,\n column: string,\n range: DateRange,\n orderBy: readonly string[],\n opts: { where?: string | undefined; extraNumeric?: readonly string[] } = {},\n ): Promise<Row[]> {\n const [from, to] = localRange(range.from, range.to);\n const out: Row[] = [];\n for (const fy of financialYearsBetween(from, to)) {\n const resource = await this.resources.forFinancialYear(packageId, fy);\n const sql = rangeSql(resource.id, column, from, to, orderBy, { where: opts.where });\n out.push(...(await this.ckan.sql<Row>(sql, opts.extraNumeric)));\n }\n return out;\n }\n\n /** Transmission constraint costs and forecasts. */\n readonly constraints = {\n /**\n * Thermal constraint cost per boundary per day (£), `thermal-constraint-costs`. One row per\n * constraint group per settlement date, e.g. SCOTEX, SSHARN, SEIMP (Scottish export\n * boundaries), ESTEX, SWALEX.\n */\n thermalCosts: async (\n from: DateInput,\n to: DateInput,\n constraintGroup?: string,\n ): Promise<ThermalConstraintCostRow[]> =>\n this.byFinancialYear(\n PACKAGES.thermalConstraintCosts,\n \"Settlement Date\",\n { from, to },\n [\"Settlement Date\", \"Constraint Group\"],\n constraintGroup\n ? { where: `${ident(\"Constraint Group\")} = ${literal(constraintGroup)}` }\n : {},\n ),\n /**\n * Daily constraint cost (£) and volume (MWh) by type: thermal, voltage, inertia, largest\n * loss (`constraint-breakdown`). Thermal volume is negative (energy turned down).\n */\n breakdown: async (from: DateInput, to: DateInput): Promise<ConstraintBreakdownRow[]> =>\n this.byFinancialYear(PACKAGES.constraintBreakdown, \"Date\", { from, to }, [\"Date\"], {\n // \"Thermal constraints cost\" is typed text in some years\n extraNumeric: [\"Thermal constraints cost\"],\n }),\n /** NESO's 24-month-ahead constraint cost forecast, £m per month (24 rows). */\n forecast24m: async (): Promise<ConstraintForecastRow[]> => {\n const r = await this.resources.byName(PACKAGES.constraintCostForecast24m, /Forecast/);\n return this.ckan.datastoreAll<ConstraintForecastRow>(r.id);\n },\n /** 24-month-ahead boundary limits (MW) per week; boundary columns vary by publication. */\n limits24m: async (): Promise<ConstraintLimitRow[]> => {\n const r = await this.resources.byName(PACKAGES.constraintLimits24m, /limits/i);\n return this.ckan.datastoreAll<ConstraintLimitRow>(r.id);\n },\n /**\n * Day-ahead forecast flow vs limit per boundary, half-hourly, for a range of local dates\n * (`day-ahead-constraint-flows-and-limits`, ~750k rows, so always filtered by SQL).\n */\n dayAheadFlows: async (\n from: DateInput,\n to: DateInput,\n constraintGroup?: string,\n ): Promise<DayAheadFlowRow[]> => {\n const [f, t] = localRange(from, to);\n const r = await this.resources.byName(PACKAGES.dayAheadConstraintFlows, /Flows and Limits/);\n const sql = rangeSql(\n r.id,\n \"Date_ Time GMT_BST\",\n `${f}T00:00:00`,\n `${nextDay(t)}T00:00:00`,\n [\"Constraint Group\", \"Date_ Time GMT_BST\"],\n {\n toExclusive: true,\n where: constraintGroup\n ? `${ident(\"Constraint Group\")} = ${literal(constraintGroup)}`\n : undefined,\n },\n );\n return this.ckan.sql<DayAheadFlowRow>(sql);\n },\n /** Daily balancing (BSUoS) costs per settlement period by category, incl. `Constraints` (£). */\n dailyBalancingCosts: async (from: DateInput, to: DateInput): Promise<DailyBalancingCostRow[]> =>\n this.byFinancialYear(PACKAGES.dailyBalancingCosts, \"SETT_DATE\", { from, to }, [\n \"SETT_DATE\",\n \"SETT_PERIOD\",\n ]),\n };\n\n /** Demand Flexibility Service (current season; archives are separate resources). */\n readonly dfs = {\n /** Published requirements per event window, by delivery date. */\n requirements: async (from: DateInput, to: DateInput): Promise<DfsRequirementRow[]> =>\n this.dfsRange<DfsRequirementRow>(\"DFS Service Requirement\", from, to, [\n \"Event ID\",\n \"From_UTC\",\n ]),\n /** Accepted/rejected bids per participant unit per event window. */\n utilisation: async (from: DateInput, to: DateInput): Promise<DfsUtilisationRow[]> =>\n this.dfsRange<DfsUtilisationRow>(\"DFS Utilisation Report\", from, to, [\n \"Event ID\",\n \"From_UTC\",\n \"DFS Unit ID\",\n ]),\n /** Procured MW, accepted cost and settled volume/cost per event window. */\n summary: async (from: DateInput, to: DateInput): Promise<DfsSummaryRow[]> =>\n this.dfsRange<DfsSummaryRow>(\"DFS Utilisation Report Summary\", from, to, [\n \"Event ID\",\n \"From_UTC\",\n ]),\n };\n\n private async dfsRange<Row extends object>(\n name: string,\n from: DateInput,\n to: DateInput,\n orderBy: readonly string[],\n ): Promise<Row[]> {\n const [f, t] = localRange(from, to);\n const r = await this.resources.byName(PACKAGES.demandFlexibility, name);\n return this.ckan.sql<Row>(rangeSql(r.id, \"Delivery Date\", f, t, orderBy));\n }\n}\n"]}
1
+ {"version":3,"sources":["../src/resources.ts","../src/client.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Financial-year arithmetic and resource-name matching for datasets split one resource per\n * GB financial year (1 April – 31 March): \"Thermal Constraint Costs Data 25-26\",\n * \"Constraint Breakdown 2025-2026\", \"Daily Balancing Costs 2025-2026\".\n */\nimport type { CkanResource } from \"./types.js\";\n\n/** Financial year a date (\"YYYY-MM-DD\") falls in, as its starting calendar year. */\nexport function financialYearOf(date: string): number {\n const year = Number(date.slice(0, 4));\n const month = Number(date.slice(5, 7));\n return month >= 4 ? year : year - 1;\n}\n\n/** Starting years of every financial year touched by an inclusive date range. */\nexport function financialYearsBetween(from: string, to: string): number[] {\n const first = financialYearOf(from);\n const last = financialYearOf(to);\n const out: number[] = [];\n for (let y = first; y <= last; y++) out.push(y);\n return out;\n}\n\nconst YEAR_SPAN = /(?<!\\d)(\\d{4}|\\d{2})\\s*[-/–]\\s*(\\d{4}|\\d{2})(?!\\d)/g;\n\n/** The financial year a resource name refers to (\"25-26\" / \"2025-2026\" → 2025), or undefined. */\nexport function financialYearInName(name: string): number | undefined {\n for (const m of name.matchAll(YEAR_SPAN)) {\n if (!m[1] || !m[2]) continue;\n const start = m[1].length === 2 ? 2000 + Number(m[1]) : Number(m[1]);\n const end = m[2].length === 2 ? 2000 + Number(m[2]) : Number(m[2]);\n if (end === start + 1) return start;\n }\n return undefined;\n}\n\n/** The datastore resource of a dataset for one financial year, if any. */\nexport function resourceForFinancialYear(\n resources: readonly CkanResource[],\n startYear: number,\n): CkanResource | undefined {\n return resources.find((r) => r.datastore_active && financialYearInName(r.name) === startYear);\n}\n","/**\n * Client for the NESO Data Portal (https://www.neso.energy/data-portal), a CKAN 2.8 instance at\n * https://api.neso.energy/api/3/action/. No API key. Datasets (\"packages\") hold \"resources\";\n * tabular resources are loaded into a Postgres datastore that `datastore_search` and\n * `datastore_search_sql` query. Many datasets split one resource per GB financial year\n * (April–March), so the helpers resolve the right resource(s) for a date range.\n *\n * const neso = new NesoClient();\n * const costs = await neso.constraints.thermalCosts(\"2025-04-01\", \"2025-04-07\"); // £/day per boundary\n * const dfs = await neso.dfs.summary(\"2026-10-20\", \"2026-10-22\");\n * const rows = await neso.ckan.sql(`SELECT * FROM \"407f3e9d-…\" WHERE \"Settlement Date\" = '2025-04-01'`);\n *\n * Licence: National Energy SO Open Data Licence v1.0 (OGL v3 based): copy, adapt, use\n * commercially, with the attribution \"Supported by National Energy SO Open Data\".\n */\nimport {\n CKAN_MAX_PAGE_SIZE,\n CkanClient,\n type CkanResource,\n type DateInput,\n HttpClient,\n type HttpClientOptions,\n ident,\n literal,\n londonDate,\n type Query,\n type RateLimit,\n rangeSql,\n UkEnergyError,\n} from \"@uk-energy/core\";\nimport { financialYearsBetween, resourceForFinancialYear } from \"./resources.js\";\nimport type {\n ConstraintBreakdownRow,\n ConstraintForecastRow,\n ConstraintLimitRow,\n DailyBalancingCostRow,\n DayAheadFlowRow,\n DfsRequirementRow,\n DfsSummaryRow,\n DfsUtilisationRow,\n ThermalConstraintCostRow,\n} from \"./types.js\";\n\nexport const BASE_URL = \"https://api.neso.energy/api/3/action\";\n\n/**\n * No limit is published; the licence only reserves the right to throttle \"excessive use\".\n * 5 rps / burst 10 is comfortably polite for a shared public CKAN.\n */\nexport const DEFAULT_RATE_LIMIT: RateLimit = { requestsPerSecond: 5, burst: 10 };\n\n/** Resources update daily or weekly; package metadata is memoised for this long. */\nexport const DEFAULT_CACHE_TTL_MS = 10 * 60 * 1000;\n\n/** The portal returns up to this many rows per `datastore_search` page. */\nexport const MAX_PAGE_SIZE = CKAN_MAX_PAGE_SIZE;\n\n/** Dataset (package) names on the portal that the helpers use. */\nexport const PACKAGES = {\n thermalConstraintCosts: \"thermal-constraint-costs\",\n constraintBreakdown: \"constraint-breakdown\",\n constraintCostForecast24m: \"24-months-ahead-constraint-cost-forecast\",\n constraintLimits24m: \"24-months-ahead-constraint-limits\",\n dayAheadConstraintFlows: \"day-ahead-constraint-flows-and-limits\",\n dailyBalancingCosts: \"daily-balancing-costs-balancing-services-use-of-system\",\n demandFlexibility: \"demand-flexibility\",\n} as const;\n\nexport interface NesoClientOptions extends Omit<HttpClientOptions, \"baseUrl\"> {\n baseUrl?: string;\n}\n\n/** Inclusive date range; strings are \"YYYY-MM-DD\", `Date`s are GB-local calendar days. */\ninterface DateRange {\n from: DateInput;\n to: DateInput;\n}\n\nexport { ident, literal, normaliseRows, rangeSql } from \"@uk-energy/core\";\n\n/** Inclusive GB-local date range: `Date`s are read as Europe/London calendar days. */\nfunction localRange(from: DateInput, to: DateInput): [string, string] {\n const f = londonDate(from);\n const t = londonDate(to);\n if (f > t) throw new UkEnergyError(`from (${f}) is after to (${t})`);\n return [f, t];\n}\n\nfunction nextDay(date: string): string {\n const d = new Date(`${date}T00:00:00Z`);\n d.setUTCDate(d.getUTCDate() + 1);\n return d.toISOString().slice(0, 10);\n}\n\nexport class NesoClient {\n readonly http: HttpClient;\n /** Raw CKAN API (`@uk-energy/core` `CkanClient`): `packageShow` (memoised), `datastoreSearch`, `datastoreAll`, `sql`… */\n readonly ckan: CkanClient;\n\n constructor(opts: NesoClientOptions = {}) {\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 this.ckan = new CkanClient(this.http, {\n packageTtlMs: opts.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS,\n maxPageSize: MAX_PAGE_SIZE,\n });\n }\n\n /** Any CKAN action; unwraps `{ success, result }` and throws `UkEnergyError` on `success: false`. */\n async action<T>(name: string, query?: Query): Promise<T> {\n return this.ckan.action<T>(name, query);\n }\n\n /** Resolving resources inside datasets. */\n readonly resources = {\n /** All datastore-backed resources of a dataset. */\n datastore: async (packageId: string): Promise<CkanResource[]> =>\n this.ckan.datastoreResources(packageId),\n /** The resource for a GB financial year (`2025` = April 2025 – March 2026), by name. */\n forFinancialYear: async (packageId: string, startYear: number): Promise<CkanResource> => {\n const pkg = await this.ckan.packageShow(packageId);\n const r = resourceForFinancialYear(pkg.resources, startYear);\n if (!r) {\n throw new UkEnergyError(\n `${packageId}: no datastore resource for financial year ${startYear}/${(startYear + 1) % 100}`,\n );\n }\n return r;\n },\n /** The datastore resource whose name matches exactly (or the regex), e.g. \"DFS Utilisation Report\". */\n byName: async (packageId: string, name: string | RegExp): Promise<CkanResource> =>\n this.ckan.resourceByName(packageId, name),\n };\n\n /** Rows of a per-financial-year dataset for an inclusive date range, across year boundaries. */\n private async byFinancialYear<Row extends object>(\n packageId: string,\n column: string,\n range: DateRange,\n orderBy: readonly string[],\n opts: { where?: string | undefined; extraNumeric?: readonly string[] } = {},\n ): Promise<Row[]> {\n const [from, to] = localRange(range.from, range.to);\n const out: Row[] = [];\n for (const fy of financialYearsBetween(from, to)) {\n const resource = await this.resources.forFinancialYear(packageId, fy);\n const sql = rangeSql(resource.id, column, from, to, orderBy, { where: opts.where });\n out.push(...(await this.ckan.sql<Row>(sql, opts.extraNumeric)));\n }\n return out;\n }\n\n /** Transmission constraint costs and forecasts. */\n readonly constraints = {\n /**\n * Thermal constraint cost per boundary per day (£), `thermal-constraint-costs`. One row per\n * constraint group per settlement date, e.g. SCOTEX, SSHARN, SEIMP (Scottish export\n * boundaries), ESTEX, SWALEX.\n */\n thermalCosts: async (\n from: DateInput,\n to: DateInput,\n constraintGroup?: string,\n ): Promise<ThermalConstraintCostRow[]> =>\n this.byFinancialYear(\n PACKAGES.thermalConstraintCosts,\n \"Settlement Date\",\n { from, to },\n [\"Settlement Date\", \"Constraint Group\"],\n constraintGroup\n ? { where: `${ident(\"Constraint Group\")} = ${literal(constraintGroup)}` }\n : {},\n ),\n /**\n * Daily constraint cost (£) and volume (MWh) by type: thermal, voltage, inertia, largest\n * loss (`constraint-breakdown`). Thermal volume is negative (energy turned down).\n */\n breakdown: async (from: DateInput, to: DateInput): Promise<ConstraintBreakdownRow[]> =>\n this.byFinancialYear(PACKAGES.constraintBreakdown, \"Date\", { from, to }, [\"Date\"], {\n // \"Thermal constraints cost\" is typed text in some years\n extraNumeric: [\"Thermal constraints cost\"],\n }),\n /** NESO's 24-month-ahead constraint cost forecast, £m per month (24 rows). */\n forecast24m: async (): Promise<ConstraintForecastRow[]> => {\n const r = await this.resources.byName(PACKAGES.constraintCostForecast24m, /Forecast/);\n return this.ckan.datastoreAll<ConstraintForecastRow>(r.id);\n },\n /** 24-month-ahead boundary limits (MW) per week; boundary columns vary by publication. */\n limits24m: async (): Promise<ConstraintLimitRow[]> => {\n const r = await this.resources.byName(PACKAGES.constraintLimits24m, /limits/i);\n return this.ckan.datastoreAll<ConstraintLimitRow>(r.id);\n },\n /**\n * Day-ahead forecast flow vs limit per boundary, half-hourly, for a range of local dates\n * (`day-ahead-constraint-flows-and-limits`, ~750k rows, so always filtered by SQL).\n */\n dayAheadFlows: async (\n from: DateInput,\n to: DateInput,\n constraintGroup?: string,\n ): Promise<DayAheadFlowRow[]> => {\n const [f, t] = localRange(from, to);\n const r = await this.resources.byName(PACKAGES.dayAheadConstraintFlows, /Flows and Limits/);\n const sql = rangeSql(\n r.id,\n \"Date_ Time GMT_BST\",\n `${f}T00:00:00`,\n `${nextDay(t)}T00:00:00`,\n [\"Constraint Group\", \"Date_ Time GMT_BST\"],\n {\n toExclusive: true,\n where: constraintGroup\n ? `${ident(\"Constraint Group\")} = ${literal(constraintGroup)}`\n : undefined,\n },\n );\n return this.ckan.sql<DayAheadFlowRow>(sql);\n },\n /** Daily balancing (BSUoS) costs per settlement period by category, incl. `Constraints` (£). */\n dailyBalancingCosts: async (from: DateInput, to: DateInput): Promise<DailyBalancingCostRow[]> =>\n this.byFinancialYear(PACKAGES.dailyBalancingCosts, \"SETT_DATE\", { from, to }, [\n \"SETT_DATE\",\n \"SETT_PERIOD\",\n ]),\n };\n\n /** Demand Flexibility Service (current season; archives are separate resources). */\n readonly dfs = {\n /** Published requirements per event window, by delivery date. */\n requirements: async (from: DateInput, to: DateInput): Promise<DfsRequirementRow[]> =>\n this.dfsRange<DfsRequirementRow>(\"DFS Service Requirement\", from, to, [\n \"Event ID\",\n \"From_UTC\",\n ]),\n /** Accepted/rejected bids per participant unit per event window. */\n utilisation: async (from: DateInput, to: DateInput): Promise<DfsUtilisationRow[]> =>\n this.dfsRange<DfsUtilisationRow>(\"DFS Utilisation Report\", from, to, [\n \"Event ID\",\n \"From_UTC\",\n \"DFS Unit ID\",\n ]),\n /** Procured MW, accepted cost and settled volume/cost per event window. */\n summary: async (from: DateInput, to: DateInput): Promise<DfsSummaryRow[]> =>\n this.dfsRange<DfsSummaryRow>(\"DFS Utilisation Report Summary\", from, to, [\n \"Event ID\",\n \"From_UTC\",\n ]),\n };\n\n private async dfsRange<Row extends object>(\n name: string,\n from: DateInput,\n to: DateInput,\n orderBy: readonly string[],\n ): Promise<Row[]> {\n const [f, t] = localRange(from, to);\n const r = await this.resources.byName(PACKAGES.demandFlexibility, name);\n return this.ckan.sql<Row>(rangeSql(r.id, \"Delivery Date\", f, t, orderBy));\n }\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uk-energy/neso",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Typed client for the NESO Data Portal (CKAN): datastore search and SQL, thermal constraint costs, constraint breakdown and forecasts, day-ahead boundary flows, Demand Flexibility Service, daily balancing costs. Part of uk-energy-sdk.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -44,7 +44,7 @@
44
44
  "uk-energy"
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"