#!/usr/bin/env python3 """ПЕРЕ-СЧЁТ ГОЛОСОВ ВНЕШНЕГО СУДЬИ (не-Claude) ПО ПРОХОДУ border. $0. Судья другой семьи отсуживал ТЕ ЖЕ файлы заданий, что и агенты-судьи пака (`aj-border-tasks/`), и клал ответы в `sol-border/`. Поэтому его счёт сопоставим с моим текст-в-текст, а не «другой замер». ⚠ Разбор здесь СВОЙ, а не `absjudge.ingest`: тот пишет в боевой каталог голосов и затёр бы прогон пака. Правила приёмки ответа те же — каждая ненулевая ось названа и подпёрта цитатой. Запуск: eval/.venv/bin/python eval/editor_tier/solscore.py """ from __future__ import annotations import json import math import random import re import statistics as st import sys from pathlib import Path OUT = Path.home() / "books" / "editor-tier" # Проход задаётся аргументом: `border` (исполнен 09.08) и `tier` (заказан фазой Д, п.Д2). PASS = "border" KEY = OUT / "blind-keys" / f"{PASS}-KEY.json" ANSW = OUT / f"sol-{PASS}" AXES = ("ВЕРНОСТЬ", "ТЕРМИН", "ЯЗЫК", "ФОРМА") SEED = 20260809 def votes() -> tuple[dict, list]: key = json.loads(KEY.read_text(encoding="utf-8")) ok, bad = {}, [] for f in sorted(ANSW.glob("*.txt")): k = key.get(f.stem) if not k: bad.append((f.stem, "токена нет в ключе")) continue txt, unit, fail = f.read_text(encoding="utf-8"), {}, None for lab, meta in k.items(): ax = {} for a in AXES: m = re.search(rf"^{lab}-{a}[ \t]*:[ \t]*(\d+)[ \t]*$", txt, re.M) if m: ax[a] = int(m.group(1)) if len(ax) != len(AXES): fail = f"не разобран блок {lab}" break wm = re.search(rf"^{lab}-ПОЧЕМУ[ \t]*:[ \t]*(.*?)(?=^\S+-(?:{'|'.join(AXES)}|ПОЧЕМУ)" rf"[ \t]*:|\Z)", txt, re.M | re.S) why = (wm.group(1) if wm else "").strip() miss = [a for a in AXES if ax[a] and not re.search(rf"(? float: """Точный при малом n, иначе сид-Монте-Карло: воспроизводимость важнее последней цифры.""" rnd, s, hits, trials = random.Random(SEED), sum(m), 0, 200_000 # noqa: S311 for _ in range(trials): if abs(sum(x * rnd.choice((1, -1)) for x in m)) >= abs(s) - 1e-12: hits += 1 return (hits + 1) / (trials + 1) def main() -> int: global PASS, KEY, ANSW # noqa: PLW0603 args = [a for a in sys.argv[1:] if not a.startswith("-")] if args: PASS = args[0] KEY, ANSW = OUT / "blind-keys" / f"{PASS}-KEY.json", OUT / f"sol-{PASS}" print(f"проход {PASS} · ключ {KEY.name} · ответы {ANSW}") if not ANSW.exists() or not list(ANSW.glob("*.txt")): print(f"ответов внешнего судьи нет в {ANSW}") return 1 ok, bad = votes() print(f"принято единиц {len(ok)} · отвергнуто {len(bad)}") for t, r in bad: print(f" ⛔ {t}: {r}") dec = [u[u["CTRLdecoy"]["decoy_from"]]["errors"] - u["CTRLdecoy"]["errors"] for u in ok.values() if "CTRLdecoy" in u and u["CTRLdecoy"].get("decoy_from") in u] flo = [u["CTRLfloorA"]["errors"] - u["CTRLfloorB"]["errors"] for u in ok.values() if "CTRLfloorA" in u and "CTRLfloorB" in u] # Семейство прохода: у `border` один контраст, у `tier` — три плюс позитивный контроль. fam = ([("R2", "R0")] if PASS == "border" else [("T1", "R0"), ("T2", "R0"), ("T3", "R0"), ("R0", "F")]) pairs = [(a, b, [u[b]["errors"] - u[a]["errors"] for u in ok.values() if a in u and b in u]) for a, b in fam] pairs = [(a, b, m) for a, b, m in pairs if len(m) >= 3] if not (dec and flo and pairs): print("контролей или боевых армов не хватает") return 1 caught = sum(1 for x in dec if x < 0) thr = 2.8 * st.pstdev(flo) / math.sqrt(len(flo)) print(f"\n ДЕКОЙ {st.mean(dec):+.2f} · поймано {caught}/{len(dec)}" + ("" if caught == len(dec) else " ⛔ сессия внешнего судьи аннулируется")) print(f" ПОЛ {st.mean(flo):+.2f} · n={len(flo)} sd={st.pstdev(flo):.2f} → ПОРОГ {thr:.2f}") for a, b, m in pairs: print(f" {a} vs {b} n={len(m)} {st.mean(m):+.2f} p={sign_p(m):.4f} " f"{'РАЗЛИЧИМ' if abs(st.mean(m)) > thr else 'ниже порога различимости'}") battle = {x for a, b, _ in pairs for x in (a, b)} lv = [r["errors"] for u in ok.values() for a, r in u.items() if a in battle] print(f" уровень (медиана боевых клеток) {st.median(lv):.1f}") return 0 if caught == len(dec) else 1 if __name__ == "__main__": sys.exit(main())