Re-freeze phase 2 before its bulk purchases: bound the ledger guard by per-model price and allow a tenant subset per process
This commit is contained in:
parent
43381fe766
commit
a725be5191
3 changed files with 286 additions and 14 deletions
213
eval/tenant_panel/itog22.py
Normal file
213
eval/tenant_panel/itog22.py
Normal file
|
|
@ -0,0 +1,213 @@
|
|||
#!/usr/bin/env python3
|
||||
"""ПЕРЕ-СЧЁТ ВСЕХ НЕСУЩИХ ЧИСЕЛ ЭКСП-22 ИЗ СЫРЬЯ. $0.
|
||||
|
||||
Зачем. Эксп-21 сдал отчёт, несущие числа которого НЕ ИМЕЛИ пути воспроизведения в дереве: часть
|
||||
бралась из stdout прогонов, утраченных инцидентом. Число, которое нельзя пере-снять командой из
|
||||
репозитория, ничем не отличается от числа по памяти. Здесь каждый блок ИТОГа печатается заново из
|
||||
персистированных артефактов, с провенансом — из какого прохода и по какой формуле.
|
||||
|
||||
Ничего не покупается и не судится: только счёт по тому, что уже лежит на диске.
|
||||
|
||||
Запуск: eval/.venv/bin/python eval/tenant_panel/itog22.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(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 economics() -> None:
|
||||
h("Ф2 (д) ЦЕНА/ЕДИНИЦА И ПРОЕКЦИЯ НА КНИГУ (1500 единиц)")
|
||||
zh = M.units_zh()
|
||||
print(f" {'жилец':26s}{'черновик':>11s}{'редактор':>11s}{'связка':>11s}{'книга $':>10s}")
|
||||
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
|
||||
d50, e50 = st.median(dc), (st.median(ec) if ec else 0.0)
|
||||
print(f" {m:26s}{d50:11.5f}{e50:11.5f}{d50 + e50:11.5f}{(d50 + e50) * 1500:10.2f}")
|
||||
print(" ⚠ DeepSeek объявил на прайс-странице 08.08 повышение цен — проекция едет с оговоркой")
|
||||
|
||||
|
||||
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}")
|
||||
for n, c in sorted(empty_paid, key=lambda x: -x[1])[:5]:
|
||||
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()
|
||||
economics()
|
||||
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}")
|
||||
money_two_ways()
|
||||
agent_runs()
|
||||
print("\nВСЁ ВЫШЕ ПЕРЕ-СНЯТО ИЗ СЫРЬЯ. Чисел без пути воспроизведения в отчёте быть не должно.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -53,8 +53,14 @@ TAGS = {
|
|||
# проекционный гард остановил скрин перед `gemini-3.1-pro-preview` — ОБЯЗАТЕЛЬНЫМ кандидатом Ф3
|
||||
# по промту. Его цена — единственная неизвестная в смете Ф3 (оценка $0.80 из $1.60), и без замера
|
||||
# половина фазы стояла бы на прайс-странице вместо провода. Пакетный потолок $5.00 не двигался.
|
||||
CEILINGS = {"Ф0": 0.40, "Ф1": 1.05, "Ф2": 2.00, "Ф3": 1.60}
|
||||
PACK_CEILING = 5.00
|
||||
# ⚠ ПОТОЛКИ Ф3 И ПАКА ПОДНЯТЫ 08.08 СЛОВОМ ВЛАДЕЛЬЦА, объявлено ДО покупок Ф3: $1.60 → $3.00 и
|
||||
# $5.00 → $6.50. Причина — замер, а не желание: обязательный кандидат промта
|
||||
# `gemini-3.1-pro-preview` тарифицирует 11 144–12 010 выходных токенов на ответ в 2 508 (скрытое
|
||||
# размышление видно ТОЛЬКО в `total_tokens`), то есть $0.152 за редакторскую единицу и $2.44 за
|
||||
# 16 единиц — больше всего прежнего потолка фазы. Альтернатива «подмножество единиц» владельцем
|
||||
# отклонена в пользу полной мощности панели.
|
||||
CEILINGS = {"Ф0": 0.40, "Ф1": 1.05, "Ф2": 2.00, "Ф3": 3.00}
|
||||
PACK_CEILING = 6.50
|
||||
|
||||
|
||||
class Pack:
|
||||
|
|
@ -76,6 +82,31 @@ class Pack:
|
|||
return self._led.afford()
|
||||
|
||||
|
||||
def model_expect(model: str) -> float:
|
||||
"""Ожидаемая цена следующего вызова ИМЕННО ЭТОЙ модели.
|
||||
|
||||
⚠ Заведено после того, как потолок Ф1 был пробит ($1.087758 против $1.05). Механизм пробоя:
|
||||
проекционный гард брал p95 УЖЕ КУПЛЕННЫХ вызовов — то есть прошлое, в котором новой модели
|
||||
ещё не было. `gemini-3.1-pro-preview` оказался в семь раз дороже любого предшественника, и
|
||||
оценка «как раньше» его не удержала. Здесь ожидание берётся по СВОИМ записям этой модели,
|
||||
а при их отсутствии считается из прайса по медианным токенам пака — консервативно, с
|
||||
множителем на скрытое размышление у провайдеров класса `additive_total`.
|
||||
"""
|
||||
mine = [json.loads(f.read_text(encoding="utf-8")).get("cost_usd", 0.0)
|
||||
for f in OUT.glob("tp*.json")
|
||||
if f.name.count("SELFTEST") == 0]
|
||||
prices = [c for c, m in ((json.loads(f.read_text(encoding="utf-8")).get("cost_usd", 0.0),
|
||||
json.loads(f.read_text(encoding="utf-8")).get("model"))
|
||||
for f in OUT.glob("tp*.json")) if m == model and c > 0]
|
||||
if len(prices) >= 2:
|
||||
return max(prices)
|
||||
if model not in ROSTER.CANDIDATES:
|
||||
return max(mine or [0.02])
|
||||
_, _, pin, _pc, pout, _t, _s = ROSTER.CANDIDATES[model]
|
||||
hidden = 5.0 if ROSTER.REASONING_BILLING.get(model) in ("additive_total", "additive") else 1.5
|
||||
return 4500 / 1e6 * pin + 3000 * hidden / 1e6 * pout
|
||||
|
||||
|
||||
class Guarded:
|
||||
"""Фазовая касса, которая не пускает покупку, пробивающую ЛЮБОЙ из двух потолков."""
|
||||
|
||||
|
|
@ -85,19 +116,27 @@ class Guarded:
|
|||
self.ceiling = CEILINGS[phase]
|
||||
self._led = BUY.Ledger(self.name, self.ceiling, TAGS[phase], default_expect=default_expect)
|
||||
self._pack = Pack()
|
||||
self.next_model: str | None = None
|
||||
|
||||
def spent(self) -> float:
|
||||
return self._led.spent()
|
||||
|
||||
def expect(self) -> float:
|
||||
self._led.spent()
|
||||
return self._led.expect()
|
||||
base = self._led.expect()
|
||||
return max(base, model_expect(self.next_model)) if self.next_model else base
|
||||
|
||||
def afford(self) -> tuple[bool, str]:
|
||||
ok, why = self._led.afford()
|
||||
if not ok:
|
||||
return ok, why
|
||||
return self._pack.afford()
|
||||
sp = self.spent()
|
||||
exp = self.expect()
|
||||
if sp + exp > self.ceiling:
|
||||
return False, (f"[СТОП {self.name}] потрачено ${sp:.6f} + ожидание ${exp:.6f} "
|
||||
f"= ${sp + exp:.6f} > потолок ${self.ceiling:.2f}")
|
||||
psp, pexp = self._pack.spent(), max(self._pack.expect(), exp)
|
||||
if psp + pexp > PACK_CEILING:
|
||||
return False, (f"[СТОП ПАК] потрачено ${psp:.6f} + ожидание ${pexp:.6f} "
|
||||
f"> потолок пака ${PACK_CEILING:.2f}")
|
||||
return True, ""
|
||||
|
||||
|
||||
@lru_cache(maxsize=64)
|
||||
|
|
@ -152,6 +191,7 @@ def purchase(led: Guarded, tag: str, model: str, client, kw: dict, **extra) -> d
|
|||
if not any(tag.startswith(p.split("*")[0]) for p in TAGS[led.phase]):
|
||||
raise SystemExit(f"⛔ тег {tag!r} не покрыт глобом кассы {led.phase} — впиши в TAGS")
|
||||
_refuse_unfrozen()
|
||||
led.next_model = model # гард считает ожидание по ЭТОЙ модели
|
||||
rec = {}
|
||||
for k in range(4):
|
||||
rec = BUY.purchase(led, tag, model, client, kw, **extra)
|
||||
|
|
@ -197,10 +237,20 @@ def selftest() -> int:
|
|||
f.unlink()
|
||||
# Проекционный гард обязан ОТКАЗЫВАТЬ, когда ожидание пробивает потолок.
|
||||
g = Guarded("Ф0")
|
||||
g._led.ceiling = 0.0 # noqa: SLF001
|
||||
ok, why = g.afford()
|
||||
g.ceiling = 0.0
|
||||
ok, _why = g.afford()
|
||||
bad += ok
|
||||
print(f"[{'OK ' if not ok else 'ПРОВАЛ'}] проекционный гард отказывает при нулевом потолке")
|
||||
# ⚠ РЕГРЕССИЯ НА ПРОБОЙ ПОТОЛКА Ф1: ожидание обязано считаться по ЭТОЙ модели, а не по
|
||||
# p95 прошлого. Дорогая новая модель не должна проходить гард только потому, что раньше
|
||||
# таких дорогих вызовов не было.
|
||||
g2 = Guarded("Ф0")
|
||||
g2.ceiling = g2.spent() + 0.05
|
||||
g2.next_model = "gemini-3.1-pro-preview"
|
||||
ok2, _ = g2.afford()
|
||||
bad += ok2
|
||||
print(f"[{'OK ' if not ok2 else 'ПРОВАЛ'}] гард видит цену дорогой НОВОЙ модели "
|
||||
f"(${model_expect('gemini-3.1-pro-preview'):.4f}), а не p95 прошлого")
|
||||
# Прайс: КАЖДАЯ модель ростера обязана считаться кассой, иначе покупка падает на середине.
|
||||
for m in ROSTER.CANDIDATES:
|
||||
try:
|
||||
|
|
|
|||
|
|
@ -178,9 +178,18 @@ def cmd_plan() -> None:
|
|||
print("⛔ СМЕТА НЕ ВЛЕЗАЕТ — резать панель по объявленному правилу или пинг владельцу")
|
||||
|
||||
|
||||
def cmd_drafts() -> None:
|
||||
def _selected(argv: list[str]) -> list[str]:
|
||||
"""Подмножество жильцов для параллельного прогона. Клетки идемпотентны по тегу, поэтому
|
||||
несколько процессов не мешают друг другу; касса читает диск, так что расход виден всем.
|
||||
⚠ Гонка ограничена сверху: перерасход не больше (процессов−1)×цена одного вызова."""
|
||||
only = [a.split("=", 1)[1] for a in argv if a.startswith("--only=")]
|
||||
ts = tenants()
|
||||
return [m for m in ts if m in only] if only else ts
|
||||
|
||||
|
||||
def cmd_drafts(argv: list[str] | None = None) -> None:
|
||||
led = MONEY.Guarded("Ф2", default_expect=0.01)
|
||||
for m in tenants():
|
||||
for m in _selected(argv or []):
|
||||
for u, en in units_all():
|
||||
rec = buy_draft(led, m, u, en)
|
||||
if rec.get("skipped"):
|
||||
|
|
@ -193,9 +202,9 @@ def cmd_drafts() -> None:
|
|||
print(f"потрачено Ф2 ${led.spent():.6f}")
|
||||
|
||||
|
||||
def cmd_edit() -> None:
|
||||
def cmd_edit(argv: list[str] | None = None) -> None:
|
||||
led = MONEY.Guarded("Ф2", default_expect=0.01)
|
||||
for m in tenants():
|
||||
for m in _selected(argv or []):
|
||||
for u, en in units_all():
|
||||
rec = buy_edit(led, m, u, en)
|
||||
if rec.get("no_draft"):
|
||||
|
|
@ -299,6 +308,6 @@ def cmd_score() -> None:
|
|||
|
||||
if __name__ == "__main__":
|
||||
a = sys.argv[1:] or ["--plan"]
|
||||
fn = {"--plan": cmd_plan, "--drafts": cmd_drafts, "--edit": cmd_edit,
|
||||
fn = {"--plan": cmd_plan, "--drafts": lambda: cmd_drafts(a), "--edit": lambda: cmd_edit(a),
|
||||
"--floor": cmd_floor, "--score": cmd_score}.get(a[0])
|
||||
fn() if fn else print(__doc__)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue