scrubdata / eval /degenerate.py
OpenAI Codex
deploy: add sponsor:openai tag (Best Use of Codex) + Codex-hardened build
16dc556
Raw
History Blame Contribute Delete
7.6 kB
"""W4.3 + W4.4 — degenerate baselines + cost-weighted damage over the paired sets.
Four scorer-pinning policies over the same dirty/clean pairs eval/paired_bench.py
walks: no-op (output = dirty), abstain-all (no-op + flags; score-identical at the
cell level — the repair metric is flag-blind by design, flags surface in audit
metrics), random-edit (seeded vandalism: 5% of cells replaced with another value
from the same column) and oracle (output = clean, headers realigned to dirty's —
23/42 pairs differ in header naming only; cell alignment is positional). They pin
the metric's floor (no-op F1 = 0, damage = 0), ceiling (oracle F1 = 1, damage = 0)
and show it punishes vandalism. Also reruns the SHIPPED pipeline (mock_plan) to
capture raw fix/damage cell counts and reports Effective-Reliability-style
cost-weighted scores score_c = fixes - c*damage_cells for c in {1, 5, 10}.
uv run python -m eval.degenerate
Writes eval/results/degenerate.json + docs/DEGENERATE_BASELINES.md. Per-pair rows
are cached incrementally (eval/results/degenerate_pairs.json) so a killed run
resumes where it stopped.
"""
from __future__ import annotations
import argparse
import json
import random
import time
from pathlib import Path
from scrubdata.executor import apply_plan
from scrubdata.planner import mock_plan
from .paired_bench import _load, pairs
from .run_real_multi import _cell_only, score
ROOT = Path(__file__).resolve().parent.parent
EDIT_FRAC = 0.05
SEED = 7
COSTS = (1, 5, 10)
def _noop(dirty, clean):
return dirty
def _abstain_all(dirty, clean):
return dirty.copy() # + flags conceptually; the cell metric is flag-blind
def _random_edit(dirty, clean, seed=SEED):
rng = random.Random(seed)
out = dirty.copy()
n, m = out.shape
uniq = [list(dict.fromkeys(out.iloc[:, j])) for j in range(m)]
for idx in rng.sample(range(n * m), max(1, int(n * m * EDIT_FRAC))):
i, j = divmod(idx, m)
alts = [v for v in uniq[j] if v != out.iat[i, j]]
if alts:
out.iat[i, j] = rng.choice(alts)
return out
def _oracle(dirty, clean):
out = clean.copy()
out.columns = dirty.columns # header-naming variants only; alignment is positional
return out
def _shipped(dirty, clean):
return apply_plan(dirty, _cell_only(mock_plan(dirty)))[0]
POLICIES = [("no-op", _noop), ("abstain-all", _abstain_all),
("random-edit", _random_edit), ("oracle", _oracle),
("shipped", _shipped)]
def _mean(xs):
xs = list(xs)
return sum(xs) / len(xs) if xs else 0.0
def main() -> None:
ap = argparse.ArgumentParser()
ap.add_argument("--only", default=None)
ap.add_argument("--out", default="eval/results/degenerate.json")
ap.add_argument("--cache", default="eval/results/degenerate_pairs.json")
args = ap.parse_args()
cache = json.load(open(args.cache)) if Path(args.cache).exists() else {}
for p in pairs():
if args.only and p.name != args.only:
continue
if p.name in cache:
continue
try:
dirty, clean = _load(p)
except Exception as e: # noqa: BLE001
print(f" {p.name}: LOAD FAILED {type(e).__name__}")
continue
entry = {}
for name, policy in POLICIES:
t0 = time.perf_counter()
m = score(dirty, clean, policy(dirty, clean))
n = min(len(dirty), len(clean))
clean_cells = n * dirty.shape[1] - m["_errors"]
entry[name] = {
"name": p.name, "errors": m["_errors"],
"f1": m["f1"], "precision": m["precision"], "recall": m["recall"],
"damage": m["damage"], "fixed": m["_fixed"], "changed": m["_changed"],
"damage_cells": round(m["damage"] * clean_cells),
"sec": round(time.perf_counter() - t0, 1)}
cache[p.name] = entry
json.dump(cache, open(args.cache, "w"), indent=1)
print(f" {p.name:<46} " + " ".join(
f"{name}={entry[name]['f1']:.3f}" for name, _ in POLICIES), flush=True)
res = {name: [cache[k][name] for k in sorted(cache)] for name, _ in POLICIES}
out = {"n_pairs": len(res["no-op"]), "edit_frac": EDIT_FRAC, "seed": SEED,
"policies": {}, "acceptance": {}}
for name, _ in POLICIES:
rows = res[name]
E, F, D = (sum(r[k] for r in rows) for k in ("errors", "fixed", "damage_cells"))
out["policies"][name] = {
"macro": {k: round(_mean(r[k] for r in rows), 4)
for k in ("f1", "precision", "recall", "damage")},
"micro": {"errors": E, "fixed": F, "changed": sum(r["changed"] for r in rows),
"damage_cells": D},
"score_c": {f"c={c}": {"raw": F - c * D,
"per_error": round((F - c * D) / E, 4)}
for c in COSTS},
"sec": round(sum(r["sec"] for r in rows), 1),
"per_pair": rows}
bad_oracle = [r["name"] for r in res["oracle"] if r["f1"] != 1.0]
bad_noop = [r["name"] for r in res["no-op"] if r["damage"] != 0.0]
out["acceptance"] = {"oracle_f1_all_exactly_1": not bad_oracle,
"noop_damage_all_exactly_0": not bad_noop,
"violations": {"oracle": bad_oracle, "no-op": bad_noop}}
Path(args.out).parent.mkdir(parents=True, exist_ok=True)
json.dump(out, open(args.out, "w"), indent=1)
P = out["policies"]
L = ["# Degenerate baselines + cost-weighted damage (W4.3 + W4.4)", "",
f"Same {out['n_pairs']} dirty/clean pairs as `eval/paired_bench.py`, scored with "
"`run_real_multi.score()` (churn-neutral F1 + damage). The degenerate policies pin",
"the metric: no-op = floor (F1 0, damage 0), oracle = ceiling (F1 1, damage 0),",
"random-edit (seeded, 5% of cells) = vandalism the metric must punish. Abstain-all",
"is score-identical to no-op — the repair metric is flag-blind by design.", "",
"| policy | macro F1 | macro P | macro R | macro damage | fixed | damage cells |",
"|---|---|---|---|---|---|---|"]
for name, _ in POLICIES:
ma, mi = P[name]["macro"], P[name]["micro"]
L.append(f"| {name} | {ma['f1']:.3f} | {ma['precision']:.3f} | {ma['recall']:.3f} "
f"| {ma['damage']:.4f} | {mi['fixed']} | {mi['damage_cells']} |")
L += ["", "## Cost-weighted scores (Effective-Reliability style, W4.4)", "",
"score_c = fixes − c·damage_cells, micro-summed over all pairs; per-error =",
f"score_c / {P['shipped']['micro']['errors']} total benchmark errors.", "",
"| policy | " + " | ".join(f"c={c} (per-error)" for c in COSTS) + " |",
"|---|" + "---|" * len(COSTS)]
for name, _ in POLICIES:
sc = P[name]["score_c"]
L.append(f"| {name} | " + " | ".join(
f"{sc[f'c={c}']['raw']} ({sc[f'c={c}']['per_error']:+.3f})" for c in COSTS) + " |")
a = out["acceptance"]
L += ["", f"Acceptance: oracle F1 = 1.0 on all pairs: **{a['oracle_f1_all_exactly_1']}** · "
f"no-op damage = 0.0 on all pairs: **{a['noop_damage_all_exactly_0']}**",
f"Repro: `uv run python -m eval.degenerate` (seed {SEED}, edit fraction {EDIT_FRAC})."]
(ROOT / "docs" / "DEGENERATE_BASELINES.md").write_text("\n".join(L) + "\n")
print(f"{out['n_pairs']} pairs x {len(POLICIES)} policies -> {args.out} "
"+ docs/DEGENERATE_BASELINES.md")
print("acceptance:", out["acceptance"])
if __name__ == "__main__":
main()