textmachine/eval/dovodka/en_axis.py

279 lines
16 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""Д3 — НЕСУЩАЯ ОСЬ en→ru на 16 единицах Кристоффа. Платный.
Зачем отдельным файлом. Механизмы контуров не дублируются: покупку ведёт `contour.cmd_run`,
переведённый на пару вызовом `contour.configure`. Здесь живёт только то, что у английской оси
СВОЁ, — базы и их источник.
⚠ ПОЧЕМУ БАЗЫ ПОКУПАЮТСЯ ЗАНОВО, А НЕ БЕРУТСЯ ИЗ ЭКСП-21. В `~/books/role-topology` лежат 75
оплаченных английских клеток (15 единиц × DRAFT/A/B/D/D_), и соблазн взять их базой велик. Нельзя:
они куплены 10.08, а пар-пакет `prompts/en-ru/` переписан и заморожен 1314.08 ДО покупок фазы Д.
Взяв старые клетки, мы получили бы арм `E0` на старых промтах и контуры поверх него на новых —
разница армов мерила бы смену промтов, а не топологию. Это конфаундер эксп-19 в чистом виде.
Старое сырьё остаётся сырьём эксп-21 и здесь не читается вообще.
Имена армов — из пре-регистрации (`23-editor-tier.md`, Д0.5), механизмы — общие с китайской осью:
D0 черновик (`deepseek-v4-flash`) база, не арм
E0 боевая связка: черновик + боевой редактор эталон оси
E1 черновик + детерминированные флаги → фиксер вид «flags»
E2 однопроходка уравненного мандата вид «onepass»
E3 черновик + смысловой критик → фиксер вид «critic»
E4 E0 + канон-фиксер ПОСЛЕ вид «canonfix»
E6 ВТОРОЙ редактор (`glm-5`) поверх той же базы касса ФД-G
Первичный контраст — `E0/D0` (H-3: покупает ли редактор что-нибудь поверх черновика). Он и
покупается первым: если денег не хватит на всю ось, несущая гипотеза всё равно будет закрыта,
а описательные армы объявлены некупленными — вслух, а не молчанием.
Запуск: en_axis.py --dry смета и что уже куплено, $0
en_axis.py --bases купить D0 и E0 (первичный контраст)
en_axis.py --arms купить E1 E2 E3 E4
en_axis.py --e6 купить E6 (касса ФД-G)
en_axis.py --selftest
"""
from __future__ import annotations
import importlib.util
import json
import sys
from pathlib import Path
REPO = Path("/home/ubuntu/projects/textmachine")
ZONE = Path(__file__).resolve().parent
for d in ("dovodka", "editor_tier", "tenant_panel", "role_topology", "bank_arbitration"):
sys.path.insert(0, str(REPO / "eval" / d))
def _load(name: str, path: Path):
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
C = _load("contour_en", ZONE / "contour.py")
PE = _load("pair_en_ax", REPO / "eval" / "role_topology" / "pair_en.py")
MONEY, PR, ROSTER, ED22, PARA = C.MONEY, C.PR, C.ROSTER, C.ED22, C.PARA
N_PER_STRATUM = 8 # 16 единиц — ровно то, что объявила мощность оси
DRAFT_MODEL = "deepseek-v4-flash" # тот же жилец-черновик, что на китайской оси
EDITOR = "deepseek-v4-pro" # боевой редактор
EDITOR2 = "glm-5" # второй редактор, H-4
# ⚠ Английские единицы длиннее китайских (1600 знаков против 1100), и перевод длиннее — умолчания
# рига 16000 может не хватить так же, как не хватило японской оси (3 пустых клетки из 27).
MAX_OUT = 32000
def units() -> list[dict]:
"""16 приколотых единиц. Манифест обязателен: чтение никогда не переотбирает."""
return PE.units(N_PER_STRATUM)
def text_of(tg: str) -> str:
f = MONEY.OUT / f"{tg}.json"
if not f.exists():
return ""
return json.loads(f.read_text(encoding="utf-8")).get("content") or ""
def base_draft(uid: str) -> str:
"""D0 — якорный черновик оси. ⚠ Гейт годности тот же, что на китайской ветке."""
t = text_of(f"dv-b-d0-{uid}")
if not t.strip():
return ""
why = C.draft_defect(t)
if why:
print(f"{uid}: черновик БРАКОВАН гейтом проекта ({why}) — единица непригодна")
return ""
return t
def base_e0(uid: str) -> str:
"""E0 — боевая связка, эталон оси (роль A0 китайской)."""
return text_of(f"dv-b-e0-{uid}")
def wire() -> None:
"""Единственная точка включения пары. Всё остальное — механизмы контура."""
C.configure(PARA.EN, units_provider=units,
base_draft_hook=base_draft, base_a0_hook=base_e0, max_out=MAX_OUT)
def _law(src: str) -> str | None:
bf = PARA.EN.bank_file
return PR.law_block(PR.terms_in(src, bf), bf)
def cmd_bases() -> None:
"""D0 и E0 — первичный контраст оси. Покупаются ПЕРВЫМИ."""
wire()
led = MONEY.Guarded("ФД-B", default_expect=0.006)
for u in units():
uid, src = u["uid"], u["source"]
tg = f"dv-b-d0-{uid}"
if not text_of(tg).strip():
rec = MONEY.purchase(led, tg, DRAFT_MODEL, ED22.client(DRAFT_MODEL),
ROSTER.call_kwargs(DRAFT_MODEL,
PR.translator_msgs(src, None, pair="en-ru")),
role="translator", uid=uid, arm="D0")
if rec.get("skipped"):
raise SystemExit(f"⛔ ГАРД ОТКАЗАЛ на {tg} — СТОП и вопрос владельцу")
for u in units():
uid, src = u["uid"], u["source"]
draft = base_draft(uid)
if not draft.strip():
print(f" {uid}: черновика нет — E0 пропущен")
continue
tg = f"dv-b-e0-{uid}"
if text_of(tg).strip():
continue
rec = MONEY.purchase(led, tg, EDITOR, ED22.client(EDITOR),
ROSTER.call_kwargs(EDITOR,
PR.editor_msgs(src, draft, _law(src),
pair="en-ru")),
role="editor", uid=uid, arm="E0")
if rec.get("skipped"):
raise SystemExit(f"⛔ ГАРД ОТКАЗАЛ на {tg} — СТОП и вопрос владельцу")
MONEY.report()
def cmd_e6() -> None:
"""E6 — ВТОРОЙ редактор поверх той же базы. Касса ФД-G, а не ФД-B."""
wire()
led = MONEY.Guarded("ФД-G", default_expect=0.013)
for u in units():
uid, src = u["uid"], u["source"]
draft = base_draft(uid)
if not draft.strip():
continue
tg = f"dv-g-e6-{uid}"
if text_of(tg).strip():
continue
rec = MONEY.purchase(led, tg, EDITOR2, ED22.client(EDITOR2),
ROSTER.call_kwargs(EDITOR2,
PR.editor_msgs(src, draft, _law(src),
pair="en-ru")),
role="editor3", uid=uid, arm="E6")
if rec.get("skipped"):
raise SystemExit(f"⛔ ГАРД ОТКАЗАЛ на {tg} — СТОП и вопрос владельцу")
MONEY.report()
def cmd_arms() -> None:
"""Армы контуров. Список можно сузить: `--arms E1 E2 E4`.
⚠ Сужение существует не для удобства, а против ЧАСТИЧНОГО арма. Когда остатка кассы не
хватает на весь арм, гард отказывает ПОСРЕДИ него, и на судейство приходит выборка меньше
объявленной — контрасты перестают быть парными, а выпадают ровно те единицы, до которых
покупка не дошла. Лучше не начинать арм, чем оборвать его: некупленный арм объявляется
вслух, оборванный выглядит купленным.
"""
wire()
which = [a for a in sys.argv[2:] if a.startswith("E")] or ["E1", "E2", "E3", "E4"]
print(f"армы к покупке: {' '.join(which)}")
C.cmd_run(which)
def cmd_floor() -> None:
"""Свой шумовой пол оси — парой независимых генераций одного лечения.
⚠ Без него у оси нет ПОРОГА: судейская разница между армами не с чем сравнить, и любой
результат читается как значимый. Пол берётся из мест смыслового критика этой же оси, обе
половины — отдельные генерации, и судят их РАЗНЫЕ сессии (забота судейского рига).
"""
wire()
C.cmd_floor()
def cmd_refill() -> None:
"""Карантин пустых клеток оси и повторная покупка с поднятым бюджетом вывода."""
wire()
tags = ([f"dv-b-{a}-{u['uid']}" for u in units() for a in ("d0", "e0")]
+ [C.tag(a, u["uid"]) for u in units() for a in ("E1", "E2", "E3", "E4")]
+ [C.tag("E3", u["uid"], "crit") for u in units()]
+ [C.tag("E6", u["uid"]) for u in units()])
n = C.quarantine_length(tags)
print(f"в карантин ушло клеток: {n}")
def cmd_dry() -> None:
"""Смета и наличие. $0. Цены — измеренные медианы фазы, а не догадки."""
wire()
us = units()
have = {a: sum(1 for u in us if text_of(C.tag(a, u["uid"])).strip())
for a in ("E1", "E2", "E3", "E4", "E6")}
have["D0"] = sum(1 for u in us if text_of(f"dv-b-d0-{u['uid']}").strip())
have["E0"] = sum(1 for u in us if text_of(f"dv-b-e0-{u['uid']}").strip())
price = {"D0": 0.0020, "E0": 0.00523, "E1": 0.00314, "E2": 0.00523,
"E3": 0.01120 + 0.00314, "E4": 0.00314, "E6": 0.01249}
what = {"D0": "черновик (база, не арм)", "E0": "боевая связка — ЭТАЛОН оси",
"E1": "флаги → фиксер", "E2": "однопроходка уравненного мандата",
"E3": "смысловой критик → фиксер", "E4": "E0 + канон-фиксер",
"E6": "второй редактор glm-5 (касса ФД-G)"}
print(f"Д3 en→ru · единиц {len(us)} · банк {len(PR.bank(PARA.EN.bank_file))} терминов\n")
print(f" {'арм':4s}{'есть':>6s}{'надо':>6s}{'$/клетка':>10s}{'смета':>9s} что это")
tot_b = tot_g = 0.0
for a in ("D0", "E0", "E1", "E2", "E3", "E4", "E6"):
need = len(us) - have[a]
cost = need * price[a]
if a == "E6":
tot_g += cost
else:
tot_b += cost
print(f" {a:4s}{have[a]:6d}{need:6d}{price[a]:10.5f}{cost:9.4f} {what[a]}")
for ph, need in (("ФД-B", tot_b), ("ФД-G", tot_g)):
sp = MONEY.Guarded(ph).spent()
ceil = MONEY.CEILINGS[ph]
left = ceil - sp
mark = "ВЛЕЗАЕТ" if need <= left else f"НЕ ВЛЕЗАЕТ, не хватает ${need - left:.4f}"
print(f"\n {ph}: потрачено ${sp:.4f} · потолок ${ceil:.2f} · остаток ${left:.4f} · "
f"нужно ${need:.4f}{mark}")
print("\n⚠ Цены — медианы, снятые на китайских единицах (~1100 знаков). Английские длиннее "
"(~1600), поэтому фактическая цена выше сметы; порядок покупки это учитывает: "
"первичный контраст E0/D0 берётся ПЕРВЫМ.")
def cmd_selftest() -> int:
bad = 0
def ck(n: str, ok: bool, d: str = "") -> None:
nonlocal bad
bad += not ok
print(f"[{'OK ' if ok else 'ПРОВАЛ'}] {n}" + (f" {d}" if d else ""))
wire()
us = units()
ck("единиц ровно 16 — как объявила мощность оси", len(us) == 16, f"их {len(us)}")
ck("страты равны (французский слой против без него)",
sum(1 for u in us if u["stratum"] == "fr") == 8)
ck("пара включена целиком", C.pair().key == "en-ru")
ck("теги оси попадают в глоб кассы ФД-B",
all(C.tag(a, "u").startswith("dv-b-") for a in ("E1", "E2", "E3", "E4")))
ck("E6 идёт в ФД-G, а не в ФД-B",
C.tag("E6", "u").startswith("dv-g-") and C.phase_of("E6") == "ФД-G")
ck("банк пары непуст (иначе канон-ось пуста и E4 неотличим от E0)",
len(PR.bank(PARA.EN.bank_file)) > 0)
# ⚠ Старое сырьё эксп-21 НЕ должно подхватываться: оно на других промтах.
old = list((Path.home() / "books" / "role-topology").glob("en2-*.json"))
ck("сырьё эксп-21 существует, но осью НЕ читается", bool(old)
and not any(str(MONEY.OUT) in str(f) for f in old), f"клеток эксп-21 {len(old)}")
ck("базы берутся из клеток ФАЗЫ", C.BASE_DRAFT_HOOK is base_draft
and C.BASE_A0_HOOK is base_e0)
got = [(a, u["uid"]) for u in us for a in ("E1", "E2", "E4")
if (MONEY.OUT / f"{C.tag(a, u['uid'])}.json").exists()]
empty = [f"{a}-{uid}" for a, uid in got if not text_of(C.tag(a, uid)).strip()]
empty += [f"D0-{u['uid']}" for u in us if not text_of(f"dv-b-d0-{u['uid']}").strip()]
empty += [f"E0-{u['uid']}" for u in us if not text_of(f"dv-b-e0-{u['uid']}").strip()]
ck("купленные клетки НЕПУСТЫ (файл ≠ годное содержимое)", not empty,
f"пустых {len(empty)}: {empty[:3]}")
print(f"\n{'ОСЬ ГОТОВА К ПОКУПКЕ' if not bad else f'ПРОВАЛОВ: {bad}'}")
return bad
if __name__ == "__main__":
a = sys.argv[1] if len(sys.argv) > 1 else "--dry"
fn = {"--dry": cmd_dry, "--bases": cmd_bases, "--arms": cmd_arms, "--floor": cmd_floor, "--refill": cmd_refill, "--e6": cmd_e6,
"--selftest": lambda: sys.exit(1 if cmd_selftest() else 0)}.get(a)
fn() if fn else print(__doc__)