diff --git a/eval/dovodka/adjud.py b/eval/dovodka/adjud.py index 5c170b0e..68ae4534 100644 --- a/eval/dovodka/adjud.py +++ b/eval/dovodka/adjud.py @@ -238,11 +238,19 @@ def score() -> int: if fp > 0.20 or tp < 0.60: print("\n⛔ адъюдикация не засчитывается — контроли не пройдены") return 1 - print("\nДОЛЯ ВЕРИФИЦИРОВАННЫХ ПРЕТЕНЗИЙ ПО АРМАМ") + # ⚠ ТОЛЬКО НЕСУЩИЕ ОСИ. Контраст считается по ВЕРНОСТЬ+ЯЗЫК (эррата П-5): ТЕРМИН отдан + # детерминированному канон-гейту, ФОРМА исключена. Претензии по этим двум осям в поправку + # НЕ идут, иначе она правила бы не ту величину. + # ⚠ И ОТДЕЛЬНО ПРО ФОРМУ — ДЕФЕКТ ПРИБОРА, НАЙДЕННЫЙ САМИМ АДЪЮДИКАТОРОМ: окно «МЕСТО В + # ПЕРЕВОДЕ» подаётся через `norm()`, то есть БЕЗ пунктуации и в нижнем регистре. Судить + # вёрстку и пунктуацию по такому окну нельзя в принципе — то, что проверяется, стёрто + # нормализацией. Ось ФОРМА исключается дважды: и как невходящая в сумму, и как непроверяемая. + print("\nДОЛЯ ВЕРИФИЦИРОВАННЫХ ПРЕТЕНЗИЙ ПО АРМАМ (только несущие оси ВЕРНОСТЬ+ЯЗЫК)") rate = {} for arm in ARMS: ks = [k for k, v in key.items() if v.get("arm") == arm and not v.get("fake") - and not v.get("planted") and k in ans] + and not v.get("planted") and k in ans + and v.get("axis") in ("ВЕРНОСТЬ", "ЯЗЫК")] if ks: rate[arm] = sum(1 for k in ks if ans[k]) / len(ks) print(f" {arm}: {rate[arm]:.0%} подтверждено (n={len(ks)})") diff --git a/eval/dovodka/itog_d4.py b/eval/dovodka/itog_d4.py index 52b39520..185e0cf1 100644 --- a/eval/dovodka/itog_d4.py +++ b/eval/dovodka/itog_d4.py @@ -50,6 +50,14 @@ def _load(name: str, path: Path): S = _load("sud", ZONE / "sud.py") AJ = sys.modules["absjudge"] +# ⚠⚠ ПЕРЕКЛЮЧИТЬ КЛЮЧИ НА СВОЙ ПАК — ИНАЧЕ ДЕКОЙ ВОССТАНАВЛИВАЕТСЯ ЧУЖИМИ ПОСАДКАМИ. +# `absjudge.KEYS` — глобаль, по умолчанию указывающая на `~/books/role-topology/blind-keys` +# (пак 21). Её переключает `sud.setup()`, но свод его НЕ вызывает: он только импортирует модуль. +# Из-за этого `AJ._plant()` в `variant()` брал PLANTS.json ЧУЖОГО пака — 7 посадок вместо наших 6, +# и первое правило там другое. Восстановленный так текст декоя не совпадает с тем, что реально +# видел судья, поэтому метрика «цитат не найдено в своём варианте» считалась против неверного +# текста. Поймано приёмкой 14.08, проверено исполнением: AJ.KEYS указывал на role-topology. +AJ.KEYS = KEYS_D4 = Path.home() / "books" / "dovodka" / "blind-keys-d4" BO = sys.modules.get("bakeoff") or _load("bakeoff", REPO / "eval" / "role_topology" / "bakeoff.py") AX = ("ВЕРНОСТЬ", "ТЕРМИН", "ЯЗЫК", "ФОРМА") @@ -158,6 +166,54 @@ def holm(ps: list[float]) -> list[float]: return adj +ALL_ARMS = ("A0", "A4", "A6", "A6F", "A3", "A2", "A1B", "A1") + + +def three_axes(d: dict) -> None: + """⚠ ТРИ ОСИ ПО КАЖДОМУ АРМУ — прямое требование заказа (:95): «Ответ по каждому арму + печатать ТРЕМЯ осями: судья · канон-покрытие · цена. H-1/H-2а/H-2б сверяются по всем трём, + а не по одной удобной». Первая редакция свода печатала ОДНУ судейскую ось по ТРЁМ первичным + контрастам, и за этим пропал ответ на прямой вопрос заказа про арм A4 («полировка … немедленный + кандидат в прод», :86-87). Поймано приёмкой 14.08. + ⚠ Канон считается на ВЫХОДЕ арма, а не на его входе: вход мерился при покупке, а сверяется + качество результата. Цена — медиана оплаченной клетки арма из сырья, а не из сметы. + """ + import json as _j # noqa: PLC0415 + import statistics as _st # noqa: PLC0415 + us = {x["uid"]: x for x in S.units()} + print("\n" + "=" * 78) + print("ТРИ ОСИ ПО КАЖДОМУ АРМУ (требование заказа :95) — судья · канон · цена") + print("=" * 78) + print(f" {'арм':5s}{'судья':>8s}{'канон':>9s}{'$/клетка':>11s} что это") + what = {"A0": "боевой ПОЛНЫЙ ПЕРЕПИС (эталон)", "A4": "перепис + канон-фиксер ПОСЛЕ", + "A6": "dspro-черновик + смысловой контур", "A6F": "dspro-черновик + флаги", + "A3": "черновик + смысловой критик", "A2": "однопроходка уравненного мандата", + "A1B": "черновик + флаги (батарея+канон)", "A1": "черновик + только канон-флаги"} + for arm in ALL_ARMS: + sc = [v["carry"] for (u, a, h), v in d.items() if a == arm] + if not sc: + continue + cov = [] + for u in us.values(): + t = S.C.base_a0(u["uid"]) if arm == "A0" else S.text_of(arm, u) + if t.strip(): + k, n = S.C.BANK.coverage(u["source"], t) + if n: + cov.append(k / n) + pr = [] + for f in S.C.OUT.glob(f"{S.C.tag(arm, '')}*.json"): + if any(x in f.name for x in (".ERROR", ".LENGTH", ".FLAGSV1")): + continue + r = _j.loads(f.read_text(encoding="utf-8")) + if r.get("cost_usd"): + pr.append(r["cost_usd"]) + print(f" {arm:5s}{_st.mean(sc):8.2f}{(_st.mean(cov) if cov else 0):9.3f}" + f"{(_st.median(pr) if pr else 0):11.5f} {what.get(arm, '')}") + print(" судья — ошибок на единицу (ВЕРНОСТЬ+ЯЗЫК), МЕНЬШЕ лучше") + print(" канон — доля покрытия банка на ВЫХОДЕ арма, БОЛЬШЕ лучше") + print(" цена — медиана оплаченной клетки арма по сырью; A0 куплен прошлыми паками") + + def main() -> int: gates_only = "--gates" in sys.argv fam: dict = {} @@ -192,6 +248,8 @@ def main() -> int: if not passes: print(" ⇒ семейство НЕ имеет права говорить «не хуже»: его вердикты по " "H-2а/H-2б понижаются до ОПИСАТЕЛЬНЫХ (П-2)") + if fam.get("claude"): + three_axes(fam["claude"]["d"]) if gates_only or not fam: return 0 print("\n" + "=" * 78)