import type { CompoundExample, Robustness, TermRow } from "../types";
const W = 660;
const H = 266;
// Left and bottom carry a tick column plus an axis title.
const PAD = { l: 64, r: 16, t: 16, b: 58 };
const AXIS = "rgba(9,15,21,.55)";
/** Recall against description coverage. CLAP is flat; it never read the text. */
function RecallChart({
points,
clap,
chance,
}: {
points: { setting: number; names: boolean; recall: number }[];
clap: number;
chance: number;
}) {
const max = Math.max(clap, ...points.map((p) => p.recall)) * 1.3;
const x = (i: number) => PAD.l + (i * (W - PAD.l - PAD.r)) / (points.length - 1);
const y = (v: number) => H - PAD.b - (v / max) * (H - PAD.t - PAD.b);
const bm = points.map((p, i) => `${i ? "L" : "M"}${x(i)},${y(p.recall)}`).join(" ");
return (
| query term | ground-truth | CLAP | BM25 | prevalence |
|---|---|---|---|---|
| {r.term} | {r.truth ?? "no label"} | {r.clap === undefined ? "—" : r.clap.toFixed(2)} | {r.bm25 === undefined ? "—" : r.bm25.toFixed(2)} | {r.prevalence === undefined ? "—" : r.prevalence.toFixed(3)} |
| voice | gender | age | use case | |
|---|---|---|---|---|
| {e.match ? "✓" : "✗"} | {e.name} | {e.gender} | {(e.age ?? "").replace(/_/g, " ")} | {(e.use_case ?? "").replace(/_/g, " ")} |
Vector search (CLAP) is compared against text-based BM25 search. Query: “{data.query}”. BM25 degrades as descriptions are deleted first, then names.
The conjunction is{" "} {(data.clap.p10 / data.chance).toFixed(1)}× chance.
Vector search is stable even when metadata is sparse.
CLAP
BM25, descriptions deleted
With descriptions gone, BM25 returns voices with narrator or{" "} audiobook in their title.
BM25, descriptions and names deleted
Nothing left to match on: every score is zero.