import type { ReportCoverage, ReportDomainSummary } from './report-projection'; /** * Deterministic key statements of the Management Report (user decision 2026-09-30). They are * language-neutral data derived only from already computed aggregates; wording lives in * `report-statement-text.ts`. No severity, risk, ranking across sources or free text is derived. */ export const REPORT_NARRATIVE_VERSION = '1.0.0'; export type ReportEvidenceRole = | 'passed' | 'deviations' | 'not-run' | 'not-applicable' | 'execution-error' | 'other-unassessed'; /** The result IDs behind one number of a statement. */ export interface ReportStatementEvidence { role: ReportEvidenceRole; domainId?: string; ids: string[]; } export interface ReportDomainFact { domainId: string; domainLabel: string; deviations: number; assessed: number; } export type ReportStatement = | { id: 'overall'; assessed: number; deviations: number; evidence: ReportStatementEvidence[] } | { id: 'focus'; /** Every domain sharing the highest absolute deviation count; ties are never broken arbitrarily. */ mostDeviations: ReportDomainFact[]; /** Every domain sharing the highest deviation share among assessed results. */ highestShare: ReportDomainFact[]; evidence: ReportStatementEvidence[]; } | { id: 'limits'; notRun: number; notApplicable: number; executionErrors: number; otherUnassessed: number; evidence: ReportStatementEvidence[]; }; const fact = (domain: ReportDomainSummary): ReportDomainFact => ({ domainId: domain.domainId, domainLabel: domain.domainLabel, deviations: domain.deviations, assessed: domain.assessed }); /** Exact comparison of deviations/assessed without floating-point rounding. */ function compareShare(left: ReportDomainSummary, right: ReportDomainSummary): number { return left.deviations * right.assessed - right.deviations * left.assessed; } function allMaxima(domains: ReportDomainSummary[], compare: (a: ReportDomainSummary, b: ReportDomainSummary) => number): ReportDomainSummary[] { const best = domains.reduce((current, item) => !current || compare(item, current) > 0 ? item : current, undefined); return best ? domains.filter(item => compare(item, best) === 0) : []; } export function buildReportStatements(domains: ReportDomainSummary[], coverage: ReportCoverage): ReportStatement[] { const statements: ReportStatement[] = [{ id: 'overall', assessed: coverage.assessed, deviations: coverage.deviations, evidence: [ { role: 'deviations', ids: coverage.evidence.deviations }, { role: 'passed', ids: coverage.evidence.passed } ] }]; const withDeviations = domains.filter(domain => domain.assessed > 0 && domain.deviations > 0); if (withDeviations.length) { const mostDeviations = allMaxima(withDeviations, (a, b) => a.deviations - b.deviations); const highestShare = allMaxima(withDeviations, compareShare); const named = [...new Map([...mostDeviations, ...highestShare].map(domain => [domain.domainId, domain])).values()]; statements.push({ id: 'focus', mostDeviations: mostDeviations.map(fact), highestShare: highestShare.map(fact), evidence: named.flatMap(domain => [ { role: 'deviations' as const, domainId: domain.domainId, ids: domain.evidence.deviations }, { role: 'passed' as const, domainId: domain.domainId, ids: domain.evidence.passed } ]) }); } const unassessed = coverage.evidence; const limits = { notRun: unassessed.notRun.length, notApplicable: unassessed.notApplicable.length, executionErrors: unassessed.executionErrors.length, otherUnassessed: unassessed.otherUnassessed.length }; if (limits.notRun + limits.notApplicable + limits.executionErrors + limits.otherUnassessed > 0) { statements.push({ id: 'limits', ...limits, evidence: ([ ['not-run', unassessed.notRun], ['not-applicable', unassessed.notApplicable], ['execution-error', unassessed.executionErrors], ['other-unassessed', unassessed.otherUnassessed] ] as const).filter(([, ids]) => ids.length).map(([role, ids]) => ({ role, ids })) }); } return statements; }