Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BJKMLHdxMwSUeYcdkwqX4W
11 KiB
Context
Решение принято в docs change unified-statement-normalization (RSHB/arm-ka-docs#116); подробный дизайн — его
design.md (docs/openspec/changes/unified-statement-normalization/design.md, до архивации; после — в архиве docs).
План реализации — plans/2026-10-18-plan-5-scan-gateway.md того же change'а.
Decisions made without discussion
Дата всех решений — 18.10.2026. Маркеры: P5-n — решения автора плана, R-n — рулинги контроллера SDD. Формат пунктов P5-n:
решение, обоснование, цена ошибки — как в плане (без сокращений).
-
P5-1. Сетка OCR — одна строка сетки на строку ИС по возрастанию
row_index, пропускиrow_indexпустыми строками не заполняются; ширина строк таблицы —max(column_index) + 1. «Плотная поrow_index» прочитано как «по порядкуrow_index»,refвсё равно несёт номер ИС. Цена: шаг 2 не видит «дыры» между строками (в реальных json их нет). -
P5-2. Нарушенная структура таблицы ИС (повтор
row_index, повторcolumn_indexв строке, нецелые индексы,rowsне список) — исключение адаптера →TABLE_PARSE_ERRORвсей таблицы. Сегодня такие ячейки молча пропускались или перезаписывались; повторrow_indexсломал бы уникальностьref. Цена: одна битая ячейка теряет таблицу (видно —error). -
P5-3.
valueячейки:None→"", не строка →str(value), безstrip;confidence: число (неbool) →[0, 100], иначе0(какclamp_confidence, §11.1 «NaN → 0»). Цена: числовойvalueИС (не встречался) даст текст"1234.0". -
P5-4. Ячейка ИС с пустым текстом —
Cell("", …), а неNone: ИС отдаёт ячейку на каждую позицию строки,None— только там, где ячейки нет. Цена: нет. -
P5-5. Шапка: поле ИС отсутствует, не объект или
form_nameпустой →None(а неCell("", 0));refполей шапки —data_ocr.<поле ИС>(пакетrefшапки в выход не переносит — уHeaderFieldего нет, план 1, раздел API);data_ocr.label(заголовок комплекта) не используется. Цена:<поле>_textпустого поля —null, а не"". -
P5-6.
evidence.title = Cell(label.form_name, …, ref="table:<i>.label");header.form_nameконтракта —document.header.form_nameпакета (=evidence.title.textкак в источнике, пустой —null, план 1 P1-29); шлюзform_nameсам не ставит и пару «кандидат → документ» не хранит. -
P5-7. Успешный json без
data_ocr, безtable_structureили с пустымtable_structure—JSON_FAILED(error): «ничего не выбрасывать молча» (§11, принцип 2). Цена: если ИС вернёт успешный пустой json рядом с нормальными, конверт будетneeds_check. Q-1 закрыт решением владельца Р-32 (подтверждает выбор плана). -
P5-8. Все json набора ошибочны — конверт
errorс кодом первого, аmetrics.warningsвсё равно перечисляютJSON_FAILEDкаждого json (как строки Среза 0): иначе коды остальных json теряются. Цена: спека называетJSON_FAILED«при успешном соседе»; наerror-конверт ядроRECOGNIZEDне считает, вреда нет. Q-2 закрыт решением владельца Р-33 (подтверждает выбор плана). -
P5-9.
TABLE_PARSE_ERROR.message=BACKEND_PARSING_ERROR: <тип>: <текст>(текст до 300 символов) — код старого §11.1 п. 9 сохранён в тексте для поиска по логам и golden. Цена: нет. -
P5-10. Предел
metrics.warnings(100 структур,messageдо 1000 символов) сохранён; при переполнении остаются всеerror, затемinfo; строки-сводки нет — новый код предупреждения вводится только словарём пакета. Список в конверте и в файле результата одинаков (спека). Цена: при > 100 предупреждениях частьinfoне видна ни там, ни там (сырой ответ — вraw/). -
P5-11.
statements_versionчитается при созданииOcrNormalizer; нетVERSION— сервис не стартует. Версия едет вNormalizeOutcome.statements_versionи вservice.statements_versionфайла результата; в конверт не пишется (в схеме конверта поля нет). Цена: dev-checkout безVERSIONне стартует (копию всегда раскладывает скрипт). -
P5-12.
api/rules/удалён целиком,SCAN_RULES_DIRубран из настроек; словари —default_rules()пакета. Цена: переопределить словарь без релиза нельзя (и не нужно — словари меняются черезarm-statements). -
P5-13.
CONTRACT_VERSION = "2.0"— для файла результата и всех конвертов, включая ack; MAJOR запроса — отдельная константа (SUPPORTED_MAJOR = 1). -
P5-14. Golden v2 —
{schema_version, status, error, metrics, documents}файла результата безjob_id,file_id,service,raw_s3_key(statements_versionменяется при каждой раскладке копии); успешный golden дополнительно проходит схему v2.0. Цена: golden не ловит регресс поляservice. -
P5-15. Перевод в контракт —
to_contract/warning_to_contractпакета (план 1, P1-26): у предупреждений документа нетlocatorи пустых необязательных ключей (схема не допускаетnullуcolumn_indexиfield), у нарушений §7 нетcolumn_key, у предупреждений файлаlocatorесть; шлюз только валидируетdictсвоими моделями. Цена: правка правил перевода — вarm-statements, а не здесь. -
P5-16. Модули:
OcrNormalizerостаётся вsrc/normalizer/ocr/__init__.py(меньше правокapp.pyи тестов), адаптер —ocr/adapter.py, обход набора —ocr/answer_set.py, ошибки json —normalizer/files.py(снимает цикл импортовsrc.normalizer↔src.normalizer.ocr); своего конвертера в контракт (contract_map.py) нет. -
P5-17. Review Focus 3 утверждает
doneдля обоих документов «БО 1 кв 2025» и «Бухгалтерская отчетность 1 кв.25» (proposal, Р-23). Падение при верном кандидате — находка для пакета, а не правка шлюза; ослабить утверждение может только контроллер рулингом. -
P5-18.
metrics.rows— число строкlinesвсех документов (как в v1.2), теперь включая строки без кода. -
P5-19.
NullNormalizerсохраняется для тестов воркера и выдаётJSON_FAILEDчерезmake_warningпакета. -
P5-20. Обход набора зовёт
normalize_candidateна каждую таблицу, а неnormalize: исключение одной таблицы —TABLE_PARSE_ERRORсlocatorтаблицы, остальные таблицы разбираются (план 1:normalizeисключения кандидата не перехватывает).AnswerSetResult.documents— документы пакета как есть (заголовок — вheader.form_name). -
R-1. Коммиты ветки несут трейлеры
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>(бриф владельца) иClaude-Session: …(план) — совпадает с обоими источниками. Цена ошибки: нет. -
R-2 (Р-36). Задача 4 копирует из
docs/contracts/examplesтолько существующие файлы верхнего уровня как есть, новых примеров контракта верхнего уровня не добавляет (решение владельца Р-36). Цена ошибки: нет. -
R-3. Усечение до 300 символов (P5-9) применяется к тексту исключения, а не ко всей строке
Тип: текст. Цена ошибки: сообщение чуть длиннее 300 символов при длинном имени типа. -
R-4 (T4).
NormalizeOutcomeвынесен вsrc/normalizer/outcome.pyс реэкспортом изsrc.normalizer— иначе цикл импортов. Цена ошибки: лишний модуль; импорт изsrc.normalizerработает как раньше. -
R-5 (T4).
app.pyвызываетOcrNormalizer()(правилаdefault_rules()внутри), а неOcrNormalizer(default_rules()): импортarm_statementsразрешён только вsrc/normalizer/**иtests/**. Цена ошибки: нет, эквивалентно. -
R-6 (T4). Сравнение с эталонами в
test_goldenвременно стояло подpytest.skipдо задачи 6 (эталоны fixtures были старого формата); в задаче 6 skip снят. Цена ошибки: окно без golden-проверки на ветке между задачами 4 и 6. -
R-7. Набор «ошибочный json + успешный json без таблиц / без документов» даёт
RECOGNITION_FAILED/ «ни одной таблицы не разобрано», а код первого ошибочного — только если успешных json нет (чтение Р-32 + Р-33; исправление T3.I-1, commit3b9e1f6). Цена ошибки: при другом чтении код ошибки конверта в смешанном случае будет другим; ядро наerror-конвертRECOGNIZEDне считает.