## 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:.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). - **P5-8.** Все json набора ошибочны — конверт `error` с кодом первого, а `metrics.warnings` всё равно перечисляют `JSON_FAILED` каждого json (как строки Среза 0): иначе коды остальных json теряются. Цена: спека называет `JSON_FAILED` «при успешном соседе»; на `error`-конверт ядро `RECOGNIZED` не считает, вреда нет (Q-2). - **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 ` (бриф владельца) и `Claude-Session: …` (план) — совпадает с обоими источниками. Цена ошибки: нет. - **R-2 (Р-36).** Задача 4 копирует из `docs/contracts/examples` только существующие файлы верхнего уровня как есть, новых примеров контракта верхнего уровня не добавляет (решение владельца Р-36). Цена ошибки: нет.