Scope the bank stem rule, manual lists and display to the pair so short Latin names stop matching unrelated Russian words

This commit is contained in:
heaven 2026-08-14 16:00:04 +03:00
parent 8584235cdc
commit d7cd45dad3

View file

@ -228,20 +228,47 @@ def _rx(dst: str) -> str:
у всех, и ни один относительный вывод эксп-22 не двигается. Числа в отчёте пере-сняты.
`(?<![^\W\d_])` = «слева не буква»: `\b` здесь не годится, он пропускает начало после цифры.
"""
# ⚠⚠ МИНИМАЛЬНЫЙ СТЕМ — ПАРАМЕТР ПАРЫ (14.08). У китайского он 0, и правило ниже работает
# ровно как прежде: числа закрытой оси зависят от этих регексов и двигаться не имеют права.
# Для пробельных пар усечение на 2 знака КАТАСТРОФИЧНО: имена там короткие, и «Восс» давало
# `Во\w*`, ловившее «Возможно» и «Военные». Замер адверсариальный — английский банк прогнан
# по РУССКИМ выходам китайской оси, где попаданий быть не может по построению: 990 ложных
# срабатываний, сломано 9 терминов из 25. Канон-ось Д3 мерила бы шум, насыщенный у всех армов
# одинаково, то есть A4 и A0 стали бы неразличимы — и это было бы НЕВИДИМО в отчёте.
# Порог 6 выбран замером, а не на глаз: он снимает однокоренные коллизии (Империя/Император,
# Мёртвые/мёртвая) ценой одного склонения (Селена→Селены). Потеря занижает покрытие ОДИНАКОВО
# у всех армов — безопасная сторона, как и поправка границы слова 10.08.
# ⚠ Граница измерения: «Восс» остаётся префиксом «восстановить» при любом пороге (6 попаданий
# на 232 тыс. знаков). Это объявлено, а не вылечено.
ms = 0 if pair().key == "zh-ru" else 6
parts = []
for raw in dst.split():
w = re.escape(raw)
if len(raw) >= 4:
parts.append(w[:-2] + r"\w*")
elif len(raw) == 3:
parts.append(w[:-1] + r"\w*")
else:
parts.append(w)
drop = 2 if len(raw) >= 4 else 1 if len(raw) == 3 else 0
drop = max(0, min(drop, len(raw) - ms)) if ms else drop
parts.append(w[:-drop] + r"\w*" if drop else (w + r"\w*" if ms else w))
return r"(?<![^\W\d_])" + r"\s+".join(parts)
# ⚠ РУЧНЫЕ ПРАВКИ КАНОНА. Пусто = терминолог принят как есть. Каждая строка — решение СЕССИИ,
# не владельца; основания печатаются `--canon` и едут в отчёт.
# ⚠ РУЧНЫЕ ПРАВКИ И ВЫБРОС — ПО ПАРАМ. Прежде оба списка были общими, и сборка ЛЮБОЙ пары
# подмешивала себе китайские данные: `MANUAL` инъектировался через `setdefault` в банк любой
# книги, а `DROP` (китайская общеязыковая лексика) уезжал в метаданные чужого банка. Сегодня
# `MANUAL` пуст, поэтому утечки не произошло — но это везение, а не устройство. Книжный термин
# в общем пар-слое есть утечка по канону проекта.
MANUAL_BY_PAIR: dict[str, dict[str, tuple[str, str]]] = {"zh-ru": {}}
DROP_BY_PAIR: dict[str, set[str]] = {"zh-ru": {"瞬间", "声音", "仿佛", "庭院"}}
def _manual() -> dict[str, tuple[str, str]]:
return MANUAL_BY_PAIR.get(pair().key, {})
def _drop() -> set[str]:
return DROP_BY_PAIR.get(pair().key, set())
MANUAL: dict[str, tuple[str, str]] = {}
# Выброшено из банка: это общеязыковая лексика, а не термины книги. Канон на них сделал бы
# метрику покрытия замером словарного совпадения, а не терминологической дисциплины.
@ -268,14 +295,14 @@ def cmd_canon() -> None:
got[src] = dst
terms = {}
for src, dst in got.items():
if src in DROP:
if src in _drop():
continue
if src in MANUAL:
dst, why = MANUAL[src]
if src in _manual():
dst, why = _manual()[src]
else:
why = ""
terms[src] = dict(dst=dst, rx=_rx(dst), manual=bool(why), why=why)
for src, (dst, why) in MANUAL.items():
for src, (dst, why) in _manual().items():
terms.setdefault(src, dict(dst=dst, rx=_rx(dst), manual=True, why=why))
# ⚠ Пишем в банк ПАРЫ. Безусловная запись в zh-путь затирала бы китайский банк при сборке
# английского, и оба прогона переписывали бы друг друга.
@ -285,15 +312,17 @@ def cmd_canon() -> None:
out_file.write_text(json.dumps(
dict(book=M.BOOK["title"] if P.key == "zh-ru" else P.key, signed=False,
note="ручной канон сессии, НЕ подпись владельца (образец D39.47)",
model=TERM_MODEL, dropped=sorted(DROP), terms=terms),
model=TERM_MODEL, dropped=sorted(_drop()), terms=terms),
ensure_ascii=False, indent=1), encoding="utf-8")
print(f"банк собран: {len(terms)} терминов · ручных правок {sum(1 for v in terms.values() if v['manual'])}"
f" · выброшено {len(DROP)}")
f" · выброшено {len(_drop())}")
cmd_show()
def cmd_show() -> None:
b = PR.bank()
# ⚠ Банк ПАРЫ, а не умолчание: после сборки английского банка показывался китайский.
P = pair()
b = PR.bank(None if P.key == "zh-ru" else P.bank_file)
if not b:
print("банка нет")
return