import { format } from "date-fns"; import type { Analysis, PredictionClass } from "../context/AnalysisContext"; const CLASS_FULL: Record = { AD: "Alzheimer's Disease", MCI: "Mild Cognitive Impairment", Healthy: "No Cognitive Impairment", }; function escapeHtml(text: string): string { return text .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """); } function imgBlock(src: string | null | undefined, alt: string): string { if (!src) return `

No image available

`; return `${escapeHtml(alt)}`; } function confidenceRows(scores?: { AD: number; MCI: number; Healthy: number }): string { if (!scores) return ""; return (["AD", "MCI", "Healthy"] as const) .map( (cls) => `${cls}${scores[cls].toFixed(1)}%${escapeHtml(CLASS_FULL[cls])}`, ) .join(""); } function buildReportHtml(result: Analysis): string { const dateStr = format(result.date, "MMMM d, yyyy 'at' h:mm a"); const isPatient = result.analysisMode === "patient"; const trialTable = isPatient && result.trialResults?.length ? `

Trial Breakdown

${result.trialResults .map( (t) => ``, ) .join("")}
TrialFilePredictionConfidence
${t.trialNo != null ? `t${t.trialNo}` : "—"} ${escapeHtml(t.fileName)} ${t.prediction} ${t.confidence.toFixed(1)}%
` : ""; const voteSection = isPatient && result.voteCounts ? `

Vote Distribution

${(["AD", "MCI", "Healthy"] as const) .map((cls) => { const count = result.voteCounts?.[cls] ?? 0; const total = result.trialCount ?? 0; const pct = total > 0 ? ((count / total) * 100).toFixed(0) : "0"; return `
${count}/${total}
${cls}
${pct}%
`; }) .join("")}
` : ""; const vizSection = !isPatient ? `

Visualizations

Spectrogram Montage

${imgBlock(result.spectrogramImage, "Spectrogram montage")}

Scalogram Montage

${imgBlock(result.scalogramMontageImage, "Scalogram montage")}
` : ""; return ` NeuroEEG Analysis Report

NeuroEEG Analysis Report

${escapeHtml(dateStr)} · ${isPatient ? "Patient-level analysis" : "Single trial analysis"}

${isPatient ? "Final Patient Prediction" : "Predicted Class"}
${result.prediction}
${escapeHtml(CLASS_FULL[result.prediction])}
${ isPatient ? `
Agreement: ${result.agreementRatio?.toFixed(1) ?? result.confidence.toFixed(1)}% · Patient ID: ${escapeHtml(result.patientId ?? "—")} · ${result.trialCount ?? 0} trials
` : `
Confidence: ${result.confidence.toFixed(1)}% · File: ${escapeHtml(result.fileName)}
` }

Confidence Scores

${ result.confidenceScores ? `${confidenceRows(result.confidenceScores)}
ClassScoreDescription
` : `

Per-class confidence scores are shown at the trial level for patient analyses.

` }

Model: ${escapeHtml(result.model ?? "Fusion")}${result.ensembleFolds && result.ensembleFolds > 1 ? ` · ${result.ensembleFolds}-fold ensemble (soft voting)` : ""}

${voteSection} ${trialTable} ${vizSection}
This analysis is intended for research and clinical decision support purposes only. Results should be interpreted by qualified medical professionals in conjunction with other clinical assessments and patient history. This system is not a substitute for professional medical diagnosis.
`; } function reportFilename(result: Analysis): string { const stamp = format(result.date, "yyyy-MM-dd"); if (result.analysisMode === "patient" && result.patientId) { return `neuroeeg-patient-${result.patientId}-${stamp}.html`; } const base = result.fileName.replace(/\.[^.]+$/, "").replace(/[^\w.-]+/g, "_"); return `neuroeeg-${base}-${stamp}.html`; } export function downloadAnalysisReport(result: Analysis): void { const html = buildReportHtml(result); const blob = new Blob([html], { type: "text/html;charset=utf-8" }); const url = URL.createObjectURL(blob); const link = document.createElement("a"); link.href = url; link.download = reportFilename(result); document.body.appendChild(link); link.click(); document.body.removeChild(link); URL.revokeObjectURL(url); }