feat: add trend history and ribbons (task 0006)

Add the longitudinal layer to the benchmark core: a per-skill JSON-lines
history that appends one summary line per run and trims oldest-first at
fifty, two stacked net-margin trend ribbons (Efficacy and Regression, the
latter leaving a gap for two-arm runs) with the current run ringed and a
net/delta/green-count readout, and per-badge fragility chips that flag the
narrowest passing case on each green axis so a barely-green skill cannot
look robust.

History persistence is opt-in via --history and is the core's only side
effect. Without it the core stays a pure transform. Two committed fixtures
(clean and fragile) and new fixture-test sections cover the append-and-trim,
both ribbons including the two-arm gap, and the chips.
This commit was merged in pull request #6.
This commit is contained in:
2026-07-24 16:37:11 -04:00
parent 9b40d9f4e5
commit f4df5d31c9
6 changed files with 1645 additions and 5 deletions

View File

@@ -3,6 +3,9 @@
# skill-level Efficacy and Regression verdicts, and the rendered report — for
# both the three-arm shape and the degenerate two-arm "no previous version"
# shape.
# It also drives the longitudinal layer: the per-skill history append-and-trim,
# the two trend ribbons including the two-arm gap in the regression series, and
# the per-badge fragility chips on a clean run and a green-but-fragile one.
# No LLM runs.
{
pkgs,
@@ -11,11 +14,13 @@ let
core = ../skills/benchmark-skill/core/benchmark_core.py;
threeArm = ./fixtures/benchmark/three-arm-bundle.json;
twoArm = ./fixtures/benchmark/two-arm-bundle.json;
clean = ./fixtures/benchmark/clean-bundle.json;
fragile = ./fixtures/benchmark/fragile-bundle.json;
in
pkgs.runCommandLocal "benchmark-core-check"
{
nativeBuildInputs = [ pkgs.python3 ];
inherit core threeArm twoArm;
inherit core threeArm twoArm clean fragile;
}
''
fail() { echo "FAIL: $1" >&2; exit 1; }
@@ -180,5 +185,97 @@ pkgs.runCommandLocal "benchmark-core-check"
grep -q 'OUT-new-regresses-baseline-t3' two.html || fail "missing new evidence for the two-arm efficacy loss"
grep -q 'OUT-baseline-regresses-baseline-t3' two.html || fail "missing baseline evidence for the two-arm efficacy loss"
# --- clean run: fully collapsed, one narrowest-margin chip, ribbons ---------
echo "the core rests a clean run fully collapsed with a lone narrowest-margin chip"
python3 "$core" "$clean" --json clean.json --html clean.html --history clean.history.jsonl \
|| fail "the core exited non-zero on the clean bundle"
python3 - clean.json <<'PY' || fail "a clean-run assertion failed"
import json, sys
r = json.load(open(sys.argv[1]))
assert r["efficacyVerdict"] == "green", r["efficacyVerdict"]
assert r["regressionVerdict"] == "green", r["regressionVerdict"]
cases = {c["name"]: c for c in r["cases"]}
# The narrowest efficacy case (fewest wins) carries the chip, the roomy one does not.
assert [ch["axis"] for ch in cases["narrowest"]["chips"]] == ["efficacy"], cases["narrowest"]["chips"]
assert cases["roomy"]["chips"] == [], cases["roomy"]["chips"]
print("clean-run assertions passed")
PY
if grep -q ' open>' clean.html; then fail "a clean run did not rest fully collapsed"; fi
grep -q 'class="chip efficacy"' clean.html || fail "clean run omits the narrowest-margin efficacy chip"
if grep -q 'class="chip regression"' clean.html; then fail "clean run shows a spurious regression chip"; fi
grep -q 'class="ribbons"' clean.html || fail "clean run omits the trend ribbons"
grep -q 'class="spark"' clean.html || fail "clean run omits a sparkline"
grep -q 'class="ring"' clean.html || fail "clean run does not ring the current run"
# --- history append-and-trim and the two-ribbon window with a two-arm gap ---
echo "the core appends to and trims the per-skill history and plots the ribbon window"
python3 - seed.history.jsonl <<'PY' || fail "seeding the history fixture failed"
import json, sys
# 55 prior runs, all three-arm except the most recent, which is two-arm and so
# must plot a gap on the regression axis once it lands inside the 7-run window.
lines = []
for i in range(55):
two = i == 54
lines.append({
"generatedAt": f"seed-{i}",
"armShape": "two-arm" if two else "three-arm",
"efficacyNet": i % 5,
"efficacyPass": True,
"regressionNet": None if two else i % 3,
"regressionPass": None if two else True,
})
open(sys.argv[1], "w").write("".join(json.dumps(l) + "\n" for l in lines))
PY
python3 "$core" "$clean" --json trend.json --history seed.history.jsonl \
|| fail "the core exited non-zero on the seeded-history run"
python3 - seed.history.jsonl trend.json <<'PY' || fail "a history/trend assertion failed"
import json, sys
lines = [json.loads(l) for l in open(sys.argv[1]) if l.strip()]
# 55 prior + this run = 56, trimmed oldest-first back to the cap of 50.
assert len(lines) == 50, len(lines)
gens = [l["generatedAt"] for l in lines]
assert "seed-0" not in gens and "seed-5" not in gens, "oldest runs were not trimmed"
assert "seed-6" in gens and "seed-54" in gens, "recent runs were wrongly trimmed"
assert lines[-1]["armShape"] == "three-arm", lines[-1]
assert lines[-1]["efficacyNet"] == 8 and lines[-1]["regressionNet"] == 1, lines[-1]
t = json.load(open(sys.argv[2]))["trend"]
eff, reg = t["efficacy"]["points"], t["regression"]["points"]
assert len(eff) == 7 and len(reg) == 7, (len(eff), len(reg))
# Efficacy has a value every run, while regression breaks at the two-arm run.
assert all(p["net"] is not None for p in eff), "efficacy series should have no gaps"
assert eff[-1]["current"] and reg[-1]["current"], "the current run is ringed on both axes"
assert reg[5]["net"] is None, [p["net"] for p in reg]
assert reg[-1]["net"] is not None, "the current three-arm run has a regression dot"
assert t["regression"]["applicable"] == 6, t["regression"]["applicable"]
print("history/trend assertions passed")
PY
# --- green-but-fragile run: independent per-badge chips, both on one case ----
echo "the core chips each fragility axis independently, and both on a doubly-fragile case"
python3 "$core" "$fragile" --json fragile.json --html fragile.html \
|| fail "the core exited non-zero on the fragile bundle"
python3 - fragile.json <<'PY' || fail "a fragility-chip assertion failed"
import json, sys
r = json.load(open(sys.argv[1]))
assert r["efficacyVerdict"] == "green" and r["regressionVerdict"] == "green"
cases = {c["name"]: c for c in r["cases"]}
# One case is both the narrowest efficacy margin and at exactly one regression
# loss, so it carries both chips.
# The others carry at most their own axis.
assert {ch["axis"] for ch in cases["both"]["chips"]} == {"efficacy", "regression"}, cases["both"]["chips"]
assert [ch["axis"] for ch in cases["reg-only"]["chips"]] == ["regression"], cases["reg-only"]["chips"]
assert cases["none"]["chips"] == [], cases["none"]["chips"]
print("fragility-chip assertions passed")
PY
grep -q 'class="chip efficacy"' fragile.html || fail "fragile run omits the efficacy chip"
grep -q 'class="chip regression"' fragile.html || fail "fragile run omits the regression chip"
touch "$out"
''