MedASR-Bench / src /components /compare /EfficiencySection.tsx
ngoan
MedASR Bench: full platform + Hugging Face Docker Space packaging
d70f132
Raw
History Blame Contribute Delete
8.62 kB
"use client";
import type { ReactElement } from "react";
import {
CartesianGrid,
ResponsiveContainer,
Scatter,
ScatterChart,
Tooltip,
XAxis,
YAxis,
ZAxis,
} from "recharts";
import ProvenanceChip from "@/components/ui/ProvenanceChip";
import { categoryLabels, fmt } from "@/lib/data";
import type { ModelRow } from "@/lib/types";
import {
CHART_AXIS_LABEL,
CHART_AXIS_TICK,
CHART_LABEL_FILL,
CHART_LABEL_KNOCKOUT,
categoryColor,
isApiCategory,
shortName,
} from "./compare-utils";
interface EffDatum {
rtfx: number;
wer: number;
paramsB: number;
model: ModelRow;
}
function renderBubble(props: unknown): ReactElement {
const { cx, cy, size, payload } = props as {
cx?: number;
cy?: number;
size?: number;
payload?: EffDatum;
};
if (typeof cx !== "number" || typeof cy !== "number" || !payload) {
return <g />;
}
const color = categoryColor[payload.model.category];
const r = Math.max(Math.sqrt((size ?? 64) / Math.PI), 4);
const side = r * Math.SQRT2; // diamond with the same half-diagonal
return (
<g>
{isApiCategory(payload.model.category) ? (
<rect
x={cx - side / 2}
y={cy - side / 2}
width={side}
height={side}
transform={`rotate(45 ${cx} ${cy})`}
style={{ fill: color }}
fillOpacity={0.14}
stroke={color}
strokeWidth={1.25}
/>
) : (
<circle
cx={cx}
cy={cy}
r={r}
style={{ fill: color }}
fillOpacity={0.14}
stroke={color}
strokeWidth={1.25}
/>
)}
<circle cx={cx} cy={cy} r={1.6} style={{ fill: color }} />
<text
x={cx}
y={cy - r - 6}
textAnchor="middle"
style={{
fill: CHART_LABEL_FILL,
fontSize: 11,
fontFamily: "var(--font-mono), 'IBM Plex Mono', monospace",
...CHART_LABEL_KNOCKOUT,
}}
>
{shortName(payload.model)}
</text>
</g>
);
}
function EffTooltip(props: unknown) {
const { active, payload } = props as {
active?: boolean;
payload?: ReadonlyArray<{ payload?: EffDatum }>;
};
const d = payload?.[0]?.payload;
if (!active || !d) return null;
return (
<div className="glass depth-2 min-w-48 rounded-sm bg-surface-2/85 px-3 py-2.5">
<p className="text-sm leading-snug text-text">{d.model.name}</p>
<p className="num mt-0.5 text-[11px] tracking-wide text-muted">
{d.model.org} · {categoryLabels[d.model.category]}
</p>
<dl className="num mt-2 space-y-1 text-[12px]">
<div className="flex items-baseline justify-between gap-6">
<dt className="text-muted">RTFx</dt>
<dd className="text-text">{fmt(d.rtfx)}×</dd>
</div>
<div className="flex items-baseline justify-between gap-6">
<dt className="text-muted">WER</dt>
<dd className="text-text">{fmt(d.wer)}</dd>
</div>
<div className="flex items-baseline justify-between gap-6">
<dt className="text-muted">Params</dt>
<dd className="text-text">{fmt(d.paramsB, 3)}B</dd>
</div>
</dl>
</div>
);
}
export interface EfficiencySectionProps {
models: ModelRow[];
/** Name of the dataset the comparison is scoped to (e.g. "ViMedCSS"). */
datasetName?: string;
}
export default function EfficiencySection({
models,
datasetName = "ViMedCSS",
}: EfficiencySectionProps) {
const data: EffDatum[] = models.flatMap((m) => {
const s = m.metrics?.test;
if (typeof s?.rtfx !== "number" || typeof s?.wer !== "number") return [];
return [{ rtfx: s.rtfx, wer: s.wer, paramsB: m.paramsB ?? 0.1, model: m }];
});
const running = models.filter((m) => m.provenance === "running");
/* a sparse plot reads as broken unless the gap is named: when most
systems are unmeasured, say so inside the plot itself */
const unmeasured = models.length - data.length;
const sparse = data.length > 0 && unmeasured > models.length / 2;
return (
<section aria-label="Efficiency: throughput against accuracy">
<div className="glass depth-2 spotlight-card rounded-sm p-5">
{data.length > 0 ? (
<figure>
<div
className="relative h-[360px] w-full"
role="img"
aria-label={`Bubble chart of RTFx throughput against WER on the ${datasetName} Test split; bubble area scales with parameter count. ${data.length} platform-measured models plotted.`}
>
{sparse && (
<p className="num absolute top-10 left-16 z-10 max-w-[34ch] rounded-sm border border-dashed border-line bg-surface/70 px-2.5 py-1.5 text-[11px] leading-relaxed tracking-wide text-muted">
{data.length} of {models.length} systems measured — RTFx only
ships with platform-verified runs; the rest of this plane is
the evidence gap, not the field.
</p>
)}
<ResponsiveContainer width="100%" height="100%">
<ScatterChart
margin={{ top: 28, right: 36, bottom: 16, left: 8 }}
>
<CartesianGrid stroke="var(--line)" strokeWidth={1} />
<XAxis
type="number"
dataKey="rtfx"
name="RTFx"
domain={[0, "auto"]}
tick={CHART_AXIS_TICK}
tickLine={{ stroke: "var(--line)" }}
axisLine={{ stroke: "var(--line)" }}
label={{
value: "RTFX × · BETTER →",
position: "insideBottomRight",
offset: -8,
style: CHART_AXIS_LABEL,
}}
/>
<YAxis
type="number"
dataKey="wer"
name="WER"
domain={[15, 65]}
tick={CHART_AXIS_TICK}
tickLine={{ stroke: "var(--line)" }}
axisLine={{ stroke: "var(--line)" }}
width={44}
label={{
value: "WER % · ↓ BETTER",
angle: -90,
position: "insideLeft",
offset: 8,
style: { ...CHART_AXIS_LABEL, textAnchor: "middle" },
}}
/>
<ZAxis
type="number"
dataKey="paramsB"
range={[80, 640]}
name="Params (B)"
/>
<Tooltip
cursor={{ stroke: "var(--line)", strokeDasharray: "3 3" }}
content={EffTooltip}
/>
<Scatter data={data} shape={renderBubble} />
</ScatterChart>
</ResponsiveContainer>
</div>
<figcaption className="hairline-t mt-4 pt-3 text-[13px] text-muted">
RTFx = audio seconds ÷ wall seconds, measured on platform
hardware. Bubble area scales with parameter count. {datasetName}{" "}
test split · normalizer v0.1.
</figcaption>
</figure>
) : (
/* slim status banner — the tall canvas is reserved for real data */
<div className="flex flex-wrap items-center gap-x-4 gap-y-2 rounded-sm border border-dashed border-line bg-surface-2/40 px-4 py-3">
<span
aria-hidden
className="pulse-dot inline-block size-2 shrink-0 rounded-full bg-accent"
/>
<p
className="num text-[12px] tracking-[0.18em] text-muted"
title="Throughput is only recorded when this platform executes the model itself; paper-imported rows never carry RTFx."
>
AWAITING PILOT DATA — RTFX REQUIRES A PLATFORM-VERIFIED RUN
</p>
{running.length > 0 && (
<ul className="flex flex-wrap items-center gap-x-4 gap-y-2">
{running.map((m) => (
<li key={m.slug} className="flex items-center gap-2">
<span className="num text-[12px] text-text">{m.name}</span>
<ProvenanceChip provenance={m.provenance} />
</li>
))}
</ul>
)}
</div>
)}
</div>
</section>
);
}