@cyanheads/eurostat-mcp-server 0.11.1 → 0.11.3
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/AGENTS.md +6 -4
- package/CLAUDE.md +6 -4
- package/README.md +20 -17
- package/changelog/0.11.x/0.11.2.md +27 -0
- package/changelog/0.11.x/0.11.3.md +45 -0
- package/changelog/template.md +10 -3
- package/dist/config/server-config.js +1 -1
- package/dist/config/server-config.js.map +1 -1
- package/dist/mcp-server/resources/definitions/eurostat-dataset.resource.d.ts +14 -1
- package/dist/mcp-server/resources/definitions/eurostat-dataset.resource.d.ts.map +1 -1
- package/dist/mcp-server/resources/definitions/eurostat-dataset.resource.js +17 -0
- package/dist/mcp-server/resources/definitions/eurostat-dataset.resource.js.map +1 -1
- package/dist/mcp-server/tools/definitions/eurostat-browse-themes.tool.d.ts +1 -1
- package/dist/mcp-server/tools/definitions/eurostat-browse-themes.tool.js +2 -2
- package/dist/mcp-server/tools/definitions/eurostat-browse-themes.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/eurostat-dataframe-describe.tool.js +2 -2
- package/dist/mcp-server/tools/definitions/eurostat-dataframe-describe.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/eurostat-dataframe-drop.tool.js +1 -1
- package/dist/mcp-server/tools/definitions/eurostat-dataframe-drop.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/eurostat-dataframe-query.tool.js +1 -1
- package/dist/mcp-server/tools/definitions/eurostat-dataframe-query.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/eurostat-download-dataset.tool.d.ts +26 -12
- package/dist/mcp-server/tools/definitions/eurostat-download-dataset.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/eurostat-download-dataset.tool.js +181 -47
- package/dist/mcp-server/tools/definitions/eurostat-download-dataset.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/eurostat-get-dataset-info.tool.d.ts +1 -1
- package/dist/mcp-server/tools/definitions/eurostat-get-dataset-info.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/eurostat-get-dataset-info.tool.js +11 -2
- package/dist/mcp-server/tools/definitions/eurostat-get-dataset-info.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/eurostat-get-dimension-values.tool.d.ts +8 -6
- package/dist/mcp-server/tools/definitions/eurostat-get-dimension-values.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/eurostat-get-dimension-values.tool.js +149 -54
- package/dist/mcp-server/tools/definitions/eurostat-get-dimension-values.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/eurostat-query-dataset.tool.d.ts +22 -8
- package/dist/mcp-server/tools/definitions/eurostat-query-dataset.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/eurostat-query-dataset.tool.js +215 -43
- package/dist/mcp-server/tools/definitions/eurostat-query-dataset.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/eurostat-search-datasets.tool.d.ts +16 -6
- package/dist/mcp-server/tools/definitions/eurostat-search-datasets.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/eurostat-search-datasets.tool.js +67 -16
- package/dist/mcp-server/tools/definitions/eurostat-search-datasets.tool.js.map +1 -1
- package/dist/mcp-server/tools/narrowing-advice.d.ts +64 -3
- package/dist/mcp-server/tools/narrowing-advice.d.ts.map +1 -1
- package/dist/mcp-server/tools/narrowing-advice.js +160 -4
- package/dist/mcp-server/tools/narrowing-advice.js.map +1 -1
- package/dist/mcp-server/tools/notice-text.d.ts +18 -0
- package/dist/mcp-server/tools/notice-text.d.ts.map +1 -0
- package/dist/mcp-server/tools/notice-text.js +40 -0
- package/dist/mcp-server/tools/notice-text.js.map +1 -0
- package/dist/mcp-server/tools/value-list-paging.d.ts +11 -1
- package/dist/mcp-server/tools/value-list-paging.d.ts.map +1 -1
- package/dist/mcp-server/tools/value-list-paging.js +13 -1
- package/dist/mcp-server/tools/value-list-paging.js.map +1 -1
- package/dist/services/eurostat-bulk/eurostat-bulk-service.d.ts +7 -5
- package/dist/services/eurostat-bulk/eurostat-bulk-service.d.ts.map +1 -1
- package/dist/services/eurostat-bulk/eurostat-bulk-service.js +145 -40
- package/dist/services/eurostat-bulk/eurostat-bulk-service.js.map +1 -1
- package/dist/services/eurostat-bulk/types.d.ts +44 -4
- package/dist/services/eurostat-bulk/types.d.ts.map +1 -1
- package/dist/services/eurostat-bulk/types.js.map +1 -1
- package/dist/services/eurostat-catalogue/dimension-value-index.d.ts +81 -0
- package/dist/services/eurostat-catalogue/dimension-value-index.d.ts.map +1 -0
- package/dist/services/eurostat-catalogue/dimension-value-index.js +410 -0
- package/dist/services/eurostat-catalogue/dimension-value-index.js.map +1 -0
- package/dist/services/eurostat-catalogue/eurostat-catalogue-service.d.ts +100 -14
- package/dist/services/eurostat-catalogue/eurostat-catalogue-service.d.ts.map +1 -1
- package/dist/services/eurostat-catalogue/eurostat-catalogue-service.js +359 -90
- package/dist/services/eurostat-catalogue/eurostat-catalogue-service.js.map +1 -1
- package/dist/services/eurostat-catalogue/search-text.d.ts +12 -0
- package/dist/services/eurostat-catalogue/search-text.d.ts.map +1 -0
- package/dist/services/eurostat-catalogue/search-text.js +31 -0
- package/dist/services/eurostat-catalogue/search-text.js.map +1 -0
- package/dist/services/eurostat-catalogue/types.d.ts +11 -0
- package/dist/services/eurostat-catalogue/types.d.ts.map +1 -1
- package/dist/services/eurostat-codelists.d.ts +2 -1
- package/dist/services/eurostat-codelists.d.ts.map +1 -1
- package/dist/services/eurostat-codelists.js +2 -1
- package/dist/services/eurostat-codelists.js.map +1 -1
- package/dist/services/eurostat-data/eurostat-data-service.d.ts +30 -2
- package/dist/services/eurostat-data/eurostat-data-service.d.ts.map +1 -1
- package/dist/services/eurostat-data/eurostat-data-service.js +209 -19
- package/dist/services/eurostat-data/eurostat-data-service.js.map +1 -1
- package/dist/services/eurostat-data/sdmx-metadata.d.ts.map +1 -1
- package/dist/services/eurostat-data/sdmx-metadata.js +4 -20
- package/dist/services/eurostat-data/sdmx-metadata.js.map +1 -1
- package/dist/services/eurostat-data/types.d.ts +108 -4
- package/dist/services/eurostat-data/types.d.ts.map +1 -1
- package/dist/services/eurostat-data/types.js.map +1 -1
- package/dist/services/eurostat-filters.d.ts +18 -6
- package/dist/services/eurostat-filters.d.ts.map +1 -1
- package/dist/services/eurostat-filters.js +24 -7
- package/dist/services/eurostat-filters.js.map +1 -1
- package/dist/services/eurostat-periods.d.ts +54 -1
- package/dist/services/eurostat-periods.d.ts.map +1 -1
- package/dist/services/eurostat-periods.js +101 -1
- package/dist/services/eurostat-periods.js.map +1 -1
- package/dist/services/xml-entities.d.ts +13 -0
- package/dist/services/xml-entities.d.ts.map +1 -0
- package/dist/services/xml-entities.js +31 -0
- package/dist/services/xml-entities.js.map +1 -0
- package/package.json +7 -7
- package/server.json +5 -5
- package/skills/eurostat-guide/SKILL.md +6 -5
- package/skills/eurostat-guide/references/sql-analysis.md +21 -5
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview The dimension value index search reads when a dataset's own text lacks a
|
|
3
|
+
* query word: the codes Eurostat's metabase lists for each dataset (`time` excluded), each
|
|
4
|
+
* labelled in English from the global codelists. Both downloads are gzip files read as
|
|
5
|
+
* streams, never held whole; the index keeps joined strings and typed arrays.
|
|
6
|
+
* @module services/eurostat-catalogue/dimension-value-index
|
|
7
|
+
*/
|
|
8
|
+
import { fetchWithTimeout } from '@cyanheads/mcp-ts-core/utils';
|
|
9
|
+
import { getServerConfig } from '../../config/server-config.js';
|
|
10
|
+
import { decodeXmlEntities } from '../xml-entities.js';
|
|
11
|
+
import { continuesWord, foldSearchText } from './search-text.js';
|
|
12
|
+
/** The period dimension, listed for every dataset; its codes are dates, not vocabulary. */
|
|
13
|
+
const TIME_DIMENSION = 'time';
|
|
14
|
+
/** Joins labels and codes. A search word holds no whitespace, so no match spans two labels. */
|
|
15
|
+
const SEPARATOR = '\n';
|
|
16
|
+
/**
|
|
17
|
+
* A codelist's opening tag, a code's opening tag, an English name, and a code's closing tag.
|
|
18
|
+
* `<s:Code\b` does not match `<s:Codelist`.
|
|
19
|
+
*/
|
|
20
|
+
const CODELIST_TOKEN = /<s:Codelist\b[^>]*?\sid="([^"]*)"|<s:Code\b[^>]*?\sid="([^"]*)"|<c:Name xml:lang="en">([^<]*)<\/c:Name>|<\/s:Code>/g;
|
|
21
|
+
const CODE_CLOSE = '</s:Code>';
|
|
22
|
+
/** The codelist dump's root element closes the document; a stream without it was cut short. */
|
|
23
|
+
const DOCUMENT_CLOSE = '</m:Structure>';
|
|
24
|
+
/**
|
|
25
|
+
* A copy of `text` in a new string. A string cut from a decoded chunk keeps the whole chunk
|
|
26
|
+
* alive, and on JavaScriptCore so does a concatenate-then-slice copy, which slices the
|
|
27
|
+
* original again; a join writes new characters on every runtime.
|
|
28
|
+
*/
|
|
29
|
+
function copyOf(text) {
|
|
30
|
+
return [text, ''].join(SEPARATOR).slice(0, -SEPARATOR.length);
|
|
31
|
+
}
|
|
32
|
+
/** Offsets of each part joined by {@link SEPARATOR}, plus one past the end. */
|
|
33
|
+
function offsetsOf(parts) {
|
|
34
|
+
const starts = new Int32Array(parts.length + 1);
|
|
35
|
+
for (let i = 0; i < parts.length; i++) {
|
|
36
|
+
starts[i + 1] = starts[i] + parts[i].length + SEPARATOR.length;
|
|
37
|
+
}
|
|
38
|
+
return starts;
|
|
39
|
+
}
|
|
40
|
+
/** The labels of the codes Eurostat's metabase lists per dataset, searchable at word starts. */
|
|
41
|
+
export class DimensionValueIndex {
|
|
42
|
+
/** Epoch ms when this index finished loading: its age, and the snapshot a cursor binds. */
|
|
43
|
+
loadedAt;
|
|
44
|
+
parts;
|
|
45
|
+
constructor(parts, loadedAt) {
|
|
46
|
+
this.parts = parts;
|
|
47
|
+
this.loadedAt = loadedAt;
|
|
48
|
+
}
|
|
49
|
+
/** Dataset, value, and label counts, for logging. */
|
|
50
|
+
get size() {
|
|
51
|
+
return {
|
|
52
|
+
datasets: this.parts.datasetIds.size,
|
|
53
|
+
labels: this.parts.labelStart.length - 1,
|
|
54
|
+
values: this.parts.valueLabel.length,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
/** The id of a dataset the metabase lists, or undefined for one it does not. */
|
|
58
|
+
datasetOf(code) {
|
|
59
|
+
return this.parts.datasetIds.get(code);
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Flags, per label id, whether `word` occurs in the folded label with no letter or digit
|
|
63
|
+
* right before it; undefined when no label carries it. One scan of the joined labels;
|
|
64
|
+
* past a label's first match the scan resumes at the next label.
|
|
65
|
+
*/
|
|
66
|
+
labelsAtWordStart(word) {
|
|
67
|
+
const { searchStart, searchText } = this.parts;
|
|
68
|
+
let flags;
|
|
69
|
+
let at = searchText.indexOf(word);
|
|
70
|
+
while (at !== -1) {
|
|
71
|
+
if (continuesWord(searchText, at)) {
|
|
72
|
+
at = searchText.indexOf(word, at + 1);
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
const label = this.labelAt(at);
|
|
76
|
+
flags ??= new Uint8Array(searchStart.length - 1);
|
|
77
|
+
flags[label] = 1;
|
|
78
|
+
at = searchText.indexOf(word, searchStart[label + 1]);
|
|
79
|
+
}
|
|
80
|
+
return flags;
|
|
81
|
+
}
|
|
82
|
+
/** Whether some value of `dataset` has a label `flags` marks. */
|
|
83
|
+
hasValue(dataset, flags) {
|
|
84
|
+
return this.someValue(dataset, (label) => flags[label] === 1);
|
|
85
|
+
}
|
|
86
|
+
/** Whether one value of `dataset` has a label every one of `flagSets` marks. */
|
|
87
|
+
hasValueMarkedByAll(dataset, flagSets) {
|
|
88
|
+
return this.someValue(dataset, (label) => flagSets.every((flags) => flags[label] === 1));
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* The values of `dataset` whose label any of `flagSets` marks: those marked by the most
|
|
92
|
+
* flag sets first, so a value carrying every word leads the ones carrying one of them,
|
|
93
|
+
* then metabase order.
|
|
94
|
+
*/
|
|
95
|
+
valuesMarked(dataset, flagSets) {
|
|
96
|
+
const { codeStart, codeText, datasetStart, datasetValues, dimensions, labelStart, labelText } = this.parts;
|
|
97
|
+
const values = [];
|
|
98
|
+
for (let at = datasetStart[dataset]; at < datasetStart[dataset + 1]; at++) {
|
|
99
|
+
const value = datasetValues[at];
|
|
100
|
+
const label = this.parts.valueLabel[value];
|
|
101
|
+
const marked = label < 0 ? 0 : flagSets.filter((flags) => flags[label] === 1).length;
|
|
102
|
+
if (marked === 0)
|
|
103
|
+
continue;
|
|
104
|
+
values.push({
|
|
105
|
+
marked,
|
|
106
|
+
value: {
|
|
107
|
+
dimension: dimensions[this.parts.valueDimension[value]],
|
|
108
|
+
code: codeText.slice(codeStart[value], codeStart[value + 1] - 1),
|
|
109
|
+
label: labelText.slice(labelStart[label], labelStart[label + 1] - 1),
|
|
110
|
+
},
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
return values.sort((a, b) => b.marked - a.marked).map(({ value }) => value);
|
|
114
|
+
}
|
|
115
|
+
someValue(dataset, test) {
|
|
116
|
+
const { datasetStart, datasetValues, valueLabel } = this.parts;
|
|
117
|
+
for (let at = datasetStart[dataset]; at < datasetStart[dataset + 1]; at++) {
|
|
118
|
+
const label = valueLabel[datasetValues[at]];
|
|
119
|
+
if (label >= 0 && test(label))
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
/** The label whose span of the joined search text holds offset `at`. */
|
|
125
|
+
labelAt(at) {
|
|
126
|
+
const { searchStart } = this.parts;
|
|
127
|
+
let low = 0;
|
|
128
|
+
let high = searchStart.length - 2;
|
|
129
|
+
while (low < high) {
|
|
130
|
+
const mid = (low + high + 1) >> 1;
|
|
131
|
+
if (searchStart[mid] <= at)
|
|
132
|
+
low = mid;
|
|
133
|
+
else
|
|
134
|
+
high = mid - 1;
|
|
135
|
+
}
|
|
136
|
+
return low;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Text of a stream in pieces that each end at a boundary, so no token spans two pieces. Only
|
|
141
|
+
* the newest chunk is searched for a boundary, so a long run without one costs its length.
|
|
142
|
+
*/
|
|
143
|
+
class BoundaryPieces {
|
|
144
|
+
pending = [];
|
|
145
|
+
boundary;
|
|
146
|
+
constructor(boundary) {
|
|
147
|
+
this.boundary = boundary;
|
|
148
|
+
}
|
|
149
|
+
/** The text up to the last boundary in `chunk`, or undefined while `chunk` holds none. */
|
|
150
|
+
cut(chunk) {
|
|
151
|
+
const at = chunk.lastIndexOf(this.boundary);
|
|
152
|
+
if (at === -1) {
|
|
153
|
+
this.pending.push(chunk);
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
const end = at + this.boundary.length;
|
|
157
|
+
this.pending.push(chunk.slice(0, end));
|
|
158
|
+
const piece = this.pending.join('');
|
|
159
|
+
this.pending = [chunk.slice(end)];
|
|
160
|
+
return piece;
|
|
161
|
+
}
|
|
162
|
+
/** The text after the last boundary. */
|
|
163
|
+
rest() {
|
|
164
|
+
const rest = this.pending.join('');
|
|
165
|
+
this.pending = [];
|
|
166
|
+
return rest;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Reads `dataset<TAB>dimension<TAB>code` lines as they stream in, `time` rows skipped. Every
|
|
171
|
+
* string it keeps is a copy, so no decoded chunk outlives its turn.
|
|
172
|
+
*/
|
|
173
|
+
class MetabaseReader {
|
|
174
|
+
line = 0;
|
|
175
|
+
lines = new BoundaryPieces('\n');
|
|
176
|
+
table = {
|
|
177
|
+
datasetIds: new Map(),
|
|
178
|
+
dimensionIds: new Map(),
|
|
179
|
+
dimensions: [],
|
|
180
|
+
rowDatasets: [],
|
|
181
|
+
rowValues: [],
|
|
182
|
+
valueCodes: [],
|
|
183
|
+
valueDimensions: [],
|
|
184
|
+
valueIds: new Map(),
|
|
185
|
+
};
|
|
186
|
+
read(chunk) {
|
|
187
|
+
const text = this.lines.cut(chunk);
|
|
188
|
+
if (text !== undefined)
|
|
189
|
+
for (const line of text.slice(0, -1).split('\n'))
|
|
190
|
+
this.add(line);
|
|
191
|
+
}
|
|
192
|
+
/** The table read. Throws when the metabase listed no dataset: a broken download, not an empty catalogue. */
|
|
193
|
+
finish() {
|
|
194
|
+
const rest = this.lines.rest();
|
|
195
|
+
if (rest)
|
|
196
|
+
this.add(rest);
|
|
197
|
+
if (this.table.datasetIds.size === 0)
|
|
198
|
+
throw new Error('The metabase listed no dataset dimension values');
|
|
199
|
+
return this.table;
|
|
200
|
+
}
|
|
201
|
+
add(text) {
|
|
202
|
+
this.line += 1;
|
|
203
|
+
const line = text.endsWith('\r') ? text.slice(0, -1) : text;
|
|
204
|
+
if (!line)
|
|
205
|
+
return;
|
|
206
|
+
const fields = line.split('\t');
|
|
207
|
+
const [dataset, dimension, code] = fields;
|
|
208
|
+
if (fields.length !== 3 || !dataset || !dimension || code === undefined) {
|
|
209
|
+
throw new Error(`Metabase line ${this.line} is not a dataset, dimension and code separated by tabs`);
|
|
210
|
+
}
|
|
211
|
+
if (dimension === TIME_DIMENSION)
|
|
212
|
+
return;
|
|
213
|
+
const table = this.table;
|
|
214
|
+
let datasetId = table.datasetIds.get(dataset);
|
|
215
|
+
if (datasetId === undefined) {
|
|
216
|
+
datasetId = table.datasetIds.size;
|
|
217
|
+
table.datasetIds.set(copyOf(dataset), datasetId);
|
|
218
|
+
}
|
|
219
|
+
let dimensionId = table.dimensionIds.get(dimension);
|
|
220
|
+
if (dimensionId === undefined) {
|
|
221
|
+
dimensionId = table.dimensions.length;
|
|
222
|
+
table.dimensions.push(copyOf(dimension));
|
|
223
|
+
table.dimensionIds.set(table.dimensions[dimensionId], dimensionId);
|
|
224
|
+
}
|
|
225
|
+
const key = `${dimensionId}\t${code}`;
|
|
226
|
+
let valueId = table.valueIds.get(key);
|
|
227
|
+
if (valueId === undefined) {
|
|
228
|
+
valueId = table.valueCodes.length;
|
|
229
|
+
table.valueIds.set(copyOf(key), valueId);
|
|
230
|
+
table.valueCodes.push(copyOf(code));
|
|
231
|
+
table.valueDimensions.push(dimensionId);
|
|
232
|
+
}
|
|
233
|
+
table.rowDatasets.push(datasetId);
|
|
234
|
+
table.rowValues.push(valueId);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Reads the codelist dump as it streams in, keeping the first English name of each code the
|
|
239
|
+
* metabase lists, from the codelist named after its dimension in upper case.
|
|
240
|
+
*/
|
|
241
|
+
class CodelistReader {
|
|
242
|
+
code;
|
|
243
|
+
codelistDimensions;
|
|
244
|
+
dimensionId;
|
|
245
|
+
labelled = 0;
|
|
246
|
+
labels;
|
|
247
|
+
/** Whether the open code, or the codelist before its first code, has had its name. */
|
|
248
|
+
named = true;
|
|
249
|
+
pieces = new BoundaryPieces(CODE_CLOSE);
|
|
250
|
+
table;
|
|
251
|
+
constructor(table) {
|
|
252
|
+
this.table = table;
|
|
253
|
+
this.labels = new Array(table.valueCodes.length);
|
|
254
|
+
this.codelistDimensions = new Map(table.dimensions.map((dimension, id) => [dimension.toUpperCase(), id]));
|
|
255
|
+
}
|
|
256
|
+
read(chunk) {
|
|
257
|
+
const piece = this.pieces.cut(chunk);
|
|
258
|
+
if (piece !== undefined)
|
|
259
|
+
this.scan(piece);
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* The index, from the metabase table and the labels read. Throws when the dump stops before
|
|
263
|
+
* its root element closes, or labels none of the metabase's codes.
|
|
264
|
+
*/
|
|
265
|
+
finish() {
|
|
266
|
+
const rest = this.pieces.rest();
|
|
267
|
+
this.scan(rest);
|
|
268
|
+
if (!rest.includes(DOCUMENT_CLOSE)) {
|
|
269
|
+
throw new Error('The codelist dump ended before its root element closed');
|
|
270
|
+
}
|
|
271
|
+
if (this.labelled === 0)
|
|
272
|
+
throw new Error('The codelists labelled none of the metabase codes');
|
|
273
|
+
return assembleIndex(this.table, this.labels);
|
|
274
|
+
}
|
|
275
|
+
scan(piece) {
|
|
276
|
+
CODELIST_TOKEN.lastIndex = 0;
|
|
277
|
+
for (let token = CODELIST_TOKEN.exec(piece); token; token = CODELIST_TOKEN.exec(piece)) {
|
|
278
|
+
const [, codelistId, codeId, name] = token;
|
|
279
|
+
if (codelistId !== undefined) {
|
|
280
|
+
this.dimensionId = this.codelistDimensions.get(decodeXmlEntities(codelistId));
|
|
281
|
+
this.named = true;
|
|
282
|
+
}
|
|
283
|
+
else if (codeId !== undefined) {
|
|
284
|
+
this.code = decodeXmlEntities(codeId);
|
|
285
|
+
this.named = false;
|
|
286
|
+
}
|
|
287
|
+
else if (name !== undefined) {
|
|
288
|
+
// The codelist's own name precedes its first code; only a code's first name labels it.
|
|
289
|
+
if (this.named || this.dimensionId === undefined)
|
|
290
|
+
continue;
|
|
291
|
+
this.named = true;
|
|
292
|
+
const valueId = this.table.valueIds.get(`${this.dimensionId}\t${this.code}`);
|
|
293
|
+
if (valueId === undefined || this.labels[valueId] !== undefined)
|
|
294
|
+
continue;
|
|
295
|
+
this.labels[valueId] = copyOf(decodeXmlEntities(name).trim());
|
|
296
|
+
this.labelled += 1;
|
|
297
|
+
}
|
|
298
|
+
else {
|
|
299
|
+
this.named = true;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
/** The index from the metabase table and each value's label. */
|
|
305
|
+
function assembleIndex(table, valueLabels) {
|
|
306
|
+
const { datasetIds, dimensions, rowDatasets, rowValues, valueCodes, valueDimensions } = table;
|
|
307
|
+
const labelIds = new Map();
|
|
308
|
+
const labels = [];
|
|
309
|
+
const valueLabel = new Int32Array(valueCodes.length).fill(-1);
|
|
310
|
+
valueLabels.forEach((label, valueId) => {
|
|
311
|
+
if (label === undefined)
|
|
312
|
+
return;
|
|
313
|
+
let labelId = labelIds.get(label);
|
|
314
|
+
if (labelId === undefined) {
|
|
315
|
+
labelId = labels.length;
|
|
316
|
+
labels.push(label);
|
|
317
|
+
labelIds.set(label, labelId);
|
|
318
|
+
}
|
|
319
|
+
valueLabel[valueId] = labelId;
|
|
320
|
+
});
|
|
321
|
+
const folded = labels.map(foldSearchText);
|
|
322
|
+
const datasetStart = new Int32Array(datasetIds.size + 1);
|
|
323
|
+
for (const datasetId of rowDatasets)
|
|
324
|
+
datasetStart[datasetId + 1] = datasetStart[datasetId + 1] + 1;
|
|
325
|
+
for (let d = 0; d < datasetIds.size; d++) {
|
|
326
|
+
datasetStart[d + 1] = datasetStart[d + 1] + datasetStart[d];
|
|
327
|
+
}
|
|
328
|
+
const fill = datasetStart.slice(0, -1);
|
|
329
|
+
const datasetValues = new Int32Array(rowValues.length);
|
|
330
|
+
rowDatasets.forEach((datasetId, row) => {
|
|
331
|
+
datasetValues[fill[datasetId]++] = rowValues[row];
|
|
332
|
+
});
|
|
333
|
+
return new DimensionValueIndex({
|
|
334
|
+
codeStart: offsetsOf(valueCodes),
|
|
335
|
+
codeText: valueCodes.join(SEPARATOR),
|
|
336
|
+
datasetIds,
|
|
337
|
+
datasetStart,
|
|
338
|
+
datasetValues,
|
|
339
|
+
dimensions,
|
|
340
|
+
labelStart: offsetsOf(labels),
|
|
341
|
+
labelText: labels.join(SEPARATOR),
|
|
342
|
+
searchStart: offsetsOf(folded),
|
|
343
|
+
searchText: folded.join(SEPARATOR),
|
|
344
|
+
valueDimension: Int32Array.from(valueDimensions),
|
|
345
|
+
valueLabel,
|
|
346
|
+
}, Date.now());
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* Build the index from the metabase (`dataset<TAB>dimension<TAB>code` lines) and the codelist
|
|
350
|
+
* dump (SDMX 2.1 structure XML), each read as a stream of decoded text. The metabase is read
|
|
351
|
+
* first, so only the labels of codes it lists are kept from the codelist named, in upper case,
|
|
352
|
+
* after each dimension. A code's first English name is its label.
|
|
353
|
+
*
|
|
354
|
+
* Throws on a metabase line that is not three tab-separated fields, a metabase that lists no
|
|
355
|
+
* dataset, a codelist dump that stops before its root element closes, or codelists that label
|
|
356
|
+
* none of the metabase's codes: each is a broken download, not an empty catalogue.
|
|
357
|
+
*/
|
|
358
|
+
export async function buildDimensionValueIndex(metabase, codelists) {
|
|
359
|
+
const metabaseReader = new MetabaseReader();
|
|
360
|
+
for await (const chunk of metabase)
|
|
361
|
+
metabaseReader.read(chunk);
|
|
362
|
+
const codelistReader = new CodelistReader(metabaseReader.finish());
|
|
363
|
+
for await (const chunk of codelists)
|
|
364
|
+
codelistReader.read(chunk);
|
|
365
|
+
return codelistReader.finish();
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* The decoded text of a gzip file at `url`, chunk by chunk. The request starts when the
|
|
369
|
+
* first chunk is asked for; `timeoutMs` bounds the whole transfer.
|
|
370
|
+
*/
|
|
371
|
+
async function* gzipText(url, timeoutMs, context) {
|
|
372
|
+
const response = await fetchWithTimeout(url, timeoutMs, context);
|
|
373
|
+
if (!response.body)
|
|
374
|
+
throw new Error(`${new URL(url).pathname} answered with no body`);
|
|
375
|
+
/**
|
|
376
|
+
* `DecompressionStream`'s writable side is typed `BufferSource`, wider than the
|
|
377
|
+
* `Uint8Array` chunks the body carries, so the transform is narrowed to the types in play.
|
|
378
|
+
*/
|
|
379
|
+
const reader = response.body
|
|
380
|
+
.pipeThrough(new DecompressionStream('gzip'))
|
|
381
|
+
.getReader();
|
|
382
|
+
const decoder = new TextDecoder('utf-8');
|
|
383
|
+
try {
|
|
384
|
+
while (true) {
|
|
385
|
+
const { done, value } = await reader.read();
|
|
386
|
+
if (done)
|
|
387
|
+
break;
|
|
388
|
+
const text = decoder.decode(value, { stream: true });
|
|
389
|
+
if (text)
|
|
390
|
+
yield text;
|
|
391
|
+
}
|
|
392
|
+
const tail = decoder.decode();
|
|
393
|
+
if (tail)
|
|
394
|
+
yield tail;
|
|
395
|
+
}
|
|
396
|
+
finally {
|
|
397
|
+
// Ends the transfer when parsing stops early; a no-op once the stream is done.
|
|
398
|
+
await reader.cancel().catch(() => undefined);
|
|
399
|
+
reader.releaseLock();
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
/**
|
|
403
|
+
* Download and build the index: the metabase first, then the codelist dump, each within
|
|
404
|
+
* `EUROSTAT_BULK_TIMEOUT_MS`. One attempt; the caller schedules retries.
|
|
405
|
+
*/
|
|
406
|
+
export function fetchDimensionValueIndex(context) {
|
|
407
|
+
const { baseUrl, bulkTimeoutMs } = getServerConfig();
|
|
408
|
+
return buildDimensionValueIndex(gzipText(`${baseUrl}/catalogue/metabase.txt.gz`, bulkTimeoutMs, context), gzipText(`${baseUrl}/sdmx/2.1/codelist/ESTAT/all/latest?compressed=true`, bulkTimeoutMs, context));
|
|
409
|
+
}
|
|
410
|
+
//# sourceMappingURL=dimension-value-index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dimension-value-index.js","sourceRoot":"","sources":["../../../src/services/eurostat-catalogue/dimension-value-index.ts"],"names":[],"mappings":"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"}
|
|
@@ -7,6 +7,27 @@ import type { Context } from '@cyanheads/mcp-ts-core';
|
|
|
7
7
|
import type { AppConfig } from '@cyanheads/mcp-ts-core/config';
|
|
8
8
|
import type { StorageService } from '@cyanheads/mcp-ts-core/storage';
|
|
9
9
|
import type { BrowseItem, DatasetResult } from './types.js';
|
|
10
|
+
/** Most distinct words a search that matches no catalogue text looks for in dimension values. */
|
|
11
|
+
export declare const MAX_VALUE_SEARCH_WORDS = 10;
|
|
12
|
+
/** Most distinct words, in query order, a search that matches nothing names as held by no dataset. */
|
|
13
|
+
export declare const MAX_UNMATCHED_WORD_CHECKS = 32;
|
|
14
|
+
/**
|
|
15
|
+
* How a dataset matched a search, best first: 0, its label holds the words as a phrase
|
|
16
|
+
* starting at a word start; 1, its label or code holds every word; 2, some word matched
|
|
17
|
+
* only in its theme breadcrumb.
|
|
18
|
+
*/
|
|
19
|
+
export type SearchTier = 0 | 1 | 2;
|
|
20
|
+
/**
|
|
21
|
+
* The tier test for one search over `words`, whitespace-free tokens as `search()` splits a
|
|
22
|
+
* query. The returned function ranks a dataset from its label, folded by
|
|
23
|
+
* {@link foldSearchText}, and its code, never its breadcrumb, so every TOC placement of a
|
|
24
|
+
* code ranks alike. The phrase is the words joined by single spaces and folded the same way;
|
|
25
|
+
* it must start where no letter or digit precedes it, so "employment rate" is not found
|
|
26
|
+
* inside "Unemployment rate". The word test is the substring test matching applies.
|
|
27
|
+
*/
|
|
28
|
+
export declare function searchTierOf(words: readonly string[]): (foldedLabel: string, code: string) => SearchTier;
|
|
29
|
+
/** Why a search that matched no catalogue text did not read dimension values. */
|
|
30
|
+
export type ValuesNotSearched = 'index_unavailable' | 'too_many_words';
|
|
10
31
|
export declare class EurostatCatalogueService {
|
|
11
32
|
private cache;
|
|
12
33
|
/** Shared in-flight refresh so concurrent callers past the TTL trigger one fetch. */
|
|
@@ -19,6 +40,11 @@ export declare class EurostatCatalogueService {
|
|
|
19
40
|
* past its TTL that the refresh could not replace.
|
|
20
41
|
*/
|
|
21
42
|
private refreshRetryAt;
|
|
43
|
+
/**
|
|
44
|
+
* The dimension value index a search that matches no catalogue text reads. Undefined until
|
|
45
|
+
* a TOC load starts the first index load.
|
|
46
|
+
*/
|
|
47
|
+
private valueIndex;
|
|
22
48
|
constructor(_config: AppConfig, _storage: StorageService);
|
|
23
49
|
/**
|
|
24
50
|
* Ensure a usable TOC is loaded. A cache younger than the configured TTL is
|
|
@@ -43,16 +69,28 @@ export declare class EurostatCatalogueService {
|
|
|
43
69
|
* Only the TOC is required. A Comext list that fails to load leaves the catalogue
|
|
44
70
|
* answering from the TOC alone: the failure is logged, and the merge is retried
|
|
45
71
|
* after `REFRESH_RETRY_COOLDOWN_MS` without re-fetching the TOC.
|
|
72
|
+
*
|
|
73
|
+
* Every TOC load also starts the dimension value index load, which nothing awaits.
|
|
46
74
|
*/
|
|
47
75
|
private fetchAndParseToc;
|
|
48
76
|
/** Retry a failed Comext merge onto the TOC already held; concurrent callers share it. */
|
|
49
77
|
private retryComextMerge;
|
|
50
78
|
private logComextFailure;
|
|
79
|
+
/**
|
|
80
|
+
* Start a dimension value index load unless one is in flight, a failed load's backoff has
|
|
81
|
+
* not elapsed, or the index in service is younger than the TOC's TTL. Catalogue calls never
|
|
82
|
+
* wait for it: the load runs detached from the calling request and its trace, logs to the
|
|
83
|
+
* server log, and settles into the slot. A failure keeps the previous index in service and
|
|
84
|
+
* holds off the next attempt for `REFRESH_RETRY_COOLDOWN_MS`, doubling with each failure in
|
|
85
|
+
* a row up to the TOC's TTL.
|
|
86
|
+
*/
|
|
87
|
+
private loadValueIndex;
|
|
51
88
|
/**
|
|
52
89
|
* Assemble a catalogue snapshot. `flows` undefined means the Comext list failed to
|
|
53
90
|
* load, which arms the merge retry. `loadedAt` is the TOC's load time — the TTL and
|
|
54
|
-
* the cursor generation both key on it. A later merge only appends entries,
|
|
55
|
-
*
|
|
91
|
+
* the cursor generation both key on it. A later merge only appends entries, and
|
|
92
|
+
* `search()` pages entries appended after a walk began behind the ones it began over, so
|
|
93
|
+
* a cursor paged over the TOC alone still pages every match once.
|
|
56
94
|
*/
|
|
57
95
|
private buildCache;
|
|
58
96
|
/** The Comext host's dataflow list — the `DS-*` collections the TOC does not carry. */
|
|
@@ -134,23 +172,46 @@ export declare class EurostatCatalogueService {
|
|
|
134
172
|
* Decode a search cursor and confirm it belongs to this search. Malformed,
|
|
135
173
|
* cross-query and superseded-snapshot cursors are rejected identically —
|
|
136
174
|
* the caller's recovery is the same in all three cases, and the cursor is
|
|
137
|
-
* opaque, so telling them apart would only describe its internal shape.
|
|
175
|
+
* opaque, so telling them apart would only describe its internal shape. `entryCount`,
|
|
176
|
+
* the snapshot's length when the walk began, can be no more than its length now.
|
|
177
|
+
* `valueIndex`, present on a walk over rows found through dimension values, is checked
|
|
178
|
+
* once `search()` knows which rows it pages.
|
|
138
179
|
*/
|
|
139
180
|
private decodeSearchCursor;
|
|
181
|
+
/** One search result row for the TOC placement `entries[i]`. */
|
|
182
|
+
private toDatasetResult;
|
|
140
183
|
/**
|
|
141
|
-
* Each dataset's search text
|
|
142
|
-
* beside punctuation removed — indexed like `entries`. Folders, which search never
|
|
184
|
+
* Each dataset's search text, indexed like `entries`. Folders, which search never
|
|
143
185
|
* returns, are undefined.
|
|
144
186
|
*/
|
|
145
187
|
private buildSearchText;
|
|
146
188
|
/**
|
|
147
189
|
* For a query that matched nothing, the words whose removal alone finds matches, with
|
|
148
|
-
* that count
|
|
149
|
-
*
|
|
150
|
-
*
|
|
151
|
-
*
|
|
190
|
+
* that count (`wordRemovalCounts`), and the words no searched text contains at all
|
|
191
|
+
* (`unmatchedWords`, with `allWordsUnmatched` when that is every word), each in query
|
|
192
|
+
* order and each omitted when empty. `words` maps each distinct token to the word as
|
|
193
|
+
* typed. One pass: a placement missing exactly one word rescues its code for that
|
|
194
|
+
* word, and a code counts once per word whichever placement rescued it — so each count
|
|
195
|
+
* equals the `totalMatches` of the same search without that word.
|
|
196
|
+
*
|
|
197
|
+
* `unmatchedWords` is drawn from the first {@link MAX_UNMATCHED_WORD_CHECKS} distinct
|
|
198
|
+
* words. `unmatchedWordsPartial` is set when a word past them is held by no text the pass
|
|
199
|
+
* read, so it may be one no dataset carries.
|
|
152
200
|
*/
|
|
153
201
|
private countWithoutEachWord;
|
|
202
|
+
/**
|
|
203
|
+
* The datasets a query that matched no catalogue text finds when a word may also occur
|
|
204
|
+
* at a word start of the English label of a value the metabase lists for the dataset.
|
|
205
|
+
* Every word must still be found, in the dataset's own text or in a value's label. Each
|
|
206
|
+
* code keeps its placement whose text holds the most words, the first such in TOC order.
|
|
207
|
+
*
|
|
208
|
+
* Ordered by fewer words found only through values; then rows where one value's label
|
|
209
|
+
* carries all of those words; then {@link searchTierOf} over the tokens the dataset's text
|
|
210
|
+
* holds, in query order; then TOC order. `marks` holds, for each word found through
|
|
211
|
+
* values, which labels carry it. `words` are the query's distinct tokens, at most
|
|
212
|
+
* {@link MAX_VALUE_SEARCH_WORDS}.
|
|
213
|
+
*/
|
|
214
|
+
private searchValues;
|
|
154
215
|
/**
|
|
155
216
|
* Search datasets by keyword. The query is tokenized on whitespace and every
|
|
156
217
|
* token must match (AND), case-insensitively, somewhere in a combined
|
|
@@ -165,27 +226,52 @@ export declare class EurostatCatalogueService {
|
|
|
165
226
|
* several TOC branches keeps its first matching placement as the canonical
|
|
166
227
|
* `themePath`, so totals and page slots count unique query targets.
|
|
167
228
|
*
|
|
229
|
+
* Matches are ordered by {@link searchTierOf} over the tokens — label phrase, then
|
|
230
|
+
* every word in label or code, then breadcrumb-only — and by that first matching
|
|
231
|
+
* placement within a tier. A tier reads the label and code alone, so it is the same
|
|
232
|
+
* whichever placement supplied the `themePath`.
|
|
233
|
+
*
|
|
168
234
|
* Results are paginated with an opaque cursor (`limit` = page size, capped at
|
|
169
235
|
* 100) bound to the normalized query and the catalogue snapshot that produced
|
|
170
236
|
* it. An unusable cursor — malformed, from another query, or from a catalogue
|
|
171
237
|
* since refreshed — is rejected as `invalid_cursor` instead of silently paging
|
|
172
|
-
* a different result set.
|
|
238
|
+
* a different result set. A Comext merge that lands mid-walk keeps the snapshot,
|
|
239
|
+
* so the cursor also carries the snapshot's length when the walk began: the
|
|
240
|
+
* datasets the merge appended page after every match the walk began over, each
|
|
241
|
+
* match still once.
|
|
242
|
+
*
|
|
243
|
+
* When no dataset's text holds every token, {@link searchValues} looks for the tokens
|
|
244
|
+
* the text lacks in the English labels of the dataset's dimension values. Its rows
|
|
245
|
+
* carry `matchedValues` and the result `matchedThroughValues`, and their cursor also
|
|
246
|
+
* binds the value index snapshot (`valueIndex`), so an index refresh mid-walk rejects it
|
|
247
|
+
* like a catalogue refresh. No search waits for the index: until one has loaded,
|
|
248
|
+
* `valuesNotSearched` is `index_unavailable`; past {@link MAX_VALUE_SEARCH_WORDS}
|
|
249
|
+
* distinct tokens it is `too_many_words`.
|
|
173
250
|
*
|
|
174
251
|
* An empty match reports the `DS-*` codes the query names that neither source lists
|
|
175
252
|
* (`undisseminatedCodes`), so the caller can be told the collection is not
|
|
176
253
|
* disseminated rather than to broaden the search, and — for two or more distinct
|
|
177
|
-
* words — each word whose removal alone finds matches (`wordRemovalCounts`)
|
|
178
|
-
*
|
|
179
|
-
* `
|
|
180
|
-
*
|
|
254
|
+
* words — each word whose removal alone finds matches (`wordRemovalCounts`) and each
|
|
255
|
+
* word no dataset's search text contains (`unmatchedWords`, as first typed), with
|
|
256
|
+
* `allWordsUnmatched` when that is every word. `unmatchedWords` reads the first
|
|
257
|
+
* {@link MAX_UNMATCHED_WORD_CHECKS} distinct words; `unmatchedWordsPartial` flags a query
|
|
258
|
+
* with a later word that may be one no dataset carries. `comextListMissing` flags that the
|
|
259
|
+
* Comext list is not merged right now, so the `DS-*` collections were not searched and
|
|
260
|
+
* absence from that list proves nothing; `catalogueLoadedAt` marks a catalogue a
|
|
261
|
+
* failed refresh could not replace.
|
|
181
262
|
*/
|
|
182
263
|
search(query: string, limit: number, cursor: string | undefined, ctx: Context): Promise<{
|
|
264
|
+
allWordsUnmatched?: true;
|
|
183
265
|
catalogueLoadedAt?: string;
|
|
184
266
|
comextListMissing?: boolean;
|
|
185
267
|
datasets: DatasetResult[];
|
|
268
|
+
matchedThroughValues?: true;
|
|
186
269
|
nextCursor?: string;
|
|
187
270
|
totalMatches: number;
|
|
188
271
|
undisseminatedCodes?: string[];
|
|
272
|
+
unmatchedWords?: string[];
|
|
273
|
+
unmatchedWordsPartial?: true;
|
|
274
|
+
valuesNotSearched?: ValuesNotSearched;
|
|
189
275
|
wordRemovalCounts?: Array<{
|
|
190
276
|
totalMatches: number;
|
|
191
277
|
word: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"eurostat-catalogue-service.d.ts","sourceRoot":"","sources":["../../../src/services/eurostat-catalogue/eurostat-catalogue-service.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;AACtD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,+BAA+B,CAAC;
|
|
1
|
+
{"version":3,"file":"eurostat-catalogue-service.d.ts","sourceRoot":"","sources":["../../../src/services/eurostat-catalogue/eurostat-catalogue-service.ts"],"names":[],"mappings":"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"}
|