textmachine/eval/tenant_panel/itog22.py

302 lines
15 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""ПЕРЕ-СЧЁТ ВСЕХ НЕСУЩИХ ЧИСЕЛ ЭКСП-22 ИЗ СЫРЬЯ. $0.
Зачем. Эксп-21 сдал отчёт, несущие числа которого НЕ ИМЕЛИ пути воспроизведения в дереве: часть
бралась из stdout прогонов, утраченных инцидентом. Число, которое нельзя пере-снять командой из
репозитория, ничем не отличается от числа по памяти. Здесь каждый блок ИТОГа печатается заново из
персистированных артефактов, с провенансом — из какого прохода и по какой формуле.
Ничего не покупается и не судится: только счёт по тому, что уже лежит на диске.
Запуск: eval/.venv/bin/python eval/tenant_panel/itog22.py
"""
from __future__ import annotations
import json
import math
import statistics as st
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 battery as B # noqa: E402
import judge as JU # noqa: E402
import material as M # noqa: E402
import money as MONEY # noqa: E402
import panel as P2 # noqa: E402
import roster as R # noqa: E402
OUT = MONEY.OUT
def h(t: str) -> None:
print(f"\n═══ {t} ═══")
def contamination() -> None:
h("Ф0.2 КОНТАМИНАЦИЯ СРЕЗА (порог: узнавание ≥3/5 = непригоден)")
f = OUT / "contamination.json"
if not f.exists():
print(" пробы нет")
return
for r in json.loads(f.read_text(encoding="utf-8")):
v = "НЕПРИГОДЕН" if r["recognized"] * 5 >= 3 * max(1, r["n"]) else "следов нет"
print(f" {r['book']:14s} {r['model']:22s} узнано {r['recognized']}/{r['n']} · "
f"клоуз {r['cloze']}/{r['n']} {v} [{r['kind']}]")
def material() -> None:
h("Ф0.2 МАТЕРИАЛ")
zs = M.units_zh()
allch = M.chapters(all_=True)
print(f" книга {M.BOOK['title']} · 首发 {M.BOOK['first_pub']} · глав скачано {len(allch)} · "
f"снято гейтом прав {sorted(M.se_dropped(allch))}")
print(f" zh-единиц {len(zs)} · знаков {min(len(u['source']) for u in zs)}"
f"{max(len(u['source']) for u in zs)} · медиана "
f"{sorted(len(u['source']) for u in zs)[len(zs) // 2]} · глав-источников "
f"{len({u['chapter'] for u in zs})} · макс. близость {M._pairwise_max_sim(zs):.3f}") # noqa: SLF001
b = BANK.PR.bank()
tot = sum(sum(u["source"].count(t) for u in zs) for t in b)
print(f" банк {len(b)} терминов · знаменатель покрытия {tot} упоминаний · signed=false")
def screen() -> None:
h("Ф1 СКРИН — по КАЖДОМУ кандидату")
res = P2.screen_result()
if not res:
print(" скрина нет")
return
print(f" {'модель':26s}{'вердикт':>11s}{'$/ед transl':>13s}{'$/ед edit':>11s} причины")
for m in R.by_out_price():
r = res.get(m, {})
print(f" {m:26s}{r.get('verdict', ''):>11s}"
f"{(r.get('price_translator') or 0):13.5f}{(r.get('price_editor') or 0):11.5f}"
f" {'; '.join(r.get('why', []))}")
def echo_rate() -> None:
h("Ф2 (в) ЭХО-МИНА — доля единиц, потребовавших ре-гена")
for m in P2.tenants():
nz, tz = P2.regen_rate(m, en=False)
ne, te = P2.regen_rate(m, en=True)
print(f" {m:26s} zh {nz}/{tz} = {nz / max(1, tz):.0%} · en {ne}/{te} = {ne / max(1, te):.0%}")
def battery_canon() -> None:
h("Ф2 (г) БАТАРЕЯ И ПОКРЫТИЕ КАНОНА (zh, детерминированно, шума нет)")
zh, cards = M.units_zh(), B.load_cards()
print(f" {'арм':32s}{'ед.':>5s}{'ханьцзы':>9s}{'типогр':>8s}{'потеря':>8s}"
f"{'канон':>14s}{'покрытие':>10s}")
for m in P2.tenants():
for kind in ("D", "E"):
rows, k, s = [], 0, 0
for u in zh:
t = P2.draft_text(m, u["uid"]) if kind == "D" else P2.edit_text(m, u["uid"])
if not t.strip():
continue
rows.append(B.run_unit(B.Unit(source=u["source"], draft=t, final=t, cards=cards)))
kk, ss = BANK.coverage(u["source"], t)
k += kk
s += ss
if rows:
print(f" {kind + '[' + m + ']':32s}{len(rows):5d}"
f"{sum(r['cjk_leak']['han_chars'] for r in rows):9d}"
f"{sum(r['typography']['violations'] for r in rows):8d}"
f"{sum(r['numbers']['lost_n'] for r in rows):8d}"
f"{f'{k}/{s}':>14s}{(k / s if s else 0):>10.3f}")
def battery_canon_f3() -> None:
"""Та же батарея по армам Ф3. Прежде §6.2 отчёта считалась ВНЕ дерева: шапка обещала, что
пере-считывается каждое число, а таблица Ф3 под пере-счёт не попадала — клейм был шире гейта."""
h("Ф3 БАТАРЕЯ И ПОКРЫТИЕ КАНОНА ПО АРМАМ РЕДАКТОРОВ (детерминированно, шума нет)")
import editors as ED # noqa: PLC0415
zh, cards = M.units_zh(), B.load_cards()
print(f" {'арм':30s}{'ед.':>5s}{'ханьцзы':>9s}{'типогр':>8s}{'потеря':>8s}"
f"{'канон':>14s}{'покрытие':>10s}{'$/ед':>10s}")
pan = ED.panel()
for i, model in enumerate(pan):
arm = f"R{i}"
rows, k, s, costs = [], 0, 0, []
for u in zh:
t = ED.text_of(arm, u["uid"])
if not t.strip():
continue
rows.append(B.run_unit(B.Unit(source=u["source"], draft=t, final=t, cards=cards)))
kk, ss = BANK.coverage(u["source"], t)
k += kk
s += ss
f = OUT / f"{ED.tag(model, u['uid'])}.json"
if f.exists():
costs.append(json.loads(f.read_text(encoding="utf-8")).get("cost_usd", 0.0))
if rows:
print(f" {arm + ' ' + model:30s}{len(rows):5d}"
f"{sum(r['cjk_leak']['han_chars'] for r in rows):9d}"
f"{sum(r['typography']['violations'] for r in rows):8d}"
f"{sum(r['numbers']['lost_n'] for r in rows):8d}"
f"{f'{k}/{s}':>14s}{(k / s if s else 0):>10.3f}"
f"{(st.median(costs) if costs else 0):10.5f}")
print(" ⚠ Клетки с ПУСТЫМ содержимым в счёт не идут — именно поэтому у части армов ед. < 16;")
print(" оплаченные пустые клетки перечислены в блоке ДЕНЬГИ.")
def _p95(xs: list[float]) -> float:
"""p95 по ЕДИНИЦАМ, ближайший ранг. При n=16 это 16-я по возрастанию, то есть максимум:
честнее так и сказать, чем интерполяцией изображать точность, которой при таком n нет."""
if not xs:
return 0.0
s = sorted(xs)
return s[min(len(s) - 1, math.ceil(0.95 * len(s)) - 1)]
def economics() -> None:
h("Ф2 (д) ЦЕНА/ЕДИНИЦА И ПРОЕКЦИЯ НА КНИГУ (1500 единиц) — p50 И p95, как заказано промтом")
zh = M.units_zh()
print(f" {'жилец':26s}{'связка p50':>11s}{'связка p95':>11s}"
f"{'книга p50':>11s}{'книга p95':>11s}")
for m in P2.tenants():
dc = [P2.draft_cost(m, u["uid"]) for u in zh]
ec = []
for u in zh:
f = OUT / f"{P2.tag_edit(m, u['uid'])}.json"
if f.exists():
ec.append(json.loads(f.read_text(encoding="utf-8")).get("cost_usd", 0.0))
if not dc:
continue
# p50/p95 берутся от СВЯЗКИ по единице, а не складываются из отдельных квантилей ролей:
# сумма p95 двух ролей — не p95 суммы, и завышала бы хвост.
pair = [d + e for d, e in zip(dc, ec, strict=False)] if ec else dc
print(f" {m:26s}{st.median(pair):11.5f}{_p95(pair):11.5f}"
f"{st.median(pair) * 1500:11.2f}{_p95(pair) * 1500:11.2f}")
print(" ⚠ DeepSeek объявил на прайс-странице 08.08 повышение цен — проекция едет с оговоркой")
def drafts_as_text() -> None:
h("Ф2 (а) ЧЕРНОВИК КАК ТЕКСТ — ОПИСАТЕЛЬНАЯ ОСЬ ЗАКАЗА (поправки не несёт, вердиктов не даёт)")
import statistics as sst # noqa: PLC0415
for pass_ in ("p2zh", "p2en"):
JU.setup(pass_)
import absjudge as AJ # noqa: PLC0415
if not AJ._by_unit(): # noqa: SLF001
continue
fl = {"p2zh": 2.03, "p2en": None}[pass_]
print(f" --- {pass_} --- порог различимости "
f"{('%.2f' % fl) if fl else 'своего пола нет (пол Ф2 снят на zh)'}")
for a, bb, _w in JU.family_desc(pass_):
m = AJ._margins(a, bb) # noqa: SLF001
if not m:
continue
mean = sst.mean(m)
mark = ("ниже порога различимости" if fl and abs(mean) < fl else
"выше порога" if fl else "")
print(f" {a} vs {bb} n={len(m):2d} {mean:+6.2f} {mark}")
print(" ⚠ Знак ПОЛОЖИТЕЛЬНЫЙ = черновик жильца лучше якорного. Ось описательная: пре-рег Ф2")
print(" сузил решающее семейство до финалов, поэтому Холм по ней НЕ считается.")
def positional() -> None:
h("ПОЗИЦИОННЫЙ НАКЛОН — ЗАМЕРЕН И ВЫЧТЕН (требование ЗАКОНа промта)")
for pass_ in ("p2zh", "p2en", "p3", "dva"):
JU.setup(pass_)
import absjudge as AJ # noqa: PLC0415
if not AJ._by_unit(): # noqa: SLF001
continue
print(f" --- {pass_} ---")
AJ.position()
print()
def judged(pass_: str) -> None:
h(f"СУДЕЙСТВО, проход {pass_}")
JU.setup(pass_)
import absjudge as AJ # noqa: PLC0415
data = AJ._by_unit() # noqa: SLF001
if not data:
print(" голосов нет")
return
AJ.controls()
print()
AJ.score()
def money_two_ways() -> None:
h("ДЕНЬГИ — ДВА НЕЗАВИСИМЫХ ПУТИ")
led = {ph: MONEY.Guarded(ph).spent() for ph in ("Ф0", "Ф1", "Ф2", "Ф3")}
# второй путь: пере-счёт из сырых usage × прайс, правило биллинга выписано заново
def indep(rec: dict) -> float:
m = rec.get("model")
if m not in R.CANDIDATES:
return rec.get("cost_usd", 0.0)
_, _, pin, pc, pout, _t, _s = R.CANDIDATES[m]
pt, cd = rec.get("prompt_tokens", 0), rec.get("cached_tokens", 0)
ct, rt = rec.get("completion_tokens", 0), rec.get("reasoning_tokens", 0)
tt = rec.get("total_tokens", 0)
mode = R.REASONING_BILLING.get(m)
out = ct + rt if mode == "additive" else (
max(tt - pt, ct + rt) if mode == "additive_total" else ct)
return (pt - cd) / 1e6 * pin + cd / 1e6 * pc + out / 1e6 * pout
tot2 = {ph: 0.0 for ph in led}
empty_paid = []
for ph, globs in MONEY.TAGS.items():
for g in globs:
for f in OUT.glob(g):
r = json.loads(f.read_text(encoding="utf-8"))
tot2[ph] += indep(r)
if r.get("cost_usd", 0) > 0 and not (r.get("content") or "").strip():
empty_paid.append((f.name, r["cost_usd"]))
print(f" {'фаза':6s}{'касса':>12s}{'пере-счёт':>12s}{'расхождение':>13s}{'потолок':>10s}")
for ph in ("Ф0", "Ф1", "Ф2", "Ф3"):
mark = " ⛔ ПРОБОЙ" if led[ph] > MONEY.CEILINGS[ph] + 1e-9 else ""
print(f" {ph:6s}{led[ph]:12.6f}{tot2[ph]:12.6f}{led[ph] - tot2[ph]:13.6f}"
f"{MONEY.CEILINGS[ph]:10.2f}{mark}")
print(f" {'ПАК':6s}{sum(led.values()):12.6f}{sum(tot2.values()):12.6f}"
f"{sum(led.values()) - sum(tot2.values()):13.6f}{MONEY.PACK_CEILING:10.2f}")
if empty_paid:
print(f" оплачено за ПУСТОЙ выход: {len(empty_paid)} вызовов "
f"${sum(c for _n, c in empty_paid):.5f}")
tot_empty = sum(c for _n, c in empty_paid)
print(f" доля пака: {tot_empty / sum(led.values()) * 100:.1f}% "
f"(${tot_empty:.5f} из ${sum(led.values()):.6f})")
for n, c in sorted(empty_paid, key=lambda x: -x[1]):
print(f" {n} ${c:.5f}")
def agent_runs() -> None:
h("АГЕНТ-ЗАПУСКИ")
rs = JU.runs_used()
print(f" израсходовано {len(rs)} из {JU.CAP}")
by: dict[str, int] = {}
for r in rs:
by[r["pass_"]] = by.get(r["pass_"], 0) + 1
for p, n in sorted(by.items()):
print(f" {p:6s} {n}")
def main() -> None:
print("ИТОГ ЭКСП-22 — ВСЕ НЕСУЩИЕ ЧИСЛА, ПЕРЕ-СНЯТЫЕ ИЗ СЫРЬЯ ($0)")
material()
contamination()
screen()
echo_rate()
battery_canon()
battery_canon_f3()
economics()
drafts_as_text()
for p in ("p2zh", "p2en", "p3", "dva"):
try:
judged(p)
except Exception as e: # noqa: BLE001
print(f"\n═══ СУДЕЙСТВО {p} ═══\n не считается: {type(e).__name__}: {e}")
positional()
money_two_ways()
agent_runs()
print("\nВСЁ ВЫШЕ ПЕРЕ-СНЯТО ИЗ СЫРЬЯ. Чисел без пути воспроизведения в отчёте быть не должно.")
if __name__ == "__main__":
main()