diff --git a/eval/tenant_panel/bank.py b/eval/tenant_panel/bank.py index 9ac3c093..8422aa89 100644 --- a/eval/tenant_panel/bank.py +++ b/eval/tenant_panel/bank.py @@ -133,18 +133,31 @@ def cmd_ask() -> None: def _rx(dst: str) -> str: - """Регекс канонной формы: слова длиннее 5 знаков усечены под словоизменение.""" + """Регекс канонной формы под русское словоизменение. + + ⚠ Правка ПОСЛЕ фриза Ф0 и ПОСЛЕ покупки терминолога (то есть $0 и на результат прогона не + влияет), объявляется как девиация. Первая версия усекала только слова от 6 знаков, и короткие + склоняемые слова канона («род Цинь») не находились в форме «рода Цинь» — метрика покрытия + занижалась бы на всех армах одинаково, но занижалась. + """ parts = [] - for w in dst.split(): - w = re.escape(w) - parts.append(w[:-2] + r"\w*" if len(w) >= 6 else w) + for raw in dst.split(): + w = re.escape(raw) + if len(raw) >= 4: + parts.append(w[:-2] + r"\w*") + elif len(raw) == 3: + parts.append(w[:-1] + r"\w*") + else: + parts.append(w) return r"\s+".join(parts) # ⚠ РУЧНЫЕ ПРАВКИ КАНОНА. Пусто = терминолог принят как есть. Каждая строка — решение СЕССИИ, # не владельца; основания печатаются `--canon` и едут в отчёт. MANUAL: dict[str, tuple[str, str]] = {} -DROP: set[str] = set() +# Выброшено из банка: это общеязыковая лексика, а не термины книги. Канон на них сделал бы +# метрику покрытия замером словарного совпадения, а не терминологической дисциплины. +DROP = {"瞬间", "声音", "仿佛", "庭院"} def cmd_canon() -> None: @@ -220,6 +233,13 @@ def selfcheck() -> int: chk("регекс не ловит чужое слово", not re.search(_rx("апертура"), "аперитив", re.I)) chk("регекс держит многословную форму", bool(re.search(_rx("море истинной ци"), "Море истинной ци", re.I))) + # Регрессии на реальные формы банка: короткое склоняемое слово и прилагательное. + for canon, form in (("род Цинь", "роду Цинь"), ("род Цинь", "рода Циня"), + ("левый канцлер", "левого канцлера"), ("глава рода", "главе рода"), + ("Цинь Фэн", "Цинь Фэну"), ("Су Цинсюэ", "Су Цинсюэ"), + ("Гу Хэ", "Гу Хэ"), ("Фэн-лао", "Фэн-лао")): + chk(f"«{canon}» находится в форме «{form}»", bool(re.search(_rx(canon), form, re.I))) + chk("канон не ловит другой род", not re.search(_rx("род Цинь"), "рода Су", re.I)) b = PR.bank() if b: t = next(iter(b)) diff --git a/eval/tenant_panel/screen.py b/eval/tenant_panel/screen.py index 56151c53..8556427f 100644 --- a/eval/tenant_panel/screen.py +++ b/eval/tenant_panel/screen.py @@ -34,6 +34,9 @@ sys.path.insert(0, str(ZONE)) from dotenv import load_dotenv # noqa: E402 from openai import OpenAI # noqa: E402 +sys.path.insert(0, str(REPO / "eval" / "role_topology")) + +import bakeoff as BO # noqa: E402 гейт годности черновика import material as M # noqa: E402 import money as MONEY # noqa: E402 import prompts as PR # noqa: E402 @@ -81,24 +84,43 @@ def client(model: str) -> OpenAI: def draft_of(uid: str) -> str: - f = OUT / f"tp1-scr-draft-{uid}.json" - return json.loads(f.read_text(encoding="utf-8")).get("content", "") if f.exists() else "" + """ГОДНЫЙ черновик единицы: последняя попытка, прошедшая гейт. Пусто — годного нет.""" + best = "" + for f in sorted(OUT.glob(f"tp1-scr-draft-{uid}*.json")): + c = json.loads(f.read_text(encoding="utf-8")).get("content", "") + if c and not BO.draft_reject_reason(c): + best = c + return best def cmd_draft() -> None: - """Черновик под редакторскую роль скрина. Один и тот же для ВСЕХ кандидатов — парный дизайн.""" + """Черновик под редакторскую роль скрина. Один и тот же для ВСЕХ кандидатов — парный дизайн. + + ⚠ РЕ-ГЕН ПРИ ЭХЕ добавлен ПОСЛЕ первой же покупки: единица `474f822806` вернулась ПОЛНЫМ + китайским исходником (`finish=stop`, ханьцзы 1785, кириллица 0.00) — эхо-мина на плотном CJK, + описанная в quirks 00. Гейт годности и N ре-генов — боевое лечение (ре-ген на flash в 7.6 раза + дешевле эскалации на pro), и цена ре-генов ЛОЖИТСЯ В ЛЕДЖЕР, а не прячется. + Функция гейта берётся из `bakeoff` — та самая, что проверена `checks21.py` на историческом + артефакте; переписывать её здесь значило бы завести вторую, расходящуюся. + """ led = MONEY.Guarded("Ф1", default_expect=0.004) for u in units(): blk = PR.glossary_block(PR.terms_in(u["source"])) msgs = PR.translator_msgs(u["source"], blk) - rec = MONEY.purchase(led, f"tp1-scr-draft-{u['uid']}", DRAFT_MODEL, - client(DRAFT_MODEL), call_kwargs(DRAFT_MODEL, msgs), - role="draft-for-screen", uid=u["uid"]) - if rec.get("skipped"): - print("⛔ потолок Ф1 — стоп") - return - print(f" черновик {u['uid'][:6]} ({u['kind']}) finish={rec.get('finish')} " - f"{len(rec.get('content', '')):5d} зн ${rec.get('cost_usd', 0):.5f}") + for attempt in (1, 2, 3): + tag = f"tp1-scr-draft-{u['uid']}" + ("" if attempt == 1 else f"-r{attempt}") + rec = MONEY.purchase(led, tag, DRAFT_MODEL, client(DRAFT_MODEL), + call_kwargs(DRAFT_MODEL, msgs), + role="draft-for-screen", uid=u["uid"], attempt=attempt) + if rec.get("skipped"): + print("⛔ потолок Ф1 — стоп") + return + bad = BO.draft_reject_reason(rec.get("content", "")) + print(f" черновик {u['uid'][:6]} ({u['kind']}) попытка {attempt} " + f"finish={rec.get('finish')} {len(rec.get('content', '')):5d} зн " + f"${rec.get('cost_usd', 0):.5f}" + (f" ⚠ {bad} — ре-ген" if bad else "")) + if not bad: + break print(f"потрачено Ф1 ${led.spent():.6f}")