mirror of
https://github.com/norandom/OpenUltraSAST.git
synced 2026-10-17 03:59:28 +00:00
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:
co-authored by
Claude Opus 5
parent
b9552518b9
commit
b8ad42c57e
@@ -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"]
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user