#!/usr/bin/env python3 """ВЕРИФИКАТОР ЧИСЕЛ ОТЧЁТА ЭКСП-22. $0. Ненулевой код возврата = отчёт не сдаётся. `itog22.py` пере-считывает числа из сырья; этот файл сверяет их с ТЕКСТОМ отчёта. Разделение нужно потому, что «число посчиталось» и «в отчёте напечатано именно оно» — разные утверждения, и эксп-21 разошёлся с сырьём ровно на этом стыке (§18: ИТОГ печатал расход фазы без одного тега, а гейт это сертифицировал). Правило: гейт сторожит НЕСУЩИЕ числа — те, на которых стоит решение владельца. Число, которого гейт не проверяет, в ИТОГе стоять не должно. """ from __future__ import annotations import json import re import sys from pathlib import Path REPO = Path("/home/ubuntu/projects/textmachine") ZONE = Path(__file__).resolve().parent sys.path.insert(0, str(ZONE)) sys.path.insert(0, str(REPO / "eval" / "role_topology")) import bank as BANK # noqa: E402 import material as M # noqa: E402 import money as MONEY # noqa: E402 import panel as P2 # noqa: E402 REPORT = REPO / "docs" / "experiments" / "22-tenant-panel.md" BAD = 0 def ck(name: str, ok: bool, detail: str = "") -> None: global BAD # noqa: PLW0603 BAD += not ok print(f"[{'OK ' if ok else 'ПРОВАЛ'}] {name}" + (f" {detail}" if detail else "")) def has(text: str, s: str) -> bool: """Число ищется по НОРМАЛИЗОВАННОМУ тексту: пробелы и переносы строк не значимы.""" return re.sub(r"\s+", "", s) in re.sub(r"\s+", "", text) def main() -> int: if not REPORT.exists(): print("отчёта нет") return 1 t = REPORT.read_text(encoding="utf-8") print("=== МАТЕРИАЛ ===") zs = M.units_zh() ck("книга и дата первой публикации названы", has(t, M.BOOK["title"]) and has(t, M.BOOK["first_pub"])) ck(f"число zh-единиц {len(zs)}", has(t, f"{len(zs)} единиц") or has(t, f"{len(zs)} zh")) ck("гейт прав объявлен числом снятых глав", has(t, "гейт прав") or has(t, "ГЕЙТ ПРАВ")) b = BANK.PR.bank() tot = sum(sum(u["source"].count(x) for u in zs) for x in b) ck(f"банк {len(b)} терминов", has(t, f"{len(b)} термин")) ck(f"знаменатель покрытия {tot}", has(t, str(tot))) print("\n=== КОНТАМИНАЦИЯ ===") f = MONEY.OUT / "contamination.json" if f.exists(): for r in json.loads(f.read_text(encoding="utf-8")): ck(f"{r['book']}/{r['model'].split('-')[0]} узнано {r['recognized']}/{r['n']}", has(t, f"{r['recognized']}/{r['n']}")) print("\n=== СКРИН Ф1 ===") res = P2.screen_result() for m, r in res.items(): if r.get("price_translator") is None: continue ck(f"{m}: цена переводчика {r['price_translator']:.5f}", has(t, f"{r['price_translator']:.5f}")) ck(f"{m}: вердикт {r['verdict']}", has(t, m)) print("\n=== ПАНЕЛЬ Ф2 ===") for m in P2.tenants(): ck(f"жилец {m} назван в отчёте", has(t, m)) print("\n=== ДЕНЬГИ ===") for ph in ("Ф0", "Ф1", "Ф2", "Ф3"): sp = MONEY.Guarded(ph).spent() if sp <= 0: continue ck(f"{ph}: расход ${sp:.6f} напечатан", has(t, f"{sp:.6f}")) if sp > MONEY.CEILINGS[ph] + 1e-9: ck(f"{ph}: ПРОБОЙ потолка объявлен", has(t, "ПРОБОЙ") or has(t, "пробит") or has(t, "пробой"), f"${sp:.6f} против ${MONEY.CEILINGS[ph]:.2f}") pack = MONEY.Pack().spent() ck(f"расход пака ${pack:.6f} напечатан", has(t, f"{pack:.6f}")) print("\n=== СУДЕЙСТВО ===") import judge as JU # noqa: PLC0415 rs = JU.runs_used() if rs: ck(f"агент-запусков {len(rs)} из {JU.CAP} напечатано", has(t, f"{len(rs)} из {JU.CAP}") or has(t, f"{len(rs)}/{JU.CAP}")) for pass_ in ("p2zh", "p2en", "p3", "dva"): votes = MONEY.OUT / f"aj-{pass_}-votes" if not votes.exists() or not list(votes.glob("*.json")): continue JU.setup(pass_) import absjudge as AJ # noqa: PLC0415 data = AJ._by_unit() # noqa: SLF001 for a, bb, _w in JU.family(pass_): m = AJ._margins(a, bb) # noqa: SLF001 if len(m) < 3: continue import statistics as sst # noqa: PLC0415 ck(f"{pass_}: контраст {a} vs {bb} = {sst.mean(m):+.2f} напечатан", has(t, f"{sst.mean(m):+.2f}") or has(t, f"{sst.mean(m):.2f}")) # контроли обязаны быть напечатаны ДО боевых чисел dec = [d for d in data.values() if "CTRLdecoy" in d] if dec: ck(f"{pass_}: декой упомянут в отчёте", has(t, "декой") or has(t, "ДЕКОЙ")) flo = [d for d in data.values() if "CTRLfloorA" in d and "CTRLfloorB" in d] if flo: ck(f"{pass_}: шумовой пол упомянут", has(t, "пол") or has(t, "ПОЛ")) print(f"\n{'ВСЕ ЧИСЛА ОТЧЁТА СХОДЯТСЯ' if not BAD else f'РАСХОЖДЕНИЙ: {BAD} — ОТЧЁТ НЕ СДАЁТСЯ'}") return BAD if __name__ == "__main__": sys.exit(1 if main() else 0)