import type { H6Breakdown, H6Report } from './h6-report.js';
const escapeHtml = (value: unknown): string => String(value ?? '')
.replaceAll('&', '&')
.replaceAll('<', '<')
.replaceAll('>', '>')
.replaceAll('"', '"')
.replaceAll("'", ''');
const number = (value: number): string => new Intl.NumberFormat('en-US').format(value);
const money = (value: number): string => `$${value.toFixed(6)}`;
function breakdownRows(rows: H6Breakdown[]): string {
if (rows.length === 0) return '
| No records in the selected scope. |
';
return rows.map((row) => `
| ${escapeHtml(row.key)} | ${number(row.runs)} | ${number(row.failures)} |
${number(row.latency_avg_ms)} ms | ${number(row.tokens)} | ${money(row.cost_usd)} |
`).join('');
}
export function renderH6ReportHtml(report: H6Report): string {
const verdictClass = report.verdict === 'pass' ? 'pass' : report.verdict === 'fail' ? 'fail' : report.verdict === 'attention' ? 'attention' : 'neutral';
const scope = [
report.scope.project ? `Project: ${report.scope.project}` : 'All projects',
report.scope.run ? `Run: ${report.scope.run}` : 'All runs',
report.scope.from ? `From: ${report.scope.from}` : null,
report.scope.to ? `To: ${report.scope.to}` : null,
].filter((value): value is string => Boolean(value));
const findings = report.findings.length
? report.findings.map((finding) => `${escapeHtml(finding.code)}${escapeHtml(finding.message)}`).join('')
: 'NO_FINDINGSNo threshold breach was detected in this scope.';
const sourceRows = report.evidence_sources.map((source) => `
| ${escapeHtml(source.source)} | ${source.present ? 'present' : 'missing'} | ${source.stale ? 'stale' : 'fresh'} |
${source.age_s === null ? '—' : `${number(source.age_s)} s`} | ${number(source.records)} | ${escapeHtml(source.path)} |
`).join('');
const warnings = report.data_quality.warnings.length
? `${report.data_quality.warnings.map((warning) => `- ${escapeHtml(warning)}
`).join('')}
`
: 'No data-quality warning.
';
return `
${escapeHtml(report.title)} · ${escapeHtml(report.report_id)}
CASAN assurance dossier · contract v${report.schema_version}
${escapeHtml(report.title)}
${scope.map((item) => `${escapeHtml(item)}`).join('')}
${escapeHtml(report.verdict)}
Generated ${escapeHtml(report.generated_at)} · ${escapeHtml(report.report_id)} · Freshness ${escapeHtml(report.freshness.status)}
Governed runs${number(report.summary.runs)}
Failure rate${report.summary.failure_rate_pct}%
P95 latency${number(report.summary.latency_ms.p95)} ms
Provider tokens${number(report.summary.tokens.provider_total)}
Actual provider cost${money(report.summary.cost_usd.provider_actual)}
Estimated cost${money(report.summary.cost_usd.estimated)}
Alerts${number(report.summary.alerts)}
Retries${number(report.summary.retries)}
Evidence freshness
| Source | Presence | State | Age | Records | Path |
${sourceRows}
Step breakdown
| Step | Runs | Failures | Avg latency | Tokens | Cost |
${breakdownRows(report.details.by_step)}
Provider/model breakdown
| Provider · model | Calls | Failures | Avg latency | Tokens | Cost |
${breakdownRows(report.details.by_provider)}
Data quality · ${escapeHtml(report.data_quality.status)}
${warnings}
`;
}