textmachine/eval/pkg7/repair_judge.py

85 lines
3.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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())