#!/usr/bin/env python3 """Blind fact check: runs//.md -> checks///.json 2 judges from different model families, neither is a contestant model: sonnet = claude-sonnet-5.5 (contestant C is Opus 5.5) gpt56 = gpt-5.6-sol (contestant D is GPT-6.1-sol) The judge never sees which arm or model wrote the page. """ import argparse, datetime as dt, json, re, subprocess, sys, time from concurrent.futures import ThreadPoolExecutor from pathlib import Path HERE = Path(__file__).resolve().parent ROOT = HERE.parent sys.path.insert(0, str(HERE)) from run import contest_block # noqa: E402 JUDGES = {"sonnet": "claude-sonnet-5.5", "gpt56": "gpt-5.6-sol"} def body_words(page: str) -> int: return len(re.split(r"\n#+\s*Sources", page)[0].split()) def check(judge: str, arm: str, c: dict) -> str: src = ROOT / "runs" / arm / f"{c['id']}.md" out = ROOT / "checks" / judge / arm / f"{c['id']}.json" if out.exists() and json.loads(out.read_text()).get("parsed"): return f"skip {judge} {arm} {c['id']}" out.parent.mkdir(parents=True, exist_ok=True) page = src.read_text() prompt = ((HERE / "FACTCHECK.md").read_text() .replace("{today}", dt.date.today().strftime("%A, %B %-d, %Y")) .replace("{contest_block}", contest_block(c)).replace("{page}", page)) t0 = time.time() cmd = ["copilot", "-p", prompt, "--model", JUDGES[judge], "--no-custom-instructions", "--disable-builtin-mcps", "--available-tools", "web_search", "web_fetch", "--allow-all-tools", "--allow-all-urls", "-s", "--no-color"] try: p = subprocess.run(cmd, capture_output=True, text=True, timeout=2400, cwd="/tmp") raw = p.stdout except subprocess.TimeoutExpired: raw = "" m = re.search(r"```json\s*(\{.*\})\s*```", raw, re.S) or re.search(r"(\{.*\})", raw, re.S) rec = {"judge": judge, "judge_model": JUDGES[judge], "arm": arm, "contest": c["id"], "wall_seconds": round(time.time() - t0, 1), "body_words": body_words(page), "parsed": None, "raw_tail": raw[-3000:]} if m: try: rec["parsed"] = json.loads(m.group(1)) except json.JSONDecodeError as e: rec["parse_error"] = str(e) out.write_text(json.dumps(rec, indent=1)) g = (rec["parsed"] or {}).get("gate", "PARSE-FAIL") return f"{dt.datetime.now():%H:%M} {judge} {arm} {c['id']}: {g} ({rec['wall_seconds']}s)" def main() -> None: ap = argparse.ArgumentParser() ap.add_argument("--arms", default="A,B,C,D") ap.add_argument("--judges", default="sonnet,gpt56") ap.add_argument("--contests", default="all") ap.add_argument("--workers", type=int, default=4) a = ap.parse_args() data = json.loads((HERE / "contests.json").read_text()) jobs = [] for c in data["contests"]: if a.contests != "all" and c["id"] not in a.contests.split(","): continue for arm in a.arms.split(","): meta = ROOT / "runs" / arm / f"{c['id']}.json" if not meta.exists(): continue for j in a.judges.split(","): jobs.append((j, arm, c)) with ThreadPoolExecutor(a.workers) as ex: for line in ex.map(lambda t: check(*t), jobs): print(line, flush=True) if __name__ == "__main__": main()