@cyanheads/eurostat-mcp-server 0.6.5 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +1 -1
- package/CLAUDE.md +1 -1
- package/README.md +85 -85
- package/changelog/0.7.x/0.7.0.md +33 -0
- package/changelog/0.8.x/0.8.0.md +30 -0
- package/dist/config/server-config.d.ts +2 -0
- package/dist/config/server-config.d.ts.map +1 -1
- package/dist/config/server-config.js +13 -0
- package/dist/config/server-config.js.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -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 +4 -2
- package/dist/mcp-server/resources/definitions/eurostat-dataset.resource.js.map +1 -1
- package/dist/mcp-server/tools/definitions/eurostat-browse-themes.tool.js +1 -1
- package/dist/mcp-server/tools/definitions/eurostat-browse-themes.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/eurostat-dataframe-describe.tool.d.ts +1 -1
- package/dist/mcp-server/tools/definitions/eurostat-dataframe-describe.tool.js +4 -4
- package/dist/mcp-server/tools/definitions/eurostat-dataframe-describe.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/eurostat-dataframe-query.tool.d.ts +2 -2
- package/dist/mcp-server/tools/definitions/eurostat-dataframe-query.tool.js +4 -4
- 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 +16 -23
- 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 +156 -65
- 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 +3 -4
- 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 +8 -9
- 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 +21 -7
- 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 +115 -19
- 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 +10 -3
- 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 +156 -24
- 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 +2 -2
- 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 +19 -6
- package/dist/mcp-server/tools/definitions/eurostat-search-datasets.tool.js.map +1 -1
- package/dist/mcp-server/tools/narrowing-advice.d.ts +22 -0
- package/dist/mcp-server/tools/narrowing-advice.d.ts.map +1 -0
- package/dist/mcp-server/tools/narrowing-advice.js +44 -0
- package/dist/mcp-server/tools/narrowing-advice.js.map +1 -0
- package/dist/services/eurostat-bulk/eurostat-bulk-service.d.ts +12 -1
- package/dist/services/eurostat-bulk/eurostat-bulk-service.d.ts.map +1 -1
- package/dist/services/eurostat-bulk/eurostat-bulk-service.js +66 -12
- package/dist/services/eurostat-bulk/eurostat-bulk-service.js.map +1 -1
- package/dist/services/eurostat-bulk/types.d.ts +5 -0
- package/dist/services/eurostat-bulk/types.d.ts.map +1 -1
- package/dist/services/eurostat-catalogue/eurostat-catalogue-service.d.ts +47 -1
- package/dist/services/eurostat-catalogue/eurostat-catalogue-service.d.ts.map +1 -1
- package/dist/services/eurostat-catalogue/eurostat-catalogue-service.js +188 -13
- package/dist/services/eurostat-catalogue/eurostat-catalogue-service.js.map +1 -1
- package/dist/services/eurostat-codelists.d.ts +20 -0
- package/dist/services/eurostat-codelists.d.ts.map +1 -1
- package/dist/services/eurostat-codelists.js +25 -0
- package/dist/services/eurostat-codelists.js.map +1 -1
- package/dist/services/eurostat-data/eurostat-data-service.d.ts +78 -9
- package/dist/services/eurostat-data/eurostat-data-service.d.ts.map +1 -1
- package/dist/services/eurostat-data/eurostat-data-service.js +306 -51
- package/dist/services/eurostat-data/eurostat-data-service.js.map +1 -1
- package/dist/services/eurostat-data/sdmx-metadata.d.ts +26 -0
- package/dist/services/eurostat-data/sdmx-metadata.d.ts.map +1 -1
- package/dist/services/eurostat-data/sdmx-metadata.js +38 -1
- package/dist/services/eurostat-data/sdmx-metadata.js.map +1 -1
- package/dist/services/eurostat-data/types.d.ts +38 -2
- 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-hosts.d.ts +17 -0
- package/dist/services/eurostat-hosts.d.ts.map +1 -0
- package/dist/services/eurostat-hosts.js +23 -0
- package/dist/services/eurostat-hosts.js.map +1 -0
- package/dist/services/eurostat-periods.d.ts +68 -0
- package/dist/services/eurostat-periods.d.ts.map +1 -0
- package/dist/services/eurostat-periods.js +204 -0
- package/dist/services/eurostat-periods.js.map +1 -0
- package/dist/services/shared-load.d.ts +16 -0
- package/dist/services/shared-load.d.ts.map +1 -0
- package/dist/services/shared-load.js +27 -0
- package/dist/services/shared-load.js.map +1 -0
- package/package.json +2 -2
- package/server.json +31 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"eurostat-browse-themes.tool.js","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/eurostat-browse-themes.tool.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,IAAI,EAAE,CAAC,EAAE,MAAM,wBAAwB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAiB,MAAM,+BAA+B,CAAC;AAChF,OAAO,EAAE,2BAA2B,EAAE,MAAM,6DAA6D,CAAC;AAE1G,MAAM,CAAC,MAAM,oBAAoB,GAAG,IAAI,CAAC,wBAAwB,EAAE;IACjE,KAAK,EAAE,iCAAiC;IACxC,WAAW,EACT,
|
|
1
|
+
{"version":3,"file":"eurostat-browse-themes.tool.js","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/eurostat-browse-themes.tool.ts"],"names":[],"mappings":"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"}
|
|
@@ -33,7 +33,7 @@ export declare const eurostatDataframeDescribe: import("@cyanheads/mcp-ts-core")
|
|
|
33
33
|
readonly reason: "canvas_not_found";
|
|
34
34
|
readonly code: JsonRpcErrorCode.NotFound;
|
|
35
35
|
readonly when: "The canvas_id is unknown or its lifetime has elapsed.";
|
|
36
|
-
readonly recovery: "Re-run eurostat_query_dataset
|
|
36
|
+
readonly recovery: "Re-run eurostat_query_dataset or eurostat_download_dataset without canvas_id and use the canvasId it returns; eurostat_get_dimension_values stages onto that canvas but never starts one.";
|
|
37
37
|
readonly thrownBy: "service";
|
|
38
38
|
}], {
|
|
39
39
|
readonly notice: z.ZodOptional<z.ZodString>;
|
|
@@ -8,10 +8,10 @@ import { JsonRpcErrorCode } from '@cyanheads/mcp-ts-core/errors';
|
|
|
8
8
|
import { acquireCanvas, getCanvas } from '../../../services/canvas-accessor.js';
|
|
9
9
|
export const eurostatDataframeDescribe = tool('eurostat_dataframe_describe', {
|
|
10
10
|
title: 'Describe Eurostat Dataframes',
|
|
11
|
-
description: 'List the tables staged on a Eurostat dataframe canvas, with their row counts and column names and types. Call this before eurostat_dataframe_query to learn the table and column names to write SQL against. The canvas_id comes from a eurostat_query_dataset or eurostat_download_dataset response that reported a staged table.
|
|
11
|
+
description: 'List the tables staged on a Eurostat dataframe canvas, with their row counts and column names and types. Call this before eurostat_dataframe_query to learn the table and column names to write SQL against. The canvas_id comes from a eurostat_query_dataset or eurostat_download_dataset response that reported a staged table. Three tools stage tables, and they write different columns, so read the columns reported here rather than assuming. The two observation stagers keep every column flat: eurostat_query_dataset gives each dimension a code column named after the dimension (e.g. "geo") plus a label companion (e.g. "geo_label"); eurostat_download_dataset gives code columns only — the bulk endpoint carries no labels — plus a "time" column. Both write the same five measure columns — obs_value, obs_flag, obs_flag_label, conf_status, conf_status_label — carrying the same codes for the same observation, so their tables join on dimension codes and time and compare like with like. A table staged from a DS-* dataset (Comext detailed trade, PRODCOM) adds obs_value_text, holding a value Eurostat published as text, such as a PRODCOM quantity unit. eurostat_get_dimension_values, given a canvas_id, stages one dimension\'s value list as two columns, code and label, which labels a download\'s code column through a join (e.g. d.geo = g.code).',
|
|
12
12
|
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
13
13
|
input: z.object({
|
|
14
|
-
canvas_id: CanvasIdSchema.describe('Canvas identifier returned as canvasId by eurostat_query_dataset or
|
|
14
|
+
canvas_id: CanvasIdSchema.describe('Canvas identifier returned as canvasId by eurostat_query_dataset, eurostat_download_dataset, or eurostat_get_dimension_values. Identifies the workspace holding the staged tables.'),
|
|
15
15
|
}),
|
|
16
16
|
output: z.object({
|
|
17
17
|
canvasId: z.string().describe('Canvas identifier the tables were read from.'),
|
|
@@ -61,7 +61,7 @@ export const eurostatDataframeDescribe = tool('eurostat_dataframe_describe', {
|
|
|
61
61
|
reason: 'canvas_not_found',
|
|
62
62
|
code: JsonRpcErrorCode.NotFound,
|
|
63
63
|
when: 'The canvas_id is unknown or its lifetime has elapsed.',
|
|
64
|
-
recovery: 'Re-run eurostat_query_dataset
|
|
64
|
+
recovery: 'Re-run eurostat_query_dataset or eurostat_download_dataset without canvas_id and use the canvasId it returns; eurostat_get_dimension_values stages onto that canvas but never starts one.',
|
|
65
65
|
thrownBy: 'service',
|
|
66
66
|
},
|
|
67
67
|
],
|
|
@@ -74,7 +74,7 @@ export const eurostatDataframeDescribe = tool('eurostat_dataframe_describe', {
|
|
|
74
74
|
const tables = await instance.describe();
|
|
75
75
|
ctx.log.info('Described canvas', { canvasId: instance.canvasId, tableCount: tables.length });
|
|
76
76
|
if (tables.length === 0) {
|
|
77
|
-
ctx.enrich.notice(`Canvas ${instance.canvasId} holds no tables. Staged tables expire on their own schedule — re-run
|
|
77
|
+
ctx.enrich.notice(`Canvas ${instance.canvasId} holds no tables. Staged tables expire on their own schedule — re-run the tool that staged the data to stage it again: eurostat_query_dataset, eurostat_download_dataset, or eurostat_get_dimension_values with this canvas_id.`);
|
|
78
78
|
}
|
|
79
79
|
return {
|
|
80
80
|
canvasId: instance.canvasId,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"eurostat-dataframe-describe.tool.js","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/eurostat-dataframe-describe.tool.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,IAAI,EAAE,CAAC,EAAE,MAAM,wBAAwB,CAAC;AACjD,OAAO,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAC/D,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AACjE,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,+BAA+B,CAAC;AAEzE,MAAM,CAAC,MAAM,yBAAyB,GAAG,IAAI,CAAC,6BAA6B,EAAE;IAC3E,KAAK,EAAE,8BAA8B;IACrC,WAAW,EACT,
|
|
1
|
+
{"version":3,"file":"eurostat-dataframe-describe.tool.js","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/eurostat-dataframe-describe.tool.ts"],"names":[],"mappings":"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"}
|
|
@@ -24,13 +24,13 @@ export declare const eurostatDataframeQuery: import("@cyanheads/mcp-ts-core").To
|
|
|
24
24
|
readonly reason: "canvas_not_found";
|
|
25
25
|
readonly code: JsonRpcErrorCode.NotFound;
|
|
26
26
|
readonly when: "The canvas_id is unknown or its lifetime has elapsed.";
|
|
27
|
-
readonly recovery: "Re-run eurostat_query_dataset
|
|
27
|
+
readonly recovery: "Re-run eurostat_query_dataset or eurostat_download_dataset without canvas_id and use the canvasId it returns; eurostat_get_dimension_values stages onto that canvas but never starts one.";
|
|
28
28
|
readonly thrownBy: "service";
|
|
29
29
|
}, {
|
|
30
30
|
readonly reason: "missing_table";
|
|
31
31
|
readonly code: JsonRpcErrorCode.NotFound;
|
|
32
32
|
readonly when: "The SQL names a table that is not staged on this canvas, or that has expired.";
|
|
33
|
-
readonly recovery: "Call eurostat_dataframe_describe for the staged table names, or re-run
|
|
33
|
+
readonly recovery: "Call eurostat_dataframe_describe for the staged table names, or re-run the tool that staged them (eurostat_query_dataset, eurostat_download_dataset, or eurostat_get_dimension_values with canvas_id).";
|
|
34
34
|
readonly thrownBy: "service";
|
|
35
35
|
}], undefined>;
|
|
36
36
|
//# sourceMappingURL=eurostat-dataframe-query.tool.d.ts.map
|
|
@@ -9,10 +9,10 @@ import { JsonRpcErrorCode } from '@cyanheads/mcp-ts-core/errors';
|
|
|
9
9
|
import { acquireCanvas, getCanvas } from '../../../services/canvas-accessor.js';
|
|
10
10
|
export const eurostatDataframeQuery = tool('eurostat_dataframe_query', {
|
|
11
11
|
title: 'Query Eurostat Dataframes',
|
|
12
|
-
description: 'Run a read-only SQL SELECT against tables staged on a Eurostat dataframe canvas — the way to reach observations past the 5,000-row inline cap of eurostat_query_dataset and past the inline preview of a eurostat_download_dataset bulk download, and to aggregate, group, or join across staged tables without re-fetching from Eurostat. Call eurostat_dataframe_describe first for the table and column names, which differ between the
|
|
12
|
+
description: 'Run a read-only SQL SELECT against tables staged on a Eurostat dataframe canvas — the way to reach observations past the 5,000-row inline cap of eurostat_query_dataset and past the inline preview of a eurostat_download_dataset bulk download, and to aggregate, group, or join across staged tables without re-fetching from Eurostat. Call eurostat_dataframe_describe first for the table and column names, which differ between the tools that stage them. Only a single SELECT statement runs: statement chaining, non-SELECT verbs, and functions that read files or external data are rejected. Columns are flat — every dimension is a code column named after the dimension, the measure is obs_value, the observation flag is obs_flag / obs_flag_label and the confidentiality marker is conf_status / conf_status_label; a "_label" companion per dimension exists only on tables eurostat_query_dataset staged. Both observation stagers write the same five measure columns with the same codes, so join their tables on dimension codes and time and compare obs_flag or conf_status across them directly; a DS-* table also carries obs_value_text, a value published as text (e.g. a PRODCOM unit "KG"). A value list eurostat_get_dimension_values staged has two columns, code and label: join it to the code column of a download (e.g. JOIN df_x g ON d.geo = g.code) to label that table.',
|
|
13
13
|
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
14
14
|
input: z.object({
|
|
15
|
-
canvas_id: CanvasIdSchema.describe('Canvas identifier returned as canvasId by eurostat_query_dataset or
|
|
15
|
+
canvas_id: CanvasIdSchema.describe('Canvas identifier returned as canvasId by eurostat_query_dataset, eurostat_download_dataset, or eurostat_get_dimension_values. Identifies the workspace holding the staged tables.'),
|
|
16
16
|
sql: z
|
|
17
17
|
.string()
|
|
18
18
|
.min(1)
|
|
@@ -43,14 +43,14 @@ export const eurostatDataframeQuery = tool('eurostat_dataframe_query', {
|
|
|
43
43
|
reason: 'canvas_not_found',
|
|
44
44
|
code: JsonRpcErrorCode.NotFound,
|
|
45
45
|
when: 'The canvas_id is unknown or its lifetime has elapsed.',
|
|
46
|
-
recovery: 'Re-run eurostat_query_dataset
|
|
46
|
+
recovery: 'Re-run eurostat_query_dataset or eurostat_download_dataset without canvas_id and use the canvasId it returns; eurostat_get_dimension_values stages onto that canvas but never starts one.',
|
|
47
47
|
thrownBy: 'service',
|
|
48
48
|
},
|
|
49
49
|
{
|
|
50
50
|
reason: 'missing_table',
|
|
51
51
|
code: JsonRpcErrorCode.NotFound,
|
|
52
52
|
when: 'The SQL names a table that is not staged on this canvas, or that has expired.',
|
|
53
|
-
recovery: 'Call eurostat_dataframe_describe for the staged table names, or re-run
|
|
53
|
+
recovery: 'Call eurostat_dataframe_describe for the staged table names, or re-run the tool that staged them (eurostat_query_dataset, eurostat_download_dataset, or eurostat_get_dimension_values with canvas_id).',
|
|
54
54
|
thrownBy: 'service',
|
|
55
55
|
},
|
|
56
56
|
],
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"eurostat-dataframe-query.tool.js","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/eurostat-dataframe-query.tool.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,IAAI,EAAE,CAAC,EAAE,MAAM,wBAAwB,CAAC;AACjD,OAAO,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAC/D,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AACjE,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,+BAA+B,CAAC;AAEzE,MAAM,CAAC,MAAM,sBAAsB,GAAG,IAAI,CAAC,0BAA0B,EAAE;IACrE,KAAK,EAAE,2BAA2B;IAClC,WAAW,EACT,
|
|
1
|
+
{"version":3,"file":"eurostat-dataframe-query.tool.js","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/eurostat-dataframe-query.tool.ts"],"names":[],"mappings":"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"}
|
|
@@ -43,9 +43,14 @@ export declare const eurostatDownloadDataset: import("@cyanheads/mcp-ts-core").T
|
|
|
43
43
|
}, {
|
|
44
44
|
readonly reason: "filter_arity";
|
|
45
45
|
readonly code: JsonRpcErrorCode.ValidationError;
|
|
46
|
-
readonly when: "Eurostat rejected the positional dimension key because it carried the wrong number of positions (SDMX faultcode 140), meaning the dataset structure has changed since the metadata call.";
|
|
46
|
+
readonly when: "Eurostat rejected the positional dimension key because it carried the wrong number of positions (SDMX faultcode 140, INVALID_QUERY_NB_FILTERS), meaning the dataset structure has changed since the metadata call.";
|
|
47
47
|
readonly recovery: "Retry without filters to download the whole dataset, or re-read the dimensions with eurostat_get_dataset_info.";
|
|
48
48
|
readonly thrownBy: "service";
|
|
49
|
+
}, {
|
|
50
|
+
readonly reason: "invalid_period";
|
|
51
|
+
readonly code: JsonRpcErrorCode.ValidationError;
|
|
52
|
+
readonly when: "since_period or until_period is not a period literal, or names a month, quarter, semester, trimester, week or day that does not exist, or since_period starts after until_period ends. Checked before any request — the bulk endpoint would otherwise roll an out-of-range period into a neighbouring one, and answer an inverted range with the whole series — and SDMX faultcode 140 TIME_PERIOD_FILTER_SPEC_INVALID maps here too.";
|
|
53
|
+
readonly recovery: "Write since_period/until_period as YYYY, YYYY-MM, YYYY-MM-DD, YYYY-Qn (1–4), YYYY-Sn (1–2), YYYY-Tn (1–3), YYYY-Mnn (01–12), YYYY-Wnn (a week the year has, up to 53) or YYYY-Dnnn (a day the year has, up to 366), for example \"2020\", \"2020-01\" or \"2020-Q1\", with since_period starting no later than until_period ends.";
|
|
49
54
|
}, {
|
|
50
55
|
readonly reason: "async_queued";
|
|
51
56
|
readonly code: JsonRpcErrorCode.ServiceUnavailable;
|
|
@@ -56,8 +61,15 @@ export declare const eurostatDownloadDataset: import("@cyanheads/mcp-ts-core").T
|
|
|
56
61
|
}, {
|
|
57
62
|
readonly reason: "no_results";
|
|
58
63
|
readonly code: JsonRpcErrorCode.NotFound;
|
|
59
|
-
readonly when: "The download completed but carried no populated observation cells.";
|
|
60
|
-
readonly recovery: "Verify the filter values with eurostat_get_dimension_values — a combination that exists in no cell
|
|
64
|
+
readonly when: "The download completed but carried no populated observation cells. Nothing is staged, and no canvas is created or touched.";
|
|
65
|
+
readonly recovery: "Verify the filter values with eurostat_get_dimension_values and keep since_period/until_period inside the series' coverage — a combination that exists in no cell and a range that misses the series both download as an empty table.";
|
|
66
|
+
}, {
|
|
67
|
+
readonly reason: "extraction_too_big";
|
|
68
|
+
readonly code: JsonRpcErrorCode.ServiceUnavailable;
|
|
69
|
+
readonly when: "Eurostat refused the extraction as too large (SDMX faultcode 413, HTTP 413): past its 5,000,000-row extraction limit, or an unfiltered download of a large DS-* Comext collection, which Eurostat serves only filtered. Covers every dataset, on either host.";
|
|
70
|
+
readonly retryable: false;
|
|
71
|
+
readonly recovery: "Add dimension filters or a narrower since_period/until_period range to shrink the extraction; the same request fails the same way on every retry.";
|
|
72
|
+
readonly thrownBy: "service";
|
|
61
73
|
}, {
|
|
62
74
|
readonly reason: "upstream_fault";
|
|
63
75
|
readonly code: JsonRpcErrorCode.ServiceUnavailable;
|
|
@@ -71,9 +83,7 @@ export declare const eurostatDownloadDataset: import("@cyanheads/mcp-ts-core").T
|
|
|
71
83
|
readonly recovery: "Omit canvas_id so the download starts a fresh canvas and returns its canvasId.";
|
|
72
84
|
readonly thrownBy: "service";
|
|
73
85
|
}], {
|
|
74
|
-
readonly
|
|
75
|
-
readonly shown: z.ZodOptional<z.ZodNumber>;
|
|
76
|
-
readonly cap: z.ZodOptional<z.ZodNumber>;
|
|
86
|
+
readonly totalCount: z.ZodNumber;
|
|
77
87
|
readonly appliedQuery: z.ZodObject<{
|
|
78
88
|
filters: z.ZodRecord<z.ZodString, z.ZodArray<z.ZodString>>;
|
|
79
89
|
sincePeriod: z.ZodOptional<z.ZodString>;
|
|
@@ -82,21 +92,4 @@ export declare const eurostatDownloadDataset: import("@cyanheads/mcp-ts-core").T
|
|
|
82
92
|
}, z.core.$strip>;
|
|
83
93
|
readonly notice: z.ZodOptional<z.ZodString>;
|
|
84
94
|
}>;
|
|
85
|
-
/** Contract reasons the bulk service's classified failures map onto. */
|
|
86
|
-
type MappedReason = 'async_queued' | 'filter_arity' | 'invalid_dimension' | 'not_found' | 'upstream_fault';
|
|
87
|
-
/**
|
|
88
|
-
* Translate a service-layer failure into the tool's declared contract.
|
|
89
|
-
*
|
|
90
|
-
* The bulk service classifies SDMX faults into `data.reason` but throws generic
|
|
91
|
-
* factory errors, because a service has no access to a tool's reason union.
|
|
92
|
-
* This resolves the reason and the caller-facing recovery hint; the handler does
|
|
93
|
-
* the throwing, so `ctx.fail` stays typed against the union rather than taking a
|
|
94
|
-
* bare string.
|
|
95
|
-
*/
|
|
96
|
-
export declare function mapBulkFailure(err: unknown, datasetCode: string): {
|
|
97
|
-
hint: string;
|
|
98
|
-
message: string;
|
|
99
|
-
reason: MappedReason;
|
|
100
|
-
} | undefined;
|
|
101
|
-
export {};
|
|
102
95
|
//# sourceMappingURL=eurostat-download-dataset.tool.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"eurostat-download-dataset.tool.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/eurostat-download-dataset.tool.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAQ,CAAC,EAAE,MAAM,wBAAwB,CAAC;AAEjD,OAAO,EAAE,gBAAgB,EAAiB,MAAM,+BAA+B,CAAC;
|
|
1
|
+
{"version":3,"file":"eurostat-download-dataset.tool.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/eurostat-download-dataset.tool.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAQ,CAAC,EAAE,MAAM,wBAAwB,CAAC;AAEjD,OAAO,EAAE,gBAAgB,EAAiB,MAAM,+BAA+B,CAAC;AAqChF,eAAO,MAAM,uBAAuB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA2clC,CAAC"}
|
|
@@ -6,9 +6,11 @@
|
|
|
6
6
|
import { tool, z } from '@cyanheads/mcp-ts-core';
|
|
7
7
|
import { CanvasIdSchema } from '@cyanheads/mcp-ts-core/canvas';
|
|
8
8
|
import { JsonRpcErrorCode } from '@cyanheads/mcp-ts-core/errors';
|
|
9
|
+
import { dimensionsToNarrow, narrowingAdvice } from '../narrowing-advice.js';
|
|
9
10
|
import { acquireCanvas, getCanvas, newTableName } from '../../../services/canvas-accessor.js';
|
|
10
11
|
import { bulkRowSchema, getEurostatBulkService, } from '../../../services/eurostat-bulk/eurostat-bulk-service.js';
|
|
11
12
|
import { getEurostatDataService } from '../../../services/eurostat-data/eurostat-data-service.js';
|
|
13
|
+
import { PERIOD_FORMS, resolvePeriods } from '../../../services/eurostat-periods.js';
|
|
12
14
|
/** Upper bound on rows echoed inline, whatever preview_limit asks for. */
|
|
13
15
|
const PREVIEW_MAX = 500;
|
|
14
16
|
/**
|
|
@@ -24,24 +26,32 @@ async function* teePreview(rows, preview, limit) {
|
|
|
24
26
|
yield row;
|
|
25
27
|
}
|
|
26
28
|
}
|
|
29
|
+
/** Put back a row already pulled off `rest`, so the canvas receives the stream whole. */
|
|
30
|
+
async function* prepend(first, rest) {
|
|
31
|
+
yield first;
|
|
32
|
+
yield* rest;
|
|
33
|
+
}
|
|
27
34
|
export const eurostatDownloadDataset = tool('eurostat_download_dataset', {
|
|
28
35
|
title: 'Download Eurostat Dataset',
|
|
29
36
|
description: 'Download a Eurostat dataset in bulk through the SDMX 2.1 TSV endpoint and stage every observation as a SQL table on the dataframe canvas — the route to a whole dataset, where eurostat_query_dataset is the route to a slice of one. The TSV wire format is roughly half the bytes of the JSON-stat body eurostat_query_dataset reads, so it reaches datasets that would otherwise time out, and it is expanded here into one row per observation. Filters take the same dimension-code map eurostat_query_dataset uses and are applied server-side by Eurostat; call eurostat_get_dataset_info first for the dimension codes and eurostat_get_dimension_values for their values. Narrow with since_period/until_period rather than asking for the most recent N periods — the TSV layout keeps a column for every period whichever is requested, so a period range is what actually shrinks the response. Transfers are bounded by a byte budget enforced while streaming: when it is spent the download stops and budgetExceeded is set, leaving a prefix of the dataset rather than an error. Only preview_limit rows come back inline. When a table is staged, call eurostat_dataframe_describe first to confirm its columns, then eurostat_dataframe_query; without a canvas, rows past the preview are not retained.',
|
|
30
37
|
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: true },
|
|
31
38
|
input: z.object({
|
|
32
|
-
dataset_code: z
|
|
39
|
+
dataset_code: z
|
|
40
|
+
.string()
|
|
41
|
+
.min(1)
|
|
42
|
+
.describe('Dataset code (e.g., "nama_10_gdp"). Required. A DS-* code — Comext detailed trade or PRODCOM, in any case — is served by the Comext host, which refuses an unfiltered download of its large collections as extraction_too_big: filter it, including freq on the trade flows, which mix annual and monthly series.'),
|
|
33
43
|
filters: z
|
|
34
44
|
.record(z.string(), z.array(z.string()))
|
|
35
45
|
.default({})
|
|
36
|
-
.describe('Dimension filters as a map of dimension code → array of accepted values, applied by Eurostat before the body is sent. Example: {"unit": ["CP_MEUR"], "na_item": ["B1G"], "geo": ["DE", "FR"]}. Omit a dimension or pass an empty array to accept every value for it. Do not put "time" here — use since_period/until_period. Naming a dimension the dataset does not have is rejected with the dataset\'s dimension list rather than silently ignored.'),
|
|
46
|
+
.describe('Dimension filters as a map of dimension code → array of accepted values, applied by Eurostat before the body is sent. Example: {"unit": ["CP_MEUR"], "na_item": ["B1G"], "geo": ["DE", "FR"]}. Omit a dimension or pass an empty array to accept every value for it. Dimension codes match in any case ("GEO" is geo). Do not put "time" here — use since_period/until_period. Naming a dimension the dataset does not have is rejected with the dataset\'s dimension list rather than silently ignored.'),
|
|
37
47
|
since_period: z
|
|
38
48
|
.string()
|
|
39
49
|
.optional()
|
|
40
|
-
.describe(
|
|
50
|
+
.describe(`Start of the period range, inclusive, sent as startPeriod. Accepted forms: ${PERIOD_FORMS} (e.g., "2020", "2023-Q1", "2024-01"). Extra leading zeros after the letter are dropped ("2020-W001" is sent as "2020-W01"), a day of the year is sent as three digits ("2026-D1" as "2026-D001"), YYYY-A1 is sent as YYYY, and a period of another frequency is mapped onto the dataset's own. A malformed or non-existent period (e.g., "2020-13") is rejected as invalid_period. The most effective way to shrink a bulk response: it removes period columns from the TSV rather than blanking their cells.`),
|
|
41
51
|
until_period: z
|
|
42
52
|
.string()
|
|
43
53
|
.optional()
|
|
44
|
-
.describe('End of the period range (e.g., "2024"), sent as endPeriod. Omit for data through the latest available period.'),
|
|
54
|
+
.describe('End of the period range, inclusive (e.g., "2024"), sent as endPeriod, in the same forms as since_period. Omit for data through the latest available period. The range must hold at least one day: a since_period that starts after until_period ends is rejected as invalid_period, while pairs of different frequencies are fine ("2020-06" to "2020").'),
|
|
45
55
|
preview_limit: z
|
|
46
56
|
.number()
|
|
47
57
|
.int()
|
|
@@ -49,7 +59,7 @@ export const eurostatDownloadDataset = tool('eurostat_download_dataset', {
|
|
|
49
59
|
.max(PREVIEW_MAX)
|
|
50
60
|
.default(50)
|
|
51
61
|
.describe('How many observations to echo inline, from the start of the download. Caps at 500. The full download is on the canvas table when one was staged; this is orientation, not the result set.'),
|
|
52
|
-
canvas_id: CanvasIdSchema.optional().describe('Reuse an existing dataframe canvas so this download lands beside earlier results and can be joined against them. Pass the canvasId a previous eurostat_download_dataset or eurostat_query_dataset response returned; omit to start a fresh canvas. Ignored on deployments without a dataframe canvas.'),
|
|
62
|
+
canvas_id: CanvasIdSchema.optional().describe('Reuse an existing dataframe canvas so this download lands beside earlier results and can be joined against them. Pass the canvasId a previous eurostat_download_dataset or eurostat_query_dataset response returned; omit to start a fresh canvas. Ignored on deployments without a dataframe canvas. A download that carries no observations fails as no_results and leaves the canvas untouched.'),
|
|
53
63
|
}),
|
|
54
64
|
output: z.object({
|
|
55
65
|
datasetCode: z.string().describe('Dataset code as provided.'),
|
|
@@ -86,7 +96,7 @@ export const eurostatDownloadDataset = tool('eurostat_download_dataset', {
|
|
|
86
96
|
observations: z
|
|
87
97
|
.array(z
|
|
88
98
|
.record(z.string(), z.union([z.string(), z.number(), z.null()]))
|
|
89
|
-
.describe('One observation as a flat row: one column per dimension holding its code, "time" for the period, then obs_value, obs_flag, obs_flag_label, conf_status, conf_status_label. A label column is null when Eurostat publishes no label for that code.'))
|
|
99
|
+
.describe('One observation as a flat row: one column per dimension holding its code, "time" for the period, then obs_value, obs_flag, obs_flag_label, conf_status, conf_status_label. A label column is null when Eurostat publishes no label for that code. Rows of a DS-* dataset also carry obs_value_text after obs_value: a value Eurostat published as text, such as a PRODCOM quantity unit ("KG"), kept verbatim with obs_value null; a PRODCOM ":C" arrives as conf_status "C" instead.'))
|
|
90
100
|
.describe('The first preview_limit observations of the download, in the order Eurostat streamed them. A prefix of the staged table, not a sample.'),
|
|
91
101
|
canvasId: z
|
|
92
102
|
.string()
|
|
@@ -102,12 +112,9 @@ export const eurostatDownloadDataset = tool('eurostat_download_dataset', {
|
|
|
102
112
|
.describe('Rows written to the canvas table. Matches rowCount. Omitted alongside tableName.'),
|
|
103
113
|
}),
|
|
104
114
|
enrichment: {
|
|
105
|
-
|
|
106
|
-
.
|
|
107
|
-
.
|
|
108
|
-
.describe('True when the inline observation preview omits rows.'),
|
|
109
|
-
shown: z.number().optional().describe('Observations returned in the inline preview.'),
|
|
110
|
-
cap: z.number().optional().describe('The preview_limit applied to inline observations.'),
|
|
115
|
+
totalCount: z
|
|
116
|
+
.number()
|
|
117
|
+
.describe('Observations the download produced — equal to rowCount. The inline observations array holds only the first preview_limit of them; budgetExceeded, not this count, says whether the download is the whole dataset.'),
|
|
111
118
|
appliedQuery: z
|
|
112
119
|
.object({
|
|
113
120
|
filters: z
|
|
@@ -121,7 +128,7 @@ export const eurostatDownloadDataset = tool('eurostat_download_dataset', {
|
|
|
121
128
|
notice: z
|
|
122
129
|
.string()
|
|
123
130
|
.optional()
|
|
124
|
-
.describe('Guidance
|
|
131
|
+
.describe('Guidance on every download: the staged table with the required eurostat_dataframe_describe then eurostat_dataframe_query sequence (or, without a canvas, what was returned inline and what was discarded), preceded by byte-budget disclosure when the budget stopped the transfer and by the inline-preview length when preview_limit returns fewer rows than were downloaded.'),
|
|
125
132
|
},
|
|
126
133
|
enrichmentTrailer: {
|
|
127
134
|
appliedQuery: {
|
|
@@ -157,10 +164,16 @@ export const eurostatDownloadDataset = tool('eurostat_download_dataset', {
|
|
|
157
164
|
{
|
|
158
165
|
reason: 'filter_arity',
|
|
159
166
|
code: JsonRpcErrorCode.ValidationError,
|
|
160
|
-
when: 'Eurostat rejected the positional dimension key because it carried the wrong number of positions (SDMX faultcode 140), meaning the dataset structure has changed since the metadata call.',
|
|
167
|
+
when: 'Eurostat rejected the positional dimension key because it carried the wrong number of positions (SDMX faultcode 140, INVALID_QUERY_NB_FILTERS), meaning the dataset structure has changed since the metadata call.',
|
|
161
168
|
recovery: 'Retry without filters to download the whole dataset, or re-read the dimensions with eurostat_get_dataset_info.',
|
|
162
169
|
thrownBy: 'service',
|
|
163
170
|
},
|
|
171
|
+
{
|
|
172
|
+
reason: 'invalid_period',
|
|
173
|
+
code: JsonRpcErrorCode.ValidationError,
|
|
174
|
+
when: 'since_period or until_period is not a period literal, or names a month, quarter, semester, trimester, week or day that does not exist, or since_period starts after until_period ends. Checked before any request — the bulk endpoint would otherwise roll an out-of-range period into a neighbouring one, and answer an inverted range with the whole series — and SDMX faultcode 140 TIME_PERIOD_FILTER_SPEC_INVALID maps here too.',
|
|
175
|
+
recovery: `Write since_period/until_period as ${PERIOD_FORMS}, for example "2020", "2020-01" or "2020-Q1", with since_period starting no later than until_period ends.`,
|
|
176
|
+
},
|
|
164
177
|
{
|
|
165
178
|
reason: 'async_queued',
|
|
166
179
|
code: JsonRpcErrorCode.ServiceUnavailable,
|
|
@@ -172,8 +185,16 @@ export const eurostatDownloadDataset = tool('eurostat_download_dataset', {
|
|
|
172
185
|
{
|
|
173
186
|
reason: 'no_results',
|
|
174
187
|
code: JsonRpcErrorCode.NotFound,
|
|
175
|
-
when: 'The download completed but carried no populated observation cells.',
|
|
176
|
-
recovery:
|
|
188
|
+
when: 'The download completed but carried no populated observation cells. Nothing is staged, and no canvas is created or touched.',
|
|
189
|
+
recovery: "Verify the filter values with eurostat_get_dimension_values and keep since_period/until_period inside the series' coverage — a combination that exists in no cell and a range that misses the series both download as an empty table.",
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
reason: 'extraction_too_big',
|
|
193
|
+
code: JsonRpcErrorCode.ServiceUnavailable,
|
|
194
|
+
when: 'Eurostat refused the extraction as too large (SDMX faultcode 413, HTTP 413): past its 5,000,000-row extraction limit, or an unfiltered download of a large DS-* Comext collection, which Eurostat serves only filtered. Covers every dataset, on either host.',
|
|
195
|
+
retryable: false,
|
|
196
|
+
recovery: 'Add dimension filters or a narrower since_period/until_period range to shrink the extraction; the same request fails the same way on every retry.',
|
|
197
|
+
thrownBy: 'service',
|
|
177
198
|
},
|
|
178
199
|
{
|
|
179
200
|
reason: 'upstream_fault',
|
|
@@ -192,9 +213,24 @@ export const eurostatDownloadDataset = tool('eurostat_download_dataset', {
|
|
|
192
213
|
],
|
|
193
214
|
async handler(input, ctx) {
|
|
194
215
|
const bulk = getEurostatBulkService();
|
|
195
|
-
|
|
196
|
-
|
|
216
|
+
// Trimmed, checked, and rewritten to canonical form: this is both what is sent and
|
|
217
|
+
// what appliedQuery echoes.
|
|
218
|
+
const periods = resolvePeriods({
|
|
219
|
+
since_period: input.since_period?.trim() || undefined,
|
|
220
|
+
until_period: input.until_period?.trim() || undefined,
|
|
221
|
+
});
|
|
222
|
+
if (!periods.ok) {
|
|
223
|
+
throw ctx.fail('invalid_period', periods.message, ctx.recoveryFor('invalid_period'));
|
|
224
|
+
}
|
|
225
|
+
const { since_period: sinceP, until_period: untilP } = periods;
|
|
197
226
|
const filters = Object.fromEntries(Object.entries(input.filters).filter(([, values]) => values.length > 0));
|
|
227
|
+
/**
|
|
228
|
+
* The positional key needs every dimension in order, not just the filtered
|
|
229
|
+
* ones, so a filtered download first reads the dataset's structure
|
|
230
|
+
* definition, a few KB whatever the dataset's size. An unfiltered download
|
|
231
|
+
* needs no key and skips it — the TSV header names the dimensions anyway.
|
|
232
|
+
*/
|
|
233
|
+
let dimensionOrder = [];
|
|
198
234
|
/**
|
|
199
235
|
* `ctx.fail` is typed against this tool's reason union, so the mapping stays
|
|
200
236
|
* here rather than inside a helper that would have to take a bare string.
|
|
@@ -204,61 +240,84 @@ export const eurostatDownloadDataset = tool('eurostat_download_dataset', {
|
|
|
204
240
|
return await run();
|
|
205
241
|
}
|
|
206
242
|
catch (err) {
|
|
207
|
-
const mapped = mapBulkFailure(err, input.dataset_code
|
|
243
|
+
const mapped = mapBulkFailure(err, input.dataset_code, {
|
|
244
|
+
dimensions: err.data?.reason === 'extraction_too_big'
|
|
245
|
+
? await dimensionsToNarrow(input.dataset_code, dimensionOrder, ctx)
|
|
246
|
+
: undefined,
|
|
247
|
+
filtered: Object.keys(filters),
|
|
248
|
+
});
|
|
208
249
|
if (!mapped)
|
|
209
250
|
throw err;
|
|
210
251
|
throw ctx.fail(mapped.reason, mapped.message, { recovery: { hint: mapped.hint } });
|
|
211
252
|
}
|
|
212
253
|
};
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
* unfiltered download needs no key and skips it — the TSV header names the
|
|
217
|
-
* dimensions anyway.
|
|
218
|
-
*/
|
|
219
|
-
const dimensionOrder = Object.keys(filters).length > 0
|
|
220
|
-
? await asContract(() => getEurostatDataService().getDimensionOrder(input.dataset_code, ctx))
|
|
221
|
-
: [];
|
|
254
|
+
if (Object.keys(filters).length > 0) {
|
|
255
|
+
dimensionOrder = await asContract(() => getEurostatDataService().getDimensionOrder(input.dataset_code, ctx));
|
|
256
|
+
}
|
|
222
257
|
const download = await asContract(() => bulk.startDownload(input.dataset_code, dimensionOrder, filters, sinceP, untilP, ctx));
|
|
223
258
|
const preview = [];
|
|
224
|
-
const
|
|
225
|
-
const canvas = getCanvas();
|
|
259
|
+
const rows = teePreview(download.rows(), preview, input.preview_limit);
|
|
226
260
|
let staged;
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
canvasId: instance.canvasId,
|
|
235
|
-
stagedRowCount: handle.rowCount,
|
|
236
|
-
tableName: handle.tableName,
|
|
237
|
-
};
|
|
238
|
-
}
|
|
239
|
-
else {
|
|
261
|
+
/**
|
|
262
|
+
* Every exit closes the row stream, which cancels the response body. A
|
|
263
|
+
* canvas that cannot be acquired, or a staging call that fails before it
|
|
264
|
+
* pulls a row, would otherwise leave the body undrained and its connection
|
|
265
|
+
* held. Closing a stream that already ended is a no-op.
|
|
266
|
+
*/
|
|
267
|
+
try {
|
|
240
268
|
/**
|
|
241
|
-
*
|
|
242
|
-
*
|
|
243
|
-
*
|
|
244
|
-
*
|
|
245
|
-
* at preview_limit instead would report a row count of 50 for a dataset of
|
|
246
|
-
* millions.
|
|
269
|
+
* Nothing touches a canvas until the stream has yielded a row. An empty
|
|
270
|
+
* download would otherwise mint a canvas the error never names, or add a
|
|
271
|
+
* 0-row table to the caller's, before failing — so it fails here instead,
|
|
272
|
+
* exactly as it does on a deployment without a canvas.
|
|
247
273
|
*/
|
|
248
|
-
|
|
249
|
-
|
|
274
|
+
const first = await rows.next();
|
|
275
|
+
if (first.done) {
|
|
276
|
+
const range = [
|
|
277
|
+
...(sinceP ? [`since_period "${sinceP}"`] : []),
|
|
278
|
+
...(untilP ? [`until_period "${untilP}"`] : []),
|
|
279
|
+
].join(' and ');
|
|
280
|
+
throw ctx.fail('no_results', `The download of "${input.dataset_code}" carried no observations.`, {
|
|
281
|
+
recovery: {
|
|
282
|
+
hint: `Eurostat returned a table with no populated cells for "${input.dataset_code}". Verify the filter values with eurostat_get_dimension_values — a combination present in no cell downloads as an empty body.${range
|
|
283
|
+
? ` Also check that ${range} overlaps the series: a range outside the series' own coverage downloads as an empty table too.`
|
|
284
|
+
: ''}`,
|
|
285
|
+
},
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
const canvas = getCanvas();
|
|
289
|
+
if (canvas) {
|
|
290
|
+
const instance = await acquireCanvas(canvas, input.canvas_id, ctx);
|
|
291
|
+
const handle = await instance.registerTable(newTableName(), prepend(first.value, rows), {
|
|
292
|
+
schema: bulkRowSchema(download.header.dimensions, download.valueText),
|
|
293
|
+
signal: ctx.signal,
|
|
294
|
+
});
|
|
295
|
+
staged = {
|
|
296
|
+
canvasId: instance.canvasId,
|
|
297
|
+
stagedRowCount: handle.rowCount,
|
|
298
|
+
tableName: handle.tableName,
|
|
299
|
+
};
|
|
250
300
|
}
|
|
301
|
+
else {
|
|
302
|
+
/**
|
|
303
|
+
* Without a canvas the stream still runs to completion. Everything past
|
|
304
|
+
* the preview is discarded, but the counts, the period coverage and the
|
|
305
|
+
* budget verdict then describe the download rather than describing the
|
|
306
|
+
* preview — and the byte budget still bounds what is transferred. Stopping
|
|
307
|
+
* at preview_limit instead would report a row count of 50 for a dataset of
|
|
308
|
+
* millions.
|
|
309
|
+
*/
|
|
310
|
+
for await (const _row of rows) {
|
|
311
|
+
// Drained for its counters; nothing is retained past the preview.
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
finally {
|
|
316
|
+
await rows.return(undefined);
|
|
251
317
|
}
|
|
252
318
|
const { stats } = download;
|
|
253
319
|
const firstPeriod = stats.periodsSeen[0];
|
|
254
320
|
const lastPeriod = stats.periodsSeen.at(-1);
|
|
255
|
-
if (stats.rowCount === 0) {
|
|
256
|
-
throw ctx.fail('no_results', `The download of "${input.dataset_code}" carried no observations.`, {
|
|
257
|
-
recovery: {
|
|
258
|
-
hint: `Eurostat returned a table with no populated cells for "${input.dataset_code}". Verify the filter values with eurostat_get_dimension_values — a combination present in no cell downloads as an empty body.`,
|
|
259
|
-
},
|
|
260
|
-
});
|
|
261
|
-
}
|
|
262
321
|
ctx.log.info('Bulk download complete', {
|
|
263
322
|
datasetCode: input.dataset_code,
|
|
264
323
|
rowCount: stats.rowCount,
|
|
@@ -275,21 +334,37 @@ export const eurostatDownloadDataset = tool('eurostat_download_dataset', {
|
|
|
275
334
|
url: download.url,
|
|
276
335
|
},
|
|
277
336
|
});
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
337
|
+
/**
|
|
338
|
+
* A preview shorter than the download is disclosed as a notice and a total, not
|
|
339
|
+
* as `truncated`: the download itself is complete, and `budgetExceeded` is what
|
|
340
|
+
* says when it is not. `notice` is last-wins, so every sentence is collected and
|
|
341
|
+
* written once.
|
|
342
|
+
*/
|
|
343
|
+
ctx.enrich.total(stats.rowCount);
|
|
281
344
|
const notices = [];
|
|
282
345
|
if (stats.budgetExceeded) {
|
|
283
346
|
notices.push(`The byte budget stopped this transfer after ${stats.bytesRead.toLocaleString()} bytes, so these ${stats.rowCount.toLocaleString()} observations are the start of "${input.dataset_code}" and not all of it. Add dimension filters or a since_period/until_period range to fit the dataset inside the budget, or raise EUROSTAT_BULK_MAX_BYTES.`);
|
|
284
347
|
}
|
|
348
|
+
if (stats.rowCount > preview.length) {
|
|
349
|
+
notices.push(`preview_limit=${input.preview_limit.toLocaleString()} returns the first ${preview.length.toLocaleString()} of ${stats.rowCount.toLocaleString()} downloaded observations inline; it does not reduce the download.`);
|
|
350
|
+
}
|
|
285
351
|
if (!staged) {
|
|
286
|
-
|
|
352
|
+
/**
|
|
353
|
+
* A narrower query is advice only for a download larger than preview_limit can
|
|
354
|
+
* ever return; below that, the rows either all came back or are one larger
|
|
355
|
+
* preview_limit away.
|
|
356
|
+
*/
|
|
357
|
+
const discarded = stats.rowCount - preview.length;
|
|
358
|
+
notices.push(discarded === 0
|
|
359
|
+
? `This deployment runs without a dataframe canvas, so nothing was staged; all ${stats.rowCount.toLocaleString()} downloaded observations are returned inline.`
|
|
360
|
+
: `This deployment runs without a dataframe canvas, so nothing was staged: only the ${preview.length.toLocaleString()} observations returned inline are retained, and the other ${discarded.toLocaleString()} were counted and discarded. ${stats.rowCount <= PREVIEW_MAX
|
|
361
|
+
? `Raise preview_limit to ${stats.rowCount.toLocaleString()} to return every one inline, or set CANVAS_PROVIDER_TYPE=duckdb to keep the download.`
|
|
362
|
+
: 'Set CANVAS_PROVIDER_TYPE=duckdb to keep the download, or use eurostat_query_dataset with dimension filters to fetch a slice small enough to return whole.'}`);
|
|
287
363
|
}
|
|
288
364
|
else {
|
|
289
365
|
notices.push(`All ${staged.stagedRowCount.toLocaleString()} downloaded observations are staged as table "${staged.tableName}" on canvas "${staged.canvasId}". Call eurostat_dataframe_describe with that canvas_id first to confirm the table and columns, then call eurostat_dataframe_query.`);
|
|
290
366
|
}
|
|
291
|
-
|
|
292
|
-
ctx.enrich.notice(notices.join(' '));
|
|
367
|
+
ctx.enrich.notice(notices.join(' '));
|
|
293
368
|
return {
|
|
294
369
|
datasetCode: input.dataset_code,
|
|
295
370
|
dimensionsUsed: download.header.dimensions,
|
|
@@ -336,11 +411,21 @@ export const eurostatDownloadDataset = tool('eurostat_download_dataset', {
|
|
|
336
411
|
* This resolves the reason and the caller-facing recovery hint; the handler does
|
|
337
412
|
* the throwing, so `ctx.fail` stays typed against the union rather than taking a
|
|
338
413
|
* bare string.
|
|
414
|
+
*
|
|
415
|
+
* `request.dimensions` is the dataset's dimension list, when known, and
|
|
416
|
+
* `request.filtered` the dimensions the request already filtered — together they let
|
|
417
|
+
* a too-large refusal name the dimensions still open to filter.
|
|
339
418
|
*/
|
|
340
|
-
|
|
419
|
+
function mapBulkFailure(err, datasetCode, request) {
|
|
341
420
|
const reason = err.data?.reason;
|
|
342
421
|
const message = err.message;
|
|
343
422
|
switch (reason) {
|
|
423
|
+
case 'extraction_too_big':
|
|
424
|
+
return {
|
|
425
|
+
reason,
|
|
426
|
+
message,
|
|
427
|
+
hint: `Eurostat will not serve this extraction of "${datasetCode}" whole, and retrying repeats the refusal. ${narrowingAdvice(datasetCode, request.dimensions, request.filtered)}`,
|
|
428
|
+
};
|
|
344
429
|
case 'not_found':
|
|
345
430
|
return {
|
|
346
431
|
reason,
|
|
@@ -359,6 +444,12 @@ export function mapBulkFailure(err, datasetCode) {
|
|
|
359
444
|
message,
|
|
360
445
|
hint: `Eurostat expected a different number of filter positions for "${datasetCode}" than its metadata reports. Re-read the dimensions with eurostat_get_dataset_info, or retry without filters.`,
|
|
361
446
|
};
|
|
447
|
+
case 'invalid_period':
|
|
448
|
+
return {
|
|
449
|
+
reason,
|
|
450
|
+
message,
|
|
451
|
+
hint: `Eurostat could not read the period range for "${datasetCode}". Write since_period/until_period as ${PERIOD_FORMS}, for example "2020", "2020-01" or "2020-Q1".`,
|
|
452
|
+
};
|
|
362
453
|
case 'async_queued':
|
|
363
454
|
return {
|
|
364
455
|
reason,
|