@uk-open-data-connectors/uk-sources 0.1.0 → 0.1.2

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.
Files changed (101) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +110 -192
  3. package/dist/ancientWoodland.d.ts.map +1 -1
  4. package/dist/ancientWoodland.js +2 -4
  5. package/dist/ancientWoodland.js.map +1 -1
  6. package/dist/apiKey.d.ts +11 -0
  7. package/dist/apiKey.d.ts.map +1 -0
  8. package/dist/apiKey.js +17 -0
  9. package/dist/apiKey.js.map +1 -0
  10. package/dist/bankRate.d.ts.map +1 -1
  11. package/dist/bankRate.js +3 -5
  12. package/dist/bankRate.js.map +1 -1
  13. package/dist/carbonIntensity.d.ts +4 -1
  14. package/dist/carbonIntensity.d.ts.map +1 -1
  15. package/dist/carbonIntensity.js +6 -2
  16. package/dist/carbonIntensity.js.map +1 -1
  17. package/dist/errors.d.ts +12 -2
  18. package/dist/errors.d.ts.map +1 -1
  19. package/dist/errors.js +8 -4
  20. package/dist/errors.js.map +1 -1
  21. package/dist/exploreEducationStatistics.d.ts +72 -0
  22. package/dist/exploreEducationStatistics.d.ts.map +1 -0
  23. package/dist/exploreEducationStatistics.js +108 -0
  24. package/dist/exploreEducationStatistics.js.map +1 -0
  25. package/dist/findATender.d.ts +76 -0
  26. package/dist/findATender.d.ts.map +1 -0
  27. package/dist/findATender.js +107 -0
  28. package/dist/findATender.js.map +1 -0
  29. package/dist/fingertipsIndicators.d.ts +67 -0
  30. package/dist/fingertipsIndicators.d.ts.map +1 -0
  31. package/dist/fingertipsIndicators.js +92 -0
  32. package/dist/fingertipsIndicators.js.map +1 -0
  33. package/dist/floodMonitoring.d.ts.map +1 -1
  34. package/dist/floodMonitoring.js +4 -9
  35. package/dist/floodMonitoring.js.map +1 -1
  36. package/dist/foodHygiene.d.ts.map +1 -1
  37. package/dist/foodHygiene.js +4 -5
  38. package/dist/foodHygiene.js.map +1 -1
  39. package/dist/http.d.ts +34 -0
  40. package/dist/http.d.ts.map +1 -0
  41. package/dist/http.js +83 -0
  42. package/dist/http.js.map +1 -0
  43. package/dist/index.d.ts +56 -0
  44. package/dist/index.d.ts.map +1 -1
  45. package/dist/index.js +28 -0
  46. package/dist/index.js.map +1 -1
  47. package/dist/londonDatastore.d.ts +46 -0
  48. package/dist/londonDatastore.d.ts.map +1 -0
  49. package/dist/londonDatastore.js +64 -0
  50. package/dist/londonDatastore.js.map +1 -0
  51. package/dist/nesoGenerationMix.d.ts +128 -0
  52. package/dist/nesoGenerationMix.d.ts.map +1 -0
  53. package/dist/nesoGenerationMix.js +241 -0
  54. package/dist/nesoGenerationMix.js.map +1 -0
  55. package/dist/nhsbsaCkan.d.ts +43 -0
  56. package/dist/nhsbsaCkan.d.ts.map +1 -0
  57. package/dist/nhsbsaCkan.js +59 -0
  58. package/dist/nhsbsaCkan.js.map +1 -0
  59. package/dist/nomis.d.ts +71 -0
  60. package/dist/nomis.d.ts.map +1 -0
  61. package/dist/nomis.js +124 -0
  62. package/dist/nomis.js.map +1 -0
  63. package/dist/onsDatasets.d.ts.map +1 -1
  64. package/dist/onsDatasets.js +3 -5
  65. package/dist/onsDatasets.js.map +1 -1
  66. package/dist/parliamentSeats.d.ts.map +1 -1
  67. package/dist/parliamentSeats.js +6 -5
  68. package/dist/parliamentSeats.js.map +1 -1
  69. package/dist/planningDatasets.d.ts.map +1 -1
  70. package/dist/planningDatasets.js +3 -5
  71. package/dist/planningDatasets.js.map +1 -1
  72. package/dist/policeCrimes.d.ts.map +1 -1
  73. package/dist/policeCrimes.js +5 -8
  74. package/dist/policeCrimes.js.map +1 -1
  75. package/dist/postcodeLookup.d.ts +55 -0
  76. package/dist/postcodeLookup.d.ts.map +1 -0
  77. package/dist/postcodeLookup.js +89 -0
  78. package/dist/postcodeLookup.js.map +1 -0
  79. package/dist/publicHealthScotland.d.ts +43 -0
  80. package/dist/publicHealthScotland.d.ts.map +1 -0
  81. package/dist/publicHealthScotland.js +59 -0
  82. package/dist/publicHealthScotland.js.map +1 -0
  83. package/dist/registry.d.ts.map +1 -1
  84. package/dist/registry.js +24 -0
  85. package/dist/registry.js.map +1 -1
  86. package/dist/tflBikePoints.d.ts.map +1 -1
  87. package/dist/tflBikePoints.js +6 -6
  88. package/dist/tflBikePoints.js.map +1 -1
  89. package/dist/tflLineStatus.d.ts +59 -0
  90. package/dist/tflLineStatus.d.ts.map +1 -0
  91. package/dist/tflLineStatus.js +91 -0
  92. package/dist/tflLineStatus.js.map +1 -0
  93. package/dist/tnaDiscovery.d.ts +76 -0
  94. package/dist/tnaDiscovery.d.ts.map +1 -0
  95. package/dist/tnaDiscovery.js +100 -0
  96. package/dist/tnaDiscovery.js.map +1 -0
  97. package/dist/ukhsaDashboard.d.ts +92 -0
  98. package/dist/ukhsaDashboard.d.ts.map +1 -0
  99. package/dist/ukhsaDashboard.js +134 -0
  100. package/dist/ukhsaDashboard.js.map +1 -0
  101. package/package.json +13 -4
@@ -0,0 +1,64 @@
1
+ import { z } from 'zod';
2
+ import { UkSourceParseError } from './errors.js';
3
+ import { httpGet } from './http.js';
4
+ import { readFixtureJson } from './fixtures.js';
5
+ /**
6
+ * The London Datastore, the Greater London Authority's open data catalogue,
7
+ * served by CKAN. Keyless, published under the Open Government Licence v3.
8
+ */
9
+ export const LONDON_DATASTORE_SOURCE_ID = 'london-datastore';
10
+ /**
11
+ * The CKAN package_list call. The service answers a 307 redirect from this
12
+ * path to /api/action/package_list, which fetch follows on its own.
13
+ */
14
+ export const LONDON_DATASTORE_PACKAGE_LIST_URL = 'https://data.london.gov.uk/api/3/action/package_list';
15
+ const LONDON_DATASTORE_PACKAGE_LIST_SCHEMA = z.object({
16
+ success: z.boolean(),
17
+ result: z.array(z.string()),
18
+ });
19
+ /**
20
+ * Parses a London Datastore package_list payload.
21
+ *
22
+ * @param payload - the raw JSON body from the CKAN package_list call
23
+ * @returns one entry per dataset in the catalogue
24
+ */
25
+ export function parseLondonDatastorePackages(payload) {
26
+ const parsed = LONDON_DATASTORE_PACKAGE_LIST_SCHEMA.safeParse(payload);
27
+ if (!parsed.success) {
28
+ throw new UkSourceParseError('london-datastore', parsed.error.message);
29
+ }
30
+ return parsed.data.result.map((name) => ({ name }));
31
+ }
32
+ /**
33
+ * Counts the London Datastore catalogue.
34
+ *
35
+ * @param packages - package names from {@link parseLondonDatastorePackages}
36
+ * @returns the number of datasets listed
37
+ */
38
+ export function summarizeLondonDatastorePackages(packages) {
39
+ return { packageCount: packages.length };
40
+ }
41
+ /**
42
+ * Lists every dataset in the London Datastore catalogue.
43
+ *
44
+ * @param options - an optional fetch implementation
45
+ * @returns the package names the catalogue returns
46
+ */
47
+ export async function fetchLondonDatastorePackages(options = {}) {
48
+ const { fetchImpl = globalThis.fetch } = options;
49
+ const response = await httpGet('london-datastore', LONDON_DATASTORE_PACKAGE_LIST_URL, {
50
+ fetchImpl,
51
+ });
52
+ return parseLondonDatastorePackages(await response.json());
53
+ }
54
+ /** London Datastore catalogue, keyless. */
55
+ export const londonDatastoreAdapter = {
56
+ id: LONDON_DATASTORE_SOURCE_ID,
57
+ name: 'London Datastore dataset catalogue',
58
+ auth: 'none',
59
+ description: 'Every dataset the Greater London Authority lists on the London Datastore.',
60
+ fetchLive: (options) => fetchLondonDatastorePackages(options?.fetchImpl === undefined ? {} : { fetchImpl: options.fetchImpl }),
61
+ parse: parseLondonDatastorePackages,
62
+ loadFixture: () => parseLondonDatastorePackages(readFixtureJson('london-datastore-2026-10-05.json')),
63
+ };
64
+ //# sourceMappingURL=londonDatastore.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"londonDatastore.js","sourceRoot":"","sources":["../src/londonDatastore.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAGhD;;;GAGG;AACH,MAAM,CAAC,MAAM,0BAA0B,GAAG,kBAAkB,CAAC;AAE7D;;;GAGG;AACH,MAAM,CAAC,MAAM,iCAAiC,GAC5C,sDAAsD,CAAC;AAazD,MAAM,oCAAoC,GAAG,CAAC,CAAC,MAAM,CAAC;IACpD,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE;IACpB,MAAM,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;CAC5B,CAAC,CAAC;AAEH;;;;;GAKG;AACH,MAAM,UAAU,4BAA4B,CAAC,OAAgB;IAC3D,MAAM,MAAM,GAAG,oCAAoC,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;IACvE,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACpB,MAAM,IAAI,kBAAkB,CAAC,kBAAkB,EAAE,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IACzE,CAAC;IACD,OAAO,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;AACtD,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,gCAAgC,CAC9C,QAAkC;IAElC,OAAO,EAAE,YAAY,EAAE,QAAQ,CAAC,MAAM,EAAE,CAAC;AAC3C,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,4BAA4B,CAChD,UAAmD,EAAE;IAErD,MAAM,EAAE,SAAS,GAAG,UAAU,CAAC,KAAK,EAAE,GAAG,OAAO,CAAC;IACjD,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,kBAAkB,EAAE,iCAAiC,EAAE;QACpF,SAAS;KACV,CAAC,CAAC;IACH,OAAO,4BAA4B,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;AAC7D,CAAC;AAED,2CAA2C;AAC3C,MAAM,CAAC,MAAM,sBAAsB,GAA4C;IAC7E,EAAE,EAAE,0BAA0B;IAC9B,IAAI,EAAE,oCAAoC;IAC1C,IAAI,EAAE,MAAM;IACZ,WAAW,EAAE,2EAA2E;IACxF,SAAS,EAAE,CAAC,OAAO,EAAE,EAAE,CACrB,4BAA4B,CAC1B,OAAO,EAAE,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,OAAO,CAAC,SAAS,EAAE,CACzE;IACH,KAAK,EAAE,4BAA4B;IACnC,WAAW,EAAE,GAAG,EAAE,CAChB,4BAA4B,CAAC,eAAe,CAAC,kCAAkC,CAAC,CAAC;CACpF,CAAC"}
@@ -0,0 +1,128 @@
1
+ import type { UkDataAdapter } from './types.js';
2
+ /**
3
+ * Half-hourly Great Britain generation by fuel, from the National Energy
4
+ * System Operator's open data portal. Keyless: the CKAN datastore search
5
+ * answers without a key, and the portal publishes under the NESO Open Data
6
+ * Licence.
7
+ *
8
+ * The portal moved house. The dataset was first published at
9
+ * <https://data.nationalgrideso.com/>, which no longer resolves now the GB
10
+ * system operator trades as NESO. The same datasets are served from
11
+ * <https://api.neso.energy/>, which is what this adapter reads.
12
+ */
13
+ export declare const NESO_GENERATION_MIX_SOURCE_ID = "neso-generation-mix";
14
+ /** The portal's CKAN API root. */
15
+ export declare const NESO_DATA_PORTAL_API_BASE_URL = "https://api.neso.energy";
16
+ /** The datastore search call every read of the portal is built from. */
17
+ export declare const NESO_DATASTORE_SEARCH_URL = "https://api.neso.energy/api/3/action/datastore_search";
18
+ /** The "Historic GB Generation Mix" resource: one row per half hour since 2009. */
19
+ export declare const NESO_GENERATION_MIX_RESOURCE_ID = "f93d1835-75bc-43e5-84ad-12472b180a98";
20
+ /** Half hours in the default window: seven whole days. */
21
+ export declare const DEFAULT_NESO_GENERATION_MIX_HALF_HOURS = 336;
22
+ /**
23
+ * The most half hours one read asks for. The datastore will answer with more,
24
+ * but a window past ten weeks stops being the recent picture the summary is
25
+ * for, and the whole window is held in memory to be rolled up.
26
+ */
27
+ export declare const MAX_NESO_GENERATION_MIX_HALF_HOURS = 3360;
28
+ /**
29
+ * Every fuel the resource reports, in the order the portal lists the columns.
30
+ * `WIND_EMB` is embedded wind: turbines on the distribution networks, counted
31
+ * separately from the transmission-connected `WIND`.
32
+ */
33
+ export declare const NESO_GENERATION_FUELS: readonly ["GAS", "COAL", "NUCLEAR", "WIND", "WIND_EMB", "HYDRO", "IMPORTS", "BIOMASS", "OTHER", "SOLAR", "STORAGE"];
34
+ /** One of the fuel columns the resource carries. */
35
+ export type NesoGenerationFuel = (typeof NESO_GENERATION_FUELS)[number];
36
+ /** One fuel's reading for one half hour. */
37
+ export interface NesoGenerationFuelReading {
38
+ fuel: NesoGenerationFuel;
39
+ /** The portal's own number for that fuel over the half hour, in megawatts. */
40
+ megawatts: number;
41
+ }
42
+ /** One half hour of the series, with the row the portal holds for it. */
43
+ export interface NesoGenerationHalfHour {
44
+ /** Start of the half hour, as the portal writes it: `2026-10-17T16:00:00`. */
45
+ datetime: string;
46
+ /** What the portal says the half hour generated in total, in megawatts. */
47
+ totalMegawatts: number;
48
+ /** The half hour's carbon intensity, in grams of CO2 per kilowatt hour. */
49
+ carbonIntensity: number;
50
+ /** The half hour's low-carbon output, as the portal counts it, in megawatts. */
51
+ lowCarbonMegawatts: number;
52
+ /** Output per fuel, in the order of {@link NESO_GENERATION_FUELS}. */
53
+ fuels: NesoGenerationFuelReading[];
54
+ }
55
+ /** One fuel rolled up across the window. */
56
+ export interface NesoGenerationFuelShare {
57
+ fuel: NesoGenerationFuel;
58
+ /** Mean output over the window, in megawatts, rounded to a whole number. */
59
+ averageMegawatts: number;
60
+ /** The most the fuel reached in any half hour of the window. */
61
+ peakMegawatts: number;
62
+ /** Its share of the window's total generation, to one decimal place. */
63
+ sharePercent: number;
64
+ }
65
+ /** A window of half hours, rolled up into the numbers a page prints. */
66
+ export interface NesoGenerationMixWindow {
67
+ /** Half hours the window holds. */
68
+ halfHourCount: number;
69
+ /** Start of the oldest half hour in the window. */
70
+ firstHalfHourAt: string;
71
+ /** Start of the newest half hour in the window. */
72
+ lastHalfHourAt: string;
73
+ /** Mean total generation over the window, in megawatts. */
74
+ averageTotalMegawatts: number;
75
+ /** Mean carbon intensity over the window, in gCO2/kWh. */
76
+ averageCarbonIntensity: number;
77
+ /** Share of the window's generation the portal counts as low carbon. */
78
+ lowCarbonSharePercent: number;
79
+ /** Mean output per fuel, largest first. */
80
+ fuelShares: NesoGenerationFuelShare[];
81
+ /** The half hour with the lowest carbon intensity. */
82
+ cleanestHalfHour: NesoGenerationHalfHour;
83
+ /** The half hour with the highest carbon intensity. */
84
+ dirtiestHalfHour: NesoGenerationHalfHour;
85
+ /** Every half hour in the window, oldest first. */
86
+ halfHours: NesoGenerationHalfHour[];
87
+ }
88
+ /**
89
+ * Builds the URL for one window of half hours.
90
+ *
91
+ * The datastore answers newest first, which is the order a page wants when it
92
+ * is showing the last few days, and the parse puts the window back in time
93
+ * order before rolling it up.
94
+ *
95
+ * @param options - how many half hours to ask for, and which resource
96
+ * @returns the datastore search URL, with its query already encoded
97
+ */
98
+ export declare function buildNesoGenerationMixUrl(options?: {
99
+ halfHours?: number;
100
+ resourceId?: string;
101
+ }): string;
102
+ /**
103
+ * Parses a datastore response into the readings the page draws.
104
+ *
105
+ * Every check here is one the page would otherwise show as a wrong number: a
106
+ * datetime that is not on a half-hour boundary, two rows for the same half
107
+ * hour, a fuel or total that is missing, or a row where the portal's own
108
+ * generation figure is not a positive number. Anything that fails throws
109
+ * rather than quietly joining the totals.
110
+ *
111
+ * @param payload - the datastore response, or the committed fixture of one
112
+ * @returns every half hour in the window, oldest first, with its totals
113
+ */
114
+ export declare function parseNesoGenerationMix(payload: unknown): NesoGenerationMixWindow;
115
+ /**
116
+ * Reads the most recent half hours of Great Britain's generation mix.
117
+ *
118
+ * @param options - how many half hours to ask for, and an optional fetch implementation
119
+ * @returns every half hour in the read window, oldest first, with its totals
120
+ */
121
+ export declare function fetchNesoGenerationMixWindow(options?: {
122
+ halfHours?: number;
123
+ resourceId?: string;
124
+ fetchImpl?: typeof globalThis.fetch;
125
+ }): Promise<NesoGenerationMixWindow>;
126
+ /** National Energy System Operator generation mix, half-hourly and keyless. */
127
+ export declare const nesoGenerationMixAdapter: UkDataAdapter<NesoGenerationMixWindow>;
128
+ //# sourceMappingURL=nesoGenerationMix.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nesoGenerationMix.d.ts","sourceRoot":"","sources":["../src/nesoGenerationMix.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhD;;;;;;;;;;GAUG;AACH,eAAO,MAAM,6BAA6B,wBAAwB,CAAC;AAEnE,kCAAkC;AAClC,eAAO,MAAM,6BAA6B,4BAA4B,CAAC;AAEvE,wEAAwE;AACxE,eAAO,MAAM,yBAAyB,0DAAmE,CAAC;AAE1G,mFAAmF;AACnF,eAAO,MAAM,+BAA+B,yCAAyC,CAAC;AAEtF,0DAA0D;AAC1D,eAAO,MAAM,sCAAsC,MAAM,CAAC;AAE1D;;;;GAIG;AACH,eAAO,MAAM,kCAAkC,OAAO,CAAC;AAEvD;;;;GAIG;AACH,eAAO,MAAM,qBAAqB,qHAYxB,CAAC;AAEX,oDAAoD;AACpD,MAAM,MAAM,kBAAkB,GAAG,CAAC,OAAO,qBAAqB,CAAC,CAAC,MAAM,CAAC,CAAC;AAExE,4CAA4C;AAC5C,MAAM,WAAW,yBAAyB;IACxC,IAAI,EAAE,kBAAkB,CAAC;IACzB,8EAA8E;IAC9E,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,yEAAyE;AACzE,MAAM,WAAW,sBAAsB;IACrC,8EAA8E;IAC9E,QAAQ,EAAE,MAAM,CAAC;IACjB,2EAA2E;IAC3E,cAAc,EAAE,MAAM,CAAC;IACvB,2EAA2E;IAC3E,eAAe,EAAE,MAAM,CAAC;IACxB,gFAAgF;IAChF,kBAAkB,EAAE,MAAM,CAAC;IAC3B,sEAAsE;IACtE,KAAK,EAAE,yBAAyB,EAAE,CAAC;CACpC;AAED,4CAA4C;AAC5C,MAAM,WAAW,uBAAuB;IACtC,IAAI,EAAE,kBAAkB,CAAC;IACzB,4EAA4E;IAC5E,gBAAgB,EAAE,MAAM,CAAC;IACzB,gEAAgE;IAChE,aAAa,EAAE,MAAM,CAAC;IACtB,wEAAwE;IACxE,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,wEAAwE;AACxE,MAAM,WAAW,uBAAuB;IACtC,mCAAmC;IACnC,aAAa,EAAE,MAAM,CAAC;IACtB,mDAAmD;IACnD,eAAe,EAAE,MAAM,CAAC;IACxB,mDAAmD;IACnD,cAAc,EAAE,MAAM,CAAC;IACvB,2DAA2D;IAC3D,qBAAqB,EAAE,MAAM,CAAC;IAC9B,0DAA0D;IAC1D,sBAAsB,EAAE,MAAM,CAAC;IAC/B,wEAAwE;IACxE,qBAAqB,EAAE,MAAM,CAAC;IAC9B,2CAA2C;IAC3C,UAAU,EAAE,uBAAuB,EAAE,CAAC;IACtC,sDAAsD;IACtD,gBAAgB,EAAE,sBAAsB,CAAC;IACzC,uDAAuD;IACvD,gBAAgB,EAAE,sBAAsB,CAAC;IACzC,mDAAmD;IACnD,SAAS,EAAE,sBAAsB,EAAE,CAAC;CACrC;AA0CD;;;;;;;;;GASG;AACH,wBAAgB,yBAAyB,CACvC,OAAO,GAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAAO,GACxD,MAAM,CAqBR;AA8DD;;;;;;;;;;;GAWG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,OAAO,GAAG,uBAAuB,CAqEhF;AAED;;;;;GAKG;AACH,wBAAsB,4BAA4B,CAChD,OAAO,GAAE;IACP,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;CAChC,GACL,OAAO,CAAC,uBAAuB,CAAC,CASlC;AAED,+EAA+E;AAC/E,eAAO,MAAM,wBAAwB,EAAE,aAAa,CAAC,uBAAuB,CAY3E,CAAC"}
@@ -0,0 +1,241 @@
1
+ import { z } from 'zod';
2
+ import { UkSourceParseError } from './errors.js';
3
+ import { readFixtureJson } from './fixtures.js';
4
+ import { httpGet } from './http.js';
5
+ /**
6
+ * Half-hourly Great Britain generation by fuel, from the National Energy
7
+ * System Operator's open data portal. Keyless: the CKAN datastore search
8
+ * answers without a key, and the portal publishes under the NESO Open Data
9
+ * Licence.
10
+ *
11
+ * The portal moved house. The dataset was first published at
12
+ * <https://data.nationalgrideso.com/>, which no longer resolves now the GB
13
+ * system operator trades as NESO. The same datasets are served from
14
+ * <https://api.neso.energy/>, which is what this adapter reads.
15
+ */
16
+ export const NESO_GENERATION_MIX_SOURCE_ID = 'neso-generation-mix';
17
+ /** The portal's CKAN API root. */
18
+ export const NESO_DATA_PORTAL_API_BASE_URL = 'https://api.neso.energy';
19
+ /** The datastore search call every read of the portal is built from. */
20
+ export const NESO_DATASTORE_SEARCH_URL = `${NESO_DATA_PORTAL_API_BASE_URL}/api/3/action/datastore_search`;
21
+ /** The "Historic GB Generation Mix" resource: one row per half hour since 2009. */
22
+ export const NESO_GENERATION_MIX_RESOURCE_ID = 'f93d1835-75bc-43e5-84ad-12472b180a98';
23
+ /** Half hours in the default window: seven whole days. */
24
+ export const DEFAULT_NESO_GENERATION_MIX_HALF_HOURS = 336;
25
+ /**
26
+ * The most half hours one read asks for. The datastore will answer with more,
27
+ * but a window past ten weeks stops being the recent picture the summary is
28
+ * for, and the whole window is held in memory to be rolled up.
29
+ */
30
+ export const MAX_NESO_GENERATION_MIX_HALF_HOURS = 3360;
31
+ /**
32
+ * Every fuel the resource reports, in the order the portal lists the columns.
33
+ * `WIND_EMB` is embedded wind: turbines on the distribution networks, counted
34
+ * separately from the transmission-connected `WIND`.
35
+ */
36
+ export const NESO_GENERATION_FUELS = [
37
+ 'GAS',
38
+ 'COAL',
39
+ 'NUCLEAR',
40
+ 'WIND',
41
+ 'WIND_EMB',
42
+ 'HYDRO',
43
+ 'IMPORTS',
44
+ 'BIOMASS',
45
+ 'OTHER',
46
+ 'SOLAR',
47
+ 'STORAGE',
48
+ ];
49
+ // One line per fuel column the resource carries, spelled out rather than
50
+ // built from NESO_GENERATION_FUELS, so the parsed record is typed with those
51
+ // keys and a renamed or missing column fails at the schema.
52
+ const NESO_GENERATION_MIX_RESPONSE_SCHEMA = z.object({
53
+ success: z.boolean(),
54
+ result: z.object({
55
+ total: z.number(),
56
+ records: z.array(z.object({
57
+ DATETIME: z.string(),
58
+ GENERATION: z.number(),
59
+ CARBON_INTENSITY: z.number(),
60
+ LOW_CARBON: z.number(),
61
+ GAS: z.number(),
62
+ COAL: z.number(),
63
+ NUCLEAR: z.number(),
64
+ WIND: z.number(),
65
+ WIND_EMB: z.number(),
66
+ HYDRO: z.number(),
67
+ IMPORTS: z.number(),
68
+ BIOMASS: z.number(),
69
+ OTHER: z.number(),
70
+ SOLAR: z.number(),
71
+ STORAGE: z.number(),
72
+ })),
73
+ }),
74
+ });
75
+ /** The portal writes datetimes to the minute, at the start of a half hour. */
76
+ const NESO_DATETIME_PATTERN = /^\d{4}-\d{2}-\d{2}T\d{2}:(00|30):00$/;
77
+ /** Milliseconds in a half hour, for checking one reading runs to the next. */
78
+ const MILLISECONDS_PER_HALF_HOUR = 30 * 60 * 1000;
79
+ /** Rounds a share to one decimal place. */
80
+ function toSharePercent(part, whole) {
81
+ return Math.round((part / whole) * 1000) / 10;
82
+ }
83
+ /**
84
+ * Builds the URL for one window of half hours.
85
+ *
86
+ * The datastore answers newest first, which is the order a page wants when it
87
+ * is showing the last few days, and the parse puts the window back in time
88
+ * order before rolling it up.
89
+ *
90
+ * @param options - how many half hours to ask for, and which resource
91
+ * @returns the datastore search URL, with its query already encoded
92
+ */
93
+ export function buildNesoGenerationMixUrl(options = {}) {
94
+ const halfHours = options.halfHours ?? DEFAULT_NESO_GENERATION_MIX_HALF_HOURS;
95
+ if (!Number.isInteger(halfHours) || halfHours < 1) {
96
+ throw new UkSourceParseError(NESO_GENERATION_MIX_SOURCE_ID, `a window of ${String(halfHours)} half hours is not a whole number of half hours`);
97
+ }
98
+ if (halfHours > MAX_NESO_GENERATION_MIX_HALF_HOURS) {
99
+ throw new UkSourceParseError(NESO_GENERATION_MIX_SOURCE_ID, `this adapter reads at most ${String(MAX_NESO_GENERATION_MIX_HALF_HOURS)} half hours at a time`);
100
+ }
101
+ const resourceId = options.resourceId ?? NESO_GENERATION_MIX_RESOURCE_ID;
102
+ const query = new URLSearchParams({
103
+ resource_id: resourceId,
104
+ sort: 'DATETIME desc',
105
+ limit: String(halfHours),
106
+ });
107
+ return `${NESO_DATASTORE_SEARCH_URL}?${query.toString()}`;
108
+ }
109
+ /** Rolls a window of half hours up into the numbers the page prints. */
110
+ function summarizeNesoGenerationMix(halfHours) {
111
+ const firstHalfHour = halfHours[0];
112
+ const lastHalfHour = halfHours.at(-1);
113
+ if (firstHalfHour === undefined || lastHalfHour === undefined) {
114
+ throw new UkSourceParseError(NESO_GENERATION_MIX_SOURCE_ID, 'the window holds no half hours');
115
+ }
116
+ let totalMegawatts = 0;
117
+ let carbonIntensityTotal = 0;
118
+ let lowCarbonMegawatts = 0;
119
+ const megawattsByFuel = new Map(NESO_GENERATION_FUELS.map((fuel) => [fuel, 0]));
120
+ const peakByFuel = new Map(NESO_GENERATION_FUELS.map((fuel) => [fuel, Number.NEGATIVE_INFINITY]));
121
+ for (const halfHour of halfHours) {
122
+ totalMegawatts += halfHour.totalMegawatts;
123
+ carbonIntensityTotal += halfHour.carbonIntensity;
124
+ lowCarbonMegawatts += halfHour.lowCarbonMegawatts;
125
+ for (const reading of halfHour.fuels) {
126
+ megawattsByFuel.set(reading.fuel, (megawattsByFuel.get(reading.fuel) ?? 0) + reading.megawatts);
127
+ peakByFuel.set(reading.fuel, Math.max(peakByFuel.get(reading.fuel) ?? 0, reading.megawatts));
128
+ }
129
+ }
130
+ const halfHourCount = halfHours.length;
131
+ const fuelShares = NESO_GENERATION_FUELS.map((fuel) => ({
132
+ fuel,
133
+ averageMegawatts: Math.round((megawattsByFuel.get(fuel) ?? 0) / halfHourCount),
134
+ peakMegawatts: peakByFuel.get(fuel) ?? 0,
135
+ sharePercent: toSharePercent(megawattsByFuel.get(fuel) ?? 0, totalMegawatts),
136
+ })).sort((left, right) => right.averageMegawatts - left.averageMegawatts);
137
+ const cleanestHalfHour = halfHours.reduce((cleanest, halfHour) => halfHour.carbonIntensity < cleanest.carbonIntensity ? halfHour : cleanest);
138
+ const dirtiestHalfHour = halfHours.reduce((dirtiest, halfHour) => halfHour.carbonIntensity > dirtiest.carbonIntensity ? halfHour : dirtiest);
139
+ return {
140
+ halfHourCount,
141
+ firstHalfHourAt: firstHalfHour.datetime,
142
+ lastHalfHourAt: lastHalfHour.datetime,
143
+ averageTotalMegawatts: Math.round(totalMegawatts / halfHourCount),
144
+ averageCarbonIntensity: Math.round(carbonIntensityTotal / halfHourCount),
145
+ lowCarbonSharePercent: toSharePercent(lowCarbonMegawatts, totalMegawatts),
146
+ fuelShares,
147
+ cleanestHalfHour,
148
+ dirtiestHalfHour,
149
+ halfHours,
150
+ };
151
+ }
152
+ /**
153
+ * Parses a datastore response into the readings the page draws.
154
+ *
155
+ * Every check here is one the page would otherwise show as a wrong number: a
156
+ * datetime that is not on a half-hour boundary, two rows for the same half
157
+ * hour, a fuel or total that is missing, or a row where the portal's own
158
+ * generation figure is not a positive number. Anything that fails throws
159
+ * rather than quietly joining the totals.
160
+ *
161
+ * @param payload - the datastore response, or the committed fixture of one
162
+ * @returns every half hour in the window, oldest first, with its totals
163
+ */
164
+ export function parseNesoGenerationMix(payload) {
165
+ const parsed = NESO_GENERATION_MIX_RESPONSE_SCHEMA.safeParse(payload);
166
+ if (!parsed.success) {
167
+ throw new UkSourceParseError(NESO_GENERATION_MIX_SOURCE_ID, parsed.error.message);
168
+ }
169
+ if (!parsed.data.success) {
170
+ throw new UkSourceParseError(NESO_GENERATION_MIX_SOURCE_ID, 'the datastore reported a failure');
171
+ }
172
+ const records = parsed.data.result.records;
173
+ if (records.length === 0) {
174
+ throw new UkSourceParseError(NESO_GENERATION_MIX_SOURCE_ID, 'the window returned no half hours');
175
+ }
176
+ const seen = new Set();
177
+ const halfHours = records.map((record) => {
178
+ if (!NESO_DATETIME_PATTERN.test(record.DATETIME)) {
179
+ throw new UkSourceParseError(NESO_GENERATION_MIX_SOURCE_ID, `expected a half hour as YYYY-MM-DDThh:mm:00, got ${JSON.stringify(record.DATETIME)}`);
180
+ }
181
+ if (seen.has(record.DATETIME)) {
182
+ throw new UkSourceParseError(NESO_GENERATION_MIX_SOURCE_ID, `two rows for the half hour starting ${record.DATETIME}`);
183
+ }
184
+ seen.add(record.DATETIME);
185
+ if (record.GENERATION <= 0) {
186
+ throw new UkSourceParseError(NESO_GENERATION_MIX_SOURCE_ID, `the half hour starting ${record.DATETIME} generated ${String(record.GENERATION)} MW`);
187
+ }
188
+ if (record.CARBON_INTENSITY <= 0) {
189
+ throw new UkSourceParseError(NESO_GENERATION_MIX_SOURCE_ID, `the half hour starting ${record.DATETIME} carries no carbon intensity`);
190
+ }
191
+ return {
192
+ datetime: record.DATETIME,
193
+ totalMegawatts: record.GENERATION,
194
+ carbonIntensity: record.CARBON_INTENSITY,
195
+ lowCarbonMegawatts: record.LOW_CARBON,
196
+ fuels: NESO_GENERATION_FUELS.map((fuel) => ({ fuel, megawatts: record[fuel] })),
197
+ };
198
+ });
199
+ halfHours.sort((left, right) => left.datetime.localeCompare(right.datetime));
200
+ for (let index = 1; index < halfHours.length; index += 1) {
201
+ const previous = halfHours[index - 1];
202
+ const current = halfHours[index];
203
+ if (previous === undefined || current === undefined) {
204
+ continue;
205
+ }
206
+ const gap = Date.parse(current.datetime) - Date.parse(previous.datetime);
207
+ if (gap !== MILLISECONDS_PER_HALF_HOUR) {
208
+ throw new UkSourceParseError(NESO_GENERATION_MIX_SOURCE_ID, `the readings jump from ${previous.datetime} to ${current.datetime}`);
209
+ }
210
+ }
211
+ return summarizeNesoGenerationMix(halfHours);
212
+ }
213
+ /**
214
+ * Reads the most recent half hours of Great Britain's generation mix.
215
+ *
216
+ * @param options - how many half hours to ask for, and an optional fetch implementation
217
+ * @returns every half hour in the read window, oldest first, with its totals
218
+ */
219
+ export async function fetchNesoGenerationMixWindow(options = {}) {
220
+ const url = buildNesoGenerationMixUrl({
221
+ ...(options.halfHours === undefined ? {} : { halfHours: options.halfHours }),
222
+ ...(options.resourceId === undefined ? {} : { resourceId: options.resourceId }),
223
+ });
224
+ const response = await httpGet(NESO_GENERATION_MIX_SOURCE_ID, url, {
225
+ ...(options.fetchImpl === undefined ? {} : { fetchImpl: options.fetchImpl }),
226
+ });
227
+ return parseNesoGenerationMix(await response.json());
228
+ }
229
+ /** National Energy System Operator generation mix, half-hourly and keyless. */
230
+ export const nesoGenerationMixAdapter = {
231
+ id: NESO_GENERATION_MIX_SOURCE_ID,
232
+ name: 'National Energy System Operator generation mix',
233
+ auth: 'none',
234
+ description: 'Half-hourly Great Britain generation by fuel, with the cleanest and dirtiest half hours in the window.',
235
+ fetchLive: (options) => fetchNesoGenerationMixWindow({
236
+ ...(options?.fetchImpl === undefined ? {} : { fetchImpl: options.fetchImpl }),
237
+ }),
238
+ parse: parseNesoGenerationMix,
239
+ loadFixture: () => parseNesoGenerationMix(readFixtureJson('neso-generation-mix-2026-10-18.json')),
240
+ };
241
+ //# sourceMappingURL=nesoGenerationMix.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nesoGenerationMix.js","sourceRoot":"","sources":["../src/nesoGenerationMix.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAGpC;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,6BAA6B,GAAG,qBAAqB,CAAC;AAEnE,kCAAkC;AAClC,MAAM,CAAC,MAAM,6BAA6B,GAAG,yBAAyB,CAAC;AAEvE,wEAAwE;AACxE,MAAM,CAAC,MAAM,yBAAyB,GAAG,GAAG,6BAA6B,gCAAgC,CAAC;AAE1G,mFAAmF;AACnF,MAAM,CAAC,MAAM,+BAA+B,GAAG,sCAAsC,CAAC;AAEtF,0DAA0D;AAC1D,MAAM,CAAC,MAAM,sCAAsC,GAAG,GAAG,CAAC;AAE1D;;;;GAIG;AACH,MAAM,CAAC,MAAM,kCAAkC,GAAG,IAAI,CAAC;AAEvD;;;;GAIG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG;IACnC,KAAK;IACL,MAAM;IACN,SAAS;IACT,MAAM;IACN,UAAU;IACV,OAAO;IACP,SAAS;IACT,SAAS;IACT,OAAO;IACP,OAAO;IACP,SAAS;CACD,CAAC;AA6DX,yEAAyE;AACzE,6EAA6E;AAC7E,4DAA4D;AAC5D,MAAM,mCAAmC,GAAG,CAAC,CAAC,MAAM,CAAC;IACnD,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE;IACpB,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC;QACf,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE;QACjB,OAAO,EAAE,CAAC,CAAC,KAAK,CACd,CAAC,CAAC,MAAM,CAAC;YACP,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE;YACpB,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE;YACtB,gBAAgB,EAAE,CAAC,CAAC,MAAM,EAAE;YAC5B,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE;YACtB,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE;YACf,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;YAChB,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE;YACnB,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;YAChB,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE;YACpB,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE;YACjB,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE;YACnB,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE;YACnB,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE;YACjB,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE;YACjB,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE;SACpB,CAAC,CACH;KACF,CAAC;CACH,CAAC,CAAC;AAEH,8EAA8E;AAC9E,MAAM,qBAAqB,GAAG,sCAAsC,CAAC;AAErE,8EAA8E;AAC9E,MAAM,0BAA0B,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;AAElD,2CAA2C;AAC3C,SAAS,cAAc,CAAC,IAAY,EAAE,KAAa;IACjD,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,GAAG,KAAK,CAAC,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;AAChD,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,yBAAyB,CACvC,UAAuD,EAAE;IAEzD,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,sCAAsC,CAAC;IAC9E,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,SAAS,CAAC,IAAI,SAAS,GAAG,CAAC,EAAE,CAAC;QAClD,MAAM,IAAI,kBAAkB,CAC1B,6BAA6B,EAC7B,eAAe,MAAM,CAAC,SAAS,CAAC,iDAAiD,CAClF,CAAC;IACJ,CAAC;IACD,IAAI,SAAS,GAAG,kCAAkC,EAAE,CAAC;QACnD,MAAM,IAAI,kBAAkB,CAC1B,6BAA6B,EAC7B,8BAA8B,MAAM,CAAC,kCAAkC,CAAC,uBAAuB,CAChG,CAAC;IACJ,CAAC;IACD,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,IAAI,+BAA+B,CAAC;IACzE,MAAM,KAAK,GAAG,IAAI,eAAe,CAAC;QAChC,WAAW,EAAE,UAAU;QACvB,IAAI,EAAE,eAAe;QACrB,KAAK,EAAE,MAAM,CAAC,SAAS,CAAC;KACzB,CAAC,CAAC;IACH,OAAO,GAAG,yBAAyB,IAAI,KAAK,CAAC,QAAQ,EAAE,EAAE,CAAC;AAC5D,CAAC;AAED,wEAAwE;AACxE,SAAS,0BAA0B,CAAC,SAAmC;IACrE,MAAM,aAAa,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC;IACnC,MAAM,YAAY,GAAG,SAAS,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IACtC,IAAI,aAAa,KAAK,SAAS,IAAI,YAAY,KAAK,SAAS,EAAE,CAAC;QAC9D,MAAM,IAAI,kBAAkB,CAAC,6BAA6B,EAAE,gCAAgC,CAAC,CAAC;IAChG,CAAC;IAED,IAAI,cAAc,GAAG,CAAC,CAAC;IACvB,IAAI,oBAAoB,GAAG,CAAC,CAAC;IAC7B,IAAI,kBAAkB,GAAG,CAAC,CAAC;IAC3B,MAAM,eAAe,GAAG,IAAI,GAAG,CAC7B,qBAAqB,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAC/C,CAAC;IACF,MAAM,UAAU,GAAG,IAAI,GAAG,CACxB,qBAAqB,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,MAAM,CAAC,iBAAiB,CAAC,CAAC,CACtE,CAAC;IAEF,KAAK,MAAM,QAAQ,IAAI,SAAS,EAAE,CAAC;QACjC,cAAc,IAAI,QAAQ,CAAC,cAAc,CAAC;QAC1C,oBAAoB,IAAI,QAAQ,CAAC,eAAe,CAAC;QACjD,kBAAkB,IAAI,QAAQ,CAAC,kBAAkB,CAAC;QAClD,KAAK,MAAM,OAAO,IAAI,QAAQ,CAAC,KAAK,EAAE,CAAC;YACrC,eAAe,CAAC,GAAG,CACjB,OAAO,CAAC,IAAI,EACZ,CAAC,eAAe,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,OAAO,CAAC,SAAS,CAC7D,CAAC;YACF,UAAU,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC;QAC/F,CAAC;IACH,CAAC;IAED,MAAM,aAAa,GAAG,SAAS,CAAC,MAAM,CAAC;IACvC,MAAM,UAAU,GAAG,qBAAqB,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QACtD,IAAI;QACJ,gBAAgB,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,eAAe,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,aAAa,CAAC;QAC9E,aAAa,EAAE,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC;QACxC,YAAY,EAAE,cAAc,CAAC,eAAe,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,cAAc,CAAC;KAC7E,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,gBAAgB,GAAG,IAAI,CAAC,gBAAgB,CAAC,CAAC;IAE1E,MAAM,gBAAgB,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC,QAAQ,EAAE,QAAQ,EAAE,EAAE,CAC/D,QAAQ,CAAC,eAAe,GAAG,QAAQ,CAAC,eAAe,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAC1E,CAAC;IACF,MAAM,gBAAgB,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC,QAAQ,EAAE,QAAQ,EAAE,EAAE,CAC/D,QAAQ,CAAC,eAAe,GAAG,QAAQ,CAAC,eAAe,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAC1E,CAAC;IAEF,OAAO;QACL,aAAa;QACb,eAAe,EAAE,aAAa,CAAC,QAAQ;QACvC,cAAc,EAAE,YAAY,CAAC,QAAQ;QACrC,qBAAqB,EAAE,IAAI,CAAC,KAAK,CAAC,cAAc,GAAG,aAAa,CAAC;QACjE,sBAAsB,EAAE,IAAI,CAAC,KAAK,CAAC,oBAAoB,GAAG,aAAa,CAAC;QACxE,qBAAqB,EAAE,cAAc,CAAC,kBAAkB,EAAE,cAAc,CAAC;QACzE,UAAU;QACV,gBAAgB;QAChB,gBAAgB;QAChB,SAAS;KACV,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,sBAAsB,CAAC,OAAgB;IACrD,MAAM,MAAM,GAAG,mCAAmC,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;IACtE,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACpB,MAAM,IAAI,kBAAkB,CAAC,6BAA6B,EAAE,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IACpF,CAAC;IACD,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;QACzB,MAAM,IAAI,kBAAkB,CAAC,6BAA6B,EAAE,kCAAkC,CAAC,CAAC;IAClG,CAAC;IACD,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC;IAC3C,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,MAAM,IAAI,kBAAkB,CAC1B,6BAA6B,EAC7B,mCAAmC,CACpC,CAAC;IACJ,CAAC;IAED,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE;QACvC,IAAI,CAAC,qBAAqB,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC;YACjD,MAAM,IAAI,kBAAkB,CAC1B,6BAA6B,EAC7B,oDAAoD,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,CACtF,CAAC;QACJ,CAAC;QACD,IAAI,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC9B,MAAM,IAAI,kBAAkB,CAC1B,6BAA6B,EAC7B,uCAAuC,MAAM,CAAC,QAAQ,EAAE,CACzD,CAAC;QACJ,CAAC;QACD,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QAC1B,IAAI,MAAM,CAAC,UAAU,IAAI,CAAC,EAAE,CAAC;YAC3B,MAAM,IAAI,kBAAkB,CAC1B,6BAA6B,EAC7B,0BAA0B,MAAM,CAAC,QAAQ,cAAc,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,KAAK,CACtF,CAAC;QACJ,CAAC;QACD,IAAI,MAAM,CAAC,gBAAgB,IAAI,CAAC,EAAE,CAAC;YACjC,MAAM,IAAI,kBAAkB,CAC1B,6BAA6B,EAC7B,0BAA0B,MAAM,CAAC,QAAQ,8BAA8B,CACxE,CAAC;QACJ,CAAC;QACD,OAAO;YACL,QAAQ,EAAE,MAAM,CAAC,QAAQ;YACzB,cAAc,EAAE,MAAM,CAAC,UAAU;YACjC,eAAe,EAAE,MAAM,CAAC,gBAAgB;YACxC,kBAAkB,EAAE,MAAM,CAAC,UAAU;YACrC,KAAK,EAAE,qBAAqB,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;SAChF,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,SAAS,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,aAAa,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC;IAC7E,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,SAAS,CAAC,MAAM,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC;QACzD,MAAM,QAAQ,GAAG,SAAS,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC;QACtC,MAAM,OAAO,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC;QACjC,IAAI,QAAQ,KAAK,SAAS,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;YACpD,SAAS;QACX,CAAC;QACD,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;QACzE,IAAI,GAAG,KAAK,0BAA0B,EAAE,CAAC;YACvC,MAAM,IAAI,kBAAkB,CAC1B,6BAA6B,EAC7B,0BAA0B,QAAQ,CAAC,QAAQ,OAAO,OAAO,CAAC,QAAQ,EAAE,CACrE,CAAC;QACJ,CAAC;IACH,CAAC;IAED,OAAO,0BAA0B,CAAC,SAAS,CAAC,CAAC;AAC/C,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,4BAA4B,CAChD,UAII,EAAE;IAEN,MAAM,GAAG,GAAG,yBAAyB,CAAC;QACpC,GAAG,CAAC,OAAO,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,OAAO,CAAC,SAAS,EAAE,CAAC;QAC5E,GAAG,CAAC,OAAO,CAAC,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,OAAO,CAAC,UAAU,EAAE,CAAC;KAChF,CAAC,CAAC;IACH,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,6BAA6B,EAAE,GAAG,EAAE;QACjE,GAAG,CAAC,OAAO,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,OAAO,CAAC,SAAS,EAAE,CAAC;KAC7E,CAAC,CAAC;IACH,OAAO,sBAAsB,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;AACvD,CAAC;AAED,+EAA+E;AAC/E,MAAM,CAAC,MAAM,wBAAwB,GAA2C;IAC9E,EAAE,EAAE,6BAA6B;IACjC,IAAI,EAAE,gDAAgD;IACtD,IAAI,EAAE,MAAM;IACZ,WAAW,EACT,wGAAwG;IAC1G,SAAS,EAAE,CAAC,OAAO,EAAE,EAAE,CACrB,4BAA4B,CAAC;QAC3B,GAAG,CAAC,OAAO,EAAE,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,OAAO,CAAC,SAAS,EAAE,CAAC;KAC9E,CAAC;IACJ,KAAK,EAAE,sBAAsB;IAC7B,WAAW,EAAE,GAAG,EAAE,CAAC,sBAAsB,CAAC,eAAe,CAAC,qCAAqC,CAAC,CAAC;CAClG,CAAC"}
@@ -0,0 +1,43 @@
1
+ import type { UkDataAdapter } from './types.js';
2
+ /**
3
+ * The NHS Business Services Authority open data portal, served by CKAN.
4
+ * Keyless, published under the Open Government Licence v3.
5
+ */
6
+ export declare const NHSBSA_CKAN_SOURCE_ID = "nhsbsa-ckan";
7
+ /** The CKAN package_list call on the NHSBSA portal. */
8
+ export declare const NHSBSA_CKAN_PACKAGE_LIST_URL = "https://opendata.nhsbsa.net/api/3/action/package_list";
9
+ /** One dataset in the NHSBSA catalogue. */
10
+ export interface NhsbsaDataset {
11
+ /** CKAN package name, the identifier used in every other CKAN call. */
12
+ name: string;
13
+ }
14
+ /** Rolled-up view of the NHSBSA catalogue. */
15
+ export interface NhsbsaCkanSummary {
16
+ packageCount: number;
17
+ }
18
+ /**
19
+ * Parses an NHSBSA package_list payload.
20
+ *
21
+ * @param payload - the raw JSON body from the CKAN package_list call
22
+ * @returns one entry per dataset in the catalogue
23
+ */
24
+ export declare function parseNhsbsaCkanPackages(payload: unknown): NhsbsaDataset[];
25
+ /**
26
+ * Counts the NHSBSA catalogue.
27
+ *
28
+ * @param datasets - dataset names from {@link parseNhsbsaCkanPackages}
29
+ * @returns the number of datasets listed
30
+ */
31
+ export declare function summarizeNhsbsaCkanPackages(datasets: NhsbsaDataset[]): NhsbsaCkanSummary;
32
+ /**
33
+ * Lists every dataset in the NHSBSA catalogue.
34
+ *
35
+ * @param options - an optional fetch implementation
36
+ * @returns the package names the catalogue returns
37
+ */
38
+ export declare function fetchNhsbsaCkanPackages(options?: {
39
+ fetchImpl?: typeof globalThis.fetch;
40
+ }): Promise<NhsbsaDataset[]>;
41
+ /** NHS Business Services Authority open data catalogue, keyless. */
42
+ export declare const nhsbsaCkanAdapter: UkDataAdapter<NhsbsaDataset[]>;
43
+ //# sourceMappingURL=nhsbsaCkan.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nhsbsaCkan.d.ts","sourceRoot":"","sources":["../src/nhsbsaCkan.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhD;;;GAGG;AACH,eAAO,MAAM,qBAAqB,gBAAgB,CAAC;AAEnD,uDAAuD;AACvD,eAAO,MAAM,4BAA4B,0DAA0D,CAAC;AAEpG,2CAA2C;AAC3C,MAAM,WAAW,aAAa;IAC5B,uEAAuE;IACvE,IAAI,EAAE,MAAM,CAAC;CACd;AAED,8CAA8C;AAC9C,MAAM,WAAW,iBAAiB;IAChC,YAAY,EAAE,MAAM,CAAC;CACtB;AAOD;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,OAAO,GAAG,aAAa,EAAE,CAMzE;AAED;;;;;GAKG;AACH,wBAAgB,2BAA2B,CAAC,QAAQ,EAAE,aAAa,EAAE,GAAG,iBAAiB,CAExF;AAED;;;;;GAKG;AACH,wBAAsB,uBAAuB,CAC3C,OAAO,GAAE;IAAE,SAAS,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAA;CAAO,GACpD,OAAO,CAAC,aAAa,EAAE,CAAC,CAI1B;AAED,oEAAoE;AACpE,eAAO,MAAM,iBAAiB,EAAE,aAAa,CAAC,aAAa,EAAE,CAW5D,CAAC"}
@@ -0,0 +1,59 @@
1
+ import { z } from 'zod';
2
+ import { UkSourceParseError } from './errors.js';
3
+ import { httpGet } from './http.js';
4
+ import { readFixtureJson } from './fixtures.js';
5
+ /**
6
+ * The NHS Business Services Authority open data portal, served by CKAN.
7
+ * Keyless, published under the Open Government Licence v3.
8
+ */
9
+ export const NHSBSA_CKAN_SOURCE_ID = 'nhsbsa-ckan';
10
+ /** The CKAN package_list call on the NHSBSA portal. */
11
+ export const NHSBSA_CKAN_PACKAGE_LIST_URL = 'https://opendata.nhsbsa.net/api/3/action/package_list';
12
+ const NHSBSA_CKAN_PACKAGE_LIST_SCHEMA = z.object({
13
+ success: z.boolean(),
14
+ result: z.array(z.string()),
15
+ });
16
+ /**
17
+ * Parses an NHSBSA package_list payload.
18
+ *
19
+ * @param payload - the raw JSON body from the CKAN package_list call
20
+ * @returns one entry per dataset in the catalogue
21
+ */
22
+ export function parseNhsbsaCkanPackages(payload) {
23
+ const parsed = NHSBSA_CKAN_PACKAGE_LIST_SCHEMA.safeParse(payload);
24
+ if (!parsed.success) {
25
+ throw new UkSourceParseError('nhsbsa-ckan', parsed.error.message);
26
+ }
27
+ return parsed.data.result.map((name) => ({ name }));
28
+ }
29
+ /**
30
+ * Counts the NHSBSA catalogue.
31
+ *
32
+ * @param datasets - dataset names from {@link parseNhsbsaCkanPackages}
33
+ * @returns the number of datasets listed
34
+ */
35
+ export function summarizeNhsbsaCkanPackages(datasets) {
36
+ return { packageCount: datasets.length };
37
+ }
38
+ /**
39
+ * Lists every dataset in the NHSBSA catalogue.
40
+ *
41
+ * @param options - an optional fetch implementation
42
+ * @returns the package names the catalogue returns
43
+ */
44
+ export async function fetchNhsbsaCkanPackages(options = {}) {
45
+ const { fetchImpl = globalThis.fetch } = options;
46
+ const response = await httpGet('nhsbsa-ckan', NHSBSA_CKAN_PACKAGE_LIST_URL, { fetchImpl });
47
+ return parseNhsbsaCkanPackages(await response.json());
48
+ }
49
+ /** NHS Business Services Authority open data catalogue, keyless. */
50
+ export const nhsbsaCkanAdapter = {
51
+ id: NHSBSA_CKAN_SOURCE_ID,
52
+ name: 'NHS Business Services Authority open data catalogue',
53
+ auth: 'none',
54
+ description: 'Every dataset the NHS Business Services Authority publishes on its CKAN portal.',
55
+ fetchLive: (options) => fetchNhsbsaCkanPackages(options?.fetchImpl === undefined ? {} : { fetchImpl: options.fetchImpl }),
56
+ parse: parseNhsbsaCkanPackages,
57
+ loadFixture: () => parseNhsbsaCkanPackages(readFixtureJson('nhsbsa-ckan-2026-10-05.json')),
58
+ };
59
+ //# sourceMappingURL=nhsbsaCkan.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nhsbsaCkan.js","sourceRoot":"","sources":["../src/nhsbsaCkan.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAGhD;;;GAGG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,aAAa,CAAC;AAEnD,uDAAuD;AACvD,MAAM,CAAC,MAAM,4BAA4B,GAAG,uDAAuD,CAAC;AAapG,MAAM,+BAA+B,GAAG,CAAC,CAAC,MAAM,CAAC;IAC/C,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE;IACpB,MAAM,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;CAC5B,CAAC,CAAC;AAEH;;;;;GAKG;AACH,MAAM,UAAU,uBAAuB,CAAC,OAAgB;IACtD,MAAM,MAAM,GAAG,+BAA+B,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;IAClE,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACpB,MAAM,IAAI,kBAAkB,CAAC,aAAa,EAAE,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IACpE,CAAC;IACD,OAAO,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;AACtD,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,2BAA2B,CAAC,QAAyB;IACnE,OAAO,EAAE,YAAY,EAAE,QAAQ,CAAC,MAAM,EAAE,CAAC;AAC3C,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAC3C,UAAmD,EAAE;IAErD,MAAM,EAAE,SAAS,GAAG,UAAU,CAAC,KAAK,EAAE,GAAG,OAAO,CAAC;IACjD,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,aAAa,EAAE,4BAA4B,EAAE,EAAE,SAAS,EAAE,CAAC,CAAC;IAC3F,OAAO,uBAAuB,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;AACxD,CAAC;AAED,oEAAoE;AACpE,MAAM,CAAC,MAAM,iBAAiB,GAAmC;IAC/D,EAAE,EAAE,qBAAqB;IACzB,IAAI,EAAE,qDAAqD;IAC3D,IAAI,EAAE,MAAM;IACZ,WAAW,EAAE,iFAAiF;IAC9F,SAAS,EAAE,CAAC,OAAO,EAAE,EAAE,CACrB,uBAAuB,CACrB,OAAO,EAAE,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,OAAO,CAAC,SAAS,EAAE,CACzE;IACH,KAAK,EAAE,uBAAuB;IAC9B,WAAW,EAAE,GAAG,EAAE,CAAC,uBAAuB,CAAC,eAAe,CAAC,6BAA6B,CAAC,CAAC;CAC3F,CAAC"}
@@ -0,0 +1,71 @@
1
+ import type { UkDataAdapter } from './types.js';
2
+ /**
3
+ * Nomis, the Office for National Statistics' online labour-market database,
4
+ * run with the University of Durham. Keyless, published under the Open
5
+ * Government Licence v3.
6
+ */
7
+ export declare const NOMIS_SOURCE_ID = "nomis";
8
+ /**
9
+ * The SDMX dataset definitions catalogue. It carries one definition per
10
+ * dataset, with the dimensions and annotations behind it.
11
+ */
12
+ export declare const NOMIS_DATASET_DEFINITIONS_URL = "https://www.nomisweb.co.uk/api/v01/dataset/def.sdmx.json";
13
+ /** Annotation titles this adapter reads out of each dataset definition. */
14
+ export declare const NOMIS_STATUS_ANNOTATION = "Status";
15
+ export declare const NOMIS_KEYWORDS_ANNOTATION = "Keywords";
16
+ export declare const NOMIS_UNITS_ANNOTATION = "Units";
17
+ export declare const NOMIS_LAST_UPDATED_ANNOTATION = "LastUpdated";
18
+ /** One dataset definition from the Nomis SDMX catalogue. */
19
+ export interface NomisDatasetDefinition {
20
+ /** Nomis dataset id, such as NM_1_1. */
21
+ id: string;
22
+ name: string;
23
+ agencyId: string;
24
+ version: number;
25
+ /** The API's own URI slug for the dataset. */
26
+ uri: string;
27
+ /** Maintenance status, such as "Current (being actively updated)". */
28
+ status: string;
29
+ /** Comma-separated keywords the catalogue tags the dataset with. */
30
+ keywords: string;
31
+ /** The units the dataset counts in, or "" when the catalogue leaves them out. */
32
+ units: string;
33
+ /** The catalogue's own last-updated stamp, or "" when it leaves it out. */
34
+ lastUpdated: string;
35
+ }
36
+ /** Dataset definitions counted by their catalogue status. */
37
+ export interface NomisStatusCount {
38
+ status: string;
39
+ datasetCount: number;
40
+ }
41
+ /** Rolled-up view of the Nomis dataset catalogue. */
42
+ export interface NomisCatalogSummary {
43
+ datasetCount: number;
44
+ statusCounts: NomisStatusCount[];
45
+ }
46
+ /**
47
+ * Parses a Nomis SDMX dataset definitions payload.
48
+ *
49
+ * @param payload - the raw JSON body from the definitions catalogue
50
+ * @returns one definition per dataset the catalogue lists
51
+ */
52
+ export declare function parseNomisDatasetDefinitions(payload: unknown): NomisDatasetDefinition[];
53
+ /**
54
+ * Counts the catalogue by dataset status.
55
+ *
56
+ * @param definitions - definitions from {@link parseNomisDatasetDefinitions}
57
+ * @returns the dataset count and a per-status breakdown, most datasets first
58
+ */
59
+ export declare function summarizeNomisDatasetDefinitions(definitions: NomisDatasetDefinition[]): NomisCatalogSummary;
60
+ /**
61
+ * Lists every dataset definition in the Nomis SDMX catalogue.
62
+ *
63
+ * @param options - an optional fetch implementation
64
+ * @returns the definitions the catalogue returns
65
+ */
66
+ export declare function fetchNomisDatasetDefinitions(options?: {
67
+ fetchImpl?: typeof globalThis.fetch;
68
+ }): Promise<NomisDatasetDefinition[]>;
69
+ /** Nomis SDMX dataset definitions, keyless. */
70
+ export declare const nomisAdapter: UkDataAdapter<NomisDatasetDefinition[]>;
71
+ //# sourceMappingURL=nomis.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nomis.d.ts","sourceRoot":"","sources":["../src/nomis.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhD;;;;GAIG;AACH,eAAO,MAAM,eAAe,UAAU,CAAC;AAEvC;;;GAGG;AACH,eAAO,MAAM,6BAA6B,6DACkB,CAAC;AAE7D,2EAA2E;AAC3E,eAAO,MAAM,uBAAuB,WAAW,CAAC;AAChD,eAAO,MAAM,yBAAyB,aAAa,CAAC;AACpD,eAAO,MAAM,sBAAsB,UAAU,CAAC;AAC9C,eAAO,MAAM,6BAA6B,gBAAgB,CAAC;AAE3D,4DAA4D;AAC5D,MAAM,WAAW,sBAAsB;IACrC,wCAAwC;IACxC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,8CAA8C;IAC9C,GAAG,EAAE,MAAM,CAAC;IACZ,sEAAsE;IACtE,MAAM,EAAE,MAAM,CAAC;IACf,oEAAoE;IACpE,QAAQ,EAAE,MAAM,CAAC;IACjB,iFAAiF;IACjF,KAAK,EAAE,MAAM,CAAC;IACd,2EAA2E;IAC3E,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,6DAA6D;AAC7D,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,qDAAqD;AACrD,MAAM,WAAW,mBAAmB;IAClC,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,gBAAgB,EAAE,CAAC;CAClC;AAiDD;;;;;GAKG;AACH,wBAAgB,4BAA4B,CAAC,OAAO,EAAE,OAAO,GAAG,sBAAsB,EAAE,CAoBvF;AAED;;;;;GAKG;AACH,wBAAgB,gCAAgC,CAC9C,WAAW,EAAE,sBAAsB,EAAE,GACpC,mBAAmB,CAYrB;AAED;;;;;GAKG;AACH,wBAAsB,4BAA4B,CAChD,OAAO,GAAE;IAAE,SAAS,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAA;CAAO,GACpD,OAAO,CAAC,sBAAsB,EAAE,CAAC,CAInC;AAED,+CAA+C;AAC/C,eAAO,MAAM,YAAY,EAAE,aAAa,CAAC,sBAAsB,EAAE,CAWhE,CAAC"}