384 lines
23 KiB
Python
384 lines
23 KiB
Python
#!/usr/bin/env python3
|
||
"""ИТОГ ЭКСПА ОДНОЙ КОМАНДОЙ. Пере-считывает КАЖДОЕ число блока «ИТОГ» отчёта из сырья. $0.
|
||
|
||
ЗАЧЕМ ЗАВЕДЁН. Два независимых аудита 08.08 (снизу вверх и сверху вниз) сошлись на одном дефекте
|
||
оснастки: несущие числа отчёта — нога 2×2, контрасты Ф2б, покрытие канона, отборщики арма G —
|
||
**в дереве пути воспроизведения не имели**. `absjudge --score` печатает семейство Ф2а и на узком
|
||
проходе 4 выдаёт пустую таблицу (в `FAMILY` нет контрастов C2/E2); `verify_report` сторожил
|
||
таблицы §2 второй редакции, деньги и батарею — и ни одного числа ИТОГа. То есть решающий слой
|
||
отчёта стоял без стража, а часть чисел бралась из stdout прогонов, утраченных инцидентом 08.08.
|
||
|
||
Это ровно тот класс дефекта, за который снята первая редакция, только для чисел, а не для денег:
|
||
число, которое нельзя пере-снять командой из дерева, ничем не отличается от числа по памяти.
|
||
|
||
ЧТО ЗДЕСЬ НЕ ДЕЛАЕТСЯ. Ничего не покупается и не судится: только счёт по персистированным голосам
|
||
и артефактам. Каждый блок печатает провенанс — из какого прохода и по какой формуле взято, — и
|
||
явно метит числа, которые в дереве не выводятся ни одним способом.
|
||
|
||
Запуск: eval/.venv/bin/python eval/role_topology/itog.py
|
||
"""
|
||
from __future__ import annotations
|
||
import json
|
||
import re
|
||
import statistics as st
|
||
import sys
|
||
from bisect import bisect_left, bisect_right
|
||
from itertools import combinations
|
||
from pathlib import Path
|
||
|
||
REPO = Path("/home/ubuntu/projects/textmachine")
|
||
sys.path.insert(0, str(REPO / "eval" / "role_topology"))
|
||
sys.path.insert(0, str(REPO / "eval" / "editor_harness"))
|
||
|
||
import absjudge as AJ # noqa: E402
|
||
import bakeoff as BO # noqa: E402
|
||
import guarded as G # noqa: E402
|
||
import phase2b as P2 # noqa: E402
|
||
|
||
OUT = Path.home() / "books" / "role-topology"
|
||
|
||
|
||
def _E(d: dict, u: str, k: str) -> float:
|
||
return d[u][k]["errors"]
|
||
|
||
|
||
def _passes() -> tuple[dict, dict, list[str]]:
|
||
a1, a2 = AJ._by_unit_pass("abs"), AJ._by_unit_pass("abs2")
|
||
return a1, a2, sorted(set(a1) & set(a2))
|
||
|
||
|
||
def _pooled(a1: dict, a2: dict, common: list[str], a: str, b: str) -> list[float]:
|
||
"""Перевес за арм `a` в пуле ДВУХ проходов: сначала среднее по проходам ВНУТРИ единицы.
|
||
|
||
⚠ Именно так, а не «средние по проходам вычесть»: пул усредняет перевес поединично, поэтому
|
||
разница СТРОГОСТИ панелей сокращается вычитанием внутри единицы и в контраст не попадает.
|
||
Уровни между панелями несопоставимы (abs 6.90 → abs2 7.92 → abs3 8.46 → abs4 9.22 ошибки на
|
||
клетку), сопоставимы только разности — на этом стоит весь ИТОГ.
|
||
"""
|
||
return [st.mean([_E(a1, u, b) - _E(a1, u, a), _E(a2, u, b) - _E(a2, u, a)])
|
||
for u in common
|
||
if a in a1[u] and b in a1[u] and a in a2[u] and b in a2[u]]
|
||
|
||
|
||
def _holm(ps: list[float]) -> list[float]:
|
||
order = sorted(range(len(ps)), key=lambda i: ps[i])
|
||
out, run = [0.0] * len(ps), 0.0
|
||
for rank, i in enumerate(order):
|
||
run = max(run, min(1.0, ps[i] * (len(ps) - rank)))
|
||
out[i] = run
|
||
return out
|
||
|
||
|
||
def p1_leg() -> None:
|
||
print("═══ ИТОГ п.1 — НОГА 2×2 (пул проходов abs+abs2, перевес против черновика F3) ═══")
|
||
a1, a2, common = _passes()
|
||
print(f"единиц в обоих проходах: {len(common)}\n")
|
||
print(f"{'арм':6s}{'n':>4s}{'перевес':>10s}{'95% ДИ':>19s}{'p':>10s}{'побед':>8s}")
|
||
for a in ("A", "B", "D_", "D"):
|
||
m = _pooled(a1, a2, common, a, "F3")
|
||
lo, hi = BO.boot_ci(m)
|
||
print(f"{a:6s}{len(m):4d}{st.mean(m):+10.2f} [{lo:+.2f}, {hi:+.2f}]"
|
||
f"{BO.sign_perm_p(m):10.4f}{sum(1 for x in m if x > 0):5d}/{len(m)}")
|
||
print("\n⚠ ПОСТ-ХОК. `D_ vs F3` и `D vs F3` в объявленном `absjudge.FAMILY` ОТСУТСТВУЮТ: нога")
|
||
print(" собрана аудитом из того же сырья уже после чтения чисел. Поправку Холма они не несут")
|
||
print(" и читаются как разведка; несущие контрасты семейства печатает `absjudge --pool`.")
|
||
m = _pooled(a1, a2, common, "A_law", "F3")
|
||
print(f"\nсправочно `A_law vs F3` (боевой конфиг редактора): {st.mean(m):+.2f}"
|
||
f" · по проходам {st.mean([_E(a1, u, 'F3') - _E(a1, u, 'A_law') for u in common]):+.2f}"
|
||
f" / {st.mean([_E(a2, u, 'F3') - _E(a2, u, 'A_law') for u in common]):+.2f}")
|
||
|
||
|
||
def p1_robust() -> None:
|
||
"""Чем несущие контрасты пробовали убить: L2O, кластеризация по главе, строгость панелей.
|
||
|
||
⚠ Заведено 08.08. До этого клеймы отчёта «0/120 при удалении любых ДВУХ единиц» и
|
||
«кластеризация по главе выживает» СЧИТАЛИСЬ ВНЕ ДЕРЕВА и в репозитории пути не имели —
|
||
тот же класс дефекта, ради которого написан этот модуль.
|
||
"""
|
||
print("\n═══ ИТОГ п.1 — ЧЕМ НЕСУЩИЕ КОНТРАСТЫ ПРОБОВАЛИ УБИТЬ ═══")
|
||
a1, a2, common = _passes()
|
||
core = [("A", "F3"), ("B", "F3"), ("D", "A")]
|
||
k_fam = len(AJ.FAMILY)
|
||
print(" LEAVE-TWO-OUT (удаляются любые ДВЕ единицы, Холм по семи гипотезам):")
|
||
for a, b in core:
|
||
m = _pooled(a1, a2, common, a, b)
|
||
bad = sum(1 for c in combinations(range(len(m)), 2)
|
||
if BO.sign_perm_p([x for i, x in enumerate(m) if i not in c]) * k_fam >= 0.05)
|
||
print(f" {a + ' vs ' + b:10s} вылетов {bad}/{len(list(combinations(range(len(m)), 2)))}")
|
||
# КЛАСТЕРИЗАЦИЯ. 16 единиц происходят не из 16 разных глав: часть глав дала по два чанка,
|
||
# и такие единицы НЕ независимы. Тест берёт средний перевес ПО ГЛАВЕ и работает на главах.
|
||
us = {u["uid"]: u["chapter"] for u in BO.units()}
|
||
chapters = sorted({us[u] for u in common})
|
||
print(f"\n КЛАСТЕРИЗАЦИЯ ПО ГЛАВЕ ИСТОЧНИКА: {len(common)} единиц из {len(chapters)} глав "
|
||
f"({sum(1 for c in chapters if sum(1 for u in common if us[u] == c) > 1)} глав дали >1 чанка)")
|
||
ps = []
|
||
for a, b in core:
|
||
m = dict(zip(common, _pooled(a1, a2, common, a, b), strict=True))
|
||
by = [st.mean([m[u] for u in common if us[u] == c]) for c in chapters]
|
||
ps.append(BO.sign_perm_p(by))
|
||
for (a, b), p, h in zip(core, ps, _holm(ps + [1.0] * (k_fam - len(ps))), strict=False):
|
||
print(f" {a + ' vs ' + b:10s} на главах p={p:.4f} · Холм {h:.4f}"
|
||
f"{' ✔ выживает' if h < 0.05 else ''}")
|
||
print("\n СТРОГОСТЬ ПАНЕЛЕЙ (ошибок на клетку) — уровни между проходами НЕСОПОСТАВИМЫ:")
|
||
for nm in ("abs", "abs2", "abs3", "abs4"):
|
||
d = AJ._by_unit_pass(nm)
|
||
cells = [r["errors"] for v in d.values() for r in v.values()]
|
||
if cells:
|
||
print(f" {nm:5s} {st.mean(cells):.2f} на {len(cells)} клетках")
|
||
print(" ⇒ поэтому ИТОГ сравнивает ТОЛЬКО внутриединичные разности, а не уровни.")
|
||
|
||
|
||
def p2_canon() -> None:
|
||
print("\n═══ ИТОГ п.2 — ПОКРЫТИЕ КАНОНА (счётная метрика, окна) ═══")
|
||
us = BO.units()
|
||
print(f"{'арм':7s}{'канон/исходник':>16s}{'покрытие':>10s}")
|
||
for a in ("F3", "A", "A_law", "B", "D", "D_", "C2", "E2"):
|
||
s = k = 0
|
||
for u in us:
|
||
x = P2.text_of(a, u["uid"]) if a in ("C2", "E2") else BO.text3_of(a, u)
|
||
for zh, (_v1, _v2, r1, _r2) in BO.IP.TERMS.items():
|
||
n = u["source"].count(zh)
|
||
if n:
|
||
s += n
|
||
k += min(len(re.findall(r1, x, re.I)), n)
|
||
print(f"{a:7s}{f'{k}/{s}':>16s}{k / s if s else 0:>10.3f}")
|
||
print(" доля упоминаний исходника, отражённых КАНОННОЙ формой; s — знаменатель на 16 единиц")
|
||
|
||
|
||
def p3_draft() -> None:
|
||
print("\n═══ ИТОГ п.3 — СВОЙ ЧЕРНОВИК ПРОТИВ КОРПУСНОГО (тот же редактор `A`) ═══")
|
||
a1, a2, common = _passes()
|
||
own = {u: st.mean([_E(a1, u, "A"), _E(a2, u, "A")]) for u in common}
|
||
for lbl, cells in (("только CTRLfloorA (клетка ИТОГа)", ("CTRLfloorA",)),
|
||
("обе клетки пола CTRLfloorA+B", ("CTRLfloorA", "CTRLfloorB"))):
|
||
corp, diff = {}, []
|
||
for u in common:
|
||
vals = [_E(d, u, c) for d in (a1, a2) for c in cells if c in d[u]]
|
||
if not vals:
|
||
continue
|
||
corp[u] = st.mean(vals)
|
||
diff.append(corp[u] - own[u])
|
||
print(f" {lbl:34s} корпусный {st.mean(list(corp.values())):.2f} против своего "
|
||
f"{st.mean([own[u] for u in corp]):.2f} · хуже "
|
||
f"{sum(1 for x in diff if x > 0)}/{len(diff)} · p={BO.sign_perm_p(diff):.4f}")
|
||
print(" клетки: CTRLfloorA=`bo2-A` · CTRLfloorB=`bo3-Aprime` (повтор того же корпусного входа)")
|
||
print(" ⚠ ИТОГ печатает вариант ПО ОДНОЙ клетке — он в большую сторону; вердикт одинаков.")
|
||
|
||
|
||
def p4_axes() -> None:
|
||
print("\n═══ ИТОГ п.4 — ОДНОПРОХОДКА С МАНДАТОМ ПРОТИВ СВЯЗКИ, разложение по осям (пул) ═══")
|
||
a1, a2, common = _passes()
|
||
import judges as J # noqa: PLC0415
|
||
for a, b in (("D", "A"), ("D_", "A")):
|
||
row = []
|
||
for ax in J.AXES:
|
||
m = [st.mean([a1[u][b][ax] - a1[u][a][ax], a2[u][b][ax] - a2[u][a][ax]])
|
||
for u in common if a in a1[u] and b in a1[u]]
|
||
row.append(f"{ax} {st.mean(m):+.2f}")
|
||
tot = _pooled(a1, a2, common, a, b)
|
||
lo, hi = BO.boot_ci(tot)
|
||
print(f" {a} vs {b}: {st.mean(tot):+.2f} [{lo:+.2f}, {hi:+.2f}] "
|
||
f"p={BO.sign_perm_p(tot):.4f} · " + " · ".join(row))
|
||
print(" ⚠ Оси разведочны: `absjudge --score` корректирует на 7 гипотез, здесь клеток 28.")
|
||
|
||
|
||
def _qe() -> dict:
|
||
f = OUT / "qe-arms.json"
|
||
return json.loads(f.read_text(encoding="utf-8")) if f.exists() else {}
|
||
|
||
|
||
def _pearson(xs: list[float], ys: list[float]) -> float:
|
||
mx, my = st.mean(xs), st.mean(ys)
|
||
num = sum((x - mx) * (y - my) for x, y in zip(xs, ys, strict=True))
|
||
den = (sum((x - mx) ** 2 for x in xs) * sum((y - my) ** 2 for y in ys)) ** 0.5
|
||
return num / den if den else float("nan")
|
||
|
||
|
||
def _route() -> tuple[dict, list[str], list[str]]:
|
||
"""Кого арм G маршрутизировал и чью правку страж ОСТАВИЛ. Ровно логика `guarded.cmd_build`.
|
||
|
||
⚠ Выигрыш забирают ТОЛЬКО оставленные окна: там, где страж откатил правку к черновику,
|
||
арм G получает счёт черновика, то есть ноль выигрыша. Первая версия этого счёта брала все
|
||
маршрутизированные и завышала долю до 46% — поймано сверкой с `--build` 08.08.
|
||
"""
|
||
a1, a2, common = _passes()
|
||
gain = dict(zip(common, _pooled(a1, a2, common, "A", "F3"), strict=True))
|
||
routed, kept = [], []
|
||
for u in BO.units():
|
||
uid = u["uid"]
|
||
if uid not in gain:
|
||
continue
|
||
draft, edited = BO.text3_of("F3", u), BO.text3_of("A", u)
|
||
if not G.flagged(draft):
|
||
continue
|
||
routed.append(uid)
|
||
if sum(G.flags(edited).values()) <= sum(G.flags(draft).values()):
|
||
kept.append(uid)
|
||
return gain, routed, kept
|
||
|
||
|
||
def _draft_cost(uid: str) -> float:
|
||
return sum(json.loads(f.read_text(encoding="utf-8")).get("cost_usd") or 0.0
|
||
for f in OUT.glob(f"bo2-DRAFT-{uid}*.json"))
|
||
|
||
|
||
def _cost_a(uid: str) -> float:
|
||
return _draft_cost(uid) + G.cost_of("bo4-A", uid)
|
||
|
||
|
||
def _cost_g(uid: str) -> float:
|
||
"""Цена арма G: черновик всегда, редактор — только флагнутым окнам."""
|
||
u = next(x for x in BO.units() if x["uid"] == uid)
|
||
return _cost_a(uid) if G.flagged(BO.text3_of("F3", u)) else _draft_cost(uid)
|
||
|
||
|
||
def p5_router() -> None:
|
||
print("\n═══ ИТОГ п.5 — РОУТИНГ: ТРИ ОТБОРЩИКА ═══")
|
||
a1, a2, common = _passes()
|
||
gain, routed, picked = _route()
|
||
tot = sum(gain.values())
|
||
got = sum(gain[u] for u in picked)
|
||
print(f" МЕХАНИЧЕСКИЙ отборщик: маршрутизировал {len(routed)}/{len(gain)} окон, страж оставил"
|
||
f" правку в {len(picked)} ⇒ забрано {got:.1f} из {tot:.1f} выигрыша = {got / tot:.0%}")
|
||
sums = sorted(sum(gain[u] for u in c) for c in combinations(gain, len(picked)))
|
||
lo, hi = bisect_left(sums, got) / len(sums), bisect_right(sums, got) / len(sums)
|
||
cg = st.mean([_cost_g(u) for u in gain])
|
||
ca = st.mean([_cost_a(u) for u in gain])
|
||
print(f" цена: G ${cg:.5f} против связки ${ca:.5f} = {cg / ca:.0%} цены за {got / tot:.0%}"
|
||
f" выигрыша ⇒ хуже случайного роутинга того же бюджета")
|
||
print(f" среди всех C({len(gain)},{len(picked)})={len(sums)} подмножеств того же размера: "
|
||
f"персентиль {lo:.1%}–{hi:.1%} (середина {(lo + hi) / 2:.1%}), связок {sums.count(got)}")
|
||
print(" ⚠ ИТОГ печатает верхнюю границу (51-й); расхождение с 46-м — только конвенция связок.")
|
||
q = _qe()
|
||
if q:
|
||
agree = tot_n = 0
|
||
for a, b, _w in AJ.FAMILY:
|
||
for u in common:
|
||
m = _pooled(a1, a2, [u], a, b)
|
||
if not m or m[0] == 0:
|
||
continue
|
||
dq = q.get(f"{b}|{u}", 0) - q.get(f"{a}|{u}", 0)
|
||
tot_n += 1
|
||
agree += (dq > 0) == (m[0] > 0)
|
||
print(f" QE как СУДЬЯ: знак совпал {agree}/{tot_n} по 7 контрастам семейства × единицы")
|
||
xs = [q.get(f"F3|{u}", 0) - q.get(f"A|{u}", 0) for u in common]
|
||
ys = [gain[u] for u in common]
|
||
print(f" QE против судьи на самом крупном контрасте `A/F3`: r={_pearson(xs, ys):+.2f}")
|
||
print(" ⚠ Это АНТИкорреляция, а не слепота: чем сильнее QE «за» редактуру, тем меньше")
|
||
print(" её выигрыш по судье. Довод против QE-роутинга сильнее, чем печатал ИТОГ.")
|
||
print(" ⚠ Числа ИТОГа «43/91» и «r=+0.06» ни одним вариантом счёта не воспроизводятся —")
|
||
print(" они из §15, утраченного инцидентом 08.08; в дереве кода пути к ним НЕТ.")
|
||
|
||
|
||
def p6_guard() -> None:
|
||
print("\n═══ ИТОГ п.6 — СТРАЖ guarded APE ═══")
|
||
gain, routed, kept = _route()
|
||
rej = [u for u in routed if u not in kept]
|
||
print(f" страж ОТКЛОНИЛ правок: {len(rej)} — " +
|
||
" · ".join(f"{u[:10]} выигрыш {gain[u]:+.1f}" for u in rej))
|
||
if rej:
|
||
print(f" средний выигрыш ОТКЛОНЁННЫХ {st.mean([gain[u] for u in rej]):+.2f} против "
|
||
f"{st.mean(list(gain.values())):+.2f} по ВСЕМ единицам ⇒ страж вырезал ЛУЧШИЕ")
|
||
print(f" ⚠ n={len(rej)}. ИТОГ числа единиц не называл; вывод стоит на двух правках.")
|
||
|
||
|
||
def p7_2b() -> None:
|
||
print("\n═══ ИТОГ п.7 — АРМЫ Ф2б (проход abs4, узкая панель) ═══")
|
||
sv = AJ.PASS
|
||
AJ.PASS = "abs4"
|
||
try:
|
||
d = AJ._by_unit()
|
||
finally:
|
||
AJ.PASS = sv
|
||
# СЕМЕЙСТВО ПРОХОДА 4 — пять СУДЕЙСКИХ контрастов.
|
||
# ⚠ ОТКЛОНЕНИЕ ОТ ОБЪЯВЛЕННОГО, объявляю: `phase2b.FAMILY` пятым членом называет
|
||
# `C_oracle vs C_real` — разрыв потолка и пола схемы. Он НЕ судейский: потолок меряется
|
||
# посаженными дефектами и детекторами ($0, `phase2b --oracle`), судье не предъявлялся и
|
||
# p-значения не имеет. В судейскую пятёрку вместо него вошёл контроль `A_law vs F3`.
|
||
# Направление подмены КОНСЕРВАТИВНО (член семейства сохранён, поправка не ослаблена), но это
|
||
# правка семейства после пре-рега, и читать её надо как девиацию, а не как замысел.
|
||
fam = [("C2", "A_law", "точечный ремонт против полного переписывания"),
|
||
("C2", "F3", "и лучше ли он черновика, который чинит"),
|
||
("E2", "D_", "обратная связка против своей базы"),
|
||
("E2", "A_law", "обратная связка против прямой"),
|
||
("A_law", "F3", "КОНТРОЛЬ: воспроизводится ли эффект редактора")]
|
||
rows = []
|
||
for a, b, w in fam:
|
||
m = [d[u][b]["errors"] - d[u][a]["errors"] for u in d if a in d[u] and b in d[u]]
|
||
rows.append((a, b, w, m, BO.sign_perm_p(m)))
|
||
holm = _holm([r[4] for r in rows])
|
||
print(f"{'контраст':16s}{'n':>3s}{'перевес':>9s}{'95% ДИ':>19s}{'p':>9s}{'p Холма':>10s}")
|
||
for (a, b, w, m, p), h in zip(rows, holm, strict=True):
|
||
lo, hi = BO.boot_ci(m)
|
||
print(f"{a + ' vs ' + b:16s}{len(m):3d}{st.mean(m):+9.2f} [{lo:+.2f}, {hi:+.2f}]"
|
||
f"{p:9.4f}{h:10.4f}{'✔' if h < 0.05 else ' '} {w}")
|
||
print(" ⚠ `E2 vs A_law` значим и в таблице ИТОГа НЕ напечатан — восстановлено ревью 08.08.")
|
||
|
||
|
||
def money() -> None:
|
||
print("\n═══ ИТОГ — ДЕНЬГИ ═══")
|
||
def s(pat: str) -> float:
|
||
out = 0.0
|
||
for f in OUT.glob(pat):
|
||
try:
|
||
x = json.loads(f.read_text(encoding="utf-8"))
|
||
except (ValueError, OSError):
|
||
continue
|
||
if isinstance(x, dict):
|
||
out += x.get("cost_usd") or 0.0
|
||
return out
|
||
print(f" пак целиком ${s('*.json'):.5f}")
|
||
print(f" фаза Ф2б: армы C/E `p2b-*` ${s('p2b-*.json'):.5f} + старые пробы `rp-*` "
|
||
f"${s('rp-*.json'):.5f} = ${s('p2b-*.json') + s('rp-*.json'):.5f}")
|
||
print(" ⚠ ИТОГ печатал как расход фазы только `p2b-*`; расход ФАЗЫ включает и `rp-*`.")
|
||
empty = {}
|
||
for f in OUT.glob("*.json"):
|
||
try:
|
||
x = json.loads(f.read_text(encoding="utf-8"))
|
||
except (ValueError, OSError):
|
||
continue
|
||
if isinstance(x, dict) and (x.get("cost_usd") or 0) > 0 \
|
||
and not (x.get("content") or x.get("answer") or "").strip():
|
||
t = f.name.split("-")[0]
|
||
e = empty.setdefault(t, [0, 0.0])
|
||
e[0] += 1
|
||
e[1] += x["cost_usd"]
|
||
tot_n = sum(v[0] for v in empty.values())
|
||
tot_s = sum(v[1] for v in empty.values())
|
||
print(f" оплачено за ПУСТОЙ выход: {tot_n} вызовов ${tot_s:.5f} — " +
|
||
" · ".join(f"{k} {v[0]} шт ${v[1]:.5f}" for k, v in sorted(empty.items())))
|
||
print(" ⚠ Счётчик смотрит ОБА поля выхода (`content` и `answer`): проба контаминации")
|
||
print(" пишет в `answer`, и счётчик по одному полю давал ложные $0.19191 на 43 вызова.")
|
||
|
||
|
||
def noise() -> None:
|
||
print("\n═══ ИТОГ — ШУМ ═══")
|
||
for nm in ("abs", "abs2", "abs4"):
|
||
d = AJ._by_unit_pass(nm)
|
||
m = [v["CTRLfloorA"]["errors"] - v["CTRLfloorB"]["errors"]
|
||
for v in d.values() if "CTRLfloorA" in v and "CTRLfloorB" in v]
|
||
if len(m) > 1:
|
||
print(f" пол рига {nm:5s}: n={len(m):2d} среднее {st.mean(m):+.2f} · "
|
||
f"sd {st.pstdev(m):.2f} — две генерации ОДНОГО арма, один судья")
|
||
print(" ⇒ разброс повтора превышает разницу между армами: на ОДНОЙ главе армы не упорядочены.")
|
||
print(" ⚠ Разложение шума на судейскую и генерационную компоненты НЕ УСТАНОВЛЕНО:")
|
||
print(" `absjudge --pool` печатает один способ счёта, два других дают другой вердикт.")
|
||
|
||
|
||
def main() -> None:
|
||
p1_leg()
|
||
p1_robust()
|
||
p2_canon()
|
||
p3_draft()
|
||
p4_axes()
|
||
p5_router()
|
||
p6_guard()
|
||
p7_2b()
|
||
print("\n═══ ИТОГ п.8 — ДРЕЙФ ═══\n штатная команда: `drift.py --score` (9 общих глав)")
|
||
money()
|
||
noise()
|
||
print("\nВСЁ ВЫШЕ ПЕРЕ-СНЯТО ИЗ СЫРЬЯ, $0. Числа, помеченные ⚠ «в дереве нет», не выводятся.")
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|