MedASR-Bench / scripts /sync-results.mjs
ngoan
MedASR Bench: full platform + Hugging Face Docker Space packaging
d70f132
Raw
History Blame Contribute Delete
5.14 kB
#!/usr/bin/env node
/**
* sync-results.mjs — pull platform run outputs into src/data/results.json.
*
* Reads, for each known model and each split (test|hard):
* ../benchmark/results/<model>/<split>/metrics.json
* ../benchmark/results/<model>/<split>/run_manifest.json (optional, logged)
*
* metrics.json keys: wer, wer_ci95 [lo,hi], cer, cs_wer, cs_wer_ci95,
* n_wer, mtr_exact, mtr_fuzzy, rtfx_infer.
*
* Upserts the matching slug's metrics into the vimedcss entry of
* src/data/results.json (shape: { datasets: [{ info, models }] }) and sets
* provenance to "platform-verified". All other rows and fields are preserved.
*
* Usage: npm run sync-results
*/
import { existsSync, readFileSync, writeFileSync } from "node:fs";
import { dirname, join, resolve } from "node:path";
import { fileURLToPath } from "node:url";
const __dirname = dirname(fileURLToPath(import.meta.url));
const PLATFORM_ROOT = resolve(__dirname, "..");
const RESULTS_ROOT = resolve(PLATFORM_ROOT, "..", "benchmark", "results");
const DATA_FILE = join(PLATFORM_ROOT, "src", "data", "results.json");
/** benchmark results dir name → leaderboard slug */
const MODEL_MAP = {
zipformer: "zipformer-30m",
qwen3asr: "qwen3-asr-0.6b",
gemini: "gemini-3.5-flash",
deepgram: "deepgram-nova-3",
};
const SPLITS = ["test", "hard"];
/** Platform runs land on the anchor dataset's leaderboard. */
const TARGET_DATASET_ID = "vimedcss";
/** metrics.json key → SplitMetrics field */
function toSplitMetrics(raw) {
const out = {};
if (typeof raw.wer === "number") out.wer = raw.wer;
if (Array.isArray(raw.wer_ci95)) out.werCi = raw.wer_ci95;
if (typeof raw.cer === "number") out.cer = raw.cer;
if (typeof raw.cs_wer === "number") out.csWer = raw.cs_wer;
if (Array.isArray(raw.cs_wer_ci95)) out.csWerCi = raw.cs_wer_ci95;
if (typeof raw.n_wer === "number") out.nWer = raw.n_wer;
if (typeof raw.mtr_exact === "number") out.mtrExact = raw.mtr_exact;
if (typeof raw.mtr_fuzzy === "number") out.mtrFuzzy = raw.mtr_fuzzy;
if (typeof raw.rtfx_infer === "number") out.rtfx = raw.rtfx_infer;
return out;
}
function readJson(path) {
return JSON.parse(readFileSync(path, "utf8"));
}
function main() {
if (!existsSync(RESULTS_ROOT)) {
console.log(`[sync-results] no results dir at ${RESULTS_ROOT} — nothing to do`);
return;
}
const data = readJson(DATA_FILE);
const entry = (data.datasets ?? []).find(
(d) => d.info?.id === TARGET_DATASET_ID,
);
if (!entry) {
console.error(
`[sync-results] dataset "${TARGET_DATASET_ID}" not found in ${DATA_FILE} — aborting`,
);
process.exitCode = 1;
return;
}
let updates = 0;
for (const [dir, slug] of Object.entries(MODEL_MAP)) {
const row = entry.models.find((m) => m.slug === slug);
if (!row) {
console.warn(`[sync-results] slug ${slug} not found in results.json — skipped`);
continue;
}
for (const split of SPLITS) {
const metricsPath = join(RESULTS_ROOT, dir, split, "metrics.json");
if (!existsSync(metricsPath)) continue;
let raw;
try {
raw = readJson(metricsPath);
} catch (err) {
console.warn(`[sync-results] unreadable ${metricsPath}: ${err.message} — skipped`);
continue;
}
const manifestPath = join(RESULTS_ROOT, dir, split, "run_manifest.json");
let manifest;
if (existsSync(manifestPath)) {
try {
manifest = readJson(manifestPath);
console.log(
`[sync-results] ${slug}/${split} manifest:`,
JSON.stringify(manifest),
);
} catch {
console.warn(`[sync-results] unreadable manifest at ${manifestPath}`);
}
}
row.metrics = row.metrics ?? {};
row.metrics[split] = toSplitMetrics(raw);
/* RTFx: the manifest computes audio_s / infer_s at run time and is the
authoritative timing record; metrics.json only snapshots it at score
time and can go stale across re-runs (observed on qwen3asr). API
runners report infer_total=0 -> manifest rtfx_infer null, so RTFx
stays absent (network latency ≠ local-hardware throughput). */
if (typeof manifest?.rtfx_infer === "number") {
row.metrics[split].rtfx = manifest.rtfx_infer;
}
/* Provenance: locally-run models are platform-verified (pinned
revision, our hardware, SOTA-eligible). Commercial APIs run through
the same harness but on the vendor's service with no pinned revision,
so they are recorded as a date-stamped "api-snapshot" instead. */
const isApi = row.category === "api-intl" || row.category === "api-vn";
row.provenance = isApi ? "api-snapshot" : "platform-verified";
updates += 1;
console.log(`[sync-results] upserted ${slug}/${split}`);
}
}
if (updates === 0) {
console.log("[sync-results] no metrics found — results.json untouched");
return;
}
writeFileSync(DATA_FILE, `${JSON.stringify(data, null, 2)}\n`, "utf8");
console.log(`[sync-results] wrote ${updates} split result(s) to ${DATA_FILE}`);
}
main();