Add article quality assurance pipeline with AI content validation - #610
Conversation
Co-authored-by: pethers <1726836+pethers@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Introduces an automated article quality scoring pipeline that evaluates generated article HTML for analysis depth, stakeholder coverage, visualization completeness, and evidence/length signals, then surfaces grades (A–F) and recommendations via generation logs and the validation CLI.
Changes:
- Added new quality scoring types (
ArticleQualityReport, component scores, grade). - Implemented
scoreArticleQuality()and component assessment functions (analysis depth / stakeholder coverage / visualization quality / overall weighted scoring + recommendations). - Integrated scoring into generation pipeline logs and
validate-articlesCLI (--quality, optional JSON output + grade distribution).
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| vitest.config.js | Excludes generated quality type stub from coverage. |
| test/unit/article-quality-scorer.test.js | Adds dedicated unit tests for scoring and recommendations. |
| src/utils/validate-articles.ts | Adds --quality scoring mode, grade distribution, and JSON output. |
| src/utils/article-quality-scorer.ts | Implements the quality scoring engine and recommendations. |
| src/types/quality.ts | Defines TypeScript types for quality reports and scoring components. |
| src/types/index.ts | Re-exports new quality types from the central types index. |
| src/generators/pipeline/generate-stage.ts | Adds informational quality scoring output during generation. |
| scripts/utils/validate-articles.js | Compiled JS update mirroring the TS CLI enhancements. |
| scripts/utils/article-quality-scorer.js | Compiled JS update for the new scorer utility. |
| scripts/types/quality.js | Compiled JS stub for the new types module. |
| scripts/generators/pipeline/generate-stage.js | Compiled JS update mirroring generation-stage integration. |
| function extractPlainText(html: string): string { | ||
| const mainMatch = /<main[^>]*>([\s\S]*?)<\/main>/u.exec(html); | ||
| const source = mainMatch?.[1] ?? html; | ||
| return source | ||
| .replace(/<script[^>]*>[\s\S]*?<\/script[^>]*>/giu, ' ') | ||
| .replace(/<[^>]+>/gu, ' ') |
| const mindmapMatch = /(?:class="mindmap"|id="mindmap")([\s\S]*?)(?:<\/(?:div|section|article)>)/u.exec(html); | ||
| if (!mindmapMatch?.[1]) return 0; | ||
|
|
||
| const section = mindmapMatch[1]; | ||
| let maxDepth = 0; | ||
| let depth = 0; | ||
| for (let i = 0; i < section.length - 3; i++) { | ||
| if (section.slice(i, i + 3) === '<ul') { | ||
| depth++; | ||
| if (depth > maxDepth) maxDepth = depth; | ||
| } else if (section.slice(i, i + 5) === '</ul>') { | ||
| depth--; | ||
| } | ||
| } | ||
| return maxDepth; |
| * Detects `<section` elements, `class="analysis-section"`, and `class="deep-analysis"`. | ||
| * | ||
| * @param html - Raw HTML string | ||
| * @returns Number of analysis sections found | ||
| */ | ||
| function countAnalysisSections(html: string): number { | ||
| const sections = countOccurrences(html, '<section'); | ||
| const analysisSections = countOccurrences(html, 'class="analysis-section"'); | ||
| const deepAnalysis = countOccurrences(html, 'class="deep-analysis"'); | ||
| return sections + analysisSections + deepAnalysis; |
| * @returns Article validation summary or null if the filename does not match | ||
| */ | ||
| function validateSingleFile(filename: string): ArticleValidationSummary | null { | ||
| const match = ARTICLE_FILENAME_PATTERN.exec(filename); |
| const result = assessAnalysisDepth(html); | ||
| expect(result.coalitionDynamicsAnalyzed).toBe(true); | ||
| }); | ||
|
|
|
@copilot apply changes based on the comments in this thread |
…section dedup, lastIndex safety, entity test Co-authored-by: pethers <1726836+pethers@users.noreply.github.com>
All 5 review comments addressed in commit
All 1610 tests pass, 0 lint errors, 0 CodeQL alerts. |
There was a problem hiding this comment.
Pull request overview
Adds an article quality assurance pipeline to the EU Parliament Monitor generation/validation flow by introducing a quality scoring engine (A–F grades, 0–100 scores) and integrating optional quality reporting into the CLI and generation pipeline.
Changes:
- Introduces new quality report types and a scoring utility that evaluates analysis depth, stakeholder coverage, visualization completeness, word count, and evidence references.
- Integrates quality scoring into the generation pipeline (informational logging) and the
validate-articlesCLI (--quality, grade distribution, optional JSON report output). - Adds unit tests for the new scorer and updates Vitest config exclusions for the new compiled type module.
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| vitest.config.js | Excludes new compiled types module from coverage. |
| src/types/quality.ts | Adds new TypeScript interfaces/types for quality scoring output. |
| src/types/index.ts | Re-exports the new quality types via the barrel. |
| src/utils/article-quality-scorer.ts | Implements the quality scoring engine and recommendation generation. |
| src/utils/validate-articles.ts | Adds --quality scoring mode, grade distribution, and JSON output support. |
| src/generators/pipeline/generate-stage.ts | Adds per-language quality scoring logs (non-blocking). |
| test/unit/article-quality-scorer.test.js | Adds unit tests for scorer components and integration behavior. |
| scripts/utils/article-quality-scorer.js | Compiled output for the new scorer utility. |
| scripts/utils/validate-articles.js | Compiled output for CLI enhancements. |
| scripts/types/quality.js | Compiled output placeholder for quality types. |
| scripts/generators/pipeline/generate-stage.js | Compiled output for pipeline integration. |
| export function assessVisualizationQuality(html: string): VisualizationQuality { | ||
| const swotPresent = html.includes('class="swot"') || html.includes('id="swot"'); | ||
| const swotDimensions = | ||
| countOccurrences(html, 'class="swot-dimension"') + | ||
| countOccurrences(html, 'data-dimension'); | ||
|
|
||
| const dashboardPresent = | ||
| html.includes('class="dashboard"') || html.includes('id="dashboard"'); | ||
| const dashboardMetrics = | ||
| countOccurrences(html, 'class="metric"') + | ||
| countOccurrences(html, 'class="dashboard-metric"'); | ||
| const dashboardTrends = | ||
| html.includes('class="trend"') || html.includes('↑') || html.includes('↓'); | ||
|
|
||
| const mindmapPresent = html.includes('class="mindmap"') || html.includes('id="mindmap"'); | ||
| const mindmapDepth = mindmapPresent ? computeMindmapDepth(html) : 0; | ||
|
|
| it('detects SWOT by class', () => { | ||
| const html = buildHtml('<div class="swot"><p>Strengths</p></div>'); | ||
| const result = assessVisualizationQuality(html); | ||
| expect(result.swotPresent).toBe(true); | ||
| }); | ||
|
|
||
| it('detects SWOT by id', () => { | ||
| const html = buildHtml('<div id="swot"><p>Analysis</p></div>'); | ||
| const result = assessVisualizationQuality(html); | ||
| expect(result.swotPresent).toBe(true); | ||
| }); | ||
|
|
||
| it('counts SWOT dimensions', () => { | ||
| const html = buildHtml( | ||
| `<div class="swot"> | ||
| <div class="swot-dimension">S</div> | ||
| <div class="swot-dimension">W</div> | ||
| <div class="swot-dimension">O</div> | ||
| <div class="swot-dimension">T</div> | ||
| </div>` | ||
| ); | ||
| const result = assessVisualizationQuality(html); | ||
| expect(result.swotDimensions).toBe(4); | ||
| }); |
| it('detects mindmap by class', () => { | ||
| const html = buildHtml('<div class="mindmap"><ul><li>Node 1</li></ul></div>'); | ||
| const result = assessVisualizationQuality(html); | ||
| expect(result.mindmapPresent).toBe(true); | ||
| }); | ||
|
|
||
| it('counts mindmap depth via mindmap-level classes', () => { | ||
| const html = buildHtml( | ||
| `<div class="mindmap"> | ||
| <div class="mindmap-level">L1</div> | ||
| <div class="mindmap-level">L2</div> | ||
| <div class="mindmap-level">L3</div> | ||
| </div>` | ||
| ); | ||
| const result = assessVisualizationQuality(html); | ||
| expect(result.mindmapDepth).toBe(3); | ||
| }); | ||
|
|
||
| it('counts mindmap ul nesting depth with inner divs (balanced tag matching)', () => { | ||
| const html = buildHtml( | ||
| `<div class="mindmap"> | ||
| <div class="inner"> | ||
| <ul><li>Top | ||
| <ul><li>Mid | ||
| <ul><li>Deep</li></ul> | ||
| </li></ul> | ||
| </li></ul> | ||
| </div> | ||
| </div>` | ||
| ); | ||
| const result = assessVisualizationQuality(html); | ||
| expect(result.mindmapDepth).toBe(3); | ||
| }); |
| it('detects dashboard by class', () => { | ||
| const html = buildHtml('<div class="dashboard"><span class="metric">42</span></div>'); | ||
| const result = assessVisualizationQuality(html); | ||
| expect(result.dashboardPresent).toBe(true); | ||
| expect(result.dashboardMetrics).toBeGreaterThan(0); | ||
| }); | ||
|
|
||
| it('detects dashboard trends via arrow symbols', () => { | ||
| const html = buildHtml('<div class="dashboard"><span>↑ 5%</span><span>↓ 2%</span></div>'); | ||
| const result = assessVisualizationQuality(html); | ||
| expect(result.dashboardTrends).toBe(true); | ||
| }); | ||
|
|
||
| it('detects dashboard trends via class', () => { | ||
| const html = buildHtml('<div class="dashboard"><span class="trend">rising</span></div>'); | ||
| const result = assessVisualizationQuality(html); | ||
| expect(result.dashboardTrends).toBe(true); | ||
| }); |
| /** Pattern matching EP document reference codes such as A9-0123 or PE-456 */ | ||
| const EP_DOCUMENT_REF_PATTERN = /[A-Z]+-\d+/gu; |
|
@copilot apply changes based on the comments in this thread and Test and Report / Validate Code (pull_request) |
…s, fix Prettier Co-authored-by: pethers <1726836+pethers@users.noreply.github.com>
All 5 review comments addressed and CI formatting failure fixed in commit
CI formatting also fixed — all 1611 tests pass, 0 lint errors, 0 CodeQL alerts, Prettier clean. |
|
@copilot apply changes based on the comments in [this thread]Test and Report / Validate Code (pull_request) |
…rettier formatting Co-authored-by: pethers <1726836+pethers@users.noreply.github.com>
Both issues addressed in commit
All 1857 tests pass, 0 ESLint errors, 0 CodeQL alerts.
Both review comments and the CodeQL alert addressed in commit
All 1857 tests pass, 0 ESLint errors, 0 CodeQL alerts. |
There was a problem hiding this comment.
Pull request overview
Adds an article-quality assurance layer to the EU Parliament Monitor generation/validation toolchain by introducing a new quality scoring engine (A–F grades, 0–100 score) and integrating it into both the generation pipeline and the validate-articles CLI.
Changes:
- Introduces
ArticleQualityReport/related scoring types and a newscoreArticleQuality()utility with analysis-depth, stakeholder, visualization, word-count, and evidence scoring. - Integrates quality scoring into the generator pipeline (informational logging only) and into
validate-articlesvia--quality+ optional JSON output. - Hardens script stripping in content/section utilities (iterative scanner) and adds unit test coverage for the scorer.
Reviewed changes
Copilot reviewed 15 out of 15 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| vitest.config.js | Excludes compiled scripts/types/quality.js from coverage, consistent with other generated type modules. |
| test/unit/article-quality-scorer.test.js | Adds extensive unit coverage for quality scoring, evidence counting, and non-English handling. |
| src/utils/validate-articles.ts | Adds --quality mode, grade distribution summary, and optional quality-report.json output. |
| src/utils/content-validator.ts | Replaces regex-based script stripping with iterative scanner before word counting. |
| src/utils/article-quality-scorer.ts | Implements the quality scoring engine (analysis depth, stakeholders, visualization, evidence, recommendations). |
| src/types/quality.ts | Adds new exported TypeScript interfaces/types for the quality pipeline. |
| src/types/index.ts | Re-exports the new quality types via the types barrel. |
| src/templates/section-builders.ts | Updates quality scoring helper to use iterative script stripping (CodeQL-safe). |
| src/generators/pipeline/generate-stage.ts | Logs quality grade/score after content validation; warns on gate failure without blocking generation. |
| scripts/utils/validate-articles.js | Compiled output of the updated CLI validator with quality mode support. |
| scripts/utils/content-validator.js | Compiled output of the updated script stripping in the validator. |
| scripts/utils/article-quality-scorer.js | Compiled output of the new scorer utility. |
| scripts/types/quality.js | Compiled (empty-runtime) output for the new TypeScript types module. |
| scripts/templates/section-builders.js | Compiled output reflecting script stripping changes. |
| scripts/generators/pipeline/generate-stage.js | Compiled output reflecting pipeline quality logging integration. |
| export interface StakeholderCoverage { | ||
| /** Stakeholder categories whose perspectives are detected in the article */ | ||
| perspectivesPresent: string[]; | ||
| /** Stakeholder categories whose perspectives appear absent from the article */ | ||
| perspectivesMissing: string[]; | ||
| /** 0–100 score based on how many of the known stakeholder types are covered */ |
| grade: ArticleGrade; | ||
| /** Actionable improvement recommendations */ | ||
| recommendations: string[]; | ||
| /** true when overallScore ≥ 40 (Grade C or better) */ | ||
| passesQualityGate: boolean; |
|
@copilot apply changes based on the comments in this thread and Code scanning results / CodeQL |
…adonly arrays Matches the convention in src/types/visualization.ts where all shared type interfaces use readonly fields and readonly arrays to prevent accidental mutation. Co-authored-by: pethers <1726836+pethers@users.noreply.github.com>
Both review comments addressed in commit
|
There was a problem hiding this comment.
Pull request overview
Adds an article quality assurance layer to EU Parliament Monitor by introducing a new quality scoring engine (A–F grading, structured reports, recommendations) and integrating it into both the generation pipeline and the validate-articles CLI.
Changes:
- Introduces
scoreArticleQuality()and supporting assessors for analysis depth, stakeholder coverage, visualization quality, and evidence/word-count scoring. - Integrates quality scoring into generation (
generate-stage) and CLI validation (validate-articles) with optional JSON reporting + grade distribution summary. - Adds new shared quality types and comprehensive Vitest unit coverage for the scorer.
Reviewed changes
Copilot reviewed 15 out of 15 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| vitest.config.js | Excludes the new compiled quality type stub from coverage. |
| test/unit/article-quality-scorer.test.js | Adds unit tests covering scoring, boundaries, evidence parsing, and non-English behavior. |
| src/utils/article-quality-scorer.ts | New scoring engine + evidence/visual detection + recommendations. |
| src/types/quality.ts | New shared types for quality reports and scoring subcomponents. |
| src/types/index.ts | Re-exports the new quality types. |
| src/generators/pipeline/generate-stage.ts | Logs quality grade/score during generation (non-blocking) and prints top recommendations on gate failure. |
| src/utils/validate-articles.ts | Adds --quality mode, grade distribution, and optional JSON output (quality-report.json). |
| src/utils/content-validator.ts | Replaces regex-based script stripping with iterative stripScriptBlocks() for safer word counting. |
| src/templates/section-builders.ts | Replaces regex-based script stripping with iterative stripScriptBlocks() in computeArticleQualityScore(). |
| scripts/utils/article-quality-scorer.js | Compiled output for the new scorer. |
| scripts/types/quality.js | Compiled type-only stub for quality types. |
| scripts/generators/pipeline/generate-stage.js | Compiled output for generation-stage integration. |
| scripts/utils/validate-articles.js | Compiled output for CLI --quality enhancements. |
| scripts/utils/content-validator.js | Compiled output for safer script stripping. |
| scripts/templates/section-builders.js | Compiled output for safer script stripping + incidental formatting changes. |
| /** | ||
| * Count non-overlapping occurrences of a CSS class or id string in HTML. | ||
| * | ||
| * @param html - HTML string to search | ||
| * @param selector - CSS class or id token to count (e.g. `class="metric"`) | ||
| * @returns Number of occurrences found | ||
| */ | ||
| function countOccurrences(html: string, selector: string): number { | ||
| let count = 0; | ||
| let index = html.indexOf(selector); | ||
| while (index !== -1) { | ||
| count++; | ||
| index = html.indexOf(selector, index + 1); | ||
| } | ||
| return count; |
| const dashboardMetrics = | ||
| countOccurrences(html, 'class="metric-card"') + | ||
| countOccurrences(html, 'class="dashboard-metric"'); |
| // Count <li> items inside perspective-evidence containers (deep-analysis generator) | ||
| const perspectiveEvidenceItems = countListItemsInClass( | ||
| htmlNoScripts, | ||
| 'class="perspective-evidence"' | ||
| ); | ||
| // Count SWOT cross-reference evidence markers (swot-content generator) | ||
| const swotRefEvidence = countOccurrences(htmlNoScripts, 'class="swot-ref-evidence"'); | ||
| // Legacy / generic evidence markers | ||
| const evidenceClasses = countOccurrences(htmlNoScripts, 'class="evidence"'); | ||
| const dataRefs = countOccurrences(htmlNoScripts, 'data-reference'); | ||
| // EP document reference codes |
Signed-off-by: James Pether Sörling <pethers@users.noreply.github.com>
|
@copilot apply changes based on the comments in this thread |
…vidence-refs counting, and build corruption Co-authored-by: pethers <1726836+pethers@users.noreply.github.com>
Both readonly review comments and the CodeQL alert were addressed in commit
All 3 review comments addressed in commit
Also fixed build corruption: duplicate |
There was a problem hiding this comment.
Pull request overview
Adds an article quality assurance layer to the generation/validation workflow, introducing a reusable scoring utility that grades generated articles (A–F) and surfaces recommendations without blocking generation.
Changes:
- Introduces
src/utils/article-quality-scorer.tsplus new shared quality-report types insrc/types/quality.ts. - Integrates quality scoring into the generation pipeline and
validate-articlesCLI (--quality, optional JSON report output). - Adds unit tests for the scorer and updates Vitest config accordingly.
Reviewed changes
Copilot reviewed 15 out of 15 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| vitest.config.js | Excludes the compiled quality type shim from coverage/execution lists. |
| test/unit/article-quality-scorer.test.js | Adds comprehensive unit tests for the new quality scorer functions and edge cases. |
| src/utils/validate-articles.ts | Adds --quality scoring mode, grade distribution summary, and optional JSON report output. |
| src/utils/content-validator.ts | Replaces regex-based <script> stripping with iterative scanning to satisfy CodeQL guidance. |
| src/utils/article-quality-scorer.ts | Implements the new scoring engine (analysis depth, stakeholders, visualization, evidence/word count) and recommendations. |
| src/types/quality.ts | Defines ArticleQualityReport and related scoring interfaces/types. |
| src/types/index.ts | Re-exports the new quality-related types from the shared types index. |
| src/templates/section-builders.ts | Updates script stripping helper docs/logic (and uses script-stripped HTML for scoring). |
| src/generators/pipeline/generate-stage.ts | Logs per-language quality grades/scores during generation (informational only). |
| scripts/** | Adds the compiled JS outputs corresponding to the new/updated TS sources. |
| * Compute the mindmap branch count by counting `class="mindmap-branch"` elements. | ||
| * Falls back to counting `<li>` elements (individual nodes/branches) within the | ||
| * mindmap section when no `mindmap-branch` classes are found, ensuring the metric | ||
| * consistently represents branch count rather than nesting depth. | ||
| * | ||
| * @param html - Raw HTML string | ||
| * @returns Number of mindmap branches detected | ||
| */ | ||
| function computeMindmapBranches(html: string): number { | ||
| // Real mindmap HTML uses class="mindmap-branch" elements | ||
| const branchCount = countOccurrences(html, 'class="mindmap-branch"'); | ||
| if (branchCount > 0) return branchCount; | ||
|
|
||
| const sectionContent = extractMindmapSection(html); | ||
| if (!sectionContent) return 0; | ||
|
|
||
| // Count <li> elements as branch nodes — each <li> represents an individual | ||
| // node in the mindmap tree, giving a branch count rather than nesting depth. |
| * @returns {@link ArticleQualityScore} with word count, section counts, and overall rating. | ||
| */ | ||
| export function computeArticleQualityScore(content: string): ArticleQualityScore { | ||
| // Remove script blocks before tag-stripping to avoid inflating word count |
|
@copilot apply changes based on the comments in this thread and Code scanning results / CodeQL |
…ment 1. computeMindmapBranches() now prefers data-branch-count attribute set by generators, falls back to mindmap-branch classes, then layer-1 list items from mindmap-branches container. 2. Remove duplicate comment in section-builders.ts. 3. Remove unused extractMindmapSection() helper. 4. CodeQL alert #73 is pre-existing on main (already fixed in PR). Co-authored-by: pethers <1726836+pethers@users.noreply.github.com>
All review comments and CodeQL addressed in commit
All 1984 tests pass, 0 ESLint errors, 0 CodeQL alerts. |
There was a problem hiding this comment.
Pull request overview
Adds an article quality assurance pipeline to EU Parliament Monitor by introducing a quality scoring engine (A–F + recommendations), wiring it into generation/validation flows, and adding unit test coverage to keep scoring behavior stable.
Changes:
- Introduces new quality report types and a new
scoreArticleQuality()scoring engine (analysis depth, stakeholder coverage, visualization quality, evidence, word count). - Integrates quality scoring into the generation pipeline and
validate-articlesCLI (--quality, optional JSON output + grade distribution). - Adds a comprehensive unit test suite for the scorer and updates coverage excludes for the generated type stub.
Reviewed changes
Copilot reviewed 15 out of 15 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| vitest.config.js | Excludes generated scripts/types/quality.js stub from coverage. |
| test/unit/article-quality-scorer.test.js | Adds extensive unit tests for scoring and edge cases. |
| src/utils/validate-articles.ts | Adds --quality scoring mode, grade distribution, and JSON report output. |
| src/utils/content-validator.ts | Switches word-count script stripping to an iterative helper (CodeQL-safe). |
| src/utils/article-quality-scorer.ts | New quality scoring implementation + helpers and recommendation generation. |
| src/types/quality.ts | Adds immutable shared interfaces/types for quality reports. |
| src/types/index.ts | Re-exports quality types from the shared types index. |
| src/templates/section-builders.ts | Updates stripScriptBlocks doc + implementation consistency (CodeQL-safe). |
| src/generators/pipeline/generate-stage.ts | Logs quality grade/score during generation and prints top recommendations on gate failure. |
| scripts/utils/validate-articles.js | Compiled JS output reflecting CLI enhancements. |
| scripts/utils/content-validator.js | Compiled JS output reflecting script-stripping changes. |
| scripts/utils/article-quality-scorer.js | Compiled JS output for the new scorer. |
| scripts/types/quality.js | Generated type-only stub for quality types. |
| scripts/templates/section-builders.js | Compiled JS output reflecting script-stripping changes. |
| scripts/generators/pipeline/generate-stage.js | Compiled JS output reflecting pipeline quality scoring integration. |
| // 3. Count layer-1 <li> children of the mindmap-branches list | ||
| return countListItemsInClass(html, 'class="mindmap-branches"'); |
| function stripScriptBlocks(html: string): string { | ||
| const OPEN = '<script'; | ||
| const CLOSE = '</script'; | ||
| let result = ''; | ||
| let pos = 0; |
| function stripScriptBlocks(html: string): string { | ||
| const OPEN = '<script'; | ||
| const CLOSE = '</script'; | ||
| let result = ''; | ||
| let pos = 0; | ||
| const lower = html.toLowerCase(); | ||
|
|
| if (!countedIndices.has(openMatch.index)) { | ||
| countedIndices.add(openMatch.index); | ||
| const startIdx = openMatch.index + openMatch[0].length; |
|
@copilot apply changes based on the comments in this thread |
…ect-child counting, fix dedup tag-start indexing Co-authored-by: pethers <1726836+pethers@users.noreply.github.com>
Both review comments and CodeQL addressed in commit
All 4 review comments addressed in commit
All 1986 tests pass, 0 ESLint errors, 0 CodeQL alerts. |
There was a problem hiding this comment.
Pull request overview
Adds an article-quality scoring/reporting capability to the EU Parliament Monitor generation + validation tooling, introducing structured quality metrics, grading, and recommendations.
Changes:
- Introduces new quality-report types and a comprehensive
scoreArticleQuality()implementation (analysis depth, stakeholders, visualization, evidence, word count). - Integrates scoring into the generation pipeline (informational logging) and the
validate-articlesCLI (--quality, optional JSON output + grade distribution). - Centralizes
<script>stripping into a shared sanitizer and updates validators/templates to reuse it; adds unit test coverage for the scorer.
Reviewed changes
Copilot reviewed 17 out of 17 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| vitest.config.js | Excludes compiled scripts/types/quality.js from coverage. |
| test/unit/article-quality-scorer.test.js | Adds extensive unit tests for scoring, grading, and edge cases. |
| src/utils/validate-articles.ts | Adds --quality scoring, grade distribution, and JSON output support. |
| src/utils/html-sanitize.ts | New shared stripScriptBlocks() helper to avoid regex-based stripping. |
| src/utils/content-validator.ts | Uses shared stripScriptBlocks() for word counting. |
| src/utils/article-quality-scorer.ts | New core scoring engine + recommendations + evidence/visual detection. |
| src/types/quality.ts | New immutable interfaces/types for quality reports and sub-scores. |
| src/types/index.ts | Re-exports the new quality types. |
| src/templates/section-builders.ts | Switches to shared stripScriptBlocks() (removes local copy). |
| src/generators/pipeline/generate-stage.ts | Logs per-language quality grade/score; warns on gate failures. |
| scripts/utils/validate-articles.js | Compiled output of src/utils/validate-articles.ts changes. |
| scripts/utils/html-sanitize.js | Compiled output of new shared sanitizer. |
| scripts/utils/content-validator.js | Compiled output reflecting shared sanitizer usage. |
| scripts/utils/article-quality-scorer.js | Compiled output of new quality scorer. |
| scripts/types/quality.js | Compiled type-only stub for quality types. |
| scripts/templates/section-builders.js | Compiled output reflecting shared sanitizer usage. |
| scripts/generators/pipeline/generate-stage.js | Compiled output reflecting pipeline logging integration. |
| const plainText = extractPlainText(html); | ||
|
|
||
| const wordCount = plainText ? plainText.split(' ').length : 0; | ||
| const analysisSections = countAnalysisSections(html); |
| const evidenceRefsItems = countListItemsInClass(htmlNoScripts, 'class="evidence-refs"'); | ||
| // Count SWOT cross-reference evidence markers (swot-content generator) | ||
| const swotRefEvidence = countOccurrences(htmlNoScripts, 'class="swot-ref-evidence"'); | ||
| // Legacy / generic evidence markers | ||
| const evidenceClasses = countOccurrences(htmlNoScripts, 'class="evidence"'); | ||
| const dataRefs = countOccurrences(htmlNoScripts, 'data-reference'); |
| total += | ||
| countListItemsInClass(sectionContent, 'class="perspective-evidence"') + | ||
| countOccurrences(sectionContent, 'class="swot-ref-evidence"') + | ||
| countOccurrences(sectionContent, 'class="evidence"') + | ||
| countOccurrences(sectionContent, 'data-reference'); |
Adds an automated quality scoring pipeline that assesses every generated article across analysis depth, stakeholder coverage, and visualization completeness — producing structured grades (A–F) and actionable improvement recommendations.
New types (
src/types/quality.ts)ArticleQualityReport— top-level report with grade, overall score (0–100), and recommendationsAnalysisDepthScore— 6 boolean dimensions (political context, coalition dynamics, historical evidence, scenario planning, confidence levels) + composite scoreStakeholderCoverage— present/missing stakeholder lists, balance score, reasoning qualityVisualizationQuality— SWOT/dashboard/mindmap/deep-analysis presence and depth metricsAll quality type interfaces use
readonlyproperties andreadonly string[]arrays to match the immutability conventions used in other shared types (e.g.src/types/visualization.ts).Quality scorer (
src/utils/article-quality-scorer.ts)Six exported functions composing into
scoreArticleQuality(html, articleId, lang, articleType):Weighted scoring: analysis depth 25%, visualization 25%, stakeholder coverage 20%, word count 15%, evidence references 15%. Grade thresholds: A ≥ 80, B ≥ 65, C ≥ 40 (gate pass), D ≥ 25, F < 25.
Non-English language support
Keyword-based analysis-depth and stakeholder scoring uses English keyword lists. For non-English articles (
lang !== 'en'), a baseline floor of 50 is applied to keyword-dependent scores to prevent systematically penalising translated content that cannot match English keywords. Additionally,generateRecommendations()skips keyword-dependent analysis-depth and stakeholder recommendations for non-English articles, since those recommendations derive from English-only keyword detection and would produce misleading suggestions for translated content.Performance optimization
Plain text is extracted once in
scoreArticleQualityand passed to sub-assessors via apreExtractedflag, eliminating redundant HTML-to-text conversions per article.Visualization detection
SWOT, dashboard, and mindmap detection uses
hasExactClassToken()— a helper that extracts allclass="..."attributes, splits values on whitespace, and checks for an exact token match. This prevents false positives from hyphenated CSS classes (e.g.dashboard-grid,dashboard-panel,dashboard-chartdo not match thedashboardtoken), while correctly detecting multi-class attributes likeclass="swot-analysis swot-multidimensional".Dashboard metric counting (multi-class aware)
dashboardMetricsusescountExactClassToken()— a helper that extracts allclass="..."attribute values, splits on whitespace, and checks for an exactmetric-cardordashboard-metrictoken match. This correctly counts multi-class elements likeclass="metric-card pipeline-on-track"andclass="metric-card coalition-shift …"that the generators actually emit, instead of relying on exact attribute-value substring matching which would miss these elements.HTML entity decoding
decodeHtmlEntitiesusesString.fromCodePoint(notfromCharCode) for correct handling of numeric entities with code points > 0xFFFF (emoji, CJK symbols), with try/catch fallback on invalid values.Mindmap branch counting (generator-aware)
VisualizationQuality.mindmapBranchesuses a three-tier detection strategy to accurately measure mindmap breadth:data-branch-countattribute from.mindmap-containerelements — this is the authoritative branch count set by the mindmap generators and avoids any counting ambiguity.class="mindmap-branch"elements whendata-branch-countis not present.<li>elements only withinclass="mindmap-branches"containers (the layer-1 branch list), restricting the count to top-level branches and avoiding inflation from nested subnodes in deep-but-narrow mindmaps.The field was renamed from
mindmapDepthtomindmapBranchesto accurately reflect that it measures branch count rather than nesting depth.Scoped deep-analysis evidence counting
deepAnalysisEvidencecountsclass="evidence",data-reference,class="perspective-evidence"list items,class="evidence-refs"list items, andclass="swot-ref-evidence"markers only within deep-analysis sections (extracted via balanced tag matching), preventing inflation from evidence markers used elsewhere in the article. The counter uses global regex iteration to scan all matching deep-analysis sections in an article, not just the first one. Sections matched by both class and id patterns are deduplicated using opening-tag index tracking, so each section contributes evidence at most once.Evidence detection matching actual generator markup
countEvidenceRefs()detects the actual CSS class patterns emitted by the generators:<li>items inside<ul class="perspective-evidence">containers (fromdeep-analysis-content.ts)<li>items inside<ul class="evidence-refs">containers (fromdeep-analysis-content.tsreasoning-chain references), handling attributed<li>tags like<li lang="en">class="swot-ref-evidence"markers (fromswot-content.ts)class="evidence"anddata-referencepatternsAll evidence counting operates on script-stripped HTML —
stripScriptBlocks()is called once up front and the cleaned HTML is used for all evidence-marker counting (not just EP doc patterns), preventing inflation from matching substrings inside inline<script>blocks and JSON-LD metadata.The
countListItemsInClass()helper usesextractContainerContent()— an element-level balanced tag matcher that finds the actual container element (e.g.<ul>) from the class attribute match position, then extracts content up to its matching closing tag. This correctly scopes<li>counting even when the container isn't wrapped in adiv/section/article. The<li>counting regex matches both plain<li>tags and attributed<li ...>tags (e.g.<li lang="en">) while excluding false matches on<link>or<listing>tags via negative lookahead.Non-overlapping occurrence counting
countOccurrences()advances the search index byselector.length(not+1) to correctly count non-overlapping occurrences as documented, preventing inflated counts when the selector could overlap with itself.Stakeholder keyword matching (word-boundary aware)
containsAnyKeyword()uses regex word-boundary matching (\b) with leading\banchors instead of simple substringincludes(). This prevents overly-generic tokens like"national"from incorrectly matching within longer words like"international", which would inflate stakeholder coverage scores. Each keyword is escaped for regex special characters and matched case-insensitively against the article text.Analysis section counting
analysisSectionscounts only<section>elements with known analysis-related class tokens (analysis,analysis-section,deep-analysis,swot-analysis,dashboard,mindmap-section,sankey-section), excluding non-analysis sections likearticle-sourcesor footer wrappers. This usescountAnalysisSections()which extracts class attributes from each<section>tag and checks for exact token matches against the allowlist, preventing inflation from structural/layout sections.Script block stripping (security hardened)
<script>blocks are stripped usingstripScriptBlocks()— an iterative index-based scanner that locates<scriptand</script>boundaries without regex. This avoids the CodeQLjs/bad-tag-filtervulnerability (high severity) that flags<script[\s\S]*?<\/script>patterns as insufficient for sanitising HTML, since regex-based script removal can be bypassed with crafted payloads. The samestripScriptBlocks()function is used inarticle-quality-scorer.ts,section-builders.ts, andcontent-validator.tsfor consistent, secure script removal across the codebase.Pipeline integration (
src/generators/pipeline/generate-stage.ts)After content validation, calls
scoreArticleQualityand logs grade/score. Quality gate failures emit warnings with top-3 recommendations but never block generation.CLI enhancement (
src/utils/validate-articles.ts)--qualityflag activates scoring on all validated articles with grade distribution summary--output=jsonwritesquality-report.jsonwith per-article scores${date}-${slug}articleId format matching the generation pipelineEP document reference detection
Evidence references are counted using focused patterns for known EP doc-id formats (TA-10-2026-0001, PE-123.456, A9-0123, P9_TA(2024)0001) with
Set<string>-based deduplication. The simple PE pattern uses a negative lookahead (PE-\d+(?!\.\d)) to prevent double-counting dotted PE references that match bothPE-\d+\.\d+andPE-\d+. Generic codes like EU-27 and EEA-32 are excluded from evidence counts.<script>blocks (e.g. JSON-LD metadata) are stripped before scanning to prevent double-counting EP doc IDs that appear in both visible content and structured metadata.Original prompt
This section details on the original issue you should resolve
<issue_title>Article Quality Assurance Pipeline with AI Content Validation</issue_title>
<issue_description>## 📋 Task Overview
Implement an end-to-end article quality assurance and AI content validation pipeline that ensures every generated article meets political analysis depth requirements, stakeholder perspective coverage, and visualization quality standards. This issue creates automated quality gates that validate AI-generated content across all article types, ensuring consistent analysis quality with multiple validation iterations and structured feedback for improvement.
🎯 Objectives
📦 Deliverables
src/utils/article-quality-scorer.ts— Comprehensive article quality assessmentsrc/utils/content-validator.ts— Extended validation for analysis depth and stakeholder coverageArticleQualityReport,AnalysisDepthScore,StakeholderCoverage,VisualizationQualitysrc/generators/pipeline/generate-stage.ts— Quality gate integrationnpm run validate-articlescommand🔒 Security & Compliance
🏗️ Technical Approach
Article Quality Scoring Framework
Quality Gate Thresholds
Validation Pipeline