113 lines
6.2 KiB
Python
113 lines
6.2 KiB
Python
#!/usr/bin/env python3
|
||
"""Проверка, что ПРОГОН прошёл правильно (владелец 05.08: «после прогона понять, что он прошёл верно»).
|
||
|
||
Проверяется не смысл результатов, а их санитария — то, что молча портит выводы:
|
||
П1 множество тегов ровно то, что запланировано: ни пропусков, ни лишних (пере-прогон чужого);
|
||
П2 ни одного обрыва по потолку вывода (`finish` ≠ stop) — обрыв даёт «модель не нашла правок»;
|
||
П3 модель и режим на проводе те, что заявлены армом (L — low, остальные — дефолт);
|
||
П4 у КАЖДОГО диффового вызова есть файл применения, и применение не молчит об отказах;
|
||
П5 деньги сходятся двумя путями: сумма записей против напечатанного скриптом итога;
|
||
П6 дифф-выходы не выродились: пустой/односимвольный ответ ≠ «NO_CHANGE по делу».
|
||
"""
|
||
from __future__ import annotations
|
||
import json
|
||
import sys
|
||
from collections import Counter
|
||
from pathlib import Path
|
||
|
||
RAW = Path.home() / "books" / "gu-zhenren" / "bank-arbitration"
|
||
FAIL = 0
|
||
|
||
|
||
def check(name: str, ok: bool, note: str = "") -> None:
|
||
global FAIL
|
||
if not ok:
|
||
FAIL += 1
|
||
print(f"[{'OK ' if ok else 'ПРОВАЛ'}] {name}" + (f" — {note}" if note else ""))
|
||
|
||
|
||
def expected() -> set[str]:
|
||
t = set()
|
||
for k in range(6):
|
||
for r in (1, 2, 3):
|
||
t |= {f"fp-LD-w{k}-r{r}", f"fp-LF-w{k}-r{r}", f"fp-N-w{k}-r{r}"}
|
||
for r in (1, 2):
|
||
t |= {f"fp-ED-w{k}-r{r}", f"fp-EF-w{k}-r{r}"}
|
||
for u in range(2):
|
||
for r in (1, 2, 3):
|
||
t |= {f"fp-GD-u{u}-r{r}", f"fp-GF-u{u}-r{r}"}
|
||
return t
|
||
|
||
|
||
def main() -> None:
|
||
recs = {}
|
||
for f in RAW.glob("fp-*.json"):
|
||
if f.name.endswith(".applied.json") or f.name.startswith("fp-draft"):
|
||
continue
|
||
recs[f.stem] = json.loads(f.read_text(encoding="utf-8"))
|
||
|
||
exp = expected()
|
||
got = set(recs)
|
||
check("П1 все запланированные вызовы есть", not (exp - got),
|
||
f"нет: {sorted(exp - got)[:8]}" if exp - got else f"{len(got)} шт.")
|
||
check("П1 лишних вызовов нет", not (got - exp), f"лишние: {sorted(got - exp)[:8]}")
|
||
|
||
fin = Counter(r["finish"] for r in recs.values())
|
||
check("П2 нет обрывов по потолку вывода", set(fin) == {"stop"}, str(dict(fin)))
|
||
|
||
models = Counter(r.get("model_returned", "?") for r in recs.values())
|
||
check("П3 модель одна на всех вызовах", len(models) == 1, str(dict(models)))
|
||
|
||
# П3а — режим НА ПРОВОДЕ, прямо из артефакта. ⚠ Инструмент добавлен по адверсариальному
|
||
# ревью 05.08: строка П3а стояла в отчёте как выполненная проверка, а кода под ней не было.
|
||
want = {t: ('{"reasoning_effort": "low"}' if t.startswith(("fp-LD", "fp-LF")) else "{}")
|
||
for t in recs}
|
||
bad = {t: json.dumps(recs[t].get("extra_body"), ensure_ascii=False)
|
||
for t in recs
|
||
if json.dumps(recs[t].get("extra_body"), ensure_ascii=False) != want[t]}
|
||
check("П3а режим на проводе совпадает с заявленным армом", not bad,
|
||
f"расходится у {len(bad)}: {list(bad)[:5]}" if bad
|
||
else f"low на {sum(1 for t in want.values() if 'low' in t)} · {{}} на "
|
||
f"{sum(1 for t in want.values() if t == '{}')}")
|
||
|
||
# ⚠ П3а проверяет, что параметр УШЁЛ, но НЕ что он ПОДЕЙСТВОВАЛ. Отличимость `low` от дефолта
|
||
# на этой модели пере-счётом 05.08 НЕ подтверждена (отчёт 19 §6.2) — держать это в виду.
|
||
print(" ⚠ уход параметра ≠ его действие: отличимость low от дефолта не установлена")
|
||
|
||
diffs = [t for t in recs if t.startswith(("fp-LD", "fp-GD", "fp-ED", "fp-N-"))]
|
||
noapp = [t for t in diffs if not (RAW / f"{t}.applied.json").exists()]
|
||
check("П4 у каждого диффа есть файл применения", not noapp, f"нет у: {noapp[:6]}")
|
||
|
||
rej = Counter()
|
||
for t in diffs:
|
||
f = RAW / f"{t}.applied.json"
|
||
if f.exists():
|
||
a = json.loads(f.read_text(encoding="utf-8"))
|
||
for k in ("rejected_notfound", "rejected_ambiguous", "rejected_overlap",
|
||
"rejected_empty", "malformed"):
|
||
rej[k] += a[k]
|
||
print(f" отказы аппликатора по всем диффам: {dict(rej)}")
|
||
|
||
tot = sum(r["cost_usd"] for r in recs.values())
|
||
drafts = sum(json.loads(f.read_text(encoding="utf-8"))["cost_usd"]
|
||
for f in RAW.glob("fp-draft-*.json"))
|
||
print(f" деньги: вызовы ${tot:.6f} + черновики ${drafts:.6f} = ${tot + drafts:.6f}")
|
||
check("П5 итог сошёлся с напечатанным скриптом ($0.349566)",
|
||
abs(tot + drafts - 0.349566) < 1e-5, f"расхождение {tot + drafts - 0.349566:+.6f}")
|
||
|
||
tiny = [(t, len(recs[t]["content"])) for t in diffs if len(recs[t]["content"]) < 20]
|
||
nc = []
|
||
for t, _ in tiny:
|
||
a = json.loads((RAW / f"{t}.applied.json").read_text(encoding="utf-8"))
|
||
nc.append((t, a["no_change"]))
|
||
check("П6 короткие дифф-ответы объяснены как NO_CHANGE",
|
||
all(v for _, v in nc), f"короткие БЕЗ no_change: {[t for t, v in nc if not v]}")
|
||
if nc:
|
||
print(f" коротких дифф-ответов {len(nc)}, из них NO_CHANGE {sum(v for _, v in nc)}")
|
||
|
||
print(f"\n{'ПРОГОН ЧИСТ' if not FAIL else f'ПРОВАЛОВ: {FAIL}'}")
|
||
sys.exit(1 if FAIL else 0)
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|