Refactor article aggregation into narrative-first journalism structure#2806
Conversation
🏷️ Automatic Labeling SummaryThis PR has been automatically labeled based on the files changed and PR metadata. Applied Labels: size-xs Label Categories
For more information, see |
🔍 Lighthouse Performance Audit
📥 Download full Lighthouse report Budget Compliance: Performance budgets enforced via |
Co-authored-by: pethers <1726836+pethers@users.noreply.github.com>
🔍 Lighthouse Performance Audit
📥 Download full Lighthouse report Budget Compliance: Performance budgets enforced via |
There was a problem hiding this comment.
Pull request overview
Refactors aggregated article output from intelligence/BLUF framing to reader-first journalism: renames the top-level section titles (Executive Brief → What Happened, Synthesis Summary → Why It Matters, Intelligence Assessment → Key Findings), prefixes audit/technical artifacts with "Deep Dive:", introduces a normalizeNarrativeTerminology post-cleaning step that rewrites BLUF headings to "Lede", renames "Decisions This Brief Supports", expands the first confidence-code mention with a plain-language gloss, and contextualizes the first HDxxxxx document id. Reader-guide labels across all 14 locales are updated and the article-generation prompt is reworded to match.
Changes:
- New
normalizeNarrativeTerminologycleaning helper wired intoaggregateAnalysis, with regex-based rewrites for BLUF/decision headings, confidence codes, andHDdocument ids. - Updated
SECTION_TITLESinaggregator/order.ts(narrative titles + "Deep Dive:" prefixes) and corresponding test expectations including audit-anchor slug changes. - Reader-guide entry labels swapped from "BLUF…" to "Lede/Ingress/…" across every
reader-guide-i18n/*.tsbundle and updated prompt copy.
Reviewed changes
Copilot reviewed 19 out of 19 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| scripts/render-lib/aggregator/cleaning/structural.ts | Adds normalizeNarrativeTerminology with per-call first-use tracking for confidence codes and HD doc ids. |
| scripts/render-lib/aggregator/aggregate.ts | Pipes each cleaned artifact body through normalizeNarrativeTerminology. |
| scripts/render-lib/aggregator/order.ts | Renames section titles to narrative wording and prefixes audit artifacts with "Deep Dive:". |
| scripts/render-lib/aggregator/reader-guide.ts | Updates the canonical executive-brief reader-guide label to "Lede and editorial decisions". |
| scripts/render-lib/aggregator/reader-guide-i18n/{en,sv,da,de,es,fi,fr,ja,ko,nl,no,zh}.ts | Localizes the executive-brief reader-guide label away from "BLUF" wording (some locales reuse English "Lede"). |
| tests/render-lib.test.ts | Updates section/title expectations and adds an integration test for the new normalization. |
| tests/render-lib-leaf-modules.test.ts | Adds a unit test for normalizeNarrativeTerminology and updates audit-anchor expectations to the "deep-dive-" prefix. |
| .github/prompts/06-article-generation.md | Reframes prompt language from "BLUF" to "journalistic lede". |
| let confidenceExplained = false; | ||
| out = out.replace(/\b(HIGH|MEDIUM|LOW)\s*\(([A-C]\d)\)/g, (match, band: string, code: string) => { | ||
| if (confidenceExplained) return match; | ||
| confidenceExplained = true; | ||
| const explanation = | ||
| band === 'HIGH' | ||
| ? 'high confidence, corroborated by multiple sources' | ||
| : band === 'MEDIUM' | ||
| ? 'medium confidence, partial corroboration' | ||
| : 'low confidence, limited corroboration'; | ||
| return `${band} (${code}, ${explanation})`; | ||
| }); | ||
|
|
||
| let firstDocContextualized = false; | ||
| out = out.replace(/\b(HD(\d{5,}))\b/g, (match, fullId: string, numericId: string) => { | ||
| if (firstDocContextualized) return match; | ||
| firstDocContextualized = true; | ||
| return `Riksdag document #${numericId} (${fullId})`; | ||
| }); |
There was a problem hiding this comment.
Fixed in 1833ffd. Hoisted the first-use flags into a caller-owned NarrativeNormalizationState; aggregateAnalysis now creates one state object and threads it through every normalizeNarrativeTerminology call, so the confidence-code gloss and Riksdag document #… contextualization fire once per article instead of once per artifact.
| export function normalizeNarrativeTerminology(body: string): string { | ||
| let out = body.replace( | ||
| /^(#{2,6})\s*(?:🎯\s*)?(?:BLUF(?:\s*\(Bottom Line Up Front\))?|Bottom Line Up Front)\s*$/gim, | ||
| '$1 Lede', | ||
| ); | ||
| out = out.replace( | ||
| /^(#{2,6})\s*Decisions This Brief Supports\s*$/gim, | ||
| '$1 Decisions and confidence context', | ||
| ); | ||
|
|
||
| let confidenceExplained = false; | ||
| out = out.replace(/\b(HIGH|MEDIUM|LOW)\s*\(([A-C]\d)\)/g, (match, band: string, code: string) => { | ||
| if (confidenceExplained) return match; | ||
| confidenceExplained = true; | ||
| const explanation = | ||
| band === 'HIGH' | ||
| ? 'high confidence, corroborated by multiple sources' | ||
| : band === 'MEDIUM' | ||
| ? 'medium confidence, partial corroboration' | ||
| : 'low confidence, limited corroboration'; | ||
| return `${band} (${code}, ${explanation})`; | ||
| }); | ||
|
|
||
| let firstDocContextualized = false; | ||
| out = out.replace(/\b(HD(\d{5,}))\b/g, (match, fullId: string, numericId: string) => { | ||
| if (firstDocContextualized) return match; | ||
| firstDocContextualized = true; | ||
| return `Riksdag document #${numericId} (${fullId})`; | ||
| }); | ||
|
|
||
| return out; | ||
| } |
There was a problem hiding this comment.
Fixed in 1833ffd. Added a lang parameter (default 'en'); the English-only rewrites now early-return for any non-English body, so localized prose is left untouched. Added a regression test asserting a Swedish body passes through unchanged.
| let firstDocContextualized = false; | ||
| out = out.replace(/\b(HD(\d{5,}))\b/g, (match, fullId: string, numericId: string) => { | ||
| if (firstDocContextualized) return match; | ||
| firstDocContextualized = true; | ||
| return `Riksdag document #${numericId} (${fullId})`; | ||
| }); |
There was a problem hiding this comment.
Documented the scope in 1833ffd. HD is the only bare Riksdag document-identifier token in these artifacts (matching BILL_ID_RE in seo/brief-extractor.ts, e.g. HD03271). The other prefixes (propositions, motions, interpellations, etc.) appear as session-scoped YYYY/NN:NNN references like prop. 2025/26:267 / MOT 2023/24:1234, where the trailing number is not a global document id — framing those as "Riksdag document #…" would be incorrect, so they're intentionally out of scope.
| export const ENTRIES: Record<string, ReaderGuideEntryI18n> = { | ||
| 'executive-brief.md': { | ||
| label: 'BLUF und redaktionelle Entscheidungen', | ||
| label: 'Lede und redaktionelle Entscheidungen', |
There was a problem hiding this comment.
Fixed in 1833ffd. Replaced the verbatim "Lede" with native journalistic terms: de → Aufmacher, fr → Chapeau, es → Entradilla, nl → Intro, no → Ingress, fi → Ingressi.
…ope doc-id + localize lede labels Co-authored-by: pethers <1726836+pethers@users.noreply.github.com>
🔍 Lighthouse Performance Audit
📥 Download full Lighthouse report Budget Compliance: Performance budgets enforced via |
This PR shifts generated articles from artifact-centric intelligence framing to a reader-first journalistic narrative. It removes BLUF-facing terminology in rendered output, improves section semantics, and adds first-use context for confidence codes and Riksdag document IDs.
Narrative section model (aggregation titles)
executive-brief.md→ What Happenedsynthesis-summary.md→ Why It Mattersintelligence-assessment.md→ Key FindingsTerminology normalization in rendered article bodies
BLUF/Bottom Line Up Frontheadings → LedeDecisions This Brief Supports→ Decisions and confidence contextHIGH (B2, …)).HDxxxxxoccurrence, adds explicit context as a Riksdag document identifier.lang === 'en'); non-English bodies pass through untouched so English copy is never injected into localized prose.HDprefix by design — the only bare Riksdag document-identifier token in these artifacts — and the rationale is documented in code (other prefixes appear as session-scopedYYYY/NN:NNNreferences whose trailing number is not a global document id).Reader Intelligence Guide wording across locales
de→ Aufmacher,fr→ Chapeau,es→ Entradilla,nl→ Intro,no→ Ingress,fi→ Ingressi (sv→ Ingress;ja/ko/zhalready localized).Prompt contract alignment
Targeted test updates