Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BJKMLHdxMwSUeYcdkwqX4W
72 lines
9.4 KiB
Markdown
72 lines
9.4 KiB
Markdown
## 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). Цена ошибки: нет.
|