#!/usr/bin/env python3 """Полигон, пакет-7, фаза B: терпимый пере-разбор судейских ответов из СЫРЬЯ. Зачем. Строгий разбор в `blind_judge.py` потерял 5 вердиктов из 80 у первого судьи: модель отдала валидный по смыслу, но структурно битый JSON — `catastrophic` и `note` оказались ВНУТРИ объекта `verdicts`, а закрывающей скобки не хватило. Ответы сохранены целиком (правило 1 полигона), поэтому починка стоит $0 и не требует повторных вызовов — ровно ради этого сырьё и сохраняется. Что делает терпимый разбор: 1) пытается строгий json.loads; 2) при неудаче достраивает недостающие закрывающие скобки (до 3) и пробует снова; 3) если после разбора `catastrophic`/`note` лежат ВНУТРИ `verdicts` — поднимает их на уровень выше; 4) отбрасывает из `verdicts` всё, что не является парой {best, worst}. Ничего не додумывает: если после этого объект не читается, строка остаётся без вердикта и попадает в отчёт как потеря. """ from __future__ import annotations import argparse import json import re from pathlib import Path CRIT = {"K1", "K2", "K5", "K6", "K9"} def tolerant(text: str) -> dict: m = re.search(r"\{.*\}", text, re.S) if not m: return {} raw = m.group(0) for extra in range(0, 4): try: v = json.loads(raw + "}" * extra) except json.JSONDecodeError: continue if not isinstance(v, dict): return {} ver = v.get("verdicts") if isinstance(ver, dict): # поднять поля, случайно вложенные в verdicts for key in ("catastrophic", "note"): if key in ver and key not in v: v[key] = ver.pop(key) v["verdicts"] = {k: x for k, x in ver.items() if k in CRIT and isinstance(x, dict) and "best" in x} return v return {} def main() -> int: ap = argparse.ArgumentParser() ap.add_argument("--judge-json", required=True, type=Path) ap.add_argument("--raw-dir", required=True, type=Path) ap.add_argument("--prefix", required=True, help="напр. gpt-5-mini_fwd") ap.add_argument("--out", required=True, type=Path) a = ap.parse_args() d = json.loads(a.judge_json.read_text(encoding="utf-8")) raw_by_id = {} for p in sorted(a.raw_dir.glob(f"{a.prefix}_*.json")): r = json.loads(p.read_text(encoding="utf-8")) raw_by_id[r["id"]] = r["response"] fixed = 0 for row in d["rows"]: if row.get("verdicts"): continue v = tolerant(raw_by_id.get(row["id"], "")) if v.get("verdicts"): row["verdicts"] = v["verdicts"] row["catastrophic"] = v.get("catastrophic", []) row["note"] = v.get("note", "") row["repaired"] = True fixed += 1 a.out.write_text(json.dumps(d, ensure_ascii=False, indent=1), encoding="utf-8") total = sum(1 for r in d["rows"] if r.get("verdicts")) print(f"{a.judge_json.name}: восстановлено {fixed}; вердиктов {total}/{len(d['rows'])} → {a.out.name}") return 0 if __name__ == "__main__": raise SystemExit(main())