"""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()