Amend the drift freeze before reading its numbers: add the same editor with the term bank, because the first arm set confounded topology with bank presence

This commit is contained in:
heaven 2026-08-07 21:06:09 +03:00
parent 3bc01c6d69
commit 059c681c9e

View file

@ -45,7 +45,12 @@ OUT = Path.home() / "books" / "role-topology"
CORP = Path.home() / "books" / "gu-zhenren" / "labels" / "raw" / "corpus.jsonl"
N_CHAPTERS = 10 # заказ промта: 810
DRIFT_ARMS = ("F3", "A", "D_") # черновик · связка · однопроходка
# ⚠ ПОПРАВКА К ФРИЗУ 07.08, внесена ДО чтения боевых чисел дрейфа и с объявленной причиной.
# Первый состав ("F3","A","D_") нёс КОНФАУНД, найденный проверкой сообщений: банк терминов
# подаётся черновику и `D_` (block="law"), а арму `A` — НЕТ (block=None, это боевой бейзлайн
# без банкноты). На оси «консистентность термина» это означало бы замер БАНКА под видом замера
# топологии. `A_law` — тот же редактор С банком — делает сравнение честным.
DRIFT_ARMS = ("F3", "A", "A_law", "D_") # черновик · связка без банка · связка с банком · однопроходка
CEIL_DRIFT = 0.60
# ⚠ Глоб покрывает ТОТ ЖЕ тег, которым идут покупки. Правило заведено после того, как касса
# армов глобила `bo2-*`, а покупки шли тегом `bo4-*`, и $0.29309 прошли мимо потолка.
@ -181,25 +186,97 @@ def terms(ch: list[dict], min_ch: int = 3) -> list[str]:
return sorted(t for t, s in seen.items() if len(s) >= min_ch)
def variants(arm: str, ch: list[dict]) -> dict:
"""Какую из ДВУХ конкурирующих русских форм термина арм выбрал в каждой главе.
Банк `inject_probe.TERMS` держит на каждый zh-термин пару соперничающих форм и регулярки для
их опознания. Дрейф это когда арм в главе k пишет одну форму, а в главе k+1 другую, при
том что zh-термин тот же. Считается детерминированно, судья не нужен.
"""
out: dict[str, dict[int, set]] = {}
for zh, (_v1, _v2, r1, r2) in BO.IP.TERMS.items():
for c in ch:
if zh not in c["source"]:
continue
txt = text(arm, c["uid"])
if not txt.strip():
continue
got = set()
if re.search(r1, txt, re.I):
got.add(0)
if re.search(r2, txt, re.I):
got.add(1)
if got:
out.setdefault(zh, {})[c["no"]] = got
return out
def cmd_score() -> None:
ch = chapters()
have = [c for c in ch if text("F3", c["uid"]).strip()]
if not have:
print("текстов нет — сначала --arms")
return
print(f"ДРЕЙФ на {len(have)} подряд идущих главах ({have[0]['no']}{have[-1]['no']})\n")
print(f"{'арм':6s}{'ты/вы смеш.':>13s}{'новых вариантов/гл.':>21s}{'знаков':>9s}")
for arm in DRIFT_ARMS:
ts = [text(arm, c["uid"]) for c in have]
ty = sum(1 for t in ts if re.search(r"\bты\b", t) and re.search(r"\bвы\b", t, re.I))
print(f"ДРЕЙФ НА СВЯЗНОМ ОТРЕЗКЕ: главы {ch[0]['no']}{ch[-1]['no']}\n")
have = {a: [c for c in ch if text(a, c["uid"]).strip()] for a in DRIFT_ARMS}
print("покрытие отрезка армами:")
for a in DRIFT_ARMS:
miss = [c["no"] for c in ch if c not in have[a]]
print(f" {a:4s} {len(have[a])}/{len(ch)} глав" + (f" · нет: {miss}" if miss else ""))
print("\nу связки нога черновика: глава без годного черновика выпадает целиком.")
print(" Однопроходка этого отказного режима не имеет ПО ПОСТРОЕНИЮ.\n")
# ⚠ МЕТРИКА ИСПРАВЛЕНА ИСПОЛНЕНИЕМ. Первая версия считала, какую из ДВУХ форм банка выбрал
# арм, и давала «расхождений нет» у всех троих. Проверка показала, что ВТОРАЯ форма не
# срабатывает НИ РАЗУ (0 попаданий на 8 терминах × 3 армах × 10 главах), то есть ноль был
# тривиален. При этом у арма есть ТРЕТЬЯ форма, банку неизвестная: 蛊师 передаётся то как
# «гу-мастер», то «Мастер Гу», то просто «мастер». Здесь считается то, что видно без
# выравнивания и сравнимо МЕЖДУ АРМАМИ на одних главах: держит ли арм канонную форму там,
# где zh-термин стоит в исходнике, и сколько раз этот признак ПЕРЕКЛЮЧАЕТСЯ между соседними
# главами. Переключение и есть дрейф; удержание — консистентность.
print(f"{'арм':5s}{'клеток':>8s}{'канон держится':>16s}{'ПЕРЕКЛЮЧЕНИЙ':>14s}{'на переход':>12s}"
f"{'ты/вы смеш.':>13s}")
common = [c for c in ch if all(text(a, c["uid"]).strip() for a in DRIFT_ARMS)]
print(f" (общих глав у всех армов: {len(common)})")
per_arm = {}
for a in DRIFT_ARMS:
cells = hold = flips = trans = 0
detail: dict[str, list] = {}
for zh, (_v1, _v2, r1, _r2) in BO.IP.TERMS.items():
seq = []
for c in common:
if zh not in c["source"]:
continue
cells += 1
ok = bool(re.search(r1, text(a, c["uid"]), re.I))
hold += ok
seq.append((c["no"], ok))
for (_n1, o1), (_n2, o2) in zip(seq, seq[1:]):
trans += 1
flips += o1 != o2
if len(seq) > 1:
detail[zh] = seq
per_arm[a] = detail
ty = sum(1 for c in common
if re.search(r"(?<![а-яё])ты(?![а-яё])", text(a, c["uid"]), re.I)
and re.search(r"(?<![а-яё])вы(?![а-яё])", text(a, c["uid"]), re.I))
print(f"{a:5s}{cells:>8d}{hold:>13d}/{cells}{flips:>14d}"
f"{(flips / trans if trans else 0):>12.2f}{ty:>10d}/{len(common)}")
print("\nПОТЕРМИННО (+ канон держится, арм написал что-то другое):")
for a in DRIFT_ARMS:
print(f" {a}:")
for zh, seq in sorted(per_arm[a].items()):
line = " ".join(f"{n}{'+' if o else ''}" for n, o in seq)
fl = sum(1 for x, y in zip(seq, seq[1:]) if x[1] != y[1])
print(f" {zh:6s} {line} переключений {fl}")
print("\nРОСТ СЛОВАРЯ (новых слов на главу; растущая кривая = арм не помнит решений):")
for a in DRIFT_ARMS:
vocab, new = set(), []
for t in ts:
w = set(re.findall(r"[а-яё]{4,}", t.lower()))
for c in have[a]:
w = set(re.findall(r"[а-яё]{4,}", text(a, c["uid"]).lower()))
new.append(len(w - vocab))
vocab |= w
print(f"{arm:6s}{ty:>10d}/{len(ts)}{st.mean(new):>21.0f}{sum(len(t) for t in ts):>9d}")
print("\n⚠ полные метрики консистентности термина и шва — после закупки, здесь только то,")
print(" что считается из одного текста; терминные требуют выравнивания zh↔ru по главам.")
half = len(new) // 2
print(f" {a:5s} первая половина {st.mean(new[:half]):.0f}/гл · "
f"вторая {st.mean(new[half:]):.0f}/гл · всего уникальных {len(vocab)}")
if __name__ == "__main__":