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
@@ -9,6 +9,7 @@ import { buildBreakingAnalysis, buildBreakingSwot, buildBreakingDashboard, build
9
9
  import { buildSwotSection } from '../swot-content.js';
10
10
  import { buildDashboardSection } from '../dashboard-content.js';
11
11
  import { buildIntelligenceMindmapSection } from '../mindmap-content.js';
12
+ import { loadAnalysisContext, buildAnalysisInsightsSection, extractAnalysisSummary, } from './article-strategy.js';
12
13
  import { pl } from '../../utils/metadata-utils.js';
13
14
  /** Base keywords shared by all Breaking News articles */
14
15
  const BREAKING_NEWS_BASE_KEYWORDS = [
@@ -100,6 +101,50 @@ function buildBreakingTitleSuffix(feedData) {
100
101
  parts.push(pl(feedData.procedures.length, 'Procedure', 'Procedures'));
101
102
  return parts.join(', ');
102
103
  }
104
+ /**
105
+ * Extract a substantive summary from an analysis file if available.
106
+ *
107
+ * @param ctx - Analysis context
108
+ * @param method - Analysis method to look up
109
+ * @param maxLength - Maximum summary length
110
+ * @returns Extracted summary or empty string
111
+ */
112
+ function extractAISummaryFromMethod(ctx, method, maxLength) {
113
+ const file = ctx.files.get(method);
114
+ if (!file)
115
+ return '';
116
+ // `extractAnalysisSummary()` already runs `prepareAnalysisBody()` which
117
+ // returns empty for scaffold content and strips non-prose blocks, so no
118
+ // separate `hasSubstantiveAIContent()` pre-check is needed.
119
+ const summary = extractAnalysisSummary(file.content, maxLength);
120
+ return summary.length > 50 ? summary : '';
121
+ }
122
+ /**
123
+ * Enrich script-generated DeepAnalysis fields with substantive AI analysis
124
+ * content when available. AI-produced analysis files (deep-analysis,
125
+ * synthesis-summary) contain real political intelligence that should
126
+ * replace generic boilerplate text in the "what", "why", and "outlook" fields.
127
+ *
128
+ * @param analysis - Script-generated DeepAnalysis object
129
+ * @param ctx - Loaded analysis context (may be null)
130
+ * @returns Enriched DeepAnalysis with AI content replacing boilerplate where available
131
+ */
132
+ function enrichAnalysisWithAIContent(analysis, ctx) {
133
+ if (!ctx)
134
+ return analysis;
135
+ const aiDeep = extractAISummaryFromMethod(ctx, 'deep-analysis', 800);
136
+ const aiSynth = extractAISummaryFromMethod(ctx, 'synthesis-summary', 600);
137
+ const aiCoalition = extractAISummaryFromMethod(ctx, 'coalition-analysis', 400);
138
+ // Distribute AI content across the deep analysis fields
139
+ const aiWhat = aiDeep || aiSynth;
140
+ const aiWhy = aiDeep && aiSynth ? aiSynth : '';
141
+ return {
142
+ ...analysis,
143
+ what: aiWhat || analysis.what,
144
+ why: aiWhy || analysis.why,
145
+ outlook: aiCoalition || analysis.outlook,
146
+ };
147
+ }
103
148
  // ─── Strategy implementation ──────────────────────────────────────────────────
104
149
  /**
105
150
  * Article strategy for {@link ArticleCategory.BREAKING_NEWS}.
@@ -127,6 +172,8 @@ export class BreakingNewsStrategy {
127
172
  * @returns Populated breaking news data payload
128
173
  */
129
174
  async fetchData(client, date) {
175
+ // Load analysis context once for all return paths (graceful: null if absent)
176
+ const analysisContext = loadAnalysisContext(date, 'breaking');
130
177
  // Step 0: Check for pre-fetched feed data file (set by --feed-data CLI arg).
131
178
  // This allows agentic workflows to pass MCP data fetched via framework tools
132
179
  // into the generator without requiring a direct MCP connection.
@@ -149,19 +196,34 @@ export class BreakingNewsStrategy {
149
196
  fetchCoalitionDynamics(client),
150
197
  ]);
151
198
  }
152
- return { date, feedData: fileFeedData, anomalyRaw, coalitionRaw, reportRaw: '' };
199
+ return {
200
+ date,
201
+ feedData: fileFeedData,
202
+ anomalyRaw,
203
+ coalitionRaw,
204
+ reportRaw: '',
205
+ analysisContext,
206
+ };
153
207
  }
154
208
  console.log(' ⚠️ Pre-fetched feed data failed to load — falling through to MCP fetch');
155
209
  }
156
210
  if (client) {
157
211
  console.log(' 📡 Fetching EP feed data (primary) and analytical context...');
158
212
  }
159
- // Step 1: Fetch feed data (PRIMARY news content) — 'today' for realtime breaking news
160
- const feedData = await fetchBreakingNewsFeedData(client, 'today');
213
+ // Step 1: Fetch feed data (PRIMARY news content) — 'one-week' to avoid
214
+ // 404s during EP recess / low-activity periods when 'today' has no data
215
+ const feedData = await fetchBreakingNewsFeedData(client, 'one-week');
161
216
  // When client is null, feedData is undefined — MCP unavailable
162
217
  if (!feedData) {
163
218
  console.log(' ⚠️ MCP unavailable — no feed data or analytical context');
164
- return { date, feedData, anomalyRaw: '', coalitionRaw: '', reportRaw: '' };
219
+ return {
220
+ date,
221
+ feedData,
222
+ anomalyRaw: '',
223
+ coalitionRaw: '',
224
+ reportRaw: '',
225
+ analysisContext,
226
+ };
165
227
  }
166
228
  const totalFeedItems = feedData.adoptedTexts.length +
167
229
  feedData.events.length +
@@ -174,14 +236,28 @@ export class BreakingNewsStrategy {
174
236
  }
175
237
  else {
176
238
  console.log(' ⚠️ No feed data available — skipping analytical context fetch');
177
- return { date, feedData, anomalyRaw: '', coalitionRaw: '', reportRaw: '' };
239
+ return {
240
+ date,
241
+ feedData,
242
+ anomalyRaw: '',
243
+ coalitionRaw: '',
244
+ reportRaw: '',
245
+ analysisContext,
246
+ };
178
247
  }
179
248
  // Step 2: Fetch analytical context only when at least one feed item is available
180
249
  const [anomalyRaw, coalitionRaw] = await Promise.all([
181
250
  fetchVotingAnomalies(client),
182
251
  fetchCoalitionDynamics(client),
183
252
  ]);
184
- return { date, feedData, anomalyRaw, coalitionRaw, reportRaw: '' };
253
+ return {
254
+ date,
255
+ feedData,
256
+ anomalyRaw,
257
+ coalitionRaw,
258
+ reportRaw: '',
259
+ analysisContext,
260
+ };
185
261
  }
186
262
  /**
187
263
  * Build the breaking news HTML body for the specified language.
@@ -193,14 +269,28 @@ export class BreakingNewsStrategy {
193
269
  buildContent(data, lang) {
194
270
  const base = buildBreakingNewsContent(data.date, data.anomalyRaw, data.coalitionRaw, data.reportRaw, '', lang, [], [], [], data.feedData);
195
271
  const analysis = buildBreakingAnalysis(data.date, data.feedData, data.anomalyRaw, data.coalitionRaw, lang);
196
- const deepSection = buildDeepAnalysisSection(analysis, lang, 'en');
272
+ // Enrich script-generated analysis with AI-produced content when available
273
+ const enriched = enrichAnalysisWithAIContent(analysis, data.analysisContext);
274
+ const deepSection = buildDeepAnalysisSection(enriched, lang, 'en');
197
275
  const mindmapData = buildBreakingMindmap(data.feedData, lang);
198
276
  const mindmapSection = buildIntelligenceMindmapSection(mindmapData, lang);
199
277
  const swotData = buildBreakingSwot(data.feedData, data.anomalyRaw, data.coalitionRaw, lang);
200
278
  const swotSection = buildSwotSection(swotData, lang);
201
279
  const dashboardData = buildBreakingDashboard(data.feedData, lang);
202
280
  const dashboardSection = buildDashboardSection(dashboardData, lang);
203
- const injection = deepSection + mindmapSection + swotSection + dashboardSection;
281
+ const analysisInsights = buildAnalysisInsightsSection(data.analysisContext, [
282
+ 'deep-analysis',
283
+ 'synthesis-summary',
284
+ 'stakeholder-analysis',
285
+ 'coalition-analysis',
286
+ 'cross-session-intelligence',
287
+ 'voting-patterns',
288
+ 'risk-matrix',
289
+ 'quantitative-swot',
290
+ 'significance-classification',
291
+ 'political-threat-landscape',
292
+ ], lang);
293
+ const injection = deepSection + mindmapSection + swotSection + dashboardSection + analysisInsights;
204
294
  // Inject before the closing </div> of .article-content
205
295
  if (injection) {
206
296
  const closingTag = '</div>';
@@ -10,6 +10,7 @@ import { buildCommitteeAnalysis, buildCommitteeSwot, buildCommitteeDashboard, bu
10
10
  import { buildSwotSection } from '../swot-content.js';
11
11
  import { buildDashboardSection } from '../dashboard-content.js';
12
12
  import { buildIntelligenceMindmapSection } from '../mindmap-content.js';
13
+ import { loadAnalysisContext, buildAnalysisInsightsSection } from './article-strategy.js';
13
14
  import { pl } from '../../utils/metadata-utils.js';
14
15
  /** European Parliament home-page URL used as source reference */
15
16
  const EP_SOURCE_URL = 'https://www.europarl.europa.eu';
@@ -356,7 +357,12 @@ export class CommitteeReportsStrategy {
356
357
  fetchEPFeedData(client, 'one-month', feedDateRange),
357
358
  ]);
358
359
  const committeeDataList = committeeDataRaw.filter((committee) => committee !== null);
359
- return { date, committeeDataList, feedData };
360
+ return {
361
+ date,
362
+ committeeDataList,
363
+ feedData,
364
+ analysisContext: loadAnalysisContext(date, 'committee-reports'),
365
+ };
360
366
  }
361
367
  /**
362
368
  * Build the committee reports HTML body.
@@ -376,7 +382,22 @@ export class CommitteeReportsStrategy {
376
382
  const swotSection = buildSwotSection(swotData, lang);
377
383
  const dashboardData = buildCommitteeDashboard(data.committeeDataList, lang);
378
384
  const dashboardSection = buildDashboardSection(dashboardData, lang);
379
- const injection = feedSection + deepSection + mindmapSection + swotSection + dashboardSection;
385
+ const analysisInsights = buildAnalysisInsightsSection(data.analysisContext, [
386
+ 'deep-analysis',
387
+ 'synthesis-summary',
388
+ 'stakeholder-analysis',
389
+ 'coalition-analysis',
390
+ 'cross-session-intelligence',
391
+ 'significance-classification',
392
+ 'impact-matrix',
393
+ 'actor-mapping',
394
+ ], lang);
395
+ const injection = feedSection +
396
+ deepSection +
397
+ mindmapSection +
398
+ swotSection +
399
+ dashboardSection +
400
+ analysisInsights;
380
401
  // Inject before the closing </div> of .article-content
381
402
  if (injection) {
382
403
  const closingTag = '</div>';
@@ -9,6 +9,7 @@ import { buildProspectiveAnalysis, buildProspectiveSwot, buildProspectiveDashboa
9
9
  import { buildSwotSection } from '../swot-content.js';
10
10
  import { buildDashboardSection } from '../dashboard-content.js';
11
11
  import { buildIntelligenceMindmapSection } from '../mindmap-content.js';
12
+ import { loadAnalysisContext, buildAnalysisInsightsSection } from './article-strategy.js';
12
13
  import { pl } from '../../utils/metadata-utils.js';
13
14
  /** Keywords shared by all Month Ahead articles */
14
15
  const MONTH_AHEAD_KEYWORDS = [
@@ -125,7 +126,15 @@ export class MonthAheadStrategy {
125
126
  ]);
126
127
  const keywords = [...MONTH_AHEAD_KEYWORDS, ...buildKeywords(monthData)];
127
128
  const monthLabel = formatMonthLabel(dateRange.start);
128
- return { date, dateRange, monthData, keywords, monthLabel, feedData };
129
+ return {
130
+ date,
131
+ dateRange,
132
+ monthData,
133
+ keywords,
134
+ monthLabel,
135
+ feedData,
136
+ analysisContext: loadAnalysisContext(date, 'month-ahead'),
137
+ };
129
138
  }
130
139
  /**
131
140
  * Build the month-ahead HTML body for the specified language.
@@ -144,7 +153,19 @@ export class MonthAheadStrategy {
144
153
  const swotSection = buildSwotSection(swotData, lang);
145
154
  const dashboardData = buildProspectiveDashboard(data.monthData, 'month', lang);
146
155
  const dashboardSection = buildDashboardSection(dashboardData, lang);
147
- return base.replace('<!-- /article-content -->', analysisSection + mindmapSection + swotSection + dashboardSection);
156
+ const analysisInsights = buildAnalysisInsightsSection(data.analysisContext, [
157
+ 'deep-analysis',
158
+ 'synthesis-summary',
159
+ 'stakeholder-analysis',
160
+ 'coalition-analysis',
161
+ 'cross-session-intelligence',
162
+ 'significance-classification',
163
+ 'political-threat-landscape',
164
+ 'risk-matrix',
165
+ 'forces-analysis',
166
+ 'legislative-velocity-risk',
167
+ ], lang);
168
+ return base.replace('<!-- /article-content -->', analysisSection + mindmapSection + swotSection + dashboardSection + analysisInsights);
148
169
  }
149
170
  /**
150
171
  * Return language-specific metadata for the month-ahead article.
@@ -9,6 +9,7 @@ import { buildVotingAnalysis, buildVotingSwot, buildVotingDashboard, buildVoting
9
9
  import { buildSwotSection } from '../swot-content.js';
10
10
  import { buildDashboardSection } from '../dashboard-content.js';
11
11
  import { buildIntelligenceMindmapSection } from '../mindmap-content.js';
12
+ import { loadAnalysisContext, buildAnalysisInsightsSection } from './article-strategy.js';
12
13
  import { pl } from '../../utils/metadata-utils.js';
13
14
  import { isPlaceholderText } from '../../constants/analysis-constants.js';
14
15
  /** Base keywords shared by all Monthly Review articles */
@@ -159,6 +160,7 @@ export class MonthlyReviewStrategy {
159
160
  questions,
160
161
  monthLabel,
161
162
  feedData,
163
+ analysisContext: loadAnalysisContext(date, 'month-in-review'),
162
164
  };
163
165
  }
164
166
  /**
@@ -178,7 +180,17 @@ export class MonthlyReviewStrategy {
178
180
  const swotSection = buildSwotSection(swotData, lang);
179
181
  const dashboardData = buildVotingDashboard(data.votingRecords, data.votingPatterns, data.anomalies, lang);
180
182
  const dashboardSection = buildDashboardSection(dashboardData, lang);
181
- return base.replace('<!-- /article-content -->', deepSection + mindmapSection + swotSection + dashboardSection);
183
+ const analysisInsights = buildAnalysisInsightsSection(data.analysisContext, [
184
+ 'deep-analysis',
185
+ 'synthesis-summary',
186
+ 'stakeholder-analysis',
187
+ 'coalition-analysis',
188
+ 'cross-session-intelligence',
189
+ 'voting-patterns',
190
+ 'significance-classification',
191
+ 'legislative-velocity-risk',
192
+ ], lang);
193
+ return base.replace('<!-- /article-content -->', deepSection + mindmapSection + swotSection + dashboardSection + analysisInsights);
182
194
  }
183
195
  /**
184
196
  * Return language-specific metadata for the monthly review article.
@@ -9,6 +9,7 @@ import { buildVotingAnalysis, buildVotingSwot, buildVotingDashboard, buildVoting
9
9
  import { buildSwotSection } from '../swot-content.js';
10
10
  import { buildDashboardSection } from '../dashboard-content.js';
11
11
  import { buildIntelligenceMindmapSection } from '../mindmap-content.js';
12
+ import { loadAnalysisContext, buildAnalysisInsightsSection } from './article-strategy.js';
12
13
  import { pl } from '../../utils/metadata-utils.js';
13
14
  import { isPlaceholderText } from '../../constants/analysis-constants.js';
14
15
  /** Base keywords shared by all Motions articles */
@@ -152,6 +153,7 @@ export class MotionsStrategy {
152
153
  anomalies,
153
154
  questions,
154
155
  feedData,
156
+ analysisContext: loadAnalysisContext(date, 'motions'),
155
157
  };
156
158
  }
157
159
  /**
@@ -178,6 +180,17 @@ export class MotionsStrategy {
178
180
  ? buildVotingDashboard(data.votingRecords, data.votingPatterns, data.anomalies, lang)
179
181
  : null;
180
182
  const dashboardSection = buildDashboardSection(dashboardData, lang);
183
+ const analysisInsights = buildAnalysisInsightsSection(data.analysisContext, [
184
+ 'deep-analysis',
185
+ 'synthesis-summary',
186
+ 'stakeholder-analysis',
187
+ 'coalition-analysis',
188
+ 'cross-session-intelligence',
189
+ 'voting-patterns',
190
+ 'political-threat-landscape',
191
+ 'risk-matrix',
192
+ 'actor-mapping',
193
+ ], lang);
181
194
  // Inject at the explicit <!-- /article-content --> marker so the section
182
195
  // stays inside the .article-content styling scope. The marker is always
183
196
  // emitted by generateMotionsContent as the last child of that wrapper and
@@ -187,7 +200,8 @@ export class MotionsStrategy {
187
200
  deepSection +
188
201
  mindmapSection +
189
202
  swotSection +
190
- dashboardSection;
203
+ dashboardSection +
204
+ analysisInsights;
191
205
  if (injection) {
192
206
  return base.replace('<!-- /article-content -->', `${injection}\n`);
193
207
  }
@@ -10,6 +10,7 @@ import { buildPropositionsAnalysis, buildPropositionsSwot, buildPropositionsDash
10
10
  import { buildSwotSection } from '../swot-content.js';
11
11
  import { buildDashboardSection } from '../dashboard-content.js';
12
12
  import { buildIntelligenceMindmapSection } from '../mindmap-content.js';
13
+ import { loadAnalysisContext, buildAnalysisInsightsSection } from './article-strategy.js';
13
14
  import { pl } from '../../utils/metadata-utils.js';
14
15
  /** Base keywords shared by all Propositions articles */
15
16
  const PROPOSITIONS_BASE_KEYWORDS = [
@@ -198,6 +199,7 @@ export class PropositionsStrategy {
198
199
  pipelineData,
199
200
  procedureHtml,
200
201
  feedData: feedResult,
202
+ analysisContext: loadAnalysisContext(date, 'propositions'),
201
203
  };
202
204
  }
203
205
  /**
@@ -218,7 +220,19 @@ export class PropositionsStrategy {
218
220
  const swotSection = buildSwotSection(swotData, lang);
219
221
  const dashboardData = buildPropositionsDashboard(data.pipelineData, lang);
220
222
  const dashboardSection = buildDashboardSection(dashboardData, lang);
221
- const injection = deepSection + mindmapSection + swotSection + dashboardSection;
223
+ const analysisInsights = buildAnalysisInsightsSection(data.analysisContext, [
224
+ 'deep-analysis',
225
+ 'synthesis-summary',
226
+ 'stakeholder-analysis',
227
+ 'coalition-analysis',
228
+ 'cross-session-intelligence',
229
+ 'risk-matrix',
230
+ 'significance-classification',
231
+ 'legislative-velocity-risk',
232
+ 'significance-scoring',
233
+ 'forces-analysis',
234
+ ], lang);
235
+ const injection = deepSection + mindmapSection + swotSection + dashboardSection + analysisInsights;
222
236
  // Inject before the closing </div> of .article-content
223
237
  if (injection) {
224
238
  const closingTag = '</div>';
@@ -9,6 +9,7 @@ import { buildProspectiveAnalysis, buildProspectiveSwot, buildProspectiveDashboa
9
9
  import { buildSwotSection } from '../swot-content.js';
10
10
  import { buildDashboardSection } from '../dashboard-content.js';
11
11
  import { buildIntelligenceMindmapSection } from '../mindmap-content.js';
12
+ import { loadAnalysisContext, buildAnalysisInsightsSection } from './article-strategy.js';
12
13
  import { pl } from '../../utils/metadata-utils.js';
13
14
  // ─── Date-range helper ────────────────────────────────────────────────────────
14
15
  /**
@@ -120,6 +121,7 @@ export class WeekAheadStrategy {
120
121
  weekData,
121
122
  keywords,
122
123
  feedData,
124
+ analysisContext: loadAnalysisContext(date, 'week-ahead'),
123
125
  };
124
126
  }
125
127
  /**
@@ -140,10 +142,26 @@ export class WeekAheadStrategy {
140
142
  const swotSection = buildSwotSection(swotData, lang);
141
143
  const dashboardData = buildProspectiveDashboard(data.weekData, 'week', lang);
142
144
  const dashboardSection = buildDashboardSection(dashboardData, lang);
145
+ const analysisInsights = buildAnalysisInsightsSection(data.analysisContext, [
146
+ 'deep-analysis',
147
+ 'synthesis-summary',
148
+ 'stakeholder-analysis',
149
+ 'coalition-analysis',
150
+ 'cross-session-intelligence',
151
+ 'significance-classification',
152
+ 'political-threat-landscape',
153
+ 'risk-matrix',
154
+ 'forces-analysis',
155
+ ], lang);
143
156
  // Inject at the explicit <!-- /article-content --> marker position so the
144
157
  // section stays inside the .article-content styling scope. The marker is
145
158
  // removed from the final HTML output to avoid unnecessary bytes.
146
- const injection = (watchSection || '') + analysisSection + mindmapSection + swotSection + dashboardSection;
159
+ const injection = (watchSection || '') +
160
+ analysisSection +
161
+ mindmapSection +
162
+ swotSection +
163
+ dashboardSection +
164
+ analysisInsights;
147
165
  if (injection) {
148
166
  return base.replace('<!-- /article-content -->', injection);
149
167
  }
@@ -9,6 +9,7 @@ import { buildVotingAnalysis, buildVotingSwot, buildVotingDashboard, buildVoting
9
9
  import { buildSwotSection } from '../swot-content.js';
10
10
  import { buildDashboardSection } from '../dashboard-content.js';
11
11
  import { buildIntelligenceMindmapSection } from '../mindmap-content.js';
12
+ import { loadAnalysisContext, buildAnalysisInsightsSection } from './article-strategy.js';
12
13
  import { pl } from '../../utils/metadata-utils.js';
13
14
  import { isPlaceholderText } from '../../constants/analysis-constants.js';
14
15
  /** Base keywords shared by all Weekly Review articles */
@@ -163,6 +164,7 @@ export class WeeklyReviewStrategy {
163
164
  anomalies,
164
165
  questions,
165
166
  feedData,
167
+ analysisContext: loadAnalysisContext(date, 'week-in-review'),
166
168
  };
167
169
  }
168
170
  /**
@@ -186,7 +188,21 @@ export class WeeklyReviewStrategy {
186
188
  const swotSection = buildSwotSection(swotData, lang);
187
189
  const dashboardData = buildVotingDashboard(data.votingRecords, data.votingPatterns, data.anomalies, lang);
188
190
  const dashboardSection = buildDashboardSection(dashboardData, lang);
189
- return base.replace('<!-- /article-content -->', adoptedTextsHtml + deepSection + mindmapSection + swotSection + dashboardSection);
191
+ const analysisInsights = buildAnalysisInsightsSection(data.analysisContext, [
192
+ 'deep-analysis',
193
+ 'synthesis-summary',
194
+ 'stakeholder-analysis',
195
+ 'coalition-analysis',
196
+ 'cross-session-intelligence',
197
+ 'voting-patterns',
198
+ 'significance-classification',
199
+ ], lang);
200
+ return base.replace('<!-- /article-content -->', adoptedTextsHtml +
201
+ deepSection +
202
+ mindmapSection +
203
+ swotSection +
204
+ dashboardSection +
205
+ analysisInsights);
190
206
  }
191
207
  /**
192
208
  * Return language-specific metadata for the weekly review article.
@@ -0,0 +1,93 @@
1
+ import type { ConfidenceLevel } from '../types/analysis.js';
2
+ import type { SynthesisFinding, AggregatedSWOT, RiskOverview, SynthesisSummary } from '../types/significance.js';
3
+ /** Parsed YAML frontmatter fields relevant to synthesis */
4
+ interface ParsedFrontmatter {
5
+ readonly method: string;
6
+ readonly confidence: ConfidenceLevel;
7
+ readonly date: string;
8
+ }
9
+ /**
10
+ * Parse YAML frontmatter from a markdown file's content.
11
+ *
12
+ * Extracts `method`, `confidence`, and `date` fields from the `---` delimited
13
+ * YAML block at the start of the file. Returns null when no valid frontmatter
14
+ * is found.
15
+ *
16
+ * @param content - Raw markdown content
17
+ * @returns Parsed frontmatter or null
18
+ */
19
+ export declare function parseFrontmatter(content: string): ParsedFrontmatter | null;
20
+ /**
21
+ * Aggregate SWOT mention counts from a body of text.
22
+ *
23
+ * @param text - Combined analysis text
24
+ * @returns SWOT counts
25
+ */
26
+ export declare function aggregateSWOT(text: string): AggregatedSWOT;
27
+ /**
28
+ * Aggregate risk-level mention counts from a body of text.
29
+ *
30
+ * @param text - Combined analysis text
31
+ * @returns Risk level counts
32
+ */
33
+ export declare function aggregateRisks(text: string): RiskOverview;
34
+ /**
35
+ * Extract the first non-empty non-frontmatter heading or paragraph as a
36
+ * one-line summary from a markdown file.
37
+ *
38
+ * @param content - Raw markdown content
39
+ * @returns One-line summary string
40
+ */
41
+ export declare function extractSummaryLine(content: string): string;
42
+ /**
43
+ * Determine the overall confidence level from a set of findings.
44
+ *
45
+ * Uses majority vote: whichever confidence level appears most often wins.
46
+ *
47
+ * @param findings - Findings with individual confidence levels
48
+ * @returns Aggregated confidence level
49
+ */
50
+ export declare function aggregateConfidence(findings: readonly SynthesisFinding[]): ConfidenceLevel;
51
+ /**
52
+ * Recursively find all `.md` analysis files under a directory.
53
+ *
54
+ * Excludes:
55
+ * - The synthesis output file itself (prevents self-contamination on re-runs)
56
+ * - The `documents/` subdirectory (per-document analysis can bloat I/O and skew aggregation)
57
+ *
58
+ * @param dir - Absolute directory path
59
+ * @returns Array of absolute file paths
60
+ */
61
+ export declare function findMarkdownFiles(dir: string): readonly string[];
62
+ /**
63
+ * Generate editorial recommendations based on aggregated analysis data.
64
+ *
65
+ * @param findings - Ranked findings
66
+ * @param swot - Aggregated SWOT counts
67
+ * @param risks - Risk level distribution
68
+ * @returns Array of recommendation strings
69
+ */
70
+ export declare function generateEditorialRecommendations(findings: readonly SynthesisFinding[], swot: AggregatedSWOT, risks: RiskOverview): readonly string[];
71
+ /**
72
+ * Build a synthesis summary from all analysis files in a date directory.
73
+ *
74
+ * Scans the directory recursively for `.md` analysis files, parses their
75
+ * frontmatter, extracts findings, aggregates SWOT and risk mentions, and
76
+ * produces a {@link SynthesisSummary} object.
77
+ *
78
+ * @param dateOutputDir - Absolute path to the date-scoped analysis directory
79
+ * @param date - ISO date string (YYYY-MM-DD)
80
+ * @returns Synthesis summary object
81
+ */
82
+ export declare function buildSynthesisSummary(dateOutputDir: string, date: string): SynthesisSummary;
83
+ /**
84
+ * Generate a markdown report from a synthesis summary.
85
+ *
86
+ * Follows the template format defined in `analysis/templates/synthesis-summary.md`.
87
+ *
88
+ * @param summary - Computed synthesis summary
89
+ * @returns Markdown string
90
+ */
91
+ export declare function formatSynthesisMarkdown(summary: SynthesisSummary): string;
92
+ export {};
93
+ //# sourceMappingURL=synthesis-summary.d.ts.map