Raise the pack ceiling to 6.15 on the owner's sanction, funding the A1B re-buy that a session defect made necessary

This commit is contained in:
heaven 2026-08-11 11:30:03 +03:00
parent 6faa52f753
commit 2fed3d060f
2 changed files with 71 additions and 24 deletions

View file

@ -165,45 +165,71 @@ def canon_gaps(src: str, out: str) -> list[str]:
# «батарея + канон-гейт»; (2) значения проверок — СЛОВАРИ, и наивный фильтр, будь он даже
# достижим, вымел бы в список мест ЗНАМЕНАТЕЛИ: «проверка sentences дала 90», «words дала 1154».
# То есть починка одного типа сделала бы арм хуже, чем сломанный. Поэтому whitelist, а не эвристика.
# ⚠⚠ ВТОРАЯ ПОЧИНКА (11.08, после того как 14 клеток A1B из 31 вернули ПУСТОТУ на потолке вывода,
# $0.170 впустую). Первая редакция whitelist'а брала поля-СЧЁТЧИКИ, и два из них были ядовиты:
#
# * `palladius.nonconformant_bank` — это счёт строк САМОГО БАНКА, не прошедших палладиевскую
# проверку (`battery.py:159` — `for d in bank_dst`). К переводу он не относится ВООБЩЕ: число
# одинаково во всех 32 единицах, потому что банк один. Фиксеру говорилось «имена/термины
# переданы мимо банка (8 шт.)», и он искал в тексте то, чего в тексте нет. Рассуждение
# выросло вшестеро против A1 (40 694 знака против 7 400) и съело весь бюджет вывода.
# * `ty_vy.mixed_in_unit` — адреса не имеет и сам докстринг проверки (`battery.py:365-370`)
# называет её «сигналом к просмотру, а не счётом ошибок»: смешение между РАЗНЫМИ
# собеседниками легитимно. Инструкцией фиксеру такое быть не может.
#
# НОРМА, ЗАВЕДЁННАЯ ЭТИМ: **флаг без адреса — не место.** Контракт арма — «черновик плюс СПИСОК
# МЕСТ»; поле, которое не может назвать, ЧТО чинить, в список не идёт и остаётся описательным.
# Поэтому ниже у каждого поля указано, откуда берётся адрес, и поля без адреса исключены.
BATTERY_VIOLATIONS = {
"palladius": ("nonconformant_bank",),
"cjk_leak": ("over_probe_threshold", "over_prod_threshold"),
"typography": ("violations", "ascii_quotes", "hyphen_as_dash"),
"numbers": ("lost_n", "invented_n"),
"ty_vy": ("mixed_in_unit",),
"gender": ("mismatched",),
# поле-признак поле-адрес (None = адрес находится по самому признаку)
"palladius": (("nonconformant_text", "nonconformant_text_words"),),
"cjk_leak": (("over_prod_threshold", None),),
"typography": (("ascii_quotes", None), ("hyphen_as_dash", None)),
"numbers": (("lost_n", "lost"), ("invented_n", "invented")),
"gender": (("mismatched", "cases"),),
}
BATTERY_SAYS = {
"palladius.nonconformant_bank": "имена/термины переданы мимо банка ({v} шт.)",
"cjk_leak.over_probe_threshold": "в переводе остались иероглифы исходника",
"cjk_leak.over_prod_threshold": "в переводе остались иероглифы исходника (сверх прод-порога)",
"typography.violations": "нарушения типографики ({v} шт.)",
"typography.ascii_quotes": "прямые кавычки вместо «ёлочек» ({v} шт.)",
"typography.hyphen_as_dash": "дефис вместо тире ({v} шт.)",
"numbers.lost_n": "числа исходника потеряны в переводе ({v} шт.)",
"numbers.invented_n": "в переводе появились числа, которых нет в исходнике ({v} шт.)",
"ty_vy.mixed_in_unit": "внутри единицы смешаны «ты» и «вы»",
"gender.mismatched": "род персонажа расходится с карточкой ({v} шт.)",
"palladius.nonconformant_text": "не-палладиевская транскрипция имени: {a}",
"cjk_leak.over_prod_threshold": "в переводе остались иероглифы исходника: {a}",
"typography.ascii_quotes": "прямые кавычки вместо «ёлочек», {v} шт.",
"typography.hyphen_as_dash": "дефис вместо тире, {v} шт.",
"numbers.lost_n": "величины исходника отсутствуют в переводе: {a}",
"numbers.invented_n": "в переводе есть величины, которых нет в исходнике: {a}",
"gender.mismatched": "род персонажа расходится с карточкой: {a}",
}
# ОПИСАТЕЛЬНЫЕ (печатаются как ось результата, фиксеру НЕ передаются — адреса нет):
# ty_vy.mixed_in_unit · palladius.nonconformant_bank · typography.violations (агрегат)
def battery_flags(u: dict, draft: str) -> list[str]:
"""Нарушения батареи на тексте. Пустой список — законный ответ; ИСКЛЮЧЕНИЕ — не законный."""
"""Нарушения батареи НА ТЕКСТЕ, каждое с адресом. Падение = дефект, НЕ глотаем."""
import battery as B # noqa: PLC0415
import re # noqa: PLC0415
bank = BANK.PR.bank()
unit = B.Unit(source=u["source"], draft=draft, final=draft,
bank_dst={v["dst"] for v in bank.values()},
term_pairs=[(t, v["dst"]) for t, v in bank.items() if u["source"].count(t)])
r = B.run_unit(unit) # падение = дефект, НЕ глотаем
r = B.run_unit(unit)
out = []
for check, fields in BATTERY_VIOLATIONS.items():
val = r.get(check)
if not isinstance(val, dict):
continue
for f in fields:
v = val.get(f)
if isinstance(v, (int, float, bool)) and v:
out.append("БАТАРЕЯ: " + BATTERY_SAYS[f"{check}.{f}"].format(v=int(v)))
for sig, addr_field in fields:
v = val.get(sig)
if not (isinstance(v, (int, float, bool)) and v):
continue
if addr_field:
addr = val.get(addr_field) or []
elif check == "cjk_leak":
addr = sorted(set(re.findall(r"[一-鿿]", draft)))[:12]
else:
addr = []
if addr_field and not addr: # признак есть, адреса нет — не место
continue
says = BATTERY_SAYS[f"{check}.{sig}"]
out.append("БАТАРЕЯ: " + says.format(
v=int(v), a="; ".join(f"«{x}»" for x in addr[:12])))
return out
@ -389,6 +415,17 @@ def cmd_selftest() -> int:
ck("в места батареи не попали ЗНАМЕНАТЕЛИ (sentences/words/lines)",
not any(w in f for f in bf for w in ("sentences", "words", "lines", "candidates")),
"; ".join(bf[:2]))
# ⚠ ФЛАГ БЕЗ АДРЕСА — НЕ МЕСТО. Норма заведена после того, как безадресный флаг (и притом
# относящийся к БАНКУ, а не к тексту) отправил фиксера в неограниченный перебор: 14 клеток
# A1B из 31 упёрлись в потолок вывода и вернули пустоту, $0.170 впустую. Гейт требует, чтобы
# каждое место батареи несло либо цитату в «ёлочках», либо счёт находимого по признаку класса.
allb = [f for x in us if base_draft(x["uid"]).strip()
for f in battery_flags(x, base_draft(x["uid"]))]
noaddr = [f for f in allb if "«" not in f and " шт." not in f]
ck("каждое место батареи несёт АДРЕС (цитату либо находимый класс)",
not noaddr, f"без адреса {len(noaddr)}: {noaddr[:2]}")
ck("батарея не флагует свойства БАНКА вместо свойств текста",
not any("мимо банка" in f for f in allb))
# ⚠ A2: арм, которого не существовало — вызов падал TypeError, ветка отказа была недостижима.
m2 = a2_msgs(u["source"])
ck("A2 собирается (однопроходка уравненного мандата)", len(m2) >= 2)

View file

@ -101,9 +101,19 @@ TAGS = {
# Это ПЕРЕКЛАДКА внутри санкционированного пакета, пакетный потолок $6.00 не двигается; сумма
# фазовых потолков 5.83 ≤ 6.00. Спрошено потому, что срабатывание гарда = вопрос владельцу по
# заказу, а пак 23 потерял пункт CONFIRM/DENY ровно за то, что решил подобное сам.
CEILINGS = {"ФД-A": 1.680, "ФД-B": 0.630, "ФД-C": 0.170, "ФД-D": 0.050,
# ⚠ ПОТОЛОК $6.00 → $6.15 (санкция владельца 11.08, «поднимай»), ФД-A 1.680 → 1.950.
# ПРИЧИНА — ОШИБКА СЕССИИ, И ЭТО ЗАПИСАНО ЗДЕСЬ, А НЕ СМЯГЧЕНО В ОТЧЁТЕ. Первая редакция списка
# мест для арма A1B несла поле `palladius.nonconformant_bank` — счёт строк САМОГО БАНКА, не
# прошедших палладиевскую проверку. К переводу оно не относится: число одинаково во всех единицах,
# и фиксер искал в тексте то, чего в тексте нет. Рассуждение выросло вшестеро против A1 (40 694
# знака против 7 400); 14 клеток из 31 упёрлись в потолок вывода и вернули ПУСТОТУ — $0.170
# впустую. Ещё $0.186 стоили 16 завершившихся клеток, отправленных в карантин ради ОДНОРОДНОСТИ
# арма: они построены на том же отравленном списке и одним армом с починенными быть не могут.
# Без этой ошибки $6.00 хватало. Норма, заведённая ею: **флаг без адреса — не место**, и селфтест
# `contour.py` теперь роняет харнесс, если безадресный флаг попадёт в список фиксеру.
CEILINGS = {"ФД-A": 1.950, "ФД-B": 0.630, "ФД-C": 0.170, "ФД-D": 0.050,
"ФД-E": 2.370, "ФД-F": 0.720, "ФД-G": 0.210}
PACK_CEILING = 6.00
PACK_CEILING = 6.15
M22.configure(ZONE=ZONE, OUT=OUT, TAGS=TAGS, CEILINGS=CEILINGS,
PACK_CEILING=PACK_CEILING, GLOB_ALL="dv-*.json", ROSTER=ROSTER)