feat(learning-harness): publish every number from the artifacts that produced it

Task 3.8. A number in a roadmap that nobody can regenerate is a claim, not a
measurement. One entry point now writes them all from the committed artifacts
and puts the denominator beside each: how many pairs could be scored, why the
rest could not, which taxonomy version scored them, the noise the family shows
against itself, and what the run cost per pair it got right. Every table says
whether hierarchical partial credit could apply, which today it cannot.

The roadmap section is replaced in place between markers rather than appended,
so the file cannot accumulate a stale number beside a fresh one, and running the
command twice without new artifacts changes nothing. With no baseline recorded
it says so instead of printing an empty table.

The corrected mechanism-lever number travels with the rest: detected five of
seventeen at a negative Youden once only train-split pairs teach, against seven
when every pair taught, with the difference named as what holdout pairs stopped
teaching. Round zero now records what it spent so the cost column has a source.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01T7XD7x5KonMdmGRkp63EQG
This commit is contained in:
Marius Ciepluch
2026-09-06 11:59:13 +02:00
co-authored by Claude Opus 5
parent b9552518b9
commit b8ad42c57e
4 changed files with 363 additions and 0 deletions
+184
View File
@@ -0,0 +1,184 @@
"""Every published number, regenerated from the artifacts that produced it (learning-harness, Req 11.1, 11.2, 4.5, 5.3).
A number in a roadmap that nobody can regenerate is a claim, not a measurement. This writes them all
from one entry point, out of the artifacts committed beside them, and puts the denominator next to each:
how many pairs could be scored, why the rest could not, which taxonomy version scored them, the noise
the family shows against itself, and what the run cost per pair it got right.
Running it twice without new artifacts changes nothing, and the roadmap section is replaced in place
rather than appended, so the file cannot accumulate stale numbers beside fresh ones.
"""
from __future__ import annotations
import json
from collections.abc import Mapping
from dataclasses import dataclass, field
from datetime import UTC, datetime
from pathlib import Path
BEGIN = "<!-- learning-harness:begin -->"
END = "<!-- learning-harness:end -->"
COMMAND = "uv run ousast learning publish"
@dataclass(frozen=True)
class Baseline:
"""One model's round-zero artifact, read back."""
model: str
metrics: dict[str, Mapping[str, object]]
floors: dict[str, Mapping[str, object]]
cost_usd: float
k_runs: int
@dataclass(frozen=True)
class PublishReport:
command: str
table: str
families: tuple[str, ...] = ()
models: tuple[str, ...] = ()
artifacts: tuple[Path, ...] = field(default_factory=tuple)
def publish(*, learning_dir: Path, measurements_dir: Path, roadmap: Path, date: str | None = None) -> PublishReport:
"""Regenerate the family numbers, write the artifact, and replace the roadmap section in place."""
stamp = date or datetime.now(UTC).strftime("%Y-%m-%d")
models = _baselines(learning_dir)
families = tuple(sorted({family for baseline in models.values() for family in baseline.metrics}))
lever = _lever(measurements_dir)
table = _table(models, families, lever)
artifacts: list[Path] = []
if models:
measurements_dir.mkdir(parents=True, exist_ok=True)
artifact = measurements_dir / f"{stamp}-learning-families.json"
artifact.write_text(
json.dumps(
{
"generated": stamp,
"command": COMMAND,
"models": {name: dict(baseline.metrics) for name, baseline in sorted(models.items())},
"floors": {name: dict(baseline.floors) for name, baseline in sorted(models.items())},
},
indent=2,
sort_keys=True,
)
+ "\n",
encoding="utf-8",
)
artifacts.append(artifact)
_replace_section(roadmap, table)
return PublishReport(command=COMMAND, table=table, families=families, models=tuple(sorted(models)), artifacts=tuple(artifacts))
def _baselines(learning_dir: Path) -> dict[str, Baseline]:
root = learning_dir / "baseline"
found: dict[str, Baseline] = {}
for directory in sorted(path for path in root.iterdir() if path.is_dir()) if root.is_dir() else []:
report = directory / "report.json"
if not report.is_file():
continue
payload = json.loads(report.read_text(encoding="utf-8"))
model = str(payload.get("model", directory.name))
found[model] = Baseline(
model=model,
metrics={str(name): block for name, block in (payload.get("metrics") or {}).items() if isinstance(block, Mapping)},
floors={str(name): block for name, block in (payload.get("floors") or {}).items() if isinstance(block, Mapping)},
cost_usd=float(payload.get("cost_usd", 0.0) or 0.0),
k_runs=int(payload.get("k_runs", 0) or 0),
)
return found
def _lever(measurements_dir: Path) -> Mapping[str, object] | None:
path = measurements_dir / "2026-09-06-mechanism-lever-split.json"
if not path.is_file():
return None
payload = json.loads(path.read_text(encoding="utf-8"))
return payload if isinstance(payload, dict) else None
def _table(models: Mapping[str, Baseline], families: tuple[str, ...], lever: Mapping[str, object] | None) -> str:
if not models:
return (
f"Generated by `{COMMAND}`.\n\nNo baseline has been recorded yet, so there is nothing to publish. "
"Run round zero first; a number without the run that produced it is a claim, not a measurement.\n"
)
lines = [
f"Generated by `{COMMAND}` from the artifacts committed beside these numbers.",
"",
"| model | family | scorable | unscorable | recall | silence | Youden | at fixed FPR | noise floor | cost per correct pair |",
"|---|---|---|---|---|---|---|---|---|---|",
]
for model, baseline in sorted(models.items()):
for family in families:
row = baseline.metrics.get(family)
if row is None:
continue
outcomes = row.get("outcomes")
raw_correct = outcomes.get("pair_correct") if isinstance(outcomes, Mapping) else None
correct = int(raw_correct) if isinstance(raw_correct, int | float) else 0
floor = baseline.floors.get(family)
raw_budget = floor.get("budget_flips") if floor is not None else None
budget = int(raw_budget) if isinstance(raw_budget, int | float) else 0
cost = baseline.cost_usd
lines.append(
f"| {model} | {family} | {row.get('scorable', 0)} (taxonomy {row.get('taxonomy_version', '')}) "
f"| {_reasons(row)} | {_num(row.get('recall'))} | {_num(row.get('silence'))} | {_num(row.get('youden'))} "
f"| {_num(row.get('fixed_fpr_recall'))} | budget {budget} | {_cost(cost, correct)} |"
)
lines += ["", _credit_note(models), ""]
if lever is not None:
lines += [_lever_note(lever), ""]
return "\n".join(lines)
def _credit_note(models: Mapping[str, Baseline]) -> str:
hierarchical = any(bool(row.get("hierarchical_credit")) for baseline in models.values() for row in baseline.metrics.values())
if hierarchical:
return "A parent-child answer earned partial credit in these figures."
return "No family in this taxonomy declares a parent, so hierarchical partial credit could not apply to any figure above."
def _lever_note(lever: Mapping[str, object]) -> str:
before = lever.get("before")
after = lever.get("after")
before_map = before if isinstance(before, Mapping) else {}
after_map = after if isinstance(after, Mapping) else {}
pairs = lever.get("holdout_pairs", 0)
return (
f"Mechanism lever on the {lever.get('slice', '')} holdout: detected {after_map.get('detected')} of {pairs} at Youden "
f"{after_map.get('youden')} once only train-split pairs teach, against detected {before_map.get('detected')} at Youden "
f"{before_map.get('youden')} when every pair taught. The difference is what holdout pairs stopped teaching."
)
def _reasons(row: Mapping[str, object]) -> str:
reasons = row.get("unscorable") or {}
if not isinstance(reasons, Mapping) or not reasons:
return "none"
return ", ".join(f"{name}: {count}" for name, count in sorted(reasons.items()))
def _num(value: object) -> str:
return f"{float(value):.3f}" if isinstance(value, int | float) else "-"
def _cost(cost: float, correct: int) -> str:
return f"{cost / correct:.2f}" if cost and correct else "-"
def _replace_section(roadmap: Path, table: str) -> None:
body = f"{BEGIN}\n\n## Classified detector numbers\n\n{table.rstrip()}\n\n{END}\n"
text = roadmap.read_text(encoding="utf-8") if roadmap.is_file() else ""
if BEGIN in text and END in text:
head, _, rest = text.partition(BEGIN)
_, _, tail = rest.partition(END)
roadmap.write_text(head + body + tail.lstrip("\n"), encoding="utf-8")
return
roadmap.parent.mkdir(parents=True, exist_ok=True)
roadmap.write_text((text.rstrip("\n") + "\n\n" if text else "") + body, encoding="utf-8")
__all__ = ["BEGIN", "COMMAND", "END", "Baseline", "PublishReport", "publish"]
+4
View File
@@ -69,6 +69,7 @@ class BaselineReport:
floors: dict[str, NoiseFloor] = field(default_factory=dict)
metrics: dict[str, dict[str, object]] = field(default_factory=dict)
scores: tuple[PairFamilyScore, ...] = ()
cost_usd: float = 0.0
def to_dict(self) -> dict[str, object]:
return {
@@ -76,6 +77,7 @@ class BaselineReport:
"k_runs": self.k_runs,
"taxonomy_version": self.taxonomy_version,
"slices": list(self.slices),
"cost_usd": self.cost_usd,
"floors": {name: floor.to_dict() for name, floor in sorted(self.floors.items())},
"metrics": {name: dict(block) for name, block in sorted(self.metrics.items())},
}
@@ -92,6 +94,7 @@ def run_baseline(
slices: Sequence[str] = DEFAULT_SLICES,
k_runs: int = DEFAULT_K_RUNS,
prompt: str | None = None,
spent_usd: Callable[[], float] | None = None,
) -> BaselineReport:
"""Clone one detector into every family, measure each family against itself, and write the artifacts."""
from ..tool_hunter import _SYSTEM_PROMPT
@@ -110,6 +113,7 @@ def run_baseline(
floors=floors,
metrics={name: block.to_dict() for name, block in metrics.items()},
scores=tuple(scores),
cost_usd=spent_usd() if spent_usd is not None else 0.0,
)
_write(out_dir, report)
return report