Re-freeze phase 2 mid-stage: split units across processes to raise throughput, and read the battle editor cell from phase 2 in the phase 3 rig
This commit is contained in:
parent
a725be5191
commit
af6d885047
4 changed files with 171 additions and 9 deletions
|
|
@ -94,9 +94,14 @@ def tag(model: str, uid: str) -> str:
|
|||
|
||||
|
||||
def text_of(arm: str, uid: str) -> str:
|
||||
"""Текст клетки Ф3. ⚠ R0 (боевой редактор) НЕ покупается заново: его клетка над якорным
|
||||
черновиком уже куплена в Ф2 (`tp2-edit`), и повторная покупка была бы вторым замером той же
|
||||
вещи. Промт это и предписывает: «клетка dspro-над-flash покупается в Ф2 ОДИН раз»."""
|
||||
if arm == "F":
|
||||
return base_text(uid)
|
||||
model = panel()[int(arm[1:])]
|
||||
if model == BATTLE:
|
||||
return P2.edit_text(P2.ANCHOR, uid)
|
||||
f = OUT / f"{tag(model, uid)}.json"
|
||||
return json.loads(f.read_text(encoding="utf-8")).get("content", "") if f.exists() else ""
|
||||
|
||||
|
|
|
|||
|
|
@ -141,12 +141,24 @@ def runs_used() -> list[dict]:
|
|||
|
||||
|
||||
def log_run(pass_: str, tokens: list[str], note: str = "") -> int:
|
||||
"""Запись агент-запуска + карта «токен → судья».
|
||||
|
||||
Карта нужна не для отчётности: без идентичности судьи нельзя ни посчитать согласие судей, ни
|
||||
разложить вердикты по популяциям — эксп-21 не персистировал её и потому не мог этого сделать
|
||||
(найдено его же ревью). Карта лежит рядом с ключом и судье не выдаётся.
|
||||
"""
|
||||
rs = runs_used()
|
||||
rs.append(dict(n=len(rs) + 1, pass_=pass_, tokens=tokens, note=note))
|
||||
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")
|
||||
if len(rs) > CAP:
|
||||
raise SystemExit(f"⛔ КАП АГЕНТ-ЗАПУСКОВ ПРОБИТ: {len(rs)} > {CAP} — стоп и пинг")
|
||||
return len(rs)
|
||||
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 cmd_plan(pass_: str) -> None:
|
||||
|
|
@ -154,6 +166,9 @@ def cmd_plan(pass_: str) -> None:
|
|||
us = units(pass_)
|
||||
amap = arm_map(pass_)
|
||||
print(f"проход {pass_} · единиц {len(us)} · армов {len(amap)}")
|
||||
if pass_.startswith("p2"):
|
||||
print(" (D_i — черновик жильца i; E_i — БОЕВОЙ редактор deepseek-v4-pro поверх ЭТОГО "
|
||||
"черновика,\n то есть буква справа называет жильца ЧЕРНОВОЙ роли, а не редактора)")
|
||||
for a, m in amap.items():
|
||||
print(f" {a:4s} → {m}")
|
||||
print("\nсемейство (впечатывается в ключ ДО ответов):")
|
||||
|
|
|
|||
|
|
@ -94,9 +94,18 @@ def tenants() -> list[str]:
|
|||
return [ANCHOR, *sorted(keep, key=lambda m: res[m]["price_translator"])]
|
||||
|
||||
|
||||
def units_all() -> list[tuple[dict, bool]]:
|
||||
"""(единица, en?) — обе оси обязательны."""
|
||||
return [(u, False) for u in M.units_zh()] + [(u, True) for u in M.units_en()]
|
||||
def units_all(part: str = "") -> list[tuple[dict, bool]]:
|
||||
"""(единица, en?) — обе оси обязательны.
|
||||
|
||||
`part="i/n"` режет СПИСОК единиц на n равных долей для параллельных процессов. Деление
|
||||
детерминированное (по позиции в уже отсортированном списке), поэтому доли не пересекаются и
|
||||
ни одна единица не теряется: это проверяется `--plan`, который печатает сумму долей.
|
||||
"""
|
||||
us = [(u, False) for u in M.units_zh()] + [(u, True) for u in M.units_en()]
|
||||
if not part:
|
||||
return us
|
||||
i, n = (int(x) for x in part.split("/"))
|
||||
return [x for k, x in enumerate(us) if k % n == i]
|
||||
|
||||
|
||||
# ⚠ ПОЛ КЛАДЁТСЯ ОТДЕЛЬНОЙ ПАРОЙ, а не «боевая клетка + повтор». Иначе одна половина пола
|
||||
|
|
@ -187,10 +196,15 @@ def _selected(argv: list[str]) -> list[str]:
|
|||
return [m for m in ts if m in only] if only else ts
|
||||
|
||||
|
||||
def _part(argv: list[str]) -> str:
|
||||
p = [a.split("=", 1)[1] for a in argv if a.startswith("--part=")]
|
||||
return p[0] if p else ""
|
||||
|
||||
|
||||
def cmd_drafts(argv: list[str] | None = None) -> None:
|
||||
led = MONEY.Guarded("Ф2", default_expect=0.01)
|
||||
for m in _selected(argv or []):
|
||||
for u, en in units_all():
|
||||
for u, en in units_all(_part(argv or [])):
|
||||
rec = buy_draft(led, m, u, en)
|
||||
if rec.get("skipped"):
|
||||
print("⛔ потолок Ф2 — стоп")
|
||||
|
|
@ -205,7 +219,7 @@ def cmd_drafts(argv: list[str] | None = None) -> None:
|
|||
def cmd_edit(argv: list[str] | None = None) -> None:
|
||||
led = MONEY.Guarded("Ф2", default_expect=0.01)
|
||||
for m in _selected(argv or []):
|
||||
for u, en in units_all():
|
||||
for u, en in units_all(_part(argv or [])):
|
||||
rec = buy_edit(led, m, u, en)
|
||||
if rec.get("no_draft"):
|
||||
print(f" {m:24s} {u['uid'][:6]} — годного черновика нет, редактура пропущена")
|
||||
|
|
|
|||
128
eval/tenant_panel/verify22.py
Normal file
128
eval/tenant_panel/verify22.py
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
#!/usr/bin/env python3
|
||||
"""ВЕРИФИКАТОР ЧИСЕЛ ОТЧЁТА ЭКСП-22. $0. Ненулевой код возврата = отчёт не сдаётся.
|
||||
|
||||
`itog22.py` пере-считывает числа из сырья; этот файл сверяет их с ТЕКСТОМ отчёта. Разделение
|
||||
нужно потому, что «число посчиталось» и «в отчёте напечатано именно оно» — разные утверждения, и
|
||||
эксп-21 разошёлся с сырьём ровно на этом стыке (§18: ИТОГ печатал расход фазы без одного тега,
|
||||
а гейт это сертифицировал).
|
||||
|
||||
Правило: гейт сторожит НЕСУЩИЕ числа — те, на которых стоит решение владельца. Число, которого
|
||||
гейт не проверяет, в ИТОГе стоять не должно.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
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"))
|
||||
|
||||
import bank as BANK # noqa: E402
|
||||
import material as M # noqa: E402
|
||||
import money as MONEY # noqa: E402
|
||||
import panel as P2 # noqa: E402
|
||||
|
||||
REPORT = REPO / "docs" / "experiments" / "22-tenant-panel.md"
|
||||
BAD = 0
|
||||
|
||||
|
||||
def ck(name: str, ok: bool, detail: str = "") -> None:
|
||||
global BAD # noqa: PLW0603
|
||||
BAD += not ok
|
||||
print(f"[{'OK ' if ok else 'ПРОВАЛ'}] {name}" + (f" {detail}" if detail else ""))
|
||||
|
||||
|
||||
def has(text: str, s: str) -> bool:
|
||||
"""Число ищется по НОРМАЛИЗОВАННОМУ тексту: пробелы и переносы строк не значимы."""
|
||||
return re.sub(r"\s+", "", s) in re.sub(r"\s+", "", text)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
if not REPORT.exists():
|
||||
print("отчёта нет")
|
||||
return 1
|
||||
t = REPORT.read_text(encoding="utf-8")
|
||||
|
||||
print("=== МАТЕРИАЛ ===")
|
||||
zs = M.units_zh()
|
||||
ck("книга и дата первой публикации названы",
|
||||
has(t, M.BOOK["title"]) and has(t, M.BOOK["first_pub"]))
|
||||
ck(f"число zh-единиц {len(zs)}", has(t, f"{len(zs)} единиц") or has(t, f"{len(zs)} zh"))
|
||||
ck("гейт прав объявлен числом снятых глав",
|
||||
has(t, "гейт прав") or has(t, "ГЕЙТ ПРАВ"))
|
||||
b = BANK.PR.bank()
|
||||
tot = sum(sum(u["source"].count(x) for u in zs) for x in b)
|
||||
ck(f"банк {len(b)} терминов", has(t, f"{len(b)} термин"))
|
||||
ck(f"знаменатель покрытия {tot}", has(t, str(tot)))
|
||||
|
||||
print("\n=== КОНТАМИНАЦИЯ ===")
|
||||
f = MONEY.OUT / "contamination.json"
|
||||
if f.exists():
|
||||
for r in json.loads(f.read_text(encoding="utf-8")):
|
||||
ck(f"{r['book']}/{r['model'].split('-')[0]} узнано {r['recognized']}/{r['n']}",
|
||||
has(t, f"{r['recognized']}/{r['n']}"))
|
||||
|
||||
print("\n=== СКРИН Ф1 ===")
|
||||
res = P2.screen_result()
|
||||
for m, r in res.items():
|
||||
if r.get("price_translator") is None:
|
||||
continue
|
||||
ck(f"{m}: цена переводчика {r['price_translator']:.5f}",
|
||||
has(t, f"{r['price_translator']:.5f}"))
|
||||
ck(f"{m}: вердикт {r['verdict']}", has(t, m))
|
||||
|
||||
print("\n=== ПАНЕЛЬ Ф2 ===")
|
||||
for m in P2.tenants():
|
||||
ck(f"жилец {m} назван в отчёте", has(t, m))
|
||||
|
||||
print("\n=== ДЕНЬГИ ===")
|
||||
for ph in ("Ф0", "Ф1", "Ф2", "Ф3"):
|
||||
sp = MONEY.Guarded(ph).spent()
|
||||
if sp <= 0:
|
||||
continue
|
||||
ck(f"{ph}: расход ${sp:.6f} напечатан", has(t, f"{sp:.6f}"))
|
||||
if sp > MONEY.CEILINGS[ph] + 1e-9:
|
||||
ck(f"{ph}: ПРОБОЙ потолка объявлен",
|
||||
has(t, "ПРОБОЙ") or has(t, "пробит") or has(t, "пробой"),
|
||||
f"${sp:.6f} против ${MONEY.CEILINGS[ph]:.2f}")
|
||||
pack = MONEY.Pack().spent()
|
||||
ck(f"расход пака ${pack:.6f} напечатан", has(t, f"{pack:.6f}"))
|
||||
|
||||
print("\n=== СУДЕЙСТВО ===")
|
||||
import judge as JU # noqa: PLC0415
|
||||
rs = JU.runs_used()
|
||||
if rs:
|
||||
ck(f"агент-запусков {len(rs)} из {JU.CAP} напечатано",
|
||||
has(t, f"{len(rs)} из {JU.CAP}") or has(t, f"{len(rs)}/{JU.CAP}"))
|
||||
for pass_ in ("p2zh", "p2en", "p3", "dva"):
|
||||
votes = MONEY.OUT / f"aj-{pass_}-votes"
|
||||
if not votes.exists() or not list(votes.glob("*.json")):
|
||||
continue
|
||||
JU.setup(pass_)
|
||||
import absjudge as AJ # noqa: PLC0415
|
||||
data = AJ._by_unit() # noqa: SLF001
|
||||
for a, bb, _w in JU.family(pass_):
|
||||
m = AJ._margins(a, bb) # noqa: SLF001
|
||||
if len(m) < 3:
|
||||
continue
|
||||
import statistics as sst # noqa: PLC0415
|
||||
ck(f"{pass_}: контраст {a} vs {bb} = {sst.mean(m):+.2f} напечатан",
|
||||
has(t, f"{sst.mean(m):+.2f}") or has(t, f"{sst.mean(m):.2f}"))
|
||||
# контроли обязаны быть напечатаны ДО боевых чисел
|
||||
dec = [d for d in data.values() if "CTRLdecoy" in d]
|
||||
if dec:
|
||||
ck(f"{pass_}: декой упомянут в отчёте", has(t, "декой") or has(t, "ДЕКОЙ"))
|
||||
flo = [d for d in data.values() if "CTRLfloorA" in d and "CTRLfloorB" in d]
|
||||
if flo:
|
||||
ck(f"{pass_}: шумовой пол упомянут", has(t, "пол") or has(t, "ПОЛ"))
|
||||
|
||||
print(f"\n{'ВСЕ ЧИСЛА ОТЧЁТА СХОДЯТСЯ' if not BAD else f'РАСХОЖДЕНИЙ: {BAD} — ОТЧЁТ НЕ СДАЁТСЯ'}")
|
||||
return BAD
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(1 if main() else 0)
|
||||
Loading…
Add table
Reference in a new issue