Skip to content

Decompose AI analysis monoliths into bounded context modules#1339

Merged
pethers merged 15 commits intomainfrom
copilot/refactor-ai-analysis-bounded-contexts
Mar 23, 2026
Merged

Decompose AI analysis monoliths into bounded context modules#1339
pethers merged 15 commits intomainfrom
copilot/refactor-ai-analysis-bounded-contexts

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Mar 22, 2026

pipeline.ts (1,850 lines) and ai-analysis-pipeline.ts (1,542 lines) were merge-conflict hotspots with no domain boundary enforcement. Decomposed both into focused single-responsibility modules.

New module structure

scripts/ai-analysis/
├── pipeline.ts              ← 279 lines (orchestrator only, was 1,850)
├── helpers.ts               ← shared doc utilities (docType, docId, extractPassage, …)
├── swot/
│   ├── index.ts             ← buildStakeholderSwot, refineStakeholderSwot, calculateConfidenceScore
│   └── placeholders.ts      ← 14-language SWOT placeholder compositions
├── domains/
│   └── index.ts             ← buildPolicyAssessment, buildWatchPoints, EU/Nordic comparative
├── visualisation/
│   └── index.ts             ← buildMindmapBranches, buildDashboardData
└── types.ts                 ← unchanged public API

scripts/generate-news-enhanced/
├── ai-analysis-pipeline.ts  ← 800 lines (was 1,542)
└── analysis-labels.ts       ← extracted 667 lines of Lang14 constants + interpolation helpers

Key decisions

  • Dependency flow is acyclic: helpersswotvisualisation, helpersdomainsvisualisation. Pipeline orchestrator imports all three.
  • Public API unchanged: aiAnalysisPipeline and runAnalysisPipeline exports preserved — no downstream changes needed.
  • Pass 2 methods extracted as standalone functions in the enhanced pipeline rather than separate class files, since the heuristic methods have no shared state.
  • coalition-detector.ts left at its current path (already well-extracted, has its own test file and import consumers).

Quality improvements from review feedback

  • Multi-language singular/plural verb agreement: Watch-point description templates (WP_*_DESC) now use correct conjugation for n === 1 across six languages:
    • English: requires/require, establishes/establish, signals/signal, reveals/reveal
    • German: erfordert/erfordern, prägt/prägen, bildet/bilden, zeigt/zeigen, signalisiert/signalisieren
    • French: nécessite/nécessitent, façonne/façonnent, établit/établissent, révèle/révèlent, signale/signalent
    • Spanish: requiere/requieren, moldea/moldean, establece/establecen, revela/revelan, señala/señalan
    • Dutch: vereist/vereisen, vormt/vormen, onthult/onthullen, signaleert/signaleren
    • Arabic: يتطلب/تتطلب (singular/plural verb agreement in WP_PROPS_DESC)
  • English noun pluralization in takeaway templates: TAKEAWAY_PROP/BET/MOT templates use %s placeholder with plural(n, lang) interpolation for correct singular/plural nouns (proposal/proposals, report/reports, motion/motions)
  • FR/ES/NL noun pluralization in takeaway templates: Added propNounForm()/betNounForm()/motNounForm() noun-form helpers for French, Spanish, and Dutch, used via %pnoun/%bnoun/%mnoun placeholders to produce correct singular/plural nouns (e.g., FR proposition législative/propositions législatives, ES moción de oposición/mociones de oposición, NL wetsvoorstel/wetsvoorstellen)
  • Stable dashboard chart colors: STABLE_TYPE_COLORS maps 13 known doc types (prop, mot, bet, sfs, ip, fpm, skr, ds, sou, dir, pressm, ext, other) to fixed hex colors; keys are sorted before assignment so the same doc type always gets the same color regardless of encounter order
  • Deterministic mindmap ordering: buildMindmapBranches() now sorts typeKeys before rendering, matching the deterministic ordering already used in buildDashboardData()
  • Deterministic docId fallback: docId() uses composite ${type}:${title} instead of literal 'unknown' to avoid ID collisions when primary identifiers are missing
  • Deterministic buildAnalysisDocId fallback: buildAnalysisDocId() uses composite type:title:date fallback (aligned with helpers.docId() pattern) instead of truncated title
  • Improved SWOT threat enrichment: refineStakeholderSwot falls back to the last available IP doc when fewer than 3 interpellations exist, so threat enrichment fires for small IP counts
  • Dutch localisation typo fix: Corrected overheidstvoorstellenoverheidsvoorstellen in GOV_STRENGTH_LABELS
  • Removed unreachable code: impactFromDocType() no longer checks for 'eu' type since normalizedDocType() maps 'eu''fpm', making the 'eu' branch unreachable

Test coverage

  • All 3,911 existing tests pass (98 files, zero changes to existing tests)
  • 50 new per-module tests across 4 files: ai-swot-builder.test.ts (25), ai-urgency-classifier.test.ts (11), ai-coalition-detector.test.ts (3), ai-visualisation.test.ts (11)
  • TypeScript strict mode (noUnusedLocals, noUnusedParameters) clean
Original prompt

This section details on the original issue you should resolve

<issue_title>🏛️ Refactor AI Analysis Bounded Contexts: Decompose 1 700-line Monolith into Domain Modules</issue_title>
<issue_description>## 📋 Issue Type
Feature — Architecture / Bounded Context Refactoring

🎯 Objective

Decompose the monolithic scripts/ai-analysis/pipeline.ts (1 701 lines) and scripts/generate-news-enhanced/ai-analysis-pipeline.ts (1 541 lines) into focused, single-responsibility modules with clear bounded contexts. This reduces merge-conflict risk (two large files are hotspots), improves test isolation, and enables parallel development of analysis features.

📊 Current State

  • scripts/ai-analysis/pipeline.ts: 1 701 lines covering SWOT generation, watch-point classification, mindmap building, dashboard data, policy assessment, and 14-language localisation in a single file
  • scripts/generate-news-enhanced/ai-analysis-pipeline.ts: 1 541 lines with Pass 1–4 heuristic logic, SWOT defaults, synthesis, and quality scoring mixed together
  • Both files are touched by nearly every news-generation feature, causing frequent merge conflicts
  • Test coverage: ai-analysis-pipeline-coverage.test.ts (71 tests) covers the monolith with broad integration tests; unit isolation is poor
  • No domain boundary enforcement — helpers from data-transformers/ are imported ad-hoc without contracts

🚀 Desired State

Three bounded sub-domains extracted from the monolith, each with its own directory, index, and test file:

scripts/ai-analysis/
├── pipeline.ts                    ← thin orchestrator (≤300 lines)
├── swot/
│   ├── index.ts                   ← SWOT entry builder + evidence validator
│   ├── placeholders.ts            ← fallback placeholder text (isolated)
│   └── types.ts                   ← SwotEntry, SwotQuadrant, EvidenceSource
├── domains/
│   ├── index.ts                   ← policy domain detection + Nordic/EU context
│   ├── urgency-classifier.ts      ← watch-point urgency from doc attributes
│   └── coalition-detector.ts     ← party divergence/convergence analysis
├── visualisation/
│   ├── index.ts                   ← mindmap + dashboard data builders
│   └── types.ts                   ← DashboardData, MindmapBranch
└── types.ts                       ← shared interfaces (unchanged public API)

🔧 Implementation Approach

Phase 1 — Extract SWOT bounded context

Move all SWOT-building logic from pipeline.ts into scripts/ai-analysis/swot/index.ts.

  • Public surface: buildStakeholderSwot(docs, lang, depth): AnalysisStakeholderSwot[]
  • Placeholder factory isolated in swot/placeholders.ts
  • Evidence validator: requiresEvidence(depth): boolean and validateEvidence(entry): boolean

Phase 2 — Extract domain + urgency context

Move detectPolicyDomains, getDomainSpecificAnalysis, assessConfidenceLevel (currently in data-transformers/policy-analysis.ts) into domains/index.ts.

  • UrgencyClassifier.classify(doc: RawDocument): WatchPointUrgency
  • CoalitionDetector.analyze(docs: RawDocument[]): CoalitionSignal

Phase 3 — Extract visualisation context

Move mindmap and dashboard builders into visualisation/index.ts.

  • All Chart.js label/color logic lives here, nowhere else
  • buildDashboardData(docs, lang): DashboardData
  • buildMindmapBranches(analysis, lang): AnalysisMindmapBranch[]

Phase 4 — Thin orchestrator

pipeline.ts becomes a ≤300-line orchestrator that:

  1. Calls swot/index.ts for Iteration 1 + 2
  2. Calls domains/index.ts for policy assessment
  3. Calls visualisation/index.ts for dashboard + mindmap
  4. Calls validateCompleteness (unchanged public interface)

Phase 5 — Parallel refactor of generate-news-enhanced/ai-analysis-pipeline.ts

Extract Pass2DeepAnalyzer, Pass3Synthesizer, Pass4QualityAssurer as separate classes with single-method contracts.

🤖 Recommended Agent

hack23-code-quality-engineer for the bounded context extraction and module decomposition.

✅ Acceptance Criteria

  • scripts/ai-analysis/pipeline.ts reduced to ≤300 lines (orchestrator only)
  • swot/index.ts, domains/index.ts, visualisation/index.ts created with JSDoc contracts
  • All 71 existing tests/ai-analysis-pipeline-coverage.test.ts tests still pass
  • New per-module unit test files: tests/ai-swot-builder.test.ts, tests/ai-urgency-classifier.test.ts, tests/ai-coalition-detector.test.ts, tests/ai-visualisation.test.ts
  • Each new module has ≥85% statement coverage
  • No circular imports between bounded contexts (verified by eslint-plugin-import/no-cycle or equivalent)
  • generate-news-enhanced/ai-analysis-pipeline.ts reduced to ≤800 lines with Pass classes extracted

📚 References

  • Repository: https://github.com/Hack23/riksdagsmonitor
  • Monolith target: scripts/ai-analysis/pipeline.ts (1 701 lines)
  • Second monolith: scripts/generate-news-enhanced/ai-analysis-pipeline.ts (1 541 lines)
  • Tests: `tests/ai-analysis-pipeli...

Custom agent used: hack23-code-quality-engineer
Expert in code refactoring, technical debt reduction, complexity reduction, maintainability improvement, and enforcing clean code principles


📍 Connect Copilot coding agent with Jira, Azure Boards or Linear to delegate work to Copilot in one click without leaving your project management tool.

@github-actions github-actions bot added the size-xs Extra small change (< 10 lines) label Mar 22, 2026
@github-actions
Copy link
Copy Markdown
Contributor

🏷️ Automatic Labeling Summary

This PR has been automatically labeled based on the files changed and PR metadata.

Applied Labels: size-xs

Label Categories

  • 🗳️ Content: news, dashboard, visualization, intelligence
  • 💻 Technology: html-css, javascript, workflow, security
  • 📊 Data: cia-data, riksdag-data, data-pipeline, schema
  • 🌍 I18n: i18n, translation, rtl
  • 🔒 ISMS: isms, iso-27001, nist-csf, cis-controls
  • 🏗️ Infrastructure: ci-cd, deployment, performance, monitoring
  • 🔄 Quality: testing, accessibility, documentation, refactor
  • 🤖 AI: agent, skill, agentic-workflow

For more information, see .github/labeler.yml.

@github-actions
Copy link
Copy Markdown
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

Copilot AI and others added 3 commits March 22, 2026 22:25
Extract stakeholder SWOT matrix construction, enrichment, refinement
and confidence scoring into scripts/ai-analysis/swot/index.ts as part
of the AI analysis pipeline refactoring.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Extract bounded-context modules from the monolithic ai-analysis pipeline:

- domains/index.ts: policy assessment builder, watch points, EU/Nordic
  comparative analysis, and all related i18n label constants
- visualisation/index.ts: mindmap branch builder, dashboard data builder,
  type palette, section label helpers

No logic changes — pure extract-and-re-export refactoring.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Extract SWOT, domains, and visualisation bounded contexts from the
1850-line monolith into focused single-responsibility modules:

- scripts/ai-analysis/helpers.ts: shared document utility functions
- scripts/ai-analysis/swot/index.ts: SWOT entry builder, stakeholder
  construction, refinement, confidence scoring
- scripts/ai-analysis/swot/placeholders.ts: 14-language placeholder text
- scripts/ai-analysis/domains/index.ts: policy assessment, watch points,
  EU/Nordic comparative analysis
- scripts/ai-analysis/visualisation/index.ts: mindmap and dashboard builders
- scripts/ai-analysis/pipeline.ts: thin orchestrator (279 lines, ≤300 target)

All 88 existing tests pass with no changes needed.

Co-authored-by: pethers <1726836+pethers@users.noreply.github.com>
Agent-Logs-Url: https://github.com/Hack23/riksdagsmonitor/sessions/7af9b9fd-c05c-47b7-a898-ce2bfd0240c9
@github-actions github-actions bot added refactor Code refactoring size-xl Extra large change (> 1000 lines) labels Mar 22, 2026
@github-actions
Copy link
Copy Markdown
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

@pethers
Copy link
Copy Markdown
Member

pethers commented Mar 22, 2026

@copilot failing checks
TypeScript & JavaScript Testing / Unit Tests (Vitest) (pull_request)
TypeScript & JavaScript Testing / Unit Tests (Vitest) (pull_request)Failing after 42s and github-code-quality bot found potential problems 2 minutes ago
scripts/ai-analysis/domains/index.ts
Comment on lines +12 to +19
import {
detectPolicyDomains,
getDomainSpecificAnalysis,
detectNarrativeFrames,
assessConfidenceLevel,
DOMAIN_NAME_TO_KEY,
type DomainKey,
} from '../../data-transformers/policy-analysis.js';
github-code-quality commented 2 minutes ago
@github-code-quality
github-code-quality
bot
2 minutes ago
Note
Unused variable, import, function or class
Unused import getDomainSpecificAnalysis.

To fix an unused import, the general approach is to remove that specific identifier from the import list while leaving all actually used imports untouched. This eliminates the unused symbol without altering runtime behavior.

In this case, in scripts/ai-analysis/domains/index.ts, you should edit the named import from ../../data-transformers/policy-analysis.js and remove getDomainSpecificAnalysis from the destructuring list on line 14. All other imported names (detectPolicyDomains, detectNarrativeFrames, assessConfidenceLevel, DOMAIN_NAME_TO_KEY, DomainKey) should remain as they are. No new methods, imports, or definitions are required; we are only narrowing the existing import list.

Suggested changeset 1 (1)
scripts/ai-analysis/domains/index.ts
Original file line number Diff line number Diff line change
import type { RawDocument } from '../../data-transformers/types.js';
import {
detectPolicyDomains,
getDomainSpecificAnalysis,
detectNarrativeFrames,
assessConfidenceLevel,
DOMAIN_NAME_TO_KEY,
Copilot uses AI. Check for mistakes.
@pethers Reply...
Dismissing the finding will mark this conversation as resolved.
scripts/ai-analysis/domains/index.ts
// Data source labels (14 languages)
// ---------------------------------------------------------------------------

const DATA_SOURCE_LABELS: LangRecord = {
github-code-quality commented 2 minutes ago
@github-code-quality
github-code-quality
bot
2 minutes ago
Note
Unused variable, import, function or class
Unused variable DATA_SOURCE_LABELS.

@pethers Reply...
Dismissing the finding will mark this conversation as resolved.
scripts/ai-analysis/domains/index.ts
ja: 'データソース', ko: '데이터 출처', zh: '数据来源',
};

const DATA_SOURCE_ITEMS: Partial<Record<Language, string[]>> = {
github-code-quality commented 2 minutes ago
@github-code-quality
github-code-quality
bot
2 minutes ago
Note
Unused variable, import, function or class
Unused variable DATA_SOURCE_ITEMS.

In general, to fix an unused-constant issue you either (a) remove the constant if it is truly dead code, or (b) integrate it into the code path that needs it (for example, by exporting it or using it where appropriate). Since DATA_SOURCE_LABELS exists alongside DATA_SOURCE_ITEMS, the best non-breaking fix is to export both so that callers of this domains module can present localized data-source labels and their corresponding items.

Concretely, in scripts/ai-analysis/domains/index.ts, near the end of the file where other exports are defined (or, if none, by adding new exports), we should add named exports for DATA_SOURCE_LABELS and DATA_SOURCE_ITEMS. This introduces a legitimate use of DATA_SOURCE_ITEMS (it becomes part of the module’s public API) without altering existing runtime behavior. No new imports or helper methods are needed; we only add an export statement that re-exports the already-declared constants.

Suggested changeset 1 (1)
scripts/ai-analysis/domains/index.ts
Original file line number Diff line number Diff line change
zh: ['议会 MCP(法律、动议、提案)', '世界银行(经济指标)', 'SCB 瑞典统计局'],
};
export { DATA_SOURCE_LABELS, DATA_SOURCE_ITEMS };
// ---------------------------------------------------------------------------
// Watch point labels (14 languages)
// ---------------------------------------------------------------------------
Copilot uses AI. Check for mistakes.
@pethers Reply...
Dismissing the finding will mark this conversation as resolved.
scripts/ai-analysis/domains/index.ts
// Dashboard labels (14 languages)
// ---------------------------------------------------------------------------

const DASHBOARD_TITLE: LangRecord = {
github-code-quality commented 2 minutes ago
@github-code-quality
github-code-quality
bot
2 minutes ago
Note
Unused variable, import, function or class
Unused variable DASHBOARD_TITLE.

In general, unused variable warnings should be fixed either by removing the unused symbol if it is truly dead code, or by wiring it into the code path where it was intended to be used (often by exporting it or referencing it where needed). Here, DASHBOARD_TITLE is a localized label object alongside other dashboard-related constants, and neighbouring constants such as DASHBOARD_DOCS_ANALYSED are exported. The least disruptive fix that preserves intended functionality is to make DASHBOARD_TITLE available to consumers by exporting it, rather than deleting it.

Concretely, in scripts/ai-analysis/domains/index.ts, around line 259, change the declaration const DASHBOARD_TITLE: LangRecord = { ... } to export const DASHBOARD_TITLE: LangRecord = { ... }. No additional imports or helper methods are required, since LangRecord is already imported from ../helpers.js, and we are not altering the shape or contents of the constant, just its visibility. This eliminates the “unused variable” warning because the constant becomes a named export that can be consumed elsewhere in the codebase.

Suggested changeset 1 (1)
scripts/ai-analysis/domains/index.ts
Original file line number Diff line number Diff line change
// Dashboard labels (14 languages)
// ---------------------------------------------------------------------------
const DASHBOARD_TITLE: LangRecord = {
export const DASHBOARD_TITLE: LangRecord = {
en: 'Document Intelligence', sv: 'Dokumentintelligens',
da: 'Dokumentefterretning', no: 'Dokumentetterretning',
fi: 'Asiakirjatiedustelu', de: 'Dokumentenintelligenz',
Copilot uses AI. Check for mistakes.
@pethers Reply...

Copilot AI and others added 2 commits March 22, 2026 22:45
Extract all Lang14 constants, helper functions (L14, pickLang, interp,
plural, betVerbForm, motVerbForm), and the Lang14 type from
ai-analysis-pipeline.ts into a dedicated analysis-labels module.
The source file is unchanged.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Decompose the 1542-line ai-analysis-pipeline.ts monolith:

- Extract 667 lines of localised Lang14 constants and interpolation helpers
  to analysis-labels.ts
- Extract Pass 2 deep analysis methods as standalone functions
- Remove section-comment separators to compact the class
- Result: 800 lines (≤800 target met)

All 177 related tests pass.

Co-authored-by: pethers <1726836+pethers@users.noreply.github.com>
Agent-Logs-Url: https://github.com/Hack23/riksdagsmonitor/sessions/7af9b9fd-c05c-47b7-a898-ce2bfd0240c9
@github-actions
Copy link
Copy Markdown
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

- ai-swot-builder.test.ts: 25 tests for SWOT builder, enriched entries,
  placeholders, confidence scoring, refinement, impact mapping, 14-lang
- ai-urgency-classifier.test.ts: 11 tests for watch points, policy
  assessment, DASHBOARD_DOCS_ANALYSED coverage
- ai-coalition-detector.test.ts: 3 interface verification tests
- ai-visualisation.test.ts: 11 tests for mindmap branches, dashboard
  data, narrative frames label

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions github-actions bot added the testing Test coverage label Mar 22, 2026
@github-actions
Copy link
Copy Markdown
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

Remove unused imports and declarations:
- domains/index.ts: remove getDomainSpecificAnalysis import, DATA_SOURCE_LABELS,
  DATA_SOURCE_ITEMS, DASHBOARD_TITLE (all in visualisation/ already)
- pipeline.ts: remove unused Language type import
- ai-analysis-pipeline.ts: remove unused Lang14 type import
- ai-urgency-classifier.test.ts: remove unused FPM test fixture

TypeScript compiles cleanly, all 3911 tests pass.

Co-authored-by: pethers <1726836+pethers@users.noreply.github.com>
Agent-Logs-Url: https://github.com/Hack23/riksdagsmonitor/sessions/7af9b9fd-c05c-47b7-a898-ce2bfd0240c9
Copilot AI changed the title [WIP] Refactor AI analysis monolith into domain modules Decompose AI analysis monoliths into bounded context modules Mar 22, 2026
Copilot AI requested a review from pethers March 22, 2026 22:59
@github-actions
Copy link
Copy Markdown
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
Copy Markdown
Contributor Author

Copilot AI commented Mar 22, 2026

@copilot failing checks
[TypeScript & JavaScript Testing / Unit Tests (Vitest) (pull_request)](https://github.com/Hack23/riksdagsmonitor/actions/runs/23414223946/job/68106677002?pr=13...

These unused imports/variables (getDomainSpecificAnalysis, DATA_SOURCE_LABELS, DATA_SOURCE_ITEMS, DASHBOARD_TITLE) were already fixed in commit 48c131a. The failing CI run was on the earlier commit 958c3bc. The latest CI run on 48c131a has completed successfully — all checks are now green.

…posite buildAnalysisDocId fallback

- WP_PROPS_DESC: fr nécessite/nécessitent, nl vereist/vereisen
- WP_COMMITTEE_DESC: fr façonne/façonnent, es moldea/moldean, nl vormt/vormen
- WP_SFS_DESC: fr établit/établissent, es establece/establecen, nl vormt/vormen
- WP_EU_DESC: de zeigt/zeigen, fr révèle/révèlent, es revela/revelan, nl onthult/onthullen
- WP_IP_DESC: de signalisiert/signalisieren, fr signale/signalent, es señala/señalan, nl signaleert/signaleren
- WP_MOTIONS_DESC: fr signale/signalent, es señala/señalan, nl signaleert/signaleren
- buildAnalysisDocId() now uses composite type:title:date fallback (aligned with helpers.docId)

Co-authored-by: pethers <1726836+pethers@users.noreply.github.com>
Agent-Logs-Url: https://github.com/Hack23/riksdagsmonitor/sessions/b0534a11-f3b4-4554-8680-5a82cebca500
@github-actions
Copy link
Copy Markdown
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
Copy Markdown
Contributor Author

Copilot AI commented Mar 23, 2026

@copilot apply changes based on the comments in this thread

Applied all 6 review comments in 1ba89fb:

  1. French/Dutch singular/plural verb agreement in WP_PROPS_DESC (nécessite/nécessitent, vereist/vereisen)
  2. French/Spanish/Dutch verb agreement in WP_COMMITTEE_DESC (façonne/façonnent, moldea/moldean, vormt/vormen)
  3. French/Spanish/Dutch verb agreement in WP_SFS_DESC (établit/établissent, establece/establecen, vormt/vormen)
  4. German/French/Spanish/Dutch verb agreement in WP_EU_DESC (zeigt/zeigen, révèle/révèlent, revela/revelan, onthult/onthullen)
  5. German/French/Spanish/Dutch verb agreement in WP_IP_DESC (signalisiert/signalisieren, signale/signalent, señala/señalan, signaleert/signaleren)
  6. buildAnalysisDocId() now uses composite type:title:date fallback aligned with helpers.docId() pattern

Also fixed WP_MOTIONS_DESC fr/es/nl for consistency. All 3,911 tests pass, TypeScript strict mode clean, CodeQL clear.

Copy link
Copy Markdown
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 11 out of 12 changed files in this pull request and generated 5 comments.

Comment on lines +126 to +133
const typeKeys = Object.keys(typeCounts);
if (typeKeys.length > 0) {
branches.push({
label: docTypesLabel(lang),
color: 'cyan',
icon: '📄',
items: typeKeys.map(t => `${localizeDocType(t, lang, typeCounts[t] ?? 0)} (${typeCounts[t] ?? 0})`),
});
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

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

In buildMindmapBranches(), typeKeys comes from Object.keys(typeCounts) without sorting. That makes the rendered document-type list order dependent on input document order (which can vary), leading to non-deterministic UI output/diffs. Consider sorting typeKeys (as buildDashboardData already does) before mapping to items.

Copilot uses AI. Check for mistakes.
skr: '#4dd0e1', // government written communications
ds: '#f48fb1', // departmental memoranda
sou: '#a5d6a7', // government inquiry reports
dir: '#ce93d8', // commission directives
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

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

STABLE_TYPE_COLORS only assigns fixed colors for a subset of types (prop/mot/bet/sfs/ip/fpm/skr/ds/sou/dir). Types like pressm, ext, and other will get palette colors based on the current sorted typeKeys index, so their colors can change depending on which other types are present in a given analysis run. If the intent is truly stable per-type coloring, add fixed mappings for the remaining expected doc types or derive palette index from a hash of the type string rather than the per-run index.

Suggested change
dir: '#ce93d8', // commission directives
dir: '#ce93d8', // commission directives
pressm: '#ff8a65', // pressmeddelanden (press releases)
ext: '#81d4fa', // external documents / references
other: '#cfd8dc', // miscellaneous / uncategorised documents

Copilot uses AI. Check for mistakes.
Comment on lines +574 to +576
'Le gouvernement a soumis %n propositions législatives sur %t — engagement politique actif',
'El gobierno ha presentado %n propuestas legislativas sobre %t — compromiso político activo',
'De regering heeft %n wetsvoorstellen ingediend over %t — actief politiek engagement',
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

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

TAKEAWAY_PROP hard-codes plural nouns in several languages (e.g. FR "propositions législatives", ES "propuestas legislativas", NL "wetsvoorstellen"). When %n is 1 this yields grammatically incorrect output (e.g. "1 propositions …"). Consider adding singular/plural variants per language or a noun-form helper similar to betVerbForm/motVerbForm.

Suggested change
'Le gouvernement a soumis %n propositions législatives sur %t — engagement politique actif',
'El gobierno ha presentado %n propuestas legislativas sobre %t — compromiso político activo',
'De regering heeft %n wetsvoorstellen ingediend over %t — actief politiek engagement',
'Le gouvernement a soumis %n proposition(s) législative(s) sur %t — engagement politique actif',
'El gobierno ha presentado %n propuesta(s) legislativa(s) sobre %t — compromiso político activo',
'De regering heeft %n wetsvoorstel(len) ingediend over %t — actief politiek engagement',

Copilot uses AI. Check for mistakes.
Comment on lines +591 to +595
'%n rapports de commission %verb %t — contrôle parlementaire engagé',
'%n informes de comité %verb %t — supervisión parlamentaria activada',
'%n commissierapporten %verb %t — parlementaire controle actief',
'%n تقارير لجان تفحص %t — الرقابة البرلمانية مفعّلة',
'%n דוחות ועדה בוחנים %t — פיקוח פרלמנטרי פעיל',
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

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

TAKEAWAY_BET hard-codes plural nouns for FR/ES/NL ("rapports", "informes", "commissierapporten") while betVerbForm() conjugates the verb for n===1. For n=1 this can produce inconsistent grammar like "1 rapports … examine". Consider singular/plural templates (or noun-form helpers) for these languages.

Suggested change
'%n rapports de commission %verb %t — contrôle parlementaire engagé',
'%n informes de comité %verb %t — supervisión parlamentaria activada',
'%n commissierapporten %verb %t — parlementaire controle actief',
'%n تقارير لجان تفحص %t — الرقابة البرلمانية مفعّلة',
'%n דוחות ועדה בוחנים %t — פיקוח פרלמנטרי פעיל',
'%n rapport(s) de commission %verb %t — contrôle parlementaire engagé',
'%n informe(s) de comité %verb %t — supervisión parlamentaria activada',
'%n commissierapport(en) %verb %t — parlementaire controle actief',
'%n تقارير لجان تفحص %t — الرقابة البرلمانية مفعّلة',
'%n דוחות ועדה בוחנים %t — פיקוח פרלמטרי פעיל',

Copilot uses AI. Check for mistakes.
'%n Oppositionsanträge fechten %t an — parteiübergreifende Debatte aktiv',
'%n motions d\'opposition %verb %t — débat interpartis actif',
'%n mociones de oposición %verb %t — debate entre partidos activo',
'%n oppositiemoties %verb %t — overpartijdebat actief',
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

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

TAKEAWAY_MOT similarly hard-codes plural nouns for FR/ES/NL ("motions", "mociones", "oppositiemoties") while motVerbForm() conjugates verbs for n===1. For n=1 this yields incorrect phrases like "1 motions … conteste". Consider singular/plural variants or language-specific noun-form helpers.

Suggested change
'%n oppositiemoties %verb %t — overpartijdebat actief',
'%n oppositiemotie%s %verb %t — overpartijdebat actief',

Copilot uses AI. Check for mistakes.
@pethers
Copy link
Copy Markdown
Member

pethers commented Mar 23, 2026

@copilot apply changes based on the comments in this thread

@github-actions
Copy link
Copy Markdown
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

…oun-form helpers for takeaway templates

1. Sort typeKeys in buildMindmapBranches() for deterministic output
2. Add pressm/ext/other to STABLE_TYPE_COLORS for complete coverage
3. Add propNounForm()/betNounForm()/motNounForm() helpers for FR/ES/NL
4. Update TAKEAWAY_PROP/BET/MOT FR/ES/NL templates to use %pnoun/%bnoun/%mnoun
5. Update call site to pass noun forms via interp()

Co-authored-by: pethers <1726836+pethers@users.noreply.github.com>
Agent-Logs-Url: https://github.com/Hack23/riksdagsmonitor/sessions/0e043233-a52a-4511-af45-393a82551558
@github-actions
Copy link
Copy Markdown
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
Copy Markdown
Contributor Author

Copilot AI commented Mar 23, 2026

@copilot apply changes based on the comments in this thread

Applied all 5 review comments in 0f19413:

  1. Sorted typeKeys in buildMindmapBranches() for deterministic output (matching buildDashboardData)
  2. Added pressm, ext, other to STABLE_TYPE_COLORS for complete doc-type coverage
  3. Added propNounForm()/betNounForm()/motNounForm() noun-form helpers for FR/ES/NL
  4. Updated TAKEAWAY_PROP/BET/MOT FR/ES/NL templates to use %pnoun/%bnoun/%mnoun placeholders
  5. Updated call site to pass noun forms via interp()

All 3,911 tests pass, TypeScript strict mode clean, CodeQL clear.

Copy link
Copy Markdown
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 11 out of 12 changed files in this pull request and generated 3 comments.

Comment on lines +149 to +154
'Actieve wetgevingspijplijn: %n overheidstvoorstellen sturen %t-beleid',
'خط أنابيب تشريعي نشط: %n مقترح حكومي يدفع سياسة %t',
'צינור חקיקה פעיל: %n הצעות ממשלה מניעות מדיניות %t',
'アクティブな立法パイプライン: %n件の政府提案が%t政策を推進',
'활발한 입법 파이프라인: %n 정부 제안이 %t 정책 추진',
'积极的立法管道:%n 项政府提案推动%t政策',
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

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

Dutch localisation typo in GOV_STRENGTH_LABELS.propositions (nl): "overheidstvoorstellen" has an extra "t" and should be "overheidsvoorstellen".

Suggested change
'Actieve wetgevingspijplijn: %n overheidstvoorstellen sturen %t-beleid',
'خط أنابيب تشريعي نشط: %n مقترح حكومي يدفع سياسة %t',
'צינור חקיקה פעיל: %n הצעות ממשלה מניעות מדיניות %t',
'アクティブな立法パイプライン: %n件の政府提案が%t政策を推進',
'활발한 입법 파이프라인: %n 정부 제안이 %t 정책 추진',
'积极的立法管道:%n 项政府提案推动%t政策',
'Actieve wetgevingspijplijn: %n overheidsvoorstellen sturen %t-beleid',
'خط أنابيب تشريعي نشط: %n مقترح حكومي يدفع سياسة %t',
'צינור חקיקה פעיל: %n הצעות ממשלה מניעות מדיניות %t',
'アクティブな立法パイプライン: %n件の政府提案が%t政策を推進',
'활발한 입법 파이프라인: %n 정부 제안이 %t 정책 추진',
'积极的立法管道:%n 项政府提案推动%t政策',

Copilot uses AI. Check for mistakes.
fr: (n) => `${n} proposition${n !== 1 ? 's' : ''} ${n === 1 ? 'nécessite' : 'nécessitent'} un examen parlementaire`,
es: (n) => `${n} proposición${n !== 1 ? 'es' : ''} ${n === 1 ? 'requiere' : 'requieren'} acción parlamentaria`,
nl: (n) => `${n} voorstel${n !== 1 ? 'len' : ''} ${n === 1 ? 'vereist' : 'vereisen'} parlementaire behandeling`,
ar: (n) => `${n} مقترح${n !== 1 ? 'ات' : ''} تتطلب إجراء برلمانيا`,
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

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

Arabic WP_PROPS_DESC uses the plural verb "تتطلب" regardless of count; for n === 1 it should use a singular verb form to avoid ungrammatical output (e.g., conditional wording for singular vs plural).

Suggested change
ar: (n) => `${n} مقترح${n !== 1 ? 'ات' : ''} تتطلب إجراء برلمانيا`,
ar: (n) => `${n} مقترح${n !== 1 ? 'ات' : ''} ${n === 1 ? 'يتطلب' : 'تتطلب'} إجراء برلمانيا`,

Copilot uses AI. Check for mistakes.

/** Derive impact from document type. */
export function impactFromDocType(dt: string): 'high' | 'medium' | 'low' {
if (['prop', 'sfs', 'bet', 'fpm', 'eu'].includes(dt)) return 'high';
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

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

impactFromDocType() checks for both 'fpm' and 'eu', but normalizedDocType() maps 'eu' → 'fpm', so the 'eu' branch is unreachable in current call sites (buildEnrichedEntry uses normalizedDocType). Consider removing 'eu' or switching to docType() if you intend to keep a distinct 'eu' type.

Suggested change
if (['prop', 'sfs', 'bet', 'fpm', 'eu'].includes(dt)) return 'high';
if (['prop', 'sfs', 'bet', 'fpm'].includes(dt)) return 'high';

Copilot uses AI. Check for mistakes.
@pethers
Copy link
Copy Markdown
Member

pethers commented Mar 23, 2026

@copilot apply changes based on the comments in this thread

…S_DESC, remove unreachable 'eu' branch in impactFromDocType

1. Fixed Dutch localisation typo: "overheidstvoorstellen" → "overheidsvoorstellen"
2. Arabic WP_PROPS_DESC now uses singular "يتطلب" for n===1 vs plural "تتطلب"
3. Removed unreachable 'eu' from impactFromDocType() since normalizedDocType() maps 'eu' → 'fpm'

Co-authored-by: pethers <1726836+pethers@users.noreply.github.com>
Agent-Logs-Url: https://github.com/Hack23/riksdagsmonitor/sessions/9c4ccc29-6c4a-4a80-a4ca-93b8f81682d7
@github-actions
Copy link
Copy Markdown
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
Copy Markdown
Contributor Author

Copilot AI commented Mar 23, 2026

@copilot apply changes based on the comments in this thread

Applied all 3 review comments in 867d89a:

  1. Fixed Dutch typo in GOV_STRENGTH_LABELS: overheidstvoorstellenoverheidsvoorstellen
  2. Arabic WP_PROPS_DESC now uses singular يتطلب for n===1 vs plural تتطلب
  3. Removed unreachable 'eu' from impactFromDocType() since normalizedDocType() maps 'eu''fpm'

All 3,911 tests pass, TypeScript strict mode clean.

Copy link
Copy Markdown
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 11 out of 12 changed files in this pull request and generated no new comments.

@pethers pethers merged commit d4ebb74 into main Mar 23, 2026
21 checks passed
@pethers pethers deleted the copilot/refactor-ai-analysis-bounded-contexts branch March 23, 2026 01:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

refactor Code refactoring size-xl Extra large change (> 1000 lines) size-xs Extra small change (< 10 lines) testing Test coverage

Projects

None yet

Development

Successfully merging this pull request may close these issues.

🏛️ Refactor AI Analysis Bounded Contexts: Decompose 1 700-line Monolith into Domain Modules

3 participants