#!/usr/bin/env python3 """ФАЗА A — СКРИН СИЛЬНОГО ТИРА. Платный (потолок $1.10), идёт через кассу пака. Пороги НЕ свои: берутся у эксп-22 побайтно (`screen.PRICE_CAP/CYR_MIN/ECHO_MAX`, `gates`, `verdict`). Порог, взятый из прошлого пака без правки, — единственная защита от подгонки состава под желаемый результат, а состав здесь и есть предмет спора: пак существует потому, что эксп-22 срезал сильный тир. ⚠ ОСОБЫЙ РЕЖИМ `kimi-k3`, объявлен ДО покупок. По отношению «цена клетки / цена выхода» внутри Kimi он ожидается ~$0.25/ед — вчетверо выше порога роли, — и `k2.6` уже отдал пустой выход на трёх клетках из четырёх при 100% доли размышления. Полный скрин был бы покупкой предсказуемого провала примерно за доллар. Берётся ОДНА клетка редакторской роли с жёстким потолком выхода: этого хватает и на ценовой вердикт, и на второй замер отказного режима. Отдай он против ожидания годный дешёвый выход — досняли бы полным скрином отдельным решением. """ from __future__ import annotations import json 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 <имя>`. При прямом запуске своя папка стоит в sys.path первой, и `import screen` находит ЭТОТ файл, а не скрин эксп-22 — молчаливый само-импорт. Ловилось трижды подряд (ростер, касса, скрин), поэтому правило одно: всё чужое — только явной загрузкой.""" 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 TP = REPO / "eval" / "tenant_panel" S22 = _load("screen22", TP / "screen.py") MONEY = _load("money_et", ZONE / "money.py") ROSTER = MONEY.ROSTER_ET PHASE = "ФA" NEW = list(ROSTER.NEW) # gpt-5.6-terra · kimi-k3 · grok-4.5 · glm-5.2 SPECIAL = {"kimi-k3": dict(units=1, roles=("editor",), max_out=4000)} ROLES = ("translator", "editor") def client(model: str): """Соединение по РАСШИРЕННОЙ таблице. Клиент эксп-22 знает только свои 11 слагов и падает KeyError на новом кандидате — тот же класс, что уронил его собственный скрин на середине.""" import os # noqa: PLC0415 from dotenv import load_dotenv # noqa: PLC0415 from openai import OpenAI # noqa: PLC0415 load_dotenv(REPO / "eval" / ".env") base, env, *_ = ROSTER.CANDIDATES[model] key = os.environ.get(env) if not key: raise SystemExit(f"нет ключа {env} для {model}") return OpenAI(base_url=base, api_key=key, timeout=600.0, max_retries=1) def tag(model: str, role: str, uid: str) -> str: return f"et-scr-{model}-{role}-{uid[:6]}" def plan() -> list[tuple[str, str, dict]]: """Что будет куплено. Печатается ДО покупок и лежит во фризе.""" us = S22.units() # те же две единицы, что скринил эксп-22 out = [] for m in ROSTER.by_new_price(): sp = SPECIAL.get(m, {}) for role in sp.get("roles", ROLES): for u in us[:sp.get("units", len(us))]: out.append((m, role, u)) return out def cmd_plan() -> None: tot = 0.0 print(f"{'модель':16s}{'роль':12s}{'ед.':>4s}{'ожидание $':>12s}") for m in ROSTER.by_new_price(): sp = SPECIAL.get(m, {}) n = len(sp.get("roles", ROLES)) * sp.get("units", len(S22.units())) exp = ROSTER.EXPECT_PER_UNIT[m] * n tot += exp print(f"{m:16s}{'/'.join(sp.get('roles', ROLES)):12s}{n:4d}{exp:12.3f}" + (" ← особый режим" if m in SPECIAL else "")) print(f"{'ИТОГО':16s}{'':12s}{'':4s}{tot:12.3f} потолок {MONEY.CEILINGS[PHASE]:.2f}") if tot > MONEY.CEILINGS[PHASE]: print("⛔ смета выше потолка фазы — СТОП, не резать молча") raise SystemExit(1) def cmd_run() -> None: led = MONEY.Guarded(PHASE) for m, role, u in plan(): t = tag(m, role, u["uid"]) if (MONEY.OUT / f"{t}.json").exists(): continue msgs = S22.msgs_for(role, u) kw = ROSTER.call_kwargs(m, msgs, max_out=SPECIAL.get(m, {}).get("max_out", 16000)) rec = MONEY.purchase(led, t, m, client(m), kw, role=role, uid=u["uid"]) if rec is None: print(f" {t}: не куплено") continue print(f" {t}: ${rec.get('cost_usd', 0):.5f} finish={rec.get('finish')} " f"знаков={len(rec.get('content') or '')}") MONEY.report() def result() -> dict: """Вердикт по каждому кандидату — порогами эксп-22, без единой правки.""" res = {} for m in NEW: rows = [] for f in MONEY.OUT.glob(f"et-scr-{m}-*.json"): rec = json.loads(f.read_text(encoding="utf-8")) u = next((x for x in S22.units() if x["uid"] == rec.get("uid")), None) rows.append(S22.gates(rec, u["source"] if u else "")) v, why = S22.verdict(rows) pt = [r["cost"] for r in rows if r["role"] == "translator"] pe = [r["cost"] for r in rows if r["role"] == "editor"] import statistics as st # noqa: PLC0415 res[m] = dict(verdict=v, why=why, n=len(rows), price_translator=(st.median(pt) if pt else None), price_editor=(st.median(pe) if pe else None), reasoning_share=(max((r["reasoning_share"] for r in rows), default=0.0)), han=sum(r["han_chars"] for r in rows)) return res def cmd_score() -> None: print(f"{'модель':16s}{'вердикт':>10s}{'ед.':>4s}{'$/ед transl':>13s}{'$/ед edit':>11s}" f"{'размышл':>9s} причины") for m, r in result().items(): print(f"{m:16s}{r['verdict']:>10s}{r['n']:4d}" f"{(r['price_translator'] or 0):13.5f}{(r['price_editor'] or 0):11.5f}" f"{r['reasoning_share']:9.1%} {'; '.join(r['why'])}") ok = [m for m, r in result().items() if r["verdict"] == "прошёл"] print(f"\nпрошли фазу A: {ok or '—'}") def selfcheck() -> int: """До покупок: пороги те же, план в потолке, теги уникальны.""" bad = 0 if (S22.PRICE_CAP, S22.CYR_MIN, S22.ECHO_MAX) != ({"translator": 0.02, "editor": 0.06}, 0.5, 0.15): print("[ПРОВАЛ] пороги эксп-22 изменились — состав пака стал бы подгонкой") bad += 1 tags = [tag(m, r, u["uid"]) for m, r, u in plan()] if len(tags) != len(set(tags)): print("[ПРОВАЛ] теги покупок не уникальны — клетки перетрут друг друга") bad += 1 if not all(t.startswith("et-scr-") for t in tags): print("[ПРОВАЛ] тег вне глоба фазы — расход будет невидим кассе") bad += 1 for m in NEW: if m not in ROSTER.THINK_ET: print(f"[ПРОВАЛ] боевая конфигурация размышления не объявлена: {m}") bad += 1 if "kimi-k3" not in SPECIAL: print("[ПРОВАЛ] особый режим kimi-k3 потерян") bad += 1 print(f"скрин: клеток в плане {len(tags)} · провалов {bad}") return bad if __name__ == "__main__": a = sys.argv[1:] or ["--plan"] fn = {"--plan": cmd_plan, "--run": cmd_run, "--score": cmd_score, "--selfcheck": lambda: sys.exit(1 if selfcheck() else 0)}.get(a[0]) if fn: fn() else: print(__doc__)