diff --git a/eval/editor_tier/judge.py b/eval/editor_tier/judge.py new file mode 100644 index 00000000..d4279269 --- /dev/null +++ b/eval/editor_tier/judge.py @@ -0,0 +1,173 @@ +#!/usr/bin/env python3 +"""СУДЕЙСТВО ПАКА «СИЛЬНЫЙ ТИР». $0 сам по себе; расходует агент-запуски (кап 60). + +Риг не пере-изобретается: `absjudge` эксп-21 настраивается через `configure`, как это делал +эксп-22. Пак-специфика здесь — только два прохода и их армы. + +ПРОХОД `tier` — панель сильного тира: T1..Tn против R0 над ТОЙ ЖЕ якорной базой, 16 единиц + эксп-22. Отвечает: остаётся ли `deepseek-v4-pro` рекомендацией при полной панели. +ПРОХОД `border`— граница `glm-5`: R0 против R2 на 32 единицах (16 эксп-22 + 16 новых). Отвечает: + разводится ли контраст −2.12 от порога при удвоении n. + +⚠ Порог различимости фиксируется ОДИН — шумовой пол СВОЕГО прохода. Двусмысленность эксп-22, где +пред-объявленный (2.03) и фактический (2.47) пороги дали для `glm-5` разный ответ, закрыта здесь +заранее, а не после замера. +""" +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")) + +import absjudge as AJ # noqa: E402 + + +def _load(name: str, path: Path): + 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 + + +PAN = _load("panel_et", ZONE / "panel.py") +MONEY, MAT, P22 = PAN.MONEY, PAN.MAT, PAN.P22 +OUT = MONEY.OUT +KEYS = OUT / "blind-keys" +RUNS = OUT / "agent-runs.json" +CAP = 60 +PASSES = ("tier", "border") + + +def arm_map(pass_: str) -> dict[str, str]: + if pass_ == "tier": + return {"R0": PAN.BATTLE, **{f"T{i}": m for i, m in enumerate(PAN.survivors(), start=1)}} + return {"R0": PAN.BATTLE, "R2": PAN.BORDER} + + +def arms(pass_: str) -> tuple[str, ...]: + return tuple(arm_map(pass_)) + + +def family(pass_: str) -> list[tuple[str, str, str]]: + """Семейство объявляется ДО выпуска и впечатывается в ключ.""" + if pass_ == "tier": + return [(a, "R0", f"редактор сильного тира {a} против боевого над ТОЙ ЖЕ базой") + for a in arms(pass_) if a != "R0"] + return [("R2", "R0", "пограничный glm-5 против боевого, n удвоено до 32")] + + +def units(pass_: str) -> list[dict]: + """Единицы прохода. У `border` их 32: 16 эксп-22 плюс 16 новых этого пака.""" + if pass_ == "tier": + return P22.M.units_zh() + return P22.M.units_zh() + MAT.units_new() + + +def text_of(pass_: str): + new_uids = {u["uid"] for u in MAT.units_new()} + + def hook(arm: str, u: dict) -> str: + if pass_ == "border": + # единица новая → тексты фазы B; единица эксп-22 → его же клетки + if u["uid"] in new_uids: + return PAN.text_b(arm, u["uid"]) + return P22.edit_text(PAN.BATTLE if arm == "R0" else PAN.BORDER, u["uid"]) \ + if arm == "R0" else _ed22(u["uid"]) + return PAN.text_c(arm, u["uid"]) + return hook + + +def _ed22(uid: str) -> str: + """Клетка R2 эксп-22 — это `glm-5` над якорной базой в его фазе Ф3.""" + import editors as ED # noqa: PLC0415 + return ED.text_of("R2", uid) + + +def floor_of(pass_: str): + """Пол СВОЕГО прохода — две независимые генерации самого дешёвого арма панели. + + ⚠ Для `border` пол берётся ОТТУДА ЖЕ и это объявлено: армы обоих проходов — редакторы над + одной якорной базой, судит одна и та же популяция, поэтому шум одного вызова у них общий. + Покупать второй такой же пол значило бы платить за то же число дважды. + """ + def hook(u: dict): + return PAN.floor_pair(u["uid"]) + return hook + + +def decoy_base(slots: dict) -> str: + for a in ("R0", "T1", "R2"): + if a in slots and (slots[a] or "").strip(): + return a + return next(iter(slots)) + + +def setup(pass_: str) -> None: + AJ.configure(OUT=OUT, KEYS=KEYS, PASS=pass_, ARMS=arms(pass_), ARMS_2B=(), + FAMILY=family(pass_), TEXT_HOOK=text_of(pass_), + UNITS_HOOK=lambda: units(pass_), FLOOR_HOOK=floor_of(pass_), + DECOY_HOOK=decoy_base, SRC_LANG="китайском") + KEYS.mkdir(parents=True, exist_ok=True) + + +def runs_used() -> list[dict]: + return json.loads(RUNS.read_text(encoding="utf-8")) if RUNS.exists() else [] + + +def log_run(pass_: str, tokens: list[str], note: str = "") -> int: + rs = runs_used() + n = len(rs) + 1 + rs.append(dict(n=n, pass_=pass_, tokens=tokens, note=note)) + RUNS.write_text(json.dumps(rs, ensure_ascii=False, indent=1), encoding="utf-8") + jf = KEYS / f"{pass_}-JUDGES.json" + m = json.loads(jf.read_text(encoding="utf-8")) if jf.exists() else {} + for t in tokens: + m[t] = f"agent-{n:02d}" + jf.write_text(json.dumps(m, ensure_ascii=False, indent=1), encoding="utf-8") + if n > CAP: + raise SystemExit(f"⛔ КАП АГЕНТ-ЗАПУСКОВ ПРОБИТ: {n} > {CAP}") + return n + + +def per_run(pass_: str) -> int: + return max(1, 20 // max(1, len(arm_map(pass_)) + 3)) + + +def cmd_plan(pass_: str) -> None: + setup(pass_) + us, amap = units(pass_), arm_map(pass_) + print(f"проход {pass_} · единиц {len(us)} · армов {len(amap)}") + for a, m in amap.items(): + print(f" {a:4s} → {m}") + print("семейство (в ключ ДО ответов):") + for a, b, w in family(pass_): + print(f" {a:4s} vs {b:4s} {w}") + have = sum(1 for u in us for a in amap if (text_of(pass_)(a, u) or "").strip()) + print(f"текстов готово {have} из {len(us) * len(amap)} · " + f"агент-запусков {len(runs_used())} из {CAP}") + + +if __name__ == "__main__": + a = sys.argv[1:] or ["--plan", "tier"] + if a[0] == "--plan": + cmd_plan(a[1] if len(a) > 1 else "tier") + elif a[0] == "--emit": + setup(a[1]) + AJ.emit() + elif a[0] == "--ingest": + setup(a[1]) + AJ.ingest() + elif a[0] == "--score": + setup(a[1]) + AJ.controls() + print() + AJ.score() + else: + print(__doc__) diff --git a/eval/editor_tier/money.py b/eval/editor_tier/money.py index f5515dff..982ecf06 100644 --- a/eval/editor_tier/money.py +++ b/eval/editor_tier/money.py @@ -47,9 +47,13 @@ OUT = Path.home() / "books" / "editor-tier" TAGS = { "ФA": ("et-scr-*.json",), # скрин сильного тира "ФB": ("et-unit-*.json",), # 16 новых единиц для границы glm-5 (черновик + R0 + R2) - "ФC": ("et-ed-*.json",), # панель редакторов поверх якорной базы эксп-22 + "ФC": ("et-ed-*.json", "et-floor-*.json"), # панель + ШУМОВОЙ ПОЛ своего прохода } -CEILINGS = {"ФA": 1.10, "ФB": 0.45, "ФC": 1.80} +# ⚠ Потолок ФC поднят $1.80 → $2.30 из ОБЪЯВЛЕННОГО В ПЛАНЕ резерва пака ($0.65), объявляется до +# покупок фазы. Две причины, обе замерные: пре-рег-критерий пропустил ТРОИХ (панель $1.844 вместо +# сметы на двоих) и плану нужен СВОЙ шумовой пол — вторая независимая генерация самого дешёвого +# арма панели, ещё $0.29. Пакетный потолок $4.00 (санкция владельца) НЕ двигается: смета пака $2.93. +CEILINGS = {"ФA": 1.10, "ФB": 0.45, "ФC": 2.30} PACK_CEILING = 4.00 M22.configure(ZONE=ZONE, OUT=OUT, TAGS=TAGS, CEILINGS=CEILINGS, diff --git a/eval/editor_tier/panel.py b/eval/editor_tier/panel.py index 9c9ebec1..6207f1a0 100644 --- a/eval/editor_tier/panel.py +++ b/eval/editor_tier/panel.py @@ -9,11 +9,12 @@ эксп-22 на его 16 единицах. База уже куплена: пере-генерация запрещена — это был бы второй замер той же вещи, а не новый арм. Клетка R0 над этой базой тоже куплена эксп-22 и переиспользуется. -⚠ ПОТОЛОК ФАЗЫ C ПОДНЯТ $1.80 → $1.95 ИЗ ОБЪЯВЛЕННОГО РЕЗЕРВА ПАКА, объявляю здесь до покупок. -Причина — замер, а не желание: пре-рег-критерий пропустил ТРОИХ, и по замеренным ценам фазы A -панель стоит $1.85. Резерв $0.65 объявлен в плане ровно на такие случаи, пакетный потолок $4.00 -(санкция владельца) НЕ двигается, суммарная смета пака $2.78. Фазовый потолок — моя разбивка -внутри санкции; тихо его пробить было бы нечестно, тихо срезать панель — тем более. +⚠ ПОТОЛОК ФАЗЫ C ПОДНЯТ $1.80 → $2.30 ИЗ ОБЪЯВЛЕННОГО РЕЗЕРВА ПАКА (см. `money.CEILINGS`), +объявляется ДО покупок фазы. Две причины, обе замерные: пре-рег-критерий пропустил ТРОИХ, и +панель по замеренным ценам фазы A стоит $1.844 вместо сметы на двоих; плюс плану нужен СВОЙ +шумовой пол — вторая независимая генерация самого дешёвого арма, ещё $0.29. Пакетный потолок +$4.00 (санкция владельца) НЕ двигается: смета пака $2.93. Фазовый потолок — моя разбивка внутри +санкции; тихо пробить его было бы нечестно, тихо срезать панель — тем более. """ from __future__ import annotations @@ -48,8 +49,7 @@ ANCHOR = "deepseek-v4-flash" # черновик боевой пары BATTLE = "deepseek-v4-pro" # R0 BORDER = "glm-5" # R2 — пограничный контраст эксп-22 -MONEY.M22.configure(CEILINGS={**MONEY.CEILINGS, "ФC": 1.95}) -MONEY.CEILINGS["ФC"] = 1.95 +FLOOR_ARM = None # заполняется survivors()[0] — самый дешёвый прошедший def survivors() -> list[str]: @@ -152,6 +152,42 @@ def cmd_c_run() -> None: MONEY.report() +def tag_floor(uid: str, gen: int) -> str: + return f"et-floor-p{gen}-{survivors()[0]}-{uid}" + + +def cmd_floor_run() -> None: + """ШУМОВОЙ ПОЛ прохода: ДВЕ независимые генерации ОДНОГО арма на одном входе. + + Истинная разница между ними — ноль, поэтому разброс их перевесов и есть шум одного вызова. + Первая генерация уже куплена фазой C (клетка панели), докупается только вторая: пол обязан + быть СВОИМ для прохода, а не заимствованным у эксп-22 — там другая панель и другой прайс. + """ + m = survivors()[0] + led = MONEY.Guarded("ФC") + for u in P22.M.units_zh(): + base = P22.draft_text(ANCHOR, u["uid"]) + if not base.strip(): + continue + t = tag_floor(u["uid"], 2) + if (MONEY.OUT / f"{t}.json").exists(): + continue + law = PR.law_block(PR.terms_in(u["source"])) + MONEY.purchase(led, t, m, SCR.client(m), + ROSTER.call_kwargs(m, PR.editor_msgs(u["source"], base, law)), + role="floor2", uid=u["uid"]) + MONEY.report() + + +def floor_pair(uid: str): + """(генерация 1, генерация 2) самого дешёвого арма панели.""" + import json # noqa: PLC0415 + a = text_c("T1", uid) + f = MONEY.OUT / f"{tag_floor(uid, 2)}.json" + b = (json.loads(f.read_text(encoding="utf-8")).get("content") or "") if f.exists() else "" + return (a, b) if a.strip() and b.strip() else None + + def text_c(arm: str, uid: str) -> str: """Тексты армов Ф C. R0 — клетка эксп-22, переиспользуется без покупки.""" import json # noqa: PLC0415 @@ -192,7 +228,7 @@ def selfcheck() -> int: if __name__ == "__main__": a = sys.argv[1:] or ["--selfcheck"] fn = {"--b-plan": cmd_b_plan, "--b-run": cmd_b_run, "--c-plan": cmd_c_plan, - "--c-run": cmd_c_run, + "--c-run": cmd_c_run, "--floor-run": cmd_floor_run, "--selfcheck": lambda: sys.exit(1 if selfcheck() else 0)}.get(a[0]) if fn: fn()