85 lines
3.7 KiB
Python
85 lines
3.7 KiB
Python
#!/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())
|