import type { ModelRow, Provenance, SplitMetrics } from "@/lib/types"; import SectionLabel from "./SectionLabel"; import TopicChart, { type TopicWer } from "./TopicChart"; export interface TopicBreakdownProps { model: ModelRow; } /** The five ViMedCSS topic domains (ghost state when no breakdown exists). */ const TOPICS = [ "Medical Sciences", "Pathology & Pathogens", "Treatments", "Nutrition", "Diagnostics", ]; /** * Per-topic data is an optional forward-compatible extension of * SplitMetrics (`topics: [{ topic, wer }]`) written by platform runs; * paper imports never carry it, so we runtime-guard the shape. */ function readTopics(metrics?: ModelRow["metrics"]): TopicWer[] | null { for (const split of ["test", "hard"] as const) { const m = metrics?.[split] as | (SplitMetrics & { topics?: unknown }) | undefined; const t = m?.topics; if ( Array.isArray(t) && t.length > 0 && t.every( (x): x is TopicWer => typeof x === "object" && x !== null && typeof (x as { topic?: unknown }).topic === "string" && typeof (x as { wer?: unknown }).wer === "number", ) ) { return t; } } return null; } function gapReason(provenance: Provenance): string { if (provenance === "running") return "the pilot run executing now records it per domain"; if (provenance === "planned") return "no run exists yet"; if (provenance === "paper-imported") return "paper imports report corpus-level scores only"; return "this submission did not include a per-domain breakdown"; } /** * Per-topic WER section: recharts horizontal bars when a run recorded the * breakdown, otherwise an evidence-gap mini-note listing the five domains. */ export default function TopicBreakdown({ model }: TopicBreakdownProps) { const topics = readTopics(model.metrics); return (
{topics ? (
) : (

Per-topic WER is unavailable for this row —{" "} {gapReason(model.provenance)}. Platform-verified runs break WER down across the five ViMedCSS domains:

    {TOPICS.map((t) => (
  • {t}
  • ))}
)}
); }