"""Build the BabyYarros evaluation booth: voice A/B + beat->paragraph + delta_cb. Reads the eval jsonls and the voice_distance summary, emits a self-contained index.html for the Booth. Three panels: A VOICE — each opening line, the three arms' continuations side by side, so the operator can SEE whether the adapter pulls arbitrary prose toward Yarros. B BEAT -> PARAGRAPH — the Skaldsong question: does the Instruct arm still take direction (on-beat / in-band / ran-on) after training on raw Yarros text. C delta_cb — the seat-free relative measure, with its A-vs-A noise floor. """ from __future__ import annotations import html, json, re, sys from pathlib import Path D = Path(sys.argv[1]) # yarros-eval dir DIST = Path(sys.argv[2]) if len(sys.argv) > 2 else None # distance stdout captured to a file OUT = Path(sys.argv[3]) if len(sys.argv) > 3 else (D / "index.html") def rows(f): p = D / f return [json.loads(l) for l in p.read_text(encoding="utf-8").splitlines()] if p.exists() else [] ARMS = [("base-unadapted", "Base · no adapter (control)"), ("base-125-tuned", "Base · Yarros LoRA (ckpt-125)"), ("instruct-tuned", "Instruct · Yarros LoRA")] voice = {} for key, _ in ARMS: for r in rows(f"voice.{key}.jsonl"): voice.setdefault((r["id"], r["seed"]), {})[key] = r prompts = {} for key, _ in ARMS: for r in rows(f"voice.{key}.jsonl"): prompts[r["id"]] = (r["tier"], r["prompt"]) def wc(t): return len(t.split()) def beat_flags(r): kh, kws = r.get("keyword_hits", 0), r.get("beat_keywords", []) ratio = kh / max(len(kws), 1) on_beat = kh >= 1 and ratio >= 0.34 # at least a third of the beat's content words rendered return on_beat, f"{kh}/{len(kws)}", r.get("in_band"), r.get("ran_on"), r.get("words", 0) beats = rows("beats.instruct.jsonl") esc = lambda s: html.escape(s or "") parts = ["""
Qwen3-4B, one epoch on the leak-gated Yarros corpus. Base LoRA = ckpt-125 (its held-out minimum). Generated on gx10, same harness per arm. Not the frozen adjudication — the voice A/B and beat test the operator asked to see.
"""] # Panel A — voice parts.append('Can the Instruct arm still take direction after training on raw Yarros continuation text? Each beat expanded to one paragraph in Yarros\' voice.
') if beats: parts.append('| beat | seed | on-beat (kw) | in-band 90–140 | ran-on | words |
|---|---|---|---|---|---|
| {esc(r.get("beat",r.get("id","")))[:64]} | ' f'{r.get("seed","")} | ' f'{("yes" if ob else "no")} {kw} | ' f'{yn(ib,True)} | {yn(ro,False)} | {w} |
beats file not present
') # Panel C — delta_cb parts.append('{esc(DIST.read_text())}')
else:
parts.append('distance summary not present
') OUT.write_text("\n".join(parts), encoding="utf-8") print(f"wrote {OUT} ({OUT.stat().st_size} bytes)")