Improve article aggregation coverage and reader guides#2429
Conversation
Agent-Logs-Url: https://github.com/Hack23/riksdagsmonitor/sessions/db1a7b8b-86f9-4703-bdc1-c08a84f72e94 Co-authored-by: pethers <1726836+pethers@users.noreply.github.com>
Agent-Logs-Url: https://github.com/Hack23/riksdagsmonitor/sessions/db1a7b8b-86f9-4703-bdc1-c08a84f72e94 Co-authored-by: pethers <1726836+pethers@users.noreply.github.com>
Agent-Logs-Url: https://github.com/Hack23/riksdagsmonitor/sessions/db1a7b8b-86f9-4703-bdc1-c08a84f72e94 Co-authored-by: pethers <1726836+pethers@users.noreply.github.com>
🏷️ Automatic Labeling SummaryThis PR has been automatically labeled based on the files changed and PR metadata. Applied Labels: documentation,testing,refactor,size-l Label Categories
For more information, see |
🔍 Lighthouse Performance Audit
📥 Download full Lighthouse report Budget Compliance: Performance budgets enforced via |
🔍 Lighthouse Performance Audit
📥 Download full Lighthouse report Budget Compliance: Performance budgets enforced via |
There was a problem hiding this comment.
Pull request overview
This PR strengthens aggregated analysis/daily/.../article.md outputs by adding an explicit artifact-coverage reconciliation section, linking supporting JSON audit artifacts (instead of inlining them), and fixing Reader Intelligence Guide audit anchors so they always point to an existing audit section (including newer political-classification.md variants).
Changes:
- Add
## Analysis Artifact Coverage Reportplus “Supporting Data Artifacts” links in## Article Sources, and include supporting.jsonfiles in the reported/returned artifact list. - Fix Reader Intelligence Guide “Audit appendix” anchor resolution (and HTML aside navigation) to target the strongest available audit section, falling back to article sources when needed.
- Add filename aliases and ordering updates for
stakeholder-impact.mdandpolitical-classification.md, with updated docs/tests and regenerated 2026-05-12 articles.
Reviewed changes
Copilot reviewed 20 out of 20 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/render-lib.test.ts | Updates aggregation-order expectations for new alias entries and appendix tail. |
| tests/render-lib-leaf-modules.test.ts | Adds unit coverage for audit-anchor selection, JSON artifact linking, and the new coverage report. |
| tests/article-aside.test.ts | Updates aside navigation tests for new audit-anchor fallback behavior. |
| scripts/validate-article.ts | Updates Reader Guide row detection to support icon-first table rows. |
| scripts/render-lib/article-aside.ts | Uses auditAnchorForArtifacts() so the audit row links to an existing audit section (or sources fallback). |
| scripts/render-lib/aggregator/sources-appendix.ts | Adds “Supporting Data Artifacts” link section and introduces buildArtifactCoverageReport(). |
| scripts/render-lib/aggregator/reader-guide.ts | Introduces auditAnchorForArtifacts() and updates audit-row anchor generation. |
| scripts/render-lib/aggregator/reader-guide-descriptions-i18n.ts | Adds reader-value aliases for stakeholder-impact.md and political-classification.md. |
| scripts/render-lib/aggregator/order.ts | Adds alias groups + ordering entries + titles for new artifact variants. |
| scripts/render-lib/aggregator/aggregate.ts | Collects supporting JSON artifacts, emits coverage report, and extends sources appendix + returned artifact list. |
| Article-Generation.md | Documents JSON artifact handling, updated order, and audit-anchor behavior. |
| analysis/daily/2026-05-12/realtime-pulse/article.md | Regenerated article includes coverage report + supporting JSON links. |
| analysis/daily/2026-05-12/propositions/executive-brief.md | Editorial fix aligning heading to “BLUF”. |
| analysis/daily/2026-05-12/propositions/article.md | Regenerated article includes new coverage report + updated guide + supporting JSON links. |
| analysis/daily/2026-05-12/motions/executive-brief.md | Editorial fix aligning heading to “BLUF”. |
| analysis/daily/2026-05-12/motions/article.md | Regenerated article includes coverage report + supporting JSON links. |
| analysis/daily/2026-05-12/interpellations/executive-brief.md | Editorial refinement to include dok_id identifiers in BLUF text. |
| analysis/daily/2026-05-12/interpellations/article.md | Regenerated article includes coverage report + supporting JSON links. |
| analysis/daily/2026-05-12/committeeReports/intelligence-assessment.md | Minor editorial tweak + trailing whitespace cleanup. |
| analysis/daily/2026-05-12/committeeReports/article.md | Regenerated article includes coverage report + supporting JSON links. |
| if (file === 'executive-brief.md') return false; | ||
| const aliases = aliasGroupFor(file); | ||
| if (aliases && [...aliases].some((alias) => emittedRootSet.has(alias))) return false; | ||
| return !rootArtifactSet.has(file); |
| ): string | null { | ||
| if (used.length === 0) return null; | ||
| if (used.length === 0 && supportingDataArtifacts.length === 0) return null; | ||
| const sourceLines = used.map((file) => { |
|
|
||
| ### Canonical political-intelligence order | ||
|
|
||
| `AGGREGATION_ORDER` in [`scripts/render-lib/aggregator.ts`](scripts/render-lib/aggregator.ts) publishes sections in this order: |
|
@copilot apply changes based on the comments in this thread and TypeScript & JavaScript Testing / Unit Tests (Vitest) (pull_request) |
Agent-Logs-Url: https://github.com/Hack23/riksdagsmonitor/sessions/8df82ab1-a439-421a-be15-1540f01d7135 Co-authored-by: pethers <1726836+pethers@users.noreply.github.com>
Addressed in 55ccc99:
Full Vitest passes locally (5461 passed / 30 skipped), as do |
🔍 Lighthouse Performance Audit
📥 Download full Lighthouse report Budget Compliance: Performance budgets enforced via |
| if (file === 'executive-brief.md') return false; | ||
| if (!rootArtifactSet.has(file)) return false; | ||
| if (emittedRootSet.has(file)) return false; | ||
| if (aliasGroupHasEmittedMember(file)) return false; |
| '## Analysis Artifact Coverage Report', | ||
| '', | ||
| 'This generated report reconciles the analysis folder with the article projection so reviewers can see what was included, what was linked as supporting data, and which canonical ordered artifacts are not visible in this run (split between "missing on disk" and "present-but-filtered" so the report matches the rendered output).', | ||
| '', | ||
| '| Coverage area | Count | Reader-facing treatment |', | ||
| '|---|---:|---|', | ||
| `| Ordered/root markdown sections | ${emittedCount} | Expanded as article sections in the narrative order above |`, | ||
| `| Per-document analyses | ${perDocCount} | Expanded under \`## Per-document intelligence\` immediately after significance scoring |`, | ||
| `| Supporting data artifacts | ${dataCount} | Linked in Article Sources, not expanded inline |`, | ||
| '', | ||
| `**Absent canonical ordered artifacts (missing from disk)**: ${fmt(input.absentOrderedArtifacts)}`, | ||
| '', | ||
| `**Present-but-filtered canonical artifacts (on disk but omitted by alias de-duplication or empty-after-cleaning)**: ${fmt(input.presentButFilteredArtifacts ?? [])}`, | ||
| ].join('\n'); |
| const collectSupportingDataArtifacts = (): string[] => { | ||
| const out: string[] = []; | ||
| const walk = (dir: string, prefix: string): void => { | ||
| for (const entry of fs.readdirSync(dir, { withFileTypes: true }) | ||
| .sort((a, b) => a.name.localeCompare(b.name))) { | ||
| const full = path.join(dir, entry.name); | ||
| const rel = prefix ? `${prefix}/${entry.name}` : entry.name; | ||
| if (entry.isDirectory()) { | ||
| // Pass-1 drafts are superseded by the Pass-2 audited artifacts and | ||
| // must not be surfaced as reader-facing supporting data. | ||
| if (isExcludedSupportingDataPath(rel)) continue; | ||
| walk(full, rel); | ||
| } else if (/\.json$/i.test(entry.name)) { | ||
| out.push(rel); | ||
| } | ||
| } | ||
| }; | ||
| walk(subfolderAbsPath, ''); | ||
| return out; | ||
| }; |
| // 1. missingFromDisk — canonical artifact filename never appeared in | ||
| // the subfolder (the analysis run never produced it). | ||
| // 2. presentButFiltered — the file existed on disk but was trimmed | ||
| // to an empty body by `cleanArtifactBody()` or skipped via the | ||
| // alias-de-duplication, so the article projection omits it. | ||
| // Reporting both keeps the coverage table honest about what the | ||
| // reader actually sees vs. what raw analysis output exists. | ||
| const aliasGroupHasEmittedMember = (file: string): boolean => { | ||
| const aliases = aliasGroupFor(file); | ||
| if (!aliases) return false; | ||
| return [...aliases].some((alias) => emittedRootSet.has(alias)); | ||
| }; | ||
| const missingFromDisk = AGGREGATION_ORDER.filter((file) => { | ||
| if (file === 'executive-brief.md') return false; | ||
| if (aliasGroupHasEmittedMember(file)) return false; | ||
| return !rootArtifactSet.has(file); | ||
| }); | ||
| // Files present on disk but skipped because another alias-group member | ||
| // was already emitted. Tracked separately so the report is accurate: | ||
| // these are not "absent" (the file exists) but they are not emitted | ||
| // either — the canonical alias wins, and only that alias is visible. | ||
| const aliasDedupedArtifacts = AGGREGATION_ORDER.filter((file) => { | ||
| if (file === 'executive-brief.md') return false; | ||
| if (!rootArtifactSet.has(file)) return false; | ||
| if (emittedRootSet.has(file)) return false; | ||
| return aliasGroupHasEmittedMember(file); | ||
| }); | ||
| // Files present on disk but trimmed to an empty body by cleanArtifactBody(). | ||
| const presentButFiltered = AGGREGATION_ORDER.filter((file) => { | ||
| if (file === 'executive-brief.md') return false; | ||
| if (!rootArtifactSet.has(file)) return false; | ||
| if (emittedRootSet.has(file)) return false; | ||
| if (aliasGroupHasEmittedMember(file)) return false; | ||
| return true; | ||
| }); |
| const missingFromDisk = AGGREGATION_ORDER.filter((file) => { | ||
| if (file === 'executive-brief.md') return false; | ||
| if (aliasGroupHasEmittedMember(file)) return false; | ||
| return !rootArtifactSet.has(file); | ||
| }); | ||
| // Files present on disk but skipped because another alias-group member | ||
| // was already emitted. Tracked separately so the report is accurate: | ||
| // these are not "absent" (the file exists) but they are not emitted | ||
| // either — the canonical alias wins, and only that alias is visible. | ||
| const aliasDedupedArtifacts = AGGREGATION_ORDER.filter((file) => { | ||
| if (file === 'executive-brief.md') return false; | ||
| if (!rootArtifactSet.has(file)) return false; | ||
| if (emittedRootSet.has(file)) return false; | ||
| return aliasGroupHasEmittedMember(file); | ||
| }); | ||
| // Files present on disk but trimmed to an empty body by cleanArtifactBody(). | ||
| const presentButFiltered = AGGREGATION_ORDER.filter((file) => { | ||
| if (file === 'executive-brief.md') return false; | ||
| if (!rootArtifactSet.has(file)) return false; | ||
| if (emittedRootSet.has(file)) return false; | ||
| if (aliasGroupHasEmittedMember(file)) return false; | ||
| return true; | ||
| }); | ||
| const absentOrderedArtifacts = missingFromDisk; |
| const lines = [ | ||
| '## Article Sources', | ||
| '', | ||
| 'Each section above projects one analysis artifact. The full audited markdown is available on GitHub:', | ||
| '', | ||
| ...sourceLines, | ||
| ]; |
| const aliasBaseName = | ||
| baseName === 'stakeholder-impact.md' | ||
| ? 'stakeholder-perspectives.md' | ||
| : baseName === 'political-classification.md' | ||
| ? 'classification-results.md' | ||
| : baseName; | ||
| return READER_VALUE_I18N[file]?.[lang] | ||
| ?? READER_VALUE_I18N[baseName]?.[lang] | ||
| ?? READER_VALUE_I18N[aliasBaseName]?.[lang]; |
|
@copilot apply changes based on the comments in this thread |
Agent-Logs-Url: https://github.com/Hack23/riksdagsmonitor/sessions/be825b9c-7b0b-4b27-aab1-af8931f22ed9 Co-authored-by: pethers <1726836+pethers@users.noreply.github.com>
All five comments addressed in 36043d3:
Three new vitest cases added covering the new buckets, supporting-data truncation annotation, and supporting-data-only preamble. Full vitest passes (5464 passed / 30 skipped) and lint is clean. |
🔍 Lighthouse Performance Audit
📥 Download full Lighthouse report Budget Compliance: Performance budgets enforced via |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 21 out of 21 changed files in this pull request and generated 3 comments.
Comments suppressed due to low confidence (1)
scripts/render-lib/aggregator/order.ts:112
political-classification.mdis now part ofAGGREGATION_ORDERand an alias ofclassification-results.md, but it isn’t currently covered by the artifact UI i18n/icon maps (ARTIFACT_TITLE_I18N/ARTIFACT_ICONinscripts/political-intelligence/i18n/artifact-i18n.ts). This means localized HTML navigation and the sources card grid may fall back to an English prettified title and a generic icon. Add explicit i18n/icon entries for this filename (or make lookups consultFILENAME_ALIASES) so both audit filenames render consistently across languages.
// ─── Phase G — Critique & alt hypotheses ───────────────────────────
'devils-advocate.md',
// ─── Phase H — Audit appendix ──────────────────────────────────────
'classification-results.md',
'political-classification.md',
'cross-reference-map.md',
'horizon-pir-rollforward.md', // long-horizon supplementary
'methodology-reflection.md',
'data-download-manifest.md',
| for (const slot of orderedSlots) { | ||
| const onDisk = slot.members.filter((m) => rootArtifactSet.has(m)); | ||
| const emitted = slot.members.filter((m) => emittedRootSet.has(m)); | ||
| if (onDisk.length === 0) { | ||
| missingFromDisk.push(slot.label); | ||
| continue; | ||
| } | ||
| if (emitted.length === 0) { | ||
| // None of the on-disk members survived cleaning. | ||
| presentButFiltered.push(onDisk.join(', ')); | ||
| continue; | ||
| } | ||
| // Slot satisfied; surface alias siblings that were skipped. | ||
| const skipped = onDisk.filter((m) => !emittedRootSet.has(m)); | ||
| if (skipped.length > 0) aliasDedupedArtifacts.push(...skipped); | ||
| } |
| const EXCLUDED_SUPPORTING_DATA_DIRS = new Set(['pass1']); | ||
|
|
||
| function isExcludedSupportingDataPath(rel: string): boolean { | ||
| for (const dir of EXCLUDED_SUPPORTING_DATA_DIRS) { | ||
| if (rel === dir || rel.startsWith(`${dir}/`)) return true; | ||
| } | ||
| return false; | ||
| } | ||
|
|
| // Stakeholder lens, parliamentary arithmetic (who can pass it), and | ||
| // voter exposure (whose interests are at stake) — clustered so the | ||
| // "WHO" question is answered as one block. | ||
| 'stakeholder-perspectives.md', | ||
| 'stakeholder-impact.md', | ||
| 'coalition-mathematics.md', | ||
| 'voter-segmentation.md', |
|
@copilot apply changes based on the comments in this thread |
…eedback Agent-Logs-Url: https://github.com/Hack23/riksdagsmonitor/sessions/eef7a52b-aa6b-4b69-b990-27a32ce86cc1 Co-authored-by: pethers <1726836+pethers@users.noreply.github.com>
All three comments addressed in f515bbc:
3 new vitest cases added; full vitest passes (5467 passed / 30 skipped) and lint is clean. |
Agent-Logs-Url: https://github.com/Hack23/riksdagsmonitor/sessions/eef7a52b-aa6b-4b69-b990-27a32ce86cc1 Co-authored-by: pethers <1726836+pethers@users.noreply.github.com>
🔍 Lighthouse Performance Audit
📥 Download full Lighthouse report Budget Compliance: Performance budgets enforced via |
| readSection('executive-brief.md', false); | ||
| sections.push(buildReaderGuide(rootArtifactSet, docsExist)); | ||
|
|
| * Number of supporting-data JSON artifacts that exist on disk but | ||
| * were truncated from `supportingDataArtifacts` because the | ||
| * collector hit its hard cap. Surfaced in the report as an explicit | ||
| * `(and N more not shown)` annotation. Optional; defaults to 0. |
🔍 Lighthouse Performance Audit
📥 Download full Lighthouse report Budget Compliance: Performance budgets enforced via |
|
@copilot apply changes based on the comments in this thread |
…count Agent-Logs-Url: https://github.com/Hack23/riksdagsmonitor/sessions/e87c624b-9e83-4386-80f9-0a544b07e303 Co-authored-by: pethers <1726836+pethers@users.noreply.github.com>
Both issues addressed in a061fd4:
|
🔍 Lighthouse Performance Audit
📥 Download full Lighthouse report Budget Compliance: Performance budgets enforced via |
buildSourcesAppendixonused.lengthfor supporting-data-only runsreaderValueForderive alias resolution fromaliasGroupForEXCLUDED_SUPPORTING_DATA_DIRS/isExcludedSupportingDataPath()artifactTitle/artifactIconalias-aware viaaliasLookupKey()emittedRootSet(post-loop) instead ofrootArtifactSet(disk) — prevents broken#anchorlinks for files cleaned-to-empty; guide spliced at index 1 to preserve Executive Brief → Guide → sections ordersupportingDataTruncatedCountJSDoc:(and N more not shown)→(+N truncated)to match actual rendered outputnpm run lintclean