euparliamentmonitor 0.8.19 → 0.8.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/package.json +7 -7
  2. package/scripts/constants/language-articles.d.ts +4 -0
  3. package/scripts/constants/language-articles.js +20 -0
  4. package/scripts/constants/language-ui.d.ts +8 -8
  5. package/scripts/constants/language-ui.js +64 -64
  6. package/scripts/constants/languages.d.ts +2 -2
  7. package/scripts/constants/languages.js +2 -2
  8. package/scripts/generators/news-enhanced.js +13 -3
  9. package/scripts/generators/pipeline/analysis-classification.d.ts +49 -0
  10. package/scripts/generators/pipeline/analysis-classification.js +333 -0
  11. package/scripts/generators/pipeline/analysis-existing.d.ts +67 -0
  12. package/scripts/generators/pipeline/analysis-existing.js +547 -0
  13. package/scripts/generators/pipeline/analysis-helpers.d.ts +140 -0
  14. package/scripts/generators/pipeline/analysis-helpers.js +266 -0
  15. package/scripts/generators/pipeline/analysis-risk.d.ts +49 -0
  16. package/scripts/generators/pipeline/analysis-risk.js +417 -0
  17. package/scripts/generators/pipeline/analysis-stage.d.ts +19 -39
  18. package/scripts/generators/pipeline/analysis-stage.js +219 -1704
  19. package/scripts/generators/pipeline/analysis-threats.d.ts +41 -0
  20. package/scripts/generators/pipeline/analysis-threats.js +142 -0
  21. package/scripts/generators/pipeline/fetch-stage.d.ts +25 -15
  22. package/scripts/generators/pipeline/fetch-stage.js +293 -117
  23. package/scripts/generators/strategies/article-strategy.d.ts +126 -7
  24. package/scripts/generators/strategies/article-strategy.js +491 -1
  25. package/scripts/generators/strategies/breaking-news-strategy.js +98 -8
  26. package/scripts/generators/strategies/committee-reports-strategy.js +23 -2
  27. package/scripts/generators/strategies/month-ahead-strategy.js +23 -2
  28. package/scripts/generators/strategies/monthly-review-strategy.js +13 -1
  29. package/scripts/generators/strategies/motions-strategy.js +15 -1
  30. package/scripts/generators/strategies/propositions-strategy.js +15 -1
  31. package/scripts/generators/strategies/week-ahead-strategy.js +19 -1
  32. package/scripts/generators/strategies/weekly-review-strategy.js +17 -1
  33. package/scripts/generators/synthesis-summary.d.ts +93 -0
  34. package/scripts/generators/synthesis-summary.js +364 -0
  35. package/scripts/index.d.ts +5 -2
  36. package/scripts/index.js +6 -1
  37. package/scripts/mcp/ep-mcp-client.d.ts +34 -1
  38. package/scripts/mcp/ep-mcp-client.js +110 -2
  39. package/scripts/mcp/mcp-connection.d.ts +3 -1
  40. package/scripts/mcp/mcp-connection.js +35 -4
  41. package/scripts/templates/article-template.js +24 -22
  42. package/scripts/templates/section-builders.js +2 -5
  43. package/scripts/types/index.d.ts +2 -1
  44. package/scripts/types/mcp.d.ts +7 -0
  45. package/scripts/types/political-classification.d.ts +1 -1
  46. package/scripts/types/quality.d.ts +9 -6
  47. package/scripts/types/significance.d.ts +130 -0
  48. package/scripts/types/significance.js +4 -0
  49. package/scripts/utils/article-quality-scorer.d.ts +13 -11
  50. package/scripts/utils/article-quality-scorer.js +36 -23
  51. package/scripts/utils/file-utils.d.ts +2 -2
  52. package/scripts/utils/file-utils.js +2 -2
  53. package/scripts/utils/html-sanitize.d.ts +10 -0
  54. package/scripts/utils/html-sanitize.js +32 -0
  55. package/scripts/utils/political-classification.d.ts +8 -7
  56. package/scripts/utils/political-classification.js +8 -7
  57. package/scripts/utils/political-risk-assessment.d.ts +1 -1
  58. package/scripts/utils/political-risk-assessment.js +1 -1
  59. package/scripts/utils/significance-scoring.d.ts +97 -0
  60. package/scripts/utils/significance-scoring.js +190 -0
@@ -157,9 +157,9 @@ function claimDir(dirPath) {
157
157
  * `mkdirSync`, preventing TOCTOU races when concurrent workflow runs
158
158
  * attempt to claim the same candidate.
159
159
  *
160
- * @param baseDir - The preferred directory path (e.g. `analysis/2026-04-02/breaking`)
160
+ * @param baseDir - The preferred directory path (e.g. `analysis/daily/2026-04-02/breaking`)
161
161
  * @returns The original `baseDir` when no completed run exists there, or a
162
- * suffixed variant (e.g. `analysis/2026-04-02/breaking-2`) otherwise.
162
+ * suffixed variant (e.g. `analysis/daily/2026-04-02/breaking-2`) otherwise.
163
163
  */
164
164
  export function resolveUniqueAnalysisDir(baseDir) {
165
165
  // If the directory doesn't exist yet or has no manifest from a prior
@@ -14,5 +14,15 @@
14
14
  * @param html - HTML string to strip
15
15
  * @returns The HTML with script blocks replaced by spaces
16
16
  */
17
+ /**
18
+ * Strip all HTML tags from a string, replacing each tag with a single space.
19
+ *
20
+ * Uses iterative index-based scanning instead of regex to avoid polynomial
21
+ * backtracking (CodeQL `js/polynomial-redos`).
22
+ *
23
+ * @param html - HTML string to strip
24
+ * @returns The text content with tags replaced by spaces
25
+ */
26
+ export declare function stripHtmlTags(html: string): string;
17
27
  export declare function stripScriptBlocks(html: string): string;
18
28
  //# sourceMappingURL=html-sanitize.d.ts.map
@@ -16,6 +16,38 @@
16
16
  * @param html - HTML string to strip
17
17
  * @returns The HTML with script blocks replaced by spaces
18
18
  */
19
+ /**
20
+ * Strip all HTML tags from a string, replacing each tag with a single space.
21
+ *
22
+ * Uses iterative index-based scanning instead of regex to avoid polynomial
23
+ * backtracking (CodeQL `js/polynomial-redos`).
24
+ *
25
+ * @param html - HTML string to strip
26
+ * @returns The text content with tags replaced by spaces
27
+ */
28
+ export function stripHtmlTags(html) {
29
+ let result = '';
30
+ let pos = 0;
31
+ while (pos < html.length) {
32
+ const openIdx = html.indexOf('<', pos);
33
+ if (openIdx < 0) {
34
+ result += html.slice(pos);
35
+ break;
36
+ }
37
+ // Copy text before the tag
38
+ result += html.slice(pos, openIdx);
39
+ // Find the closing '>'
40
+ const closeIdx = html.indexOf('>', openIdx + 1);
41
+ if (closeIdx < 0) {
42
+ // Unclosed tag — keep the rest as-is
43
+ result += html.slice(openIdx);
44
+ break;
45
+ }
46
+ result += ' ';
47
+ pos = closeIdx + 1;
48
+ }
49
+ return result;
50
+ }
19
51
  export function stripScriptBlocks(html) {
20
52
  const OPEN = '<script';
21
53
  const CLOSE = '</script';
@@ -72,7 +72,7 @@ export declare function classifyPoliticalActors(data: ClassificationInput): Poli
72
72
  */
73
73
  export declare function analyzePoliticalForces(data: ClassificationInput): PoliticalForcesAnalysis;
74
74
  /**
75
- * Initialize the `analysis/{date}/` directory structure.
75
+ * Initialize the `analysis/daily/{date}/` directory structure.
76
76
  *
77
77
  * Creates the following sub-directories if they do not already exist:
78
78
  * - `classification/` — Political classification results
@@ -87,7 +87,7 @@ export declare function analyzePoliticalForces(data: ClassificationInput): Polit
87
87
  * - `data/mcp-responses/` — Raw MCP tool call responses
88
88
  *
89
89
  * When article-type scoping is used (recommended for agentic workflows),
90
- * the caller should pass a scoped path such as `analysis/{date}/{slug}`.
90
+ * the caller should pass a scoped path such as `analysis/daily/{date}/{slug}`.
91
91
  *
92
92
  * @param baseDir - Base directory for analysis output (typically `analysis/`)
93
93
  * @param date - ISO date string used as the run folder name (YYYY-MM-DD).
@@ -98,10 +98,10 @@ export declare function analyzePoliticalForces(data: ClassificationInput): Polit
98
98
  * @example
99
99
  * ```ts
100
100
  * const runDir = initializeAnalysisDirectory('./analysis', '2026-03-26');
101
- * // Creates: ./analysis/2026-03-26/classification/
102
- * // ./analysis/2026-03-26/data/
103
- * // ./analysis/2026-03-26/threat-assessment/
104
- * // ./analysis/2026-03-26/risk-scoring/
101
+ * // Creates: ./analysis/daily/2026-03-26/classification/
102
+ * // ./analysis/daily/2026-03-26/data/
103
+ * // ./analysis/daily/2026-03-26/threat-assessment/
104
+ * // ./analysis/daily/2026-03-26/risk-scoring/
105
105
  * ```
106
106
  */
107
107
  export declare function initializeAnalysisDirectory(baseDir: string, date: string): string;
@@ -134,7 +134,8 @@ export declare function serializeFrontmatter(fm: AnalysisFrontmatter): string;
134
134
  *
135
135
  * @example
136
136
  * ```ts
137
- * writeAnalysisFile('./analysis/2026-03-26/classification/significance-assessment.md', fm, body);
137
+ * // {article-type-slug} varies by workflow (e.g. 'plenary-session', 'week-ahead')
138
+ * writeAnalysisFile('./analysis/daily/2026-03-26/{article-type-slug}/classification/significance-classification.md', fm, body);
138
139
  * ```
139
140
  */
140
141
  export declare function writeAnalysisFile(filePath: string, frontmatter: AnalysisFrontmatter, content: string): void;
@@ -681,7 +681,7 @@ export function analyzePoliticalForces(data) {
681
681
  }
682
682
  // ─── Analysis directory & file utilities ─────────────────────────────────────
683
683
  /**
684
- * Initialize the `analysis/{date}/` directory structure.
684
+ * Initialize the `analysis/daily/{date}/` directory structure.
685
685
  *
686
686
  * Creates the following sub-directories if they do not already exist:
687
687
  * - `classification/` — Political classification results
@@ -696,7 +696,7 @@ export function analyzePoliticalForces(data) {
696
696
  * - `data/mcp-responses/` — Raw MCP tool call responses
697
697
  *
698
698
  * When article-type scoping is used (recommended for agentic workflows),
699
- * the caller should pass a scoped path such as `analysis/{date}/{slug}`.
699
+ * the caller should pass a scoped path such as `analysis/daily/{date}/{slug}`.
700
700
  *
701
701
  * @param baseDir - Base directory for analysis output (typically `analysis/`)
702
702
  * @param date - ISO date string used as the run folder name (YYYY-MM-DD).
@@ -707,10 +707,10 @@ export function analyzePoliticalForces(data) {
707
707
  * @example
708
708
  * ```ts
709
709
  * const runDir = initializeAnalysisDirectory('./analysis', '2026-03-26');
710
- * // Creates: ./analysis/2026-03-26/classification/
711
- * // ./analysis/2026-03-26/data/
712
- * // ./analysis/2026-03-26/threat-assessment/
713
- * // ./analysis/2026-03-26/risk-scoring/
710
+ * // Creates: ./analysis/daily/2026-03-26/classification/
711
+ * // ./analysis/daily/2026-03-26/data/
712
+ * // ./analysis/daily/2026-03-26/threat-assessment/
713
+ * // ./analysis/daily/2026-03-26/risk-scoring/
714
714
  * ```
715
715
  */
716
716
  export function initializeAnalysisDirectory(baseDir, date) {
@@ -806,7 +806,8 @@ export function serializeFrontmatter(fm) {
806
806
  *
807
807
  * @example
808
808
  * ```ts
809
- * writeAnalysisFile('./analysis/2026-03-26/classification/significance-assessment.md', fm, body);
809
+ * // {article-type-slug} varies by workflow (e.g. 'plenary-session', 'week-ahead')
810
+ * writeAnalysisFile('./analysis/daily/2026-03-26/{article-type-slug}/classification/significance-classification.md', fm, body);
810
811
  * ```
811
812
  */
812
813
  export function writeAnalysisFile(filePath, frontmatter, content) {
@@ -91,7 +91,7 @@ export declare function runAgentRiskAssessment(assessmentId: string, date: strin
91
91
  /**
92
92
  * Generate a structured markdown document from an agent risk assessment workflow.
93
93
  * Produces a YAML-frontmatter header and all risk sections in markdown format
94
- * suitable for writing to `analysis/{date}/risk-scoring/agent-risk-workflow.md`.
94
+ * suitable for writing to `analysis/daily/{date}/risk-scoring/agent-risk-workflow.md`.
95
95
  *
96
96
  * @param assessment - Completed agent risk assessment workflow
97
97
  * @returns Markdown string with YAML frontmatter and full risk analysis
@@ -385,7 +385,7 @@ export function runAgentRiskAssessment(assessmentId, date, articleType, identifi
385
385
  /**
386
386
  * Generate a structured markdown document from an agent risk assessment workflow.
387
387
  * Produces a YAML-frontmatter header and all risk sections in markdown format
388
- * suitable for writing to `analysis/{date}/risk-scoring/agent-risk-workflow.md`.
388
+ * suitable for writing to `analysis/daily/{date}/risk-scoring/agent-risk-workflow.md`.
389
389
  *
390
390
  * @param assessment - Completed agent risk assessment workflow
391
391
  * @returns Markdown string with YAML frontmatter and full risk analysis
@@ -0,0 +1,97 @@
1
+ /**
2
+ * @module Utils/SignificanceScoring
3
+ * @description 5-dimension composite significance scoring engine for EP events.
4
+ *
5
+ * Scores each event across five dimensions (0–10 each) and computes a weighted
6
+ * composite score used for publication prioritisation decisions:
7
+ *
8
+ * | Dimension | Weight |
9
+ * |----------------------------|--------|
10
+ * | Parliamentary Significance | 0.25 |
11
+ * | Policy Impact | 0.25 |
12
+ * | Public Interest | 0.20 |
13
+ * | Temporal Urgency | 0.15 |
14
+ * | Institutional Relevance | 0.15 |
15
+ *
16
+ * Decision thresholds follow the analysis template:
17
+ *
18
+ * | Composite | Decision |
19
+ * |-----------|----------|
20
+ * | 0.0 – 3.9 | skip |
21
+ * | 4.0 – 5.9 | hold |
22
+ * | ≥ 6.0 | publish |
23
+ *
24
+ * @see analysis/templates/significance-scoring.md
25
+ */
26
+ import type { SignificanceScore, SignificanceScoringInput, SignificanceBatchResult, PublicationDecision } from '../types/significance.js';
27
+ /** Weight applied to Parliamentary Significance dimension */
28
+ export declare const WEIGHT_PARLIAMENTARY = 0.25;
29
+ /** Weight applied to Policy Impact dimension */
30
+ export declare const WEIGHT_POLICY = 0.25;
31
+ /** Weight applied to Public Interest dimension */
32
+ export declare const WEIGHT_PUBLIC_INTEREST = 0.2;
33
+ /** Weight applied to Temporal Urgency dimension */
34
+ export declare const WEIGHT_URGENCY = 0.15;
35
+ /** Weight applied to Institutional / Cross-Group Relevance dimension */
36
+ export declare const WEIGHT_INSTITUTIONAL = 0.15;
37
+ /** Composite score at or above which the decision is "publish" */
38
+ export declare const THRESHOLD_PUBLISH = 6;
39
+ /** Composite score at or above which the decision is "hold" (below publish) */
40
+ export declare const THRESHOLD_HOLD = 4;
41
+ /**
42
+ * Clamp a numeric value to the 0–10 scoring range.
43
+ *
44
+ * @param value - Raw numeric input
45
+ * @returns Value clamped to [0, 10]
46
+ */
47
+ export declare function clampScore(value: number): number;
48
+ /**
49
+ * Derive a publication decision from a composite score.
50
+ *
51
+ * @param composite - Weighted composite score (0–10)
52
+ * @returns Publication decision
53
+ */
54
+ export declare function deriveDecision(composite: number): PublicationDecision;
55
+ /**
56
+ * Compute a composite significance score for a single event.
57
+ *
58
+ * All dimension values are clamped to [0, 10]. The composite is the
59
+ * weighted average using the standard template weights.
60
+ *
61
+ * @param input - Dimension scores for one event
62
+ * @returns Significance score with composite and publication decision
63
+ */
64
+ export declare function scoreSignificance(input: SignificanceScoringInput): SignificanceScore;
65
+ /**
66
+ * Score a batch of events and return ranked results with a summary.
67
+ *
68
+ * Events are scored individually then sorted by composite score descending.
69
+ *
70
+ * @param inputs - Array of event scoring inputs
71
+ * @returns Batch result with ranked scores and decision summary counts
72
+ */
73
+ export declare function scoreBatch(inputs: readonly SignificanceScoringInput[]): SignificanceBatchResult;
74
+ /**
75
+ * Generate a markdown report for a single significance score.
76
+ *
77
+ * Produces a table matching the template format with dimension breakdown,
78
+ * composite calculation, and publication decision.
79
+ *
80
+ * @param score - Computed significance score
81
+ * @param title - Event title
82
+ * @param reference - Optional EP reference identifier
83
+ * @returns Markdown string
84
+ */
85
+ export declare function formatScoreMarkdown(score: SignificanceScore, title: string, reference?: string): string;
86
+ /**
87
+ * Generate a batch scoring markdown table.
88
+ *
89
+ * Produces the Section 2 batch table from the template format.
90
+ * Scores must be in the same order as inputs (one score per input).
91
+ *
92
+ * @param inputs - Scoring inputs with titles and references
93
+ * @param scores - Pre-computed significance scores (same order as inputs)
94
+ * @returns Markdown table string
95
+ */
96
+ export declare function formatBatchMarkdown(inputs: readonly SignificanceScoringInput[], scores: readonly SignificanceScore[]): string;
97
+ //# sourceMappingURL=significance-scoring.d.ts.map
@@ -0,0 +1,190 @@
1
+ // SPDX-FileCopyrightText: 2024-2026 Hack23 AB
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ // ─── Markdown sanitization ────────────────────────────────────────────────────
4
+ /**
5
+ * Sanitize untrusted text for safe use in a Markdown table cell.
6
+ *
7
+ * Escapes pipe characters, backslashes, and HTML entities, then normalizes
8
+ * whitespace to prevent table layout corruption from external EP data.
9
+ *
10
+ * @param input - Untrusted cell text
11
+ * @returns Sanitized text safe for Markdown table cells
12
+ */
13
+ function sanitizeMdCell(input) {
14
+ return input
15
+ .replace(/\\/gu, '\\\\')
16
+ .replace(/\|/gu, '\\|')
17
+ .replace(/&/gu, '&amp;')
18
+ .replace(/</gu, '&lt;')
19
+ .replace(/>/gu, '&gt;')
20
+ .replace(/[\r\n]+/gu, ' ')
21
+ .trim();
22
+ }
23
+ /**
24
+ * Normalize a reference string: treat empty / whitespace-only values as
25
+ * missing so that the table cell shows a placeholder instead of blank.
26
+ *
27
+ * @param ref - Optional reference string
28
+ * @returns The trimmed reference, or undefined if empty/missing
29
+ */
30
+ function normalizeRef(ref) {
31
+ if (!ref)
32
+ return undefined;
33
+ const trimmed = ref.trim();
34
+ return trimmed.length > 0 ? trimmed : undefined;
35
+ }
36
+ // ─── Scoring constants ────────────────────────────────────────────────────────
37
+ /** Weight applied to Parliamentary Significance dimension */
38
+ export const WEIGHT_PARLIAMENTARY = 0.25;
39
+ /** Weight applied to Policy Impact dimension */
40
+ export const WEIGHT_POLICY = 0.25;
41
+ /** Weight applied to Public Interest dimension */
42
+ export const WEIGHT_PUBLIC_INTEREST = 0.2;
43
+ /** Weight applied to Temporal Urgency dimension */
44
+ export const WEIGHT_URGENCY = 0.15;
45
+ /** Weight applied to Institutional / Cross-Group Relevance dimension */
46
+ export const WEIGHT_INSTITUTIONAL = 0.15;
47
+ /** Minimum score floor (dimension and composite) */
48
+ const SCORE_MIN = 0;
49
+ /** Maximum score ceiling (dimension and composite) */
50
+ const SCORE_MAX = 10;
51
+ /** Composite score at or above which the decision is "publish" */
52
+ export const THRESHOLD_PUBLISH = 6.0;
53
+ /** Composite score at or above which the decision is "hold" (below publish) */
54
+ export const THRESHOLD_HOLD = 4.0;
55
+ // ─── Helpers ──────────────────────────────────────────────────────────────────
56
+ /**
57
+ * Clamp a numeric value to the 0–10 scoring range.
58
+ *
59
+ * @param value - Raw numeric input
60
+ * @returns Value clamped to [0, 10]
61
+ */
62
+ export function clampScore(value) {
63
+ if (!Number.isFinite(value))
64
+ return SCORE_MIN;
65
+ return Math.min(SCORE_MAX, Math.max(SCORE_MIN, value));
66
+ }
67
+ /**
68
+ * Derive a publication decision from a composite score.
69
+ *
70
+ * @param composite - Weighted composite score (0–10)
71
+ * @returns Publication decision
72
+ */
73
+ export function deriveDecision(composite) {
74
+ if (composite >= THRESHOLD_PUBLISH)
75
+ return 'publish';
76
+ if (composite >= THRESHOLD_HOLD)
77
+ return 'hold';
78
+ return 'skip';
79
+ }
80
+ // ─── Core scoring ─────────────────────────────────────────────────────────────
81
+ /**
82
+ * Compute a composite significance score for a single event.
83
+ *
84
+ * All dimension values are clamped to [0, 10]. The composite is the
85
+ * weighted average using the standard template weights.
86
+ *
87
+ * @param input - Dimension scores for one event
88
+ * @returns Significance score with composite and publication decision
89
+ */
90
+ export function scoreSignificance(input) {
91
+ const parliamentarySignificance = clampScore(input.parliamentarySignificance);
92
+ const policyImpact = clampScore(input.policyImpact);
93
+ const publicInterest = clampScore(input.publicInterest);
94
+ const temporalUrgency = clampScore(input.temporalUrgency);
95
+ const institutionalRelevance = clampScore(input.institutionalRelevance);
96
+ const composite = parliamentarySignificance * WEIGHT_PARLIAMENTARY +
97
+ policyImpact * WEIGHT_POLICY +
98
+ publicInterest * WEIGHT_PUBLIC_INTEREST +
99
+ temporalUrgency * WEIGHT_URGENCY +
100
+ institutionalRelevance * WEIGHT_INSTITUTIONAL;
101
+ const roundedComposite = Math.round(composite * 100) / 100;
102
+ return {
103
+ parliamentarySignificance,
104
+ policyImpact,
105
+ publicInterest,
106
+ temporalUrgency,
107
+ institutionalRelevance,
108
+ composite: roundedComposite,
109
+ decision: deriveDecision(roundedComposite),
110
+ };
111
+ }
112
+ /**
113
+ * Score a batch of events and return ranked results with a summary.
114
+ *
115
+ * Events are scored individually then sorted by composite score descending.
116
+ *
117
+ * @param inputs - Array of event scoring inputs
118
+ * @returns Batch result with ranked scores and decision summary counts
119
+ */
120
+ export function scoreBatch(inputs) {
121
+ const scores = inputs.map(scoreSignificance);
122
+ // Sort by composite descending (stable sort preserves input order for ties)
123
+ const ranked = [...scores].sort((a, b) => b.composite - a.composite);
124
+ const summary = { publish: 0, hold: 0, skip: 0 };
125
+ for (const s of ranked) {
126
+ summary[s.decision]++;
127
+ }
128
+ return { scores: ranked, summary };
129
+ }
130
+ /**
131
+ * Generate a markdown report for a single significance score.
132
+ *
133
+ * Produces a table matching the template format with dimension breakdown,
134
+ * composite calculation, and publication decision.
135
+ *
136
+ * @param score - Computed significance score
137
+ * @param title - Event title
138
+ * @param reference - Optional EP reference identifier
139
+ * @returns Markdown string
140
+ */
141
+ export function formatScoreMarkdown(score, title, reference) {
142
+ const safeTitle = sanitizeMdCell(title);
143
+ const safeRef = normalizeRef(reference);
144
+ const refLine = safeRef ? `| **EP Reference** | \`${sanitizeMdCell(safeRef)}\` |\n` : '';
145
+ const decisionEmoji = score.decision === 'publish' ? '📰' : score.decision === 'hold' ? '📋' : '🗄️';
146
+ const decisionLabel = score.decision === 'publish' ? 'Publish' : score.decision === 'hold' ? 'Hold' : 'Skip';
147
+ return `### ${safeTitle}
148
+
149
+ | Field | Value |
150
+ |-------|-------|
151
+ | **Event** | ${safeTitle} |
152
+ ${refLine}
153
+ | Dimension | Raw Score | Weight | Weighted Score |
154
+ |-----------|:---------:|:------:|:--------------:|
155
+ | Parliamentary Significance | ${score.parliamentarySignificance.toFixed(1)} | ${WEIGHT_PARLIAMENTARY} | ${(score.parliamentarySignificance * WEIGHT_PARLIAMENTARY).toFixed(2)} |
156
+ | Policy Impact | ${score.policyImpact.toFixed(1)} | ${WEIGHT_POLICY} | ${(score.policyImpact * WEIGHT_POLICY).toFixed(2)} |
157
+ | Public Interest | ${score.publicInterest.toFixed(1)} | ${WEIGHT_PUBLIC_INTEREST} | ${(score.publicInterest * WEIGHT_PUBLIC_INTEREST).toFixed(2)} |
158
+ | Temporal Urgency | ${score.temporalUrgency.toFixed(1)} | ${WEIGHT_URGENCY} | ${(score.temporalUrgency * WEIGHT_URGENCY).toFixed(2)} |
159
+ | Institutional Relevance | ${score.institutionalRelevance.toFixed(1)} | ${WEIGHT_INSTITUTIONAL} | ${(score.institutionalRelevance * WEIGHT_INSTITUTIONAL).toFixed(2)} |
160
+ | **COMPOSITE SCORE** | — | — | **${score.composite.toFixed(2)} / 10** |
161
+
162
+ **Decision:** ${decisionEmoji} **${decisionLabel}**
163
+ `;
164
+ }
165
+ /**
166
+ * Generate a batch scoring markdown table.
167
+ *
168
+ * Produces the Section 2 batch table from the template format.
169
+ * Scores must be in the same order as inputs (one score per input).
170
+ *
171
+ * @param inputs - Scoring inputs with titles and references
172
+ * @param scores - Pre-computed significance scores (same order as inputs)
173
+ * @returns Markdown table string
174
+ */
175
+ export function formatBatchMarkdown(inputs, scores) {
176
+ const header = '| Event | EP Reference | Parl. | Policy | Public | Urgency | Instit. | **Composite** | Decision |';
177
+ const separator = '|-------|-------------|:-----:|:------:|:------:|:-------:|:-------:|:-------------:|----------|';
178
+ if (inputs.length !== scores.length) {
179
+ throw new Error(`formatBatchMarkdown: inputs.length (${inputs.length}) !== scores.length (${scores.length}). Arrays must be aligned.`);
180
+ }
181
+ const rows = inputs.map((input, i) => {
182
+ const s = scores[i];
183
+ const decisionLabel = s.decision === 'publish' ? 'Publish' : s.decision === 'hold' ? 'Hold' : 'Skip';
184
+ const safeTitle = sanitizeMdCell(input.title);
185
+ const safeRef = normalizeRef(input.reference);
186
+ return `| ${safeTitle} | ${safeRef ? sanitizeMdCell(safeRef) : '—'} | ${s.parliamentarySignificance.toFixed(1)} | ${s.policyImpact.toFixed(1)} | ${s.publicInterest.toFixed(1)} | ${s.temporalUrgency.toFixed(1)} | ${s.institutionalRelevance.toFixed(1)} | **${s.composite.toFixed(2)}** | ${decisionLabel} |`;
187
+ });
188
+ return [header, separator, ...rows].join('\n');
189
+ }
190
+ //# sourceMappingURL=significance-scoring.js.map