9.4 KiB
Raw Blame History

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).

  • 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 <noreply@anthropic.com> (бриф владельца) и Claude-Session: … (план) — совпадает с обоими источниками. Цена ошибки: нет.

  • R-2 (Р-36). Задача 4 копирует из docs/contracts/examples только существующие файлы верхнего уровня как есть, новых примеров контракта верхнего уровня не добавляет (решение владельца Р-36). Цена ошибки: нет.