Skip to content

Enhance month-ahead: add strategic legislative forecasting, pipeline tracking, and trend analysis#607

Merged
pethers merged 15 commits intomainfrom
copilot/enhance-month-ahead-forecasting
Feb 27, 2026
Merged

Enhance month-ahead: add strategic legislative forecasting, pipeline tracking, and trend analysis#607
pethers merged 15 commits intomainfrom
copilot/enhance-month-ahead-forecasting

Conversation

Copy link
Contributor

Copilot AI commented Feb 26, 2026

Enhances scripts/news-types/month-ahead.ts from a simple calendar listing into a strategic legislative intelligence brief by integrating three additional MCP data sources: committee reports, propositions, and motions.

Changes Made

Core Generator (scripts/news-types/month-ahead.ts)

  • Added get_betankanden, get_propositioner, get_motioner to REQUIRED_TOOLS
  • Pipeline fetches (get_betankanden ×20, get_propositioner ×15, get_motioner ×50) run unconditionally before the calendar early-return, using Promise.all with graceful degradation (console.warn on failure)
  • Early-return now skips generation only when all data sources are empty (events, documents, committee reports, propositions, and motions)
  • Dynamic riksmöte derived from endDate (not today) so articles spanning the August→September session boundary always fetch the correct parliamentary session's pipeline data; 0-based month >= 8 pattern matches motions.ts and committee-reports.ts
  • crossReferences.sources uses noun-style identifiers (betankanden, propositioner, motioner) consistent with other generators
  • MonthAheadValidationResult extended with hasLegislativePipeline; passed gate requires all fields including hasLegislativePipeline
  • checkLegislativePipeline matches specific <h2> section headings emitted by the generator across all 14 languages (including Policy Trends headings for JA/KO/ZH/AR/HE)

Content Generator (scripts/data-transformers/content-generators.ts)

  • New generateMonthAheadContent function adds three sections in 14 languages:
    • Strategic Legislative Outlook — top 8 propositions
    • Committee Pipeline — top 5 committees × 3 reports each; grouping falls back to rec.committee when rec.organ is absent
    • Policy Trends — top 5 policy domains, top 4 parties by motion volume (filters out unknown and other sentinels); escapeHtml applied after template execution to avoid double-escaping
  • Committee grouping uses rec.organ ?? rec.committee ?? 'unknown'

Dispatcher (scripts/data-transformers/index.ts)

  • generateArticleContent routes month-ahead to the dedicated generateMonthAheadContent instead of reusing week-ahead

Validation (scripts/validate-cross-references.ts)

  • Added month-ahead entry to REQUIRED_TOOLS_PER_TYPE enforcing get_calendar_events, get_betankanden, get_propositioner, get_motioner

Tests (tests/news-types/month-ahead.test.ts)

  • Mock signatures use string | null for the rm parameter, matching actual MCP client signatures
  • Test-local MonthAheadValidationResult interface includes hasLegislativePipeline: boolean
  • 8 new tests added (21 total), including a test asserting article generation proceeds when calendar is empty but pipeline data (propositions) is present

Testing

  • ✅ All 21 month-ahead tests pass
  • ✅ Full test suite passes
  • ✅ CodeQL 0 alerts
Original prompt

This section details on the original issue you should resolve

<issue_title>Improve Month Ahead: Add strategic legislative forecasting, pipeline tracking, and trend analysis</issue_title>
<issue_description>## 📋 Issue Type
Enhancement — Extend Month Ahead articles with strategic legislative forecasting and trend analysis

🎯 Objective

Enhance scripts/news-types/month-ahead.ts to go beyond basic calendar event listing into strategic legislative forecasting. Add deeper document analysis, committee pipeline tracking, budget cycle awareness, and cross-party legislative momentum analysis.

📊 Current State

  • File: scripts/news-types/month-ahead.ts (291 lines — smallest of all 8 article types)
  • REQUIRED_TOOLS: get_calendar_events, search_dokument (conditional fallback)
  • Gap: Month ahead articles are calendar-driven but lack strategic depth — they list events but don't forecast legislative outcomes, track committee pipelines, or identify emerging policy trends
  • Impact: Monthly previews read like expanded calendars rather than strategic intelligence briefs

🚀 Desired State

  • Month ahead articles include strategic legislative forecasts
  • Committee report pipeline tracking (what's coming through which committee)
  • Budget cycle awareness (flag critical fiscal deadlines)
  • Cross-party legislative momentum (which parties are pushing which policies)
  • Historical comparison (same month in previous parliamentary terms)
  • Identification of policy convergence/divergence trends
  • Enhanced template with "Strategic Outlook" and "Legislative Pipeline" sections

🔧 Implementation Approach

1. Add legislative pipeline tracking (~100 lines)

// Track propositions and motions in committee pipeline
const pendingBetankanden = await mcpClient.call('get_betankanden', {
  rm: currentRiksmote,
  limit: 20
});
const pendingPropositioner = await mcpClient.call('get_propositioner', {
  rm: currentRiksmote,
  limit: 15
});
  • Map propositions/motions to their committee processing stage
  • Estimate when key items will reach plenary voting

2. Add trend analysis (~80 lines)

// Analyze motion volume and topic distribution
const recentMotions = await mcpClient.call('get_motioner', {
  rm: currentRiksmote,
  limit: 50
});
  • Identify which policy domains are most active
  • Track party-by-party legislative activity levels
  • Flag unusual patterns (surge in motions, committee bottlenecks)

3. Budget cycle integration (~40 lines)

  • Detect proximity to key fiscal milestones (spring/autumn budget, economic forecasts)
  • Highlight finance committee deadlines
  • Flag EU-related fiscal coordination events

4. Update REQUIRED_TOOLS

Add newly integrated tools: get_betankanden, get_propositioner, get_motioner

5. Enhanced article templates

  • Add "Strategic Legislative Outlook" section
  • Add "Committee Pipeline" summary
  • Add "Policy Trends" analysis
  • Update all 14 language templates

🤖 Recommended Agent

code-quality-engineer — TypeScript implementation extending the smallest article generator

✅ Acceptance Criteria

  • Legislative pipeline tracking implemented (committee → plenary flow)
  • Trend analysis identifies active policy domains
  • Budget cycle awareness flags fiscal milestones
  • REQUIRED_TOOLS updated to include newly integrated tools
  • Article templates include "Strategic Outlook" and "Pipeline" sections
  • All 14 language templates updated
  • Prospective tone maintained (forecasting, not reporting)
  • Article quality validation passes
  • No regression in existing month-ahead generation
  • File grows from 291 lines to ~500 lines (proportional to other article types)

📚 References

  • File: scripts/news-types/month-ahead.ts (entire file — smallest article type)
  • MCP Tools: get_betankanden, get_propositioner, get_motioner, get_calendar_events
  • Pattern reference: scripts/news-types/weekly-review.ts (full enrichment pipeline example)
  • Content generators: scripts/data-transformers/content-generators.ts
  • Policy analysis: scripts/data-transformers/policy-analysis.ts
    </issue_description>

<agent_instructions>Extend month-ahead articles in scripts/news-types/month-ahead.ts with strategic legislative forecasting. Add get_betankanden, get_propositioner, get_motioner tool integrations. Add "Strategic Outlook" and "Legislative Pipeline" template sections. Update REQUIRED_TOOLS. Follow patterns from weekly-review.ts enrichment pipeline. Run tests with npx vitest run.</agent_instructions>

Comments on the Issue (you are @copilot in this section)


💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.

…ng, and trend analysis

- Add get_betankanden, get_propositioner, get_motioner to REQUIRED_TOOLS
- Fetch committee reports (20), propositions (15), motions (50) in parallel
- Calculate riksmöte dynamically from current date (Sep-Jun parliamentary year)
- Add generateMonthAheadContent with Strategic Legislative Outlook,
  Committee Pipeline, and Policy Trends sections (14-language support)
- Update data-transformers dispatcher to use new month-ahead generator
- Add hasLegislativePipeline to MonthAheadValidationResult
- Extend test mock with new MCP methods; 7 new tests (20 total)

Co-authored-by: pethers <1726836+pethers@users.noreply.github.com>
Copilot AI changed the title [WIP] Improve Month Ahead with strategic forecasting and analysis Enhance month-ahead: add strategic legislative forecasting, pipeline tracking, and trend analysis Feb 26, 2026
@pethers pethers requested a review from Copilot February 27, 2026 02:12
@github-actions github-actions bot added testing Test coverage refactor Code refactoring size-l Large change (250-1000 lines) labels Feb 27, 2026
@github-actions
Copy link
Contributor

🔍 Lighthouse Performance Audit

Category Score Status
Performance 85/100 🟡
Accessibility 95/100 🟢
Best Practices 90/100 🟢
SEO 95/100 🟢

📥 Download full Lighthouse report

Budget Compliance: Performance budgets enforced via budget.json

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR enhances the month-ahead article generator from a simple calendar listing into a strategic legislative intelligence brief by integrating three additional MCP data sources: committee reports, propositions, and motions. The changes transform the smallest article generator (291 lines) into a comprehensive strategic forecasting tool.

Changes:

  • Added parallel fetching of committee reports (×20), propositions (×15), and motions (×50) using Promise.all with graceful degradation
  • Implemented dynamic riksmöte (parliamentary session) calculation based on the current date
  • Extended article validation with hasLegislativePipeline check to ensure strategic content is present
  • Created new generateMonthAheadContent function with three additional sections: Strategic Legislative Outlook (top 8 propositions), Committee Pipeline (5 committees × 3 reports), and Policy Trends (top 5 domains, top 4 parties by motion volume)
  • Updated REQUIRED_TOOLS to include get_betankanden, get_propositioner, get_motioner
  • Added comprehensive test coverage for new MCP tool integrations

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.

File Description
scripts/news-types/month-ahead.ts Core generator extended with parallel legislative pipeline data fetching, dynamic riksmöte calculation, and enhanced validation
scripts/data-transformers/content-generators.ts New generateMonthAheadContent function with strategic legislative sections in 14 languages
scripts/data-transformers/index.ts Dispatcher updated to route month-ahead to dedicated content generator instead of reusing week-ahead
tests/news-types/month-ahead.test.ts Test coverage extended with mock methods and 7 new tests for legislative pipeline integration

hasForwardLookingTone,
hasStrategicContext,
hasLegislativePipeline,
passed: hasCalendarEvents && hasMinimumSources && hasForwardLookingTone && hasStrategicContext
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The validation logic for passed does not include the new hasLegislativePipeline field in its boolean expression. This means articles can pass validation without having legislative pipeline content, which contradicts the PR's goal of making this content a core strategic feature. Consider adding hasLegislativePipeline to the passed condition: passed: hasCalendarEvents && hasMinimumSources && hasForwardLookingTone && hasStrategicContext && hasLegislativePipeline

Suggested change
passed: hasCalendarEvents && hasMinimumSources && hasForwardLookingTone && hasStrategicContext
passed:
hasCalendarEvents &&
hasMinimumSources &&
hasForwardLookingTone &&
hasStrategicContext &&
hasLegislativePipeline

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in a7eb61dhasLegislativePipeline is now included in the passed condition.

Comment on lines 109 to 115
// Determine current riksmöte (Swedish parliamentary year: Sep 1 → Jun/Jul of next year).
// Any date in September or later belongs to the new year's session (e.g. 2025-09-01 → "2025/26").
const year = today.getFullYear();
const month = today.getMonth() + 1; // 1-12
const currentRiksmote = month >= 9
? `${year}/${String(year + 1).slice(-2)}`
: `${year - 1}/${String(year).slice(-2)}`;
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The riksmöte calculation uses a dynamic calculation based on the current date, while weekly-review.ts and monthly-review.ts hardcode '2025/26'. This creates an inconsistency in the codebase. While the dynamic approach is more maintainable long-term, it differs from the established pattern. Either all generators should use dynamic calculation, or month-ahead should follow the existing hardcoded pattern for consistency. The dynamic approach is technically superior but breaks the established convention. Consider documenting why this generator differs from the pattern.

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a comment in a7eb61d explaining the intentional divergence: month-ahead uses dynamic calculation so it remains accurate across parliamentary year boundaries without requiring a code change each September, unlike weekly-review/monthly-review which are short-lived generators where hardcoding is acceptable.

Comment on lines 1227 to 1229
const domainIntro = domTpl
? domTpl(escapeHtml(domainList), motions.length)
: `${motions.length} motions identify active policy domains: ${escapeHtml(domainList)}.`;
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the content generator, the domain list is escaped after being joined with comma separators, but the template functions then embed this pre-escaped value. The escaping happens at line 1228 with escapeHtml(domainList), but domainList is the result of joining domain strings that may already contain HTML-safe characters from detectPolicyDomains. While not a security issue (escaping is conservative), this creates double-escaped output if domains contain special characters. The pattern in other generators (e.g., generateCommitteeContent at line 360) escapes the final assembled string. Consider moving escapeHtml to be applied directly to domainIntro after template function execution for consistency.

Suggested change
const domainIntro = domTpl
? domTpl(escapeHtml(domainList), motions.length)
: `${motions.length} motions identify active policy domains: ${escapeHtml(domainList)}.`;
const domainIntroRaw = domTpl
? domTpl(domainList, motions.length)
: `${motions.length} motions identify active policy domains: ${domainList}.`;
const domainIntro = escapeHtml(domainIntroRaw);

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in a7eb61d — domain list is now passed raw to the template function and escapeHtml is applied to the assembled string afterward, matching the pattern used in other generators.

Comment on lines 27 to 29
fetchCommitteeReports: Mock<(limit: number, rm: string) => Promise<unknown[]>>;
fetchPropositions: Mock<(limit: number, rm: string) => Promise<unknown[]>>;
fetchMotions: Mock<(limit: number, rm: string) => Promise<unknown[]>>;
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The mock function signatures define the second parameter as rm: string (line 27-29), but according to the actual MCP client implementation (scripts/mcp-client/client.ts:394-420), the rm parameter is nullable with type string | null. The mock should match the actual signature: Mock<(limit: number, rm: string | null) => Promise<unknown[]>>. This mismatch could mask bugs where null is passed but tests don't catch it.

Suggested change
fetchCommitteeReports: Mock<(limit: number, rm: string) => Promise<unknown[]>>;
fetchPropositions: Mock<(limit: number, rm: string) => Promise<unknown[]>>;
fetchMotions: Mock<(limit: number, rm: string) => Promise<unknown[]>>;
fetchCommitteeReports: Mock<(limit: number, rm: string | null) => Promise<unknown[]>>;
fetchPropositions: Mock<(limit: number, rm: string | null) => Promise<unknown[]>>;
fetchMotions: Mock<(limit: number, rm: string | null) => Promise<unknown[]>>;

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in a7eb61d — mock signatures now use string | null to match the actual MCP client signatures.

@pethers
Copy link
Member

pethers commented Feb 27, 2026

@copilot apply changes based on the comments in this thread

- Include hasLegislativePipeline in validateMonthAhead passed condition
- Fix double-escaping in Policy Trends domain list (escape after template)
- Correct mock signatures to use string | null for rm parameter
- Document why dynamic riksmöte differs from hardcoded pattern in other generators

Co-authored-by: pethers <1726836+pethers@users.noreply.github.com>
@github-actions
Copy link
Contributor

🔍 Lighthouse Performance Audit

Category Score Status
Performance 85/100 🟡
Accessibility 95/100 🟢
Best Practices 90/100 🟢
SEO 95/100 🟢

📥 Download full Lighthouse report

Budget Compliance: Performance budgets enforced via budget.json

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 5 comments.

Comment on lines 382 to 385
const pipelineKeywords = ['pipeline', 'committee', 'proposition', 'motion', 'report', 'betank'];
return pipelineKeywords.some(keyword =>
(article.content as string).toLowerCase().includes(keyword)
);
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

checkLegislativePipeline (and the overall passed gate) is currently based on a small set of English keywords (plus betank), which will likely produce false negatives for non-English month-ahead articles (e.g. Spanish headings use “Proceso en comité”, Swedish uses “betänkande” with diacritics). Since passed now requires this check, consider switching to language-agnostic structural markers inserted by the generator (e.g. presence of the Month-Ahead sections’ <h2> blocks and/or .document-entry blocks), or broaden the keyword detection to include the localized terms used in the generated content (including betänk).

Suggested change
const pipelineKeywords = ['pipeline', 'committee', 'proposition', 'motion', 'report', 'betank'];
return pipelineKeywords.some(keyword =>
(article.content as string).toLowerCase().includes(keyword)
);
// Broad, language-aware keyword set for legislative pipeline concepts.
// Includes English plus localized terms used in generated content
// (e.g. Swedish "betänkande", Spanish "Proceso en comité", etc.).
const pipelineKeywords = [
// English
'pipeline',
'committee',
'proposition',
'motion',
'report',
// Swedish (and ASCII fallbacks)
'betank',
'betänk',
'betänkande',
'utskott',
'lagstiftningsprocess',
// Spanish
'proceso en comité',
'proceso en comite',
'proceso legislativo',
'comité',
'comite',
'informe',
'dictamen',
'proposición',
'proposicion',
'propuesta',
// German
'ausschuss',
'ausschüsse',
'gesetzgebungsverfahren',
'vorlage',
'bericht',
// French
'commission',
'procédure législative',
'procedure legislative',
'rapport',
'proposition de loi',
// Nordic / other committee terms (short stems)
'komité',
'komite',
'kommitté',
'kommission',
'komisjon'
];
const content = (article.content as string).toLowerCase();
return pipelineKeywords.some(keyword => content.includes(keyword));

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in d14f2e0checkLegislativePipeline now includes localized terms for all 14 languages: Swedish (betänk, utskott), German (ausschuss, vorlage, bericht), French (commission, rapport), Spanish (comité, informe, dictamen, propuesta), Finnish (valiokunta), Nordic variants (komité, kommitté, kommission), plus Japanese, Korean, Chinese, Arabic, and Hebrew committee terms.

Comment on lines 47 to 53
export const REQUIRED_TOOLS: readonly string[] = [
'get_calendar_events',
'search_dokument', // conditional: used only when calendar is empty
'get_betankanden',
'get_propositioner',
'get_motioner',
];
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

REQUIRED_TOOLS is updated here, but the repository’s cross-reference tool validation map in scripts/validate-cross-references.ts (REQUIRED_TOOLS_PER_TYPE) currently has no entry for month-ahead. That means any CI/QA that relies on that map won’t enforce the new required tools for month-ahead articles. Consider adding a month-ahead entry there to keep tool-requirements consistent across validation layers.

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in d14f2e0 — added a 'month-ahead' entry to REQUIRED_TOOLS_PER_TYPE listing get_calendar_events, get_betankanden, get_propositioner, get_motioner.

// Group reports by committee
const byCommittee: Record<string, RawDocument[]> = {};
reports.forEach(r => {
const key = (r as Record<string, string>).organ || 'unknown';
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The committee grouping key uses only organ, but RawDocument supports both organ and committee (and other parts of the codebase use organ ?? committee). If fetchCommitteeReports returns committee codes under committee, this will incorrectly bucket them under unknown. Consider falling back to committee (or the same resolution logic used elsewhere) when building byCommittee.

Suggested change
const key = (r as Record<string, string>).organ || 'unknown';
const rec = r as Record<string, string>;
const key = rec.organ ?? rec.committee ?? 'unknown';

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in d14f2e0 — committee grouping now uses rec2.organ ?? rec2.committee ?? 'unknown'.


// Top parties by motion volume
const topParties = Object.entries(byPartyTrend)
.filter(([k]) => k !== 'unknown')
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

normalizePartyKey() maps missing/"unknown" parties to 'other', but this filter excludes 'unknown' instead. As a result, the “Top parties” list can include the sentinel 'other' bucket and potentially crowd out real parties. Consider filtering out 'other' (to match the existing pattern used elsewhere in this file).

Suggested change
.filter(([k]) => k !== 'unknown')
.filter(([k]) => k !== 'other')

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in d14f2e0 — filter is now .filter(([k]) => k !== 'unknown' && k !== 'other') to exclude both sentinels.

Comment on lines 229 to 238
it('should include hasLegislativePipeline in validation result', () => {
const article: ArticleInput = {
content: 'Committee pipeline report proposition motion scheduled for next month.',
sources: ['source1', 'source2', 'source3']
};

const validation = monthAheadModule.validateMonthAhead(article);
expect(validation).toHaveProperty('hasLegislativePipeline');
expect(validation.hasLegislativePipeline).toBe(true);
});
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The local MonthAheadValidationResult type defined in this test file doesn’t include the new hasLegislativePipeline field, but the test asserts on validation.hasLegislativePipeline. If/when tests are typechecked, this will fail. Consider updating the test-local interface to include hasLegislativePipeline: boolean.

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in d14f2e0 — added hasLegislativePipeline: boolean to the test-local MonthAheadValidationResult interface.

@pethers
Copy link
Member

pethers commented Feb 27, 2026

@copilot apply changes based on the comments in this thread

- Broaden checkLegislativePipeline to include localized terms (SV, DE, FR, ES, FI, JA, KO, ZH, AR, HE)
- Add month-ahead entry to REQUIRED_TOOLS_PER_TYPE in validate-cross-references.ts
- Fall back to rec.committee when rec.organ is absent in committee grouping
- Filter out both 'unknown' and 'other' from top-parties list
- Add hasLegislativePipeline to test-local MonthAheadValidationResult interface

Co-authored-by: pethers <1726836+pethers@users.noreply.github.com>
@github-actions
Copy link
Contributor

🔍 Lighthouse Performance Audit

Category Score Status
Performance 85/100 🟡
Accessibility 95/100 🟢
Best Practices 90/100 🟢
SEO 95/100 🟢

📥 Download full Lighthouse report

Budget Compliance: Performance budgets enforced via budget.json

…ons.ts

Co-authored-by: pethers <1726836+pethers@users.noreply.github.com>
@github-actions
Copy link
Contributor

🔍 Lighthouse Performance Audit

Category Score Status
Performance 85/100 🟡
Accessibility 95/100 🟢
Best Practices 90/100 🟢
SEO 95/100 🟢

📥 Download full Lighthouse report

Budget Compliance: Performance budgets enforced via budget.json

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.

Comment on lines 151 to 154
@@ -138,23 +154,57 @@ export async function generateMonthAhead(options: GenerationOptions = {}): Promi
}
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the events.length === 0 path, the function returns early when documents.length === 0, which prevents the new legislative pipeline fetches from running. That means month-ahead generation can be skipped even if get_betankanden/get_propositioner/get_motioner would have returned useful data for the Strategic Outlook / Pipeline / Trends sections. Consider moving the pipeline fetch before this early-return, or only skipping when both calendar/doc fallbacks and the pipeline results are empty.

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 258aa6c — the pipeline fetch (get_betankanden, get_propositioner, get_motioner) is now moved before the early-return block and always runs unconditionally. The early-return condition is updated to only skip generation when all data sources are empty: events, documents, committee reports, propositions, and motions.

Comment on lines 252 to 255
sources: events.length > 0
? ['calendar_events', 'get_betankanden', 'get_propositioner', 'get_motioner']
: ['calendar_events', 'search_dokument', 'get_betankanden', 'get_propositioner', 'get_motioner'],
},
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

crossReferences.sources is now populated with MCP tool names like get_betankanden/get_propositioner/get_motioner, but other generators use stable source identifiers (e.g. betankanden, propositioner, motioner, calendar_events). This inconsistency makes cross-run summaries harder to compare and can break any consumers that expect the existing identifier style. Consider switching these entries to the same noun-style identifiers used elsewhere (and keep tool names confined to mcpCalls).

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 258aa6ccrossReferences.sources now uses noun-style identifiers ('betankanden', 'propositioner', 'motioner') matching the established pattern in committee-reports.ts and motions.ts.

@pethers
Copy link
Member

pethers commented Feb 27, 2026

@copilot apply changes based on the comments in this thread

…ences.sources

Co-authored-by: pethers <1726836+pethers@users.noreply.github.com>
@github-actions
Copy link
Contributor

🔍 Lighthouse Performance Audit

Category Score Status
Performance 85/100 🟡
Accessibility 95/100 🟢
Best Practices 90/100 🟢
SEO 95/100 🟢

📥 Download full Lighthouse report

Budget Compliance: Performance budgets enforced via budget.json

@github-actions
Copy link
Contributor

🔍 Lighthouse Performance Audit

Category Score Status
Performance 85/100 🟡
Accessibility 95/100 🟢
Best Practices 90/100 🟢
SEO 95/100 🟢

📥 Download full Lighthouse report

Budget Compliance: Performance budgets enforced via budget.json

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.

Comment on lines 115 to 119
const year = today.getFullYear();
const month = today.getMonth(); // 0-11, align with motions.ts pattern
const currentRiksmote = month >= 8
? `${year}/${String(year + 1).slice(-2)}`
: `${year - 1}/${String(year).slice(-2)}`;
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

currentRiksmote is derived from today, but Month-Ahead covers a range (today → today + daysAhead). If generation runs in late August, the article range can cross into September (new parliamentary session) while currentRiksmote still points to the previous session, so the betänkanden/propositioner/motioner fetches can pull the wrong session’s pipeline data. Consider deriving the riksmöte from endDate (or detecting a Sep-1 boundary crossing and fetching/merging both sessions) so the pipeline sections stay accurate around the session rollover.

Suggested change
const year = today.getFullYear();
const month = today.getMonth(); // 0-11, align with motions.ts pattern
const currentRiksmote = month >= 8
? `${year}/${String(year + 1).slice(-2)}`
: `${year - 1}/${String(year).slice(-2)}`;
const sessionRefYear = endDate.getFullYear();
const sessionRefMonth = endDate.getMonth(); // 0-11, align with motions.ts pattern
const currentRiksmote = sessionRefMonth >= 8
? `${sessionRefYear}/${String(sessionRefYear + 1).slice(-2)}`
: `${sessionRefYear - 1}/${String(sessionRefYear).slice(-2)}`;

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 0414377currentRiksmote is now derived from endDate instead of today, so a late-August run whose article window crosses the Sep-1 boundary correctly fetches the new session's pipeline data.

Comment on lines +154 to +160
it('should fetch committee reports from MCP', async () => {
const result = await monthAheadModule.generateMonthAhead({
languages: ['en']
});

expect(mockClientInstance.fetchCommitteeReports).toHaveBeenCalled();
expect(result.mcpCalls!.some((call: MCPCallRecord) => call.tool === 'get_betankanden')).toBe(true);
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new pipeline fetches mean Month-Ahead should generate an article even when get_calendar_events is empty, as long as any of betänkanden/propositioner/motioner returns data. There’s no test covering this new behavior (only the all-empty skip path). Add a test where calendar+searchDocuments are empty but one of the pipeline mocks returns a non-empty list, and assert that files > 0 and the generated HTML includes the expected <h2> pipeline section heading(s).

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added in 0414377 — new test 'should generate article when calendar is empty but pipeline data is present' sets fetchCalendarEvents and searchDocuments to return [], returns two propositions from fetchPropositions, and asserts files > 0 and html contains Strategic Legislative Outlook.

@pethers
Copy link
Member

pethers commented Feb 27, 2026

@copilot apply changes based on the comments in this thread

…ne-only test

Co-authored-by: pethers <1726836+pethers@users.noreply.github.com>
@github-actions
Copy link
Contributor

🔍 Lighthouse Performance Audit

Category Score Status
Performance 85/100 🟡
Accessibility 95/100 🟢
Best Practices 90/100 🟢
SEO 95/100 🟢

📥 Download full Lighthouse report

Budget Compliance: Performance budgets enforced via budget.json

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated no new comments.

@pethers pethers marked this pull request as ready for review February 27, 2026 13:32
@pethers pethers merged commit 6f42b82 into main Feb 27, 2026
21 checks passed
@pethers pethers deleted the copilot/enhance-month-ahead-forecasting branch February 27, 2026 13:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

refactor Code refactoring size-l Large change (250-1000 lines) testing Test coverage

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Improve Month Ahead: Add strategic legislative forecasting, pipeline tracking, and trend analysis

3 participants