192 lines
9.4 KiB
Python
192 lines
9.4 KiB
Python
#!/usr/bin/env python3
|
||
"""Ф3 — ПАНЕЛЬ РЕДАКТОРОВ поверх ОДНОЙ якорной базы.
|
||
|
||
База — ЗАМОРОЖЕННЫЙ боевой черновик `deepseek-v4-flash` из Ф2 (тот самый, что прошёл гейт эха).
|
||
Все редакторы правят побайтно один текст, поэтому контраст между ними чист от разницы черновиков —
|
||
ровно того конфаунда, который эксп-21 назвал носителем 70% эффекта.
|
||
|
||
Конфигурация редактора одинакова у всех: full-regen боевым `editor.md` + ЗАКОН-БЛОК (D39.104).
|
||
Единственное различие клеток — модель. Это проверяется `--isolation` до покупок.
|
||
|
||
`gemini-3.1-pro-preview` — ОБЯЗАТЕЛЬНЫЙ кандидат промта: он идёт в Ф3 независимо от скрина
|
||
(гипотеза эксп-04 «gemini-pro — аутлаер прозы»), скрин-числа при нём печатаются.
|
||
|
||
Вторая база (лучший черновик Ф2) — ОПЦИЯ промта: берётся только если Ф2 дал отрыв выше пола И
|
||
смета влезает; решение и его основание печатаются `--plan`.
|
||
|
||
Запуск: --plan состав панели, смета, изоляция, $0
|
||
--isolation чем клетки различаются — сверка сообщений, $0
|
||
--run покупка редактур
|
||
--score батарея, канон, цена, $0
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
import json
|
||
import os
|
||
import statistics as st
|
||
import sys
|
||
import time
|
||
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"))
|
||
|
||
from dotenv import load_dotenv # noqa: E402
|
||
from openai import OpenAI # noqa: E402
|
||
|
||
import bank as BANK # noqa: E402
|
||
import battery as B # noqa: E402
|
||
import material as M # noqa: E402
|
||
import money as MONEY # noqa: E402
|
||
import panel as P2 # noqa: E402
|
||
import prompts as PR # noqa: E402
|
||
from roster import CANDIDATES, call_kwargs # noqa: E402
|
||
|
||
load_dotenv(REPO / "eval" / ".env")
|
||
OUT = MONEY.OUT
|
||
|
||
BATTLE = "deepseek-v4-pro" # R0 — боевой редактор, база сравнения
|
||
MANDATORY = "gemini-3.1-pro-preview" # обязательный кандидат промта
|
||
MAX_EDITORS = 5
|
||
|
||
|
||
def client(model: str) -> OpenAI:
|
||
base, env, *_ = CANDIDATES[model]
|
||
return OpenAI(api_key=os.environ[env], base_url=base, timeout=900)
|
||
|
||
|
||
def panel() -> list[str]:
|
||
"""R0 = боевой редактор · затем обязательный кандидат · затем прошедшие скрин по цене.
|
||
|
||
Порядок фиксирован, потому что имена армов (R0, R1, …) впечатываются в ключ судейского
|
||
прохода: сдвиг порядка после выпуска заданий сделал бы семейство неповторимым.
|
||
"""
|
||
res = P2.screen_result()
|
||
out = [BATTLE, MANDATORY]
|
||
ok = [m for m, r in res.items()
|
||
if r["verdict"] == "прошёл" and m not in out and r.get("price_editor") is not None]
|
||
ok.sort(key=lambda m: res[m]["price_editor"])
|
||
return (out + ok)[:MAX_EDITORS]
|
||
|
||
|
||
def base_text(uid: str) -> str:
|
||
"""Якорная база: замороженный боевой черновик Ф2."""
|
||
return P2.draft_text(P2.ANCHOR, uid)
|
||
|
||
|
||
def tag(model: str, uid: str) -> str:
|
||
return f"tp3-ed-{model}-{uid}"
|
||
|
||
|
||
def text_of(arm: str, uid: str) -> str:
|
||
if arm == "F":
|
||
return base_text(uid)
|
||
model = panel()[int(arm[1:])]
|
||
f = OUT / f"{tag(model, uid)}.json"
|
||
return json.loads(f.read_text(encoding="utf-8")).get("content", "") if f.exists() else ""
|
||
|
||
|
||
def msgs_for(model: str, u: dict) -> list[dict]:
|
||
return PR.editor_msgs(u["source"], base_text(u["uid"]),
|
||
PR.law_block(PR.terms_in(u["source"])))
|
||
|
||
|
||
def cmd_isolation() -> None:
|
||
"""Клетки обязаны различаться ТОЛЬКО моделью. Проверяется сборкой сообщений, а не верой."""
|
||
u = M.units_zh()[0]
|
||
ms = {m: msgs_for(m, u) for m in panel()}
|
||
ref = ms[BATTLE]
|
||
bad = 0
|
||
for m, v in ms.items():
|
||
same = [a["content"] == b["content"] for a, b in zip(ref, v, strict=True)]
|
||
ok = all(same) and len(ref) == len(v)
|
||
bad += not ok
|
||
print(f"[{'OK ' if ok else 'ПРОВАЛ'}] {m:26s} сообщения совпадают с базой "
|
||
f"({len(v)} блоков)")
|
||
print(f" закон-блок в сообщениях: {'да' if any('КАНОНИЧЕСКИЕ ПЕРЕВОДЫ' in x['content'] for x in ref) else 'НЕТ'}")
|
||
print(f" база — замороженный черновик {P2.ANCHOR}, {len(base_text(u['uid']))} знаков")
|
||
print(f"\n{'ИЗОЛЯЦИЯ ЧИСТА' if not bad else f'ПРОВАЛОВ: {bad}'}")
|
||
|
||
|
||
def cmd_plan() -> None:
|
||
pan, res, us = panel(), P2.screen_result(), M.units_zh()
|
||
print(f"ПАНЕЛЬ Ф3: {len(pan)} редакторов × {len(us)} единиц поверх ОДНОЙ якорной базы")
|
||
est = 0.0
|
||
for i, m in enumerate(pan):
|
||
r = res.get(m, {})
|
||
price = r.get("price_editor")
|
||
free = " (куплено в Ф2, переиспользуется)" if m == BATTLE else ""
|
||
if m != BATTLE:
|
||
est += (price or 0.02) * len(us)
|
||
print(f" R{i} {m:26s} скрин={r.get('verdict', 'не скринился'):12s} "
|
||
f"$/ед={price if price is not None else float('nan'):.5f}{free}")
|
||
print(f"\nсмета ${est:.4f} при потолке ${MONEY.CEILINGS['Ф3']:.2f}")
|
||
if est > MONEY.CEILINGS["Ф3"]:
|
||
print("⛔ СМЕТА НЕ ВЛЕЗАЕТ — сократить панель или пинг владельцу ДО покупок")
|
||
|
||
|
||
def cmd_run() -> None:
|
||
led = MONEY.Guarded("Ф3", default_expect=0.03)
|
||
for m in panel():
|
||
if m == BATTLE:
|
||
print(f" {m}: клетка куплена в Ф2 (tp2-edit) — не перекупается")
|
||
continue
|
||
for u in M.units_zh():
|
||
if not base_text(u["uid"]):
|
||
print(f" ⚠ {u['uid'][:6]}: якорной базы нет — клетка пропущена")
|
||
continue
|
||
rec = MONEY.purchase(led, tag(m, u["uid"]), m, client(m),
|
||
call_kwargs(m, msgs_for(m, u)),
|
||
role="editor3", uid=u["uid"], editor=m)
|
||
if rec.get("skipped"):
|
||
print("⛔ потолок Ф3 — стоп")
|
||
return
|
||
print(f" {m:26s} {u['uid'][:6]} finish={str(rec.get('finish')):9s} "
|
||
f"{len(rec.get('content', '')):5d} зн ${rec.get('cost_usd', 0):.5f}")
|
||
time.sleep(0.15)
|
||
print(f"потрачено Ф3 ${led.spent():.6f}")
|
||
|
||
|
||
def cmd_score() -> None:
|
||
pan, us, cards = panel(), M.units_zh(), B.load_cards()
|
||
print(f"{'арм':30s}{'ед.':>5s}{'ханьцзы':>9s}{'типогр':>8s}{'канон':>14s}"
|
||
f"{'покрытие':>10s}{'$/ед':>10s}")
|
||
for i, m in enumerate(pan + ["БАЗА"]):
|
||
rows, k, s, costs = [], 0, 0, []
|
||
for u in us:
|
||
t = base_text(u["uid"]) if m == "БАЗА" else text_of(f"R{i}", 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 m == "БАЗА":
|
||
costs.append(P2.draft_cost(P2.ANCHOR, u["uid"]))
|
||
elif m == BATTLE:
|
||
f = OUT / f"{P2.tag_edit(P2.ANCHOR, u['uid'])}.json"
|
||
if f.exists():
|
||
costs.append(json.loads(f.read_text(encoding="utf-8")).get("cost_usd", 0.0))
|
||
else:
|
||
f = OUT / f"{tag(m, u['uid'])}.json"
|
||
if f.exists():
|
||
costs.append(json.loads(f.read_text(encoding="utf-8")).get("cost_usd", 0.0))
|
||
if not rows:
|
||
continue
|
||
label = "БАЗА (черновик)" if m == "БАЗА" else f"R{i} {m}"
|
||
print(f"{label: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"{f'{k}/{s}':>14s}{(k / s if s else 0):>10.3f}"
|
||
f"{(st.median(costs) if costs else 0):>10.5f}")
|
||
led = MONEY.Guarded("Ф3")
|
||
print(f"\nпотрачено Ф3 ${led.spent():.6f} из ${led.ceiling:.2f}")
|
||
|
||
|
||
if __name__ == "__main__":
|
||
a = sys.argv[1:] or ["--plan"]
|
||
fn = {"--plan": cmd_plan, "--isolation": cmd_isolation, "--run": cmd_run,
|
||
"--score": cmd_score}.get(a[0])
|
||
fn() if fn else print(__doc__)
|