euparliamentmonitor 0.8.46 → 0.8.48
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/README.md +1 -1
- package/package.json +4 -3
- package/scripts/aggregator/analysis-aggregator.d.ts +25 -0
- package/scripts/aggregator/analysis-aggregator.js +27 -1
- package/scripts/aggregator/article-generator.js +13 -5
- package/scripts/aggregator/article-html.js +9 -5
- package/scripts/aggregator/article-metadata.d.ts +17 -0
- package/scripts/aggregator/article-metadata.js +32 -3
- package/scripts/aggregator/forward-statements-registry.js +506 -0
- package/scripts/aggregator/prior-run-diff.js +357 -0
- package/scripts/constants/config.d.ts +11 -0
- package/scripts/constants/config.js +31 -0
- package/scripts/constants/language-articles.js +14 -14
- package/scripts/mcp/ep-mcp-client.d.ts +74 -13
- package/scripts/mcp/ep-mcp-client.js +202 -17
- package/scripts/mcp/ep-open-data-client.d.ts +265 -0
- package/scripts/mcp/ep-open-data-client.js +446 -0
- package/scripts/mcp/imf-mcp-client.d.ts +12 -0
- package/scripts/mcp/imf-mcp-client.js +44 -1
- package/scripts/types/mcp.d.ts +1 -1
- package/scripts/validate-analysis-completeness.js +218 -0
package/README.md
CHANGED
|
@@ -163,7 +163,7 @@ The published platform at **[euparliamentmonitor.com](https://euparliamentmonito
|
|
|
163
163
|
|
|
164
164
|
**MCP Server Integration**: The project uses the
|
|
165
165
|
[European-Parliament-MCP-Server](https://github.com/Hack23/European-Parliament-MCP-Server)
|
|
166
|
-
v1.2.
|
|
166
|
+
v1.2.15 for accessing real EU Parliament data via the Model Context Protocol.
|
|
167
167
|
|
|
168
168
|
- **MCP Server Status**: ✅ Fully operational — 60+ EP data tools available
|
|
169
169
|
(feeds, direct lookups, analytical tools, intelligence correlation)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "euparliamentmonitor",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.48",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "European Parliament Intelligence Platform - Monitor political activity with systematic transparency",
|
|
6
6
|
"main": "scripts/index.js",
|
|
@@ -62,6 +62,7 @@
|
|
|
62
62
|
"build:check-tests": "tsc --project tsconfig.test.json --noEmit",
|
|
63
63
|
"copy-vendor": "node scripts/copy-vendor.js",
|
|
64
64
|
"validate-analysis": "node scripts/validate-analysis-completeness.js",
|
|
65
|
+
"prior-run-diff": "node scripts/aggregator/prior-run-diff.js",
|
|
65
66
|
"generate-article": "node scripts/aggregator/article-generator.js",
|
|
66
67
|
"generate-article:all": "node scripts/aggregator/article-generator.js --all",
|
|
67
68
|
"generate-news-indexes": "node scripts/generators/news-indexes.js",
|
|
@@ -158,7 +159,7 @@
|
|
|
158
159
|
"husky": "9.1.7",
|
|
159
160
|
"jscpd": "4.0.9",
|
|
160
161
|
"lint-staged": "16.4.0",
|
|
161
|
-
"mermaid": "
|
|
162
|
+
"mermaid": "11.14.0",
|
|
162
163
|
"papaparse": "5.5.3",
|
|
163
164
|
"prettier": "3.8.3",
|
|
164
165
|
"ts-api-utils": "2.5.0",
|
|
@@ -171,7 +172,7 @@
|
|
|
171
172
|
"node": ">=25"
|
|
172
173
|
},
|
|
173
174
|
"dependencies": {
|
|
174
|
-
"european-parliament-mcp-server": "1.2.
|
|
175
|
+
"european-parliament-mcp-server": "1.2.15",
|
|
175
176
|
"markdown-it": "^14.1.1",
|
|
176
177
|
"markdown-it-anchor": "^9.2.0",
|
|
177
178
|
"markdown-it-attrs": "^4.3.1",
|
|
@@ -2,6 +2,17 @@ import { type ArtifactSection } from './artifact-order.js';
|
|
|
2
2
|
/** Raw manifest shape as committed by the analysis pipeline. */
|
|
3
3
|
export interface AnalysisManifest {
|
|
4
4
|
readonly articleType: string;
|
|
5
|
+
/**
|
|
6
|
+
* Legacy plural variant emitted by some pre-aggregator-pipeline workflows.
|
|
7
|
+
* Used as a fallback when `articleType` is absent so historic runs with
|
|
8
|
+
* `articleTypes: ["<slug>"]` can still be aggregated.
|
|
9
|
+
*/
|
|
10
|
+
readonly articleTypes?: readonly string[];
|
|
11
|
+
/**
|
|
12
|
+
* Legacy field emitted by older breaking-run manifests. Used as the last
|
|
13
|
+
* fallback when neither `articleType` nor `articleTypes` is present.
|
|
14
|
+
*/
|
|
15
|
+
readonly runType?: string;
|
|
5
16
|
readonly runId?: string;
|
|
6
17
|
readonly date?: string;
|
|
7
18
|
readonly analysisDir?: string;
|
|
@@ -167,6 +178,20 @@ export declare function renderAnalysisIndex(included: readonly IncludedArtifact[
|
|
|
167
178
|
* @returns Markdown block containing the guide table
|
|
168
179
|
*/
|
|
169
180
|
export declare function renderReaderIntelligenceGuide(sections: readonly TocSection[], included: readonly IncludedArtifact[]): string;
|
|
181
|
+
/**
|
|
182
|
+
* Resolve the article-type slug from a manifest, tolerating legacy schemas.
|
|
183
|
+
*
|
|
184
|
+
* Resolution order (highest precedence first):
|
|
185
|
+
* 1. `articleType` — canonical singular field
|
|
186
|
+
* 2. `articleTypes[0]` — pre-aggregator-pipeline plural array
|
|
187
|
+
* 3. `runType` — legacy field on older breaking-run manifests
|
|
188
|
+
*
|
|
189
|
+
* Falls back to `'unknown'` when none of the above is a non-empty string.
|
|
190
|
+
*
|
|
191
|
+
* @param manifest - Parsed manifest (any of the supported schemas)
|
|
192
|
+
* @returns Article-type slug usable as a filename component
|
|
193
|
+
*/
|
|
194
|
+
export declare function resolveArticleTypeFromManifest(manifest: AnalysisManifest): string;
|
|
170
195
|
/**
|
|
171
196
|
* Read, clean, and concatenate every artifact declared by the run's manifest
|
|
172
197
|
* (with discovery fallback when manifest.files is missing), returning a
|
|
@@ -471,6 +471,32 @@ function appendSection(runDir, runDirRelPath, sectionId, sectionTitle, paths, se
|
|
|
471
471
|
}
|
|
472
472
|
sectionMarkdown.push('');
|
|
473
473
|
}
|
|
474
|
+
/**
|
|
475
|
+
* Resolve the article-type slug from a manifest, tolerating legacy schemas.
|
|
476
|
+
*
|
|
477
|
+
* Resolution order (highest precedence first):
|
|
478
|
+
* 1. `articleType` — canonical singular field
|
|
479
|
+
* 2. `articleTypes[0]` — pre-aggregator-pipeline plural array
|
|
480
|
+
* 3. `runType` — legacy field on older breaking-run manifests
|
|
481
|
+
*
|
|
482
|
+
* Falls back to `'unknown'` when none of the above is a non-empty string.
|
|
483
|
+
*
|
|
484
|
+
* @param manifest - Parsed manifest (any of the supported schemas)
|
|
485
|
+
* @returns Article-type slug usable as a filename component
|
|
486
|
+
*/
|
|
487
|
+
export function resolveArticleTypeFromManifest(manifest) {
|
|
488
|
+
if (typeof manifest.articleType === 'string' && manifest.articleType) {
|
|
489
|
+
return manifest.articleType;
|
|
490
|
+
}
|
|
491
|
+
const first = manifest.articleTypes?.[0];
|
|
492
|
+
if (typeof first === 'string' && first) {
|
|
493
|
+
return first;
|
|
494
|
+
}
|
|
495
|
+
if (typeof manifest.runType === 'string' && manifest.runType) {
|
|
496
|
+
return manifest.runType;
|
|
497
|
+
}
|
|
498
|
+
return 'unknown';
|
|
499
|
+
}
|
|
474
500
|
/**
|
|
475
501
|
* Read, clean, and concatenate every artifact declared by the run's manifest
|
|
476
502
|
* (with discovery fallback when manifest.files is missing), returning a
|
|
@@ -516,7 +542,7 @@ export function aggregateAnalysisRun(options) {
|
|
|
516
542
|
consumed.add(p);
|
|
517
543
|
}
|
|
518
544
|
const tradecraftFiles = options.tradecraftFiles ?? discoverTradecraftFiles(repoRoot);
|
|
519
|
-
const articleType = manifest
|
|
545
|
+
const articleType = resolveArticleTypeFromManifest(manifest);
|
|
520
546
|
const date = manifest.date ?? guessDateFromRunDir(runDirRelPath);
|
|
521
547
|
const runId = manifest.runId ?? path.basename(runDir);
|
|
522
548
|
const gateResult = latestGateResult(manifest);
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
import fs from 'fs';
|
|
20
20
|
import path from 'path';
|
|
21
21
|
import { pathToFileURL } from 'url';
|
|
22
|
-
import { aggregateAnalysisRun } from './analysis-aggregator.js';
|
|
22
|
+
import { aggregateAnalysisRun, resolveArticleTypeFromManifest, } from './analysis-aggregator.js';
|
|
23
23
|
import { resolveArticleMetadata, extractStrongProseLine, } from './article-metadata.js';
|
|
24
24
|
import { renderMarkdown } from './markdown-renderer.js';
|
|
25
25
|
import { wrapArticleHtml, getArticleFilename } from './article-html.js';
|
|
@@ -528,14 +528,21 @@ function readRunCandidate(runDir, manifestPath) {
|
|
|
528
528
|
catch {
|
|
529
529
|
return null;
|
|
530
530
|
}
|
|
531
|
-
|
|
531
|
+
// Resolve via the same precedence used by the aggregator (articleType →
|
|
532
|
+
// articleTypes[0] → runType) so legacy-schema manifests are picked up by
|
|
533
|
+
// batch mode rather than silently skipped.
|
|
534
|
+
const articleType = resolveArticleTypeFromManifest(parsed);
|
|
532
535
|
if (!articleType || articleType === 'unknown')
|
|
533
536
|
return null;
|
|
534
537
|
const dateFromManifest = typeof parsed.date === 'string' ? parsed.date : '';
|
|
535
538
|
const date = /^\d{4}-\d{2}-\d{2}$/.test(dateFromManifest)
|
|
536
539
|
? dateFromManifest
|
|
537
540
|
: dateFromRunPath(runDir);
|
|
538
|
-
const runId = typeof parsed.runId === 'string' && parsed.runId
|
|
541
|
+
const runId = typeof parsed.runId === 'string' && parsed.runId
|
|
542
|
+
? parsed.runId
|
|
543
|
+
: typeof parsed.runId === 'number'
|
|
544
|
+
? String(parsed.runId)
|
|
545
|
+
: path.basename(runDir);
|
|
539
546
|
return { runDir, articleType, date, runId };
|
|
540
547
|
}
|
|
541
548
|
/**
|
|
@@ -609,8 +616,9 @@ function readManifestMetadata(runDir) {
|
|
|
609
616
|
try {
|
|
610
617
|
const parsed = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
|
|
611
618
|
const manifest = {};
|
|
612
|
-
|
|
613
|
-
|
|
619
|
+
const resolvedType = resolveArticleTypeFromManifest(parsed);
|
|
620
|
+
if (resolvedType && resolvedType !== 'unknown') {
|
|
621
|
+
Object.assign(manifest, { articleType: resolvedType });
|
|
614
622
|
}
|
|
615
623
|
if (typeof parsed.date === 'string') {
|
|
616
624
|
Object.assign(manifest, { date: parsed.date });
|
|
@@ -10,11 +10,15 @@
|
|
|
10
10
|
* consistent with the rest of the site.
|
|
11
11
|
*
|
|
12
12
|
* The output is a complete HTML5 document. No inline `<script>` is emitted
|
|
13
|
-
* in the body. Mermaid is loaded from the vendored ESM bundle
|
|
14
|
-
*
|
|
15
|
-
*
|
|
13
|
+
* in the body. Mermaid is loaded from the same-origin vendored ESM bundle
|
|
14
|
+
* (copied to `js/vendor/mermaid/` by `scripts/copy-vendor.js`) via
|
|
15
|
+
* `<script type="module" src="../js/mermaid-init.js?v=<MERMAID_VERSION>" defer>`
|
|
16
|
+
* so CSP stays `script-src 'self'`. The `?v=` query parameter is sourced
|
|
17
|
+
* from `devDependencies.mermaid` in `package.json` (a fixed pin like
|
|
18
|
+
* `11.14.0`); regenerating articles after a Mermaid bump invalidates
|
|
19
|
+
* browser and CloudFront caches automatically.
|
|
16
20
|
*/
|
|
17
|
-
import { BASE_URL, createThemeToggleButton, THEME_TOGGLE_SCRIPT } from '../constants/config.js';
|
|
21
|
+
import { BASE_URL, createThemeToggleButton, MERMAID_VERSION, THEME_TOGGLE_SCRIPT, } from '../constants/config.js';
|
|
18
22
|
import { ALL_LANGUAGES, LANGUAGE_NAMES, LANGUAGE_FLAGS, PAGE_TITLES, SKIP_LINK_TEXTS, HEADER_SUBTITLE_LABELS, THEME_TOGGLE_LABELS, TOC_ARIA_LABELS, getLocalizedString, getTextDirection, } from '../constants/languages.js';
|
|
19
23
|
import { escapeHTML } from '../utils/file-utils.js';
|
|
20
24
|
import { buildSiteFooter } from '../templates/section-builders.js';
|
|
@@ -181,7 +185,7 @@ ${hreflangLinks}
|
|
|
181
185
|
<meta name="theme-color" content="#003399">
|
|
182
186
|
<link rel="stylesheet" href="../styles.css">
|
|
183
187
|
<script type="application/ld+json">${jsonLdString}</script>
|
|
184
|
-
<script type="module" src="../js/mermaid-init.js" defer></script>
|
|
188
|
+
<script type="module" src="../js/mermaid-init.js?v=${MERMAID_VERSION}" defer></script>
|
|
185
189
|
</head>
|
|
186
190
|
<body>
|
|
187
191
|
<a href="#main" class="skip-link">${escapeHTML(skipLinkText)}</a>
|
|
@@ -165,6 +165,23 @@ export declare function deriveWeekRange(date: string): {
|
|
|
165
165
|
readonly start: string;
|
|
166
166
|
readonly end: string;
|
|
167
167
|
};
|
|
168
|
+
/**
|
|
169
|
+
* Return the D-36 → D-8 reporting window for the `week-in-review`
|
|
170
|
+
* article type. EP roll-call voting data is published with a 2–6 week
|
|
171
|
+
* lag, so using the most-recent 7 days structurally produces a
|
|
172
|
+
* vote-empty dataset. Shifting 8 days back and widening to 28 days
|
|
173
|
+
* (start = D-36, end = D-8) ensures the window always contains at
|
|
174
|
+
* least one full EP plenary week with published roll-call data
|
|
175
|
+
* (ADR-006). Direction is consistent with the workflow's
|
|
176
|
+
* `DATE_FROM` (start = D-36) → `DATE_TO` (end = D-8) variables.
|
|
177
|
+
*
|
|
178
|
+
* @param date - ISO article date string (`YYYY-MM-DD`) — typically TODAY
|
|
179
|
+
* @returns `{ start: D-36, end: D-8 }` both as `YYYY-MM-DD` ISO strings
|
|
180
|
+
*/
|
|
181
|
+
export declare function deriveReportingWindowForWeekInReview(date: string): {
|
|
182
|
+
readonly start: string;
|
|
183
|
+
readonly end: string;
|
|
184
|
+
};
|
|
168
185
|
/**
|
|
169
186
|
* Return a human-friendly month label for an ISO date — English month
|
|
170
187
|
* name + four-digit year (e.g. `April 2026`). The non-English template
|
|
@@ -447,7 +447,12 @@ function safeReaddir(dir) {
|
|
|
447
447
|
*/
|
|
448
448
|
export function buildTemplateFallback(articleType, date, committee) {
|
|
449
449
|
const map = Object.create(null);
|
|
450
|
-
|
|
450
|
+
// week-in-review uses the D-36→D-8 reporting window (ADR-006) so that
|
|
451
|
+
// EP roll-call voting data — published 2–6 weeks after the sitting —
|
|
452
|
+
// is always available in the analysis window.
|
|
453
|
+
const weekRange = articleType === 'week-in-review'
|
|
454
|
+
? deriveReportingWindowForWeekInReview(date)
|
|
455
|
+
: deriveWeekRange(date);
|
|
451
456
|
const monthLabel = deriveMonthLabel(date);
|
|
452
457
|
const committeeLabel = committee && committee.trim().length > 0 ? committee : 'Main Committees';
|
|
453
458
|
for (const lang of ALL_LANGUAGES) {
|
|
@@ -503,6 +508,8 @@ function templateForType(lang, articleType, inputs) {
|
|
|
503
508
|
};
|
|
504
509
|
}
|
|
505
510
|
}
|
|
511
|
+
/** Milliseconds in one UTC day — used by date-window derivation helpers. */
|
|
512
|
+
const MS_PER_DAY = 86_400_000;
|
|
506
513
|
/**
|
|
507
514
|
* Parse an ISO date and return the `[start, end]` week range as ISO
|
|
508
515
|
* strings. Week starts on Monday and ends on the following Sunday.
|
|
@@ -518,10 +525,32 @@ export function deriveWeekRange(date) {
|
|
|
518
525
|
const day = parsed.getUTCDay();
|
|
519
526
|
// Shift so Monday = 0, Sunday = 6.
|
|
520
527
|
const shift = (day + 6) % 7;
|
|
521
|
-
const startMs = parsed.getTime() - shift *
|
|
522
|
-
const endMs = startMs + 6 *
|
|
528
|
+
const startMs = parsed.getTime() - shift * MS_PER_DAY;
|
|
529
|
+
const endMs = startMs + 6 * MS_PER_DAY;
|
|
523
530
|
return { start: formatIsoDate(new Date(startMs)), end: formatIsoDate(new Date(endMs)) };
|
|
524
531
|
}
|
|
532
|
+
/**
|
|
533
|
+
* Return the D-36 → D-8 reporting window for the `week-in-review`
|
|
534
|
+
* article type. EP roll-call voting data is published with a 2–6 week
|
|
535
|
+
* lag, so using the most-recent 7 days structurally produces a
|
|
536
|
+
* vote-empty dataset. Shifting 8 days back and widening to 28 days
|
|
537
|
+
* (start = D-36, end = D-8) ensures the window always contains at
|
|
538
|
+
* least one full EP plenary week with published roll-call data
|
|
539
|
+
* (ADR-006). Direction is consistent with the workflow's
|
|
540
|
+
* `DATE_FROM` (start = D-36) → `DATE_TO` (end = D-8) variables.
|
|
541
|
+
*
|
|
542
|
+
* @param date - ISO article date string (`YYYY-MM-DD`) — typically TODAY
|
|
543
|
+
* @returns `{ start: D-36, end: D-8 }` both as `YYYY-MM-DD` ISO strings
|
|
544
|
+
*/
|
|
545
|
+
export function deriveReportingWindowForWeekInReview(date) {
|
|
546
|
+
const parsed = parseIsoDate(date);
|
|
547
|
+
if (!parsed)
|
|
548
|
+
return { start: date, end: date };
|
|
549
|
+
return {
|
|
550
|
+
start: formatIsoDate(new Date(parsed.getTime() - 36 * MS_PER_DAY)),
|
|
551
|
+
end: formatIsoDate(new Date(parsed.getTime() - 8 * MS_PER_DAY)),
|
|
552
|
+
};
|
|
553
|
+
}
|
|
525
554
|
/**
|
|
526
555
|
* Return a human-friendly month label for an ISO date — English month
|
|
527
556
|
* name + four-digit year (e.g. `April 2026`). The non-English template
|