#!/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/` переписан и заморожен 13–14.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__)