199 lines
9.3 KiB
Python
199 lines
9.3 KiB
Python
#!/usr/bin/env python3
|
||
"""ПЕРЕ-СЧЁТ ВСЕХ НЕСУЩИХ ЧИСЕЛ ПАКА ИЗ СЫРЬЯ. $0.
|
||
|
||
Число, которое нельзя пере-снять командой из репозитория, ничем не отличается от числа по памяти.
|
||
Здесь каждый блок ИТОГа печатается заново из персистированных артефактов, деньги — двумя
|
||
независимыми путями (касса и пере-счёт из сырых `usage` × прайс).
|
||
|
||
Запуск: eval/.venv/bin/python eval/editor_tier/itog23.py
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
import json
|
||
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(REPO / "eval" / "tenant_panel"))
|
||
sys.path.insert(0, str(REPO / "eval" / "role_topology"))
|
||
|
||
|
||
def _load(name: str, path: Path):
|
||
import importlib.util # noqa: PLC0415
|
||
spec = importlib.util.spec_from_file_location(name, path)
|
||
mod = importlib.util.module_from_spec(spec)
|
||
sys.modules[name] = mod
|
||
spec.loader.exec_module(mod)
|
||
return mod
|
||
|
||
|
||
JU = _load("judge_et", ZONE / "judge.py")
|
||
PAN, SCR, MONEY, MAT = JU.PAN, JU.PAN.SCR, JU.MONEY, JU.MAT
|
||
ROSTER = SCR.ROSTER
|
||
OUT = MONEY.OUT
|
||
|
||
|
||
def h(t: str) -> None:
|
||
print(f"\n═══ {t} ═══")
|
||
|
||
|
||
def material() -> None:
|
||
h("МАТЕРИАЛ")
|
||
old, new = JU.P22.M.units_zh(), MAT.units_new()
|
||
print(f" единиц эксп-22 (приколоты манифестом) {len(old)} · глав "
|
||
f"{sorted(u['chapter'] for u in old)}")
|
||
ln = sorted(len(u["source"]) for u in new)
|
||
print(f" НОВЫХ единиц {len(new)} · глав {sorted(u['chapter'] for u in new)}")
|
||
print(f" знаков {ln[0]}…{ln[-1]} медиана {ln[len(ln) // 2]} · "
|
||
f"макс. близость {JU.P22.M._pairwise_max_sim(new):.3f}") # noqa: SLF001
|
||
print(f" снято гейтом прав из своего пула: {MAT.rights_dropped(set()) or '—'}"
|
||
if False else f" пересечений с эксп-22: "
|
||
f"{len({u['uid'] for u in new} & {u['uid'] for u in old})}")
|
||
|
||
|
||
def screen() -> None:
|
||
h("ФАЗА A — СКРИН СИЛЬНОГО ТИРА (пороги эксп-22, не свои)")
|
||
rs = SCR.result()
|
||
print(f" {'модель':16s}{'вердикт':>10s}{'ед.':>4s}{'$/ед edit':>11s}{'размышл':>9s} причины")
|
||
for m, r in rs.items():
|
||
print(f" {m:16s}{r['verdict']:>10s}{r['n']:4d}{(r['price_editor'] or 0):11.5f}"
|
||
f"{r['reasoning_share']:9.1%} {'; '.join(r['why'])}")
|
||
print(f" прошли: {[m for m, r in rs.items() if r['verdict'] == 'прошёл']}")
|
||
print(" СПРАВКА (полный критерий эксп-22, с переводческим порогом, которого нет в пре-реге):")
|
||
for m, r in rs.items():
|
||
print(f" {m:16s}{r['verdict_full22']:>10s} $/ед transl "
|
||
f"{(r['price_translator'] or 0):.5f}")
|
||
|
||
|
||
def judged(pass_: str) -> None:
|
||
h(f"СУДЕЙСТВО, проход {pass_}")
|
||
JU.setup(pass_)
|
||
import absjudge as AJ # noqa: PLC0415
|
||
if not AJ._by_unit(): # noqa: SLF001
|
||
print(" голосов нет")
|
||
return
|
||
AJ.controls()
|
||
print()
|
||
AJ.score()
|
||
|
||
|
||
def positional() -> None:
|
||
h("ПОЗИЦИОННЫЙ НАКЛОН — ЗАМЕРЕН И ВЫЧТЕН (требование ЗАКОНа)")
|
||
for pass_ in JU.PASSES:
|
||
JU.setup(pass_)
|
||
import absjudge as AJ # noqa: PLC0415
|
||
if not AJ._by_unit(): # noqa: SLF001
|
||
continue
|
||
print(f" --- {pass_} ---")
|
||
AJ.position()
|
||
print()
|
||
|
||
|
||
def money_two_ways() -> None:
|
||
h("ДЕНЬГИ — ДВА НЕЗАВИСИМЫХ ПУТИ")
|
||
led = {ph: MONEY.Guarded(ph).spent() for ph in MONEY.TAGS}
|
||
|
||
def indep(rec: dict) -> float:
|
||
m = rec.get("model")
|
||
if m not in ROSTER.CANDIDATES:
|
||
return rec.get("cost_usd", 0.0)
|
||
_, _, pin, pc, pout, _t, _s = ROSTER.CANDIDATES[m]
|
||
pt, cd = rec.get("prompt_tokens", 0), rec.get("cached_tokens", 0)
|
||
out = ROSTER.billed_output(m, rec.get("completion_tokens", 0),
|
||
rec.get("reasoning_tokens", 0),
|
||
rec.get("total_tokens", 0), pt)
|
||
return (pt - cd) / 1e6 * pin + cd / 1e6 * pc + out / 1e6 * pout
|
||
|
||
tot2 = {ph: 0.0 for ph in led}
|
||
empty = []
|
||
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.append((f.name, r["cost_usd"]))
|
||
print(f" {'фаза':6s}{'касса':>12s}{'пере-счёт':>12s}{'расхождение':>13s}{'потолок':>10s}")
|
||
for ph in MONEY.TAGS:
|
||
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:
|
||
t = sum(c for _n, c in empty)
|
||
print(f" оплачено за ПУСТОЙ выход: {len(empty)} вызовов ${t:.5f} "
|
||
f"({t / max(1e-9, sum(led.values())) * 100:.1f}% пака)")
|
||
for n, c in sorted(empty, key=lambda x: -x[1]):
|
||
print(f" {n} ${c:.5f}")
|
||
|
||
|
||
def _cost(f: Path) -> float:
|
||
return json.loads(f.read_text(encoding="utf-8")).get("cost_usd", 0.0) if f.exists() else 0.0
|
||
|
||
|
||
def economics() -> None:
|
||
"""⚠ Строка R0 считается ИЗ СЫРЬЯ эксп-22, а не через `hasattr(P22,"edit_cost")`.
|
||
|
||
Прежняя редакция спрашивала у эксп-22 метод, которого у него нет (есть только `draft_cost`), —
|
||
строка молча выходила нулевой и отфильтровывалась, а в отчёт попадали числа, снятые где-то ещё
|
||
и друг с другом не сходившиеся (0.00545 × 1500 = $8.18, напечатано $13.45). Поймано приёмкой.
|
||
Колонки печатаются ДВУМЯ семействами: редактура отдельно и СВЯЗКА черновик+редактор — сравнивать
|
||
связку одного арма с редактурой другого нельзя, а именно это отчёт и делал.
|
||
"""
|
||
h("ЦЕНА РЕДАКТОРСКОЙ КЛЕТКИ, p50 И p95 ПО ЕДИНИЦАМ")
|
||
us = JU.P22.M.units_zh()
|
||
T22 = JU.P22.OUT
|
||
base = [_cost(T22 / f"{JU.P22.tag_draft(PAN.ANCHOR, u['uid'])}.json") for u in us]
|
||
rows = [("R0 " + PAN.BATTLE,
|
||
[_cost(T22 / f"{JU.P22.tag_edit(PAN.ANCHOR, u['uid'])}.json") for u in us])]
|
||
for i, m in enumerate(PAN.survivors(), start=1):
|
||
rows.append((f"T{i} {m}", [_cost(OUT / f"{PAN.tag_c(m, u['uid'])}.json") for u in us]))
|
||
|
||
def q(cs: list[float], p: int) -> float:
|
||
s = sorted(c for c in cs if c > 0)
|
||
return s[min(len(s) - 1, -(-p * len(s) // 100) - 1)] if s else 0.0
|
||
|
||
print(f" {'арм':26s}{'ед.':>5s}{'p50 edit':>10s}{'p95 edit':>10s}"
|
||
f"{'p50 связка':>12s}{'книга p50':>12s}{'книга p95':>12s}")
|
||
for name, cs in rows:
|
||
nz = [c for c in cs if c > 0]
|
||
if not nz:
|
||
continue
|
||
link = [c + b for c, b in zip(cs, base, strict=True) if c > 0 and b > 0]
|
||
print(f" {name:26s}{len(nz):5d}{st.median(nz):10.5f}{q(nz, 95):10.5f}"
|
||
f"{(st.median(link) if link else 0):12.5f}"
|
||
f"{(st.median(link) if link else 0) * 1500:12.2f}{q(link, 95) * 1500:12.2f}")
|
||
print(f" якорный черновик (общая база связки): p50 {st.median([b for b in base if b > 0]):.5f}"
|
||
if any(base) else " якорного черновика в сырье нет")
|
||
|
||
|
||
def agent_runs() -> None:
|
||
h("АГЕНТ-ЗАПУСКИ")
|
||
for p in JU.PASSES:
|
||
v = OUT / f"aj-{p}-votes"
|
||
t = OUT / f"aj-{p}-tasks"
|
||
print(f" {p:8s} заданий {len(list(t.glob('*.txt'))) if t.exists() else 0} · "
|
||
f"голосов принято {len(list(v.glob('*.json'))) if v.exists() else 0}")
|
||
|
||
|
||
def main() -> None:
|
||
print("ИТОГ ПАКА «СИЛЬНЫЙ ТИР» — ВСЕ НЕСУЩИЕ ЧИСЛА ИЗ СЫРЬЯ ($0)")
|
||
material()
|
||
screen()
|
||
economics()
|
||
for p in JU.PASSES:
|
||
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()
|