#!/usr/bin/env python3 """Verifier for the scorer replication task. Independently recomputes every reported table cell from the shipped traces with the trusted reference scorer, then compares the agent's /workspace/results.csv cell-by-cell. Every reported metric is a deterministic function of the traces, so each point estimate must match within POINT_TOL (default 1e-6). Writes /logs/verifier/reward.json (and reward.txt). reward = 1.0 iff no rows are missing and no point comparison fails. """ from __future__ import annotations import csv import json import os import sys sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) import reference_scorer as ref # noqa: E402 WORKSPACE = os.getenv("WORKSPACE", "/workspace") AGENT_CSV = os.path.join(WORKSPACE, "results.csv") REWARD_DIR = os.getenv("REWARD_DIR", "/logs/verifier") POINT_TOL = float(os.getenv("POINT_TOL", "1e-6")) KEY = ("model", "condition", "subtask", "metric") def _key(row: dict) -> tuple: return tuple(row[k].strip() for k in KEY) def _num(x): x = (x or "").strip() return None if x == "" else float(x) def load_agent(path: str) -> dict[tuple, dict]: if not os.path.exists(path): return {} out = {} with open(path, encoding="utf-8") as fh: reader = csv.DictReader(fh) missing_cols = {*KEY, "value"} - set(reader.fieldnames or []) if missing_cols: raise SystemExit(f"agent CSV missing columns: {sorted(missing_cols)}") for r in reader: out[_key(r)] = r return out def main() -> int: os.makedirs(REWARD_DIR, exist_ok=True) reference = {(_r["model"], _r["condition"], _r["subtask"], _r["metric"]): _r for _r in ref.compute_all(WORKSPACE)} agent = load_agent(AGENT_CSV) missing, extra = [], sorted(set(agent) - set(reference)) point_fail = [] for key, exp in reference.items(): got = agent.get(key) if got is None: missing.append(key) continue # point estimate -- deterministic, must match exactly gv = _num(got.get("value")) if gv is None or abs(gv - exp["value"]) > POINT_TOL: point_fail.append((key, exp["value"], gv)) n = len(reference) point_ok = not missing and not point_fail passed = point_ok reward = { "reward": 1.0 if passed else 0.0, "point_estimates_exact": point_ok, "n_cells": n, "n_missing_rows": len(missing), "n_extra_rows": len(extra), "n_point_fail": len(point_fail), "point_exact_frac": round(1 - len(point_fail) / n, 6) if n else 0.0, "point_tol": POINT_TOL, } with open(os.path.join(REWARD_DIR, "reward.json"), "w", encoding="utf-8") as fh: json.dump(reward, fh, indent=2) with open(os.path.join(REWARD_DIR, "reward.txt"), "w", encoding="utf-8") as fh: fh.write("1" if passed else "0") # human-readable summary -> run log print(json.dumps(reward, indent=2)) for label, items in (("MISSING ROW", missing[:20]), ("POINT MISMATCH", point_fail[:20])): for it in items: print(f" {label}: {it}") if extra: print(f" (note) {len(extra)} extra rows not in reference, e.g. {extra[:5]}") return 0 if passed else 1 if __name__ == "__main__": sys.exit(main())