123 lines
17 KiB
Markdown
123 lines
17 KiB
Markdown
# Design: unified-normalization-tp
|
||
|
||
## Контекст
|
||
|
||
Change переносит в tabular-parser требования общей нормализации ББ/ОФР: шаг 1 (кандидат в форму из книги Excel)
|
||
пишет адаптер сервиса, шаги 2–8 выполняет копия пакета `arm_statements`, результат и конверт — контракт v2.0.
|
||
Дизайн и решения Р-1…Р-24 — change `unified-statement-normalization` в arm-ka-docs (issue RSHB/arm-ka-docs#116);
|
||
реализация — план 4 (`plans/2026-10-18-plan-4-tabular-parser.md` в docs), issue RSHB/arm-tabular-parser#54, ветка
|
||
`feat/54-statements-v2`.
|
||
|
||
Номера P4-n — решения автора плана, R-n — рулинги контроллера при исполнении (нумерация сквозная, R-3 не присваивался).
|
||
Дата всех решений ниже — 18.10.2026.
|
||
|
||
## Decisions made without discussion
|
||
|
||
### Решения автора плана (P4-n)
|
||
|
||
- **P4-1.** Переход на общий код и на контракт v2.0 — одна задача (7): модели, копия схем, сборка книги, путь ОСВ и
|
||
воркер меняются вместе; «контракт v2.0 в копии» отдельной последней задачей не вынесен — модели 2.0 без копии 2.0 (или
|
||
наоборот) валят `result_contract` и тест идентичности. Golden и манифест — сразу за переключением (задача 8), до
|
||
удаления старого кода; между задачами 7 и 8 проверка идёт с `ARM_KA_FIXTURES=/nonexistent`. Цена: одна крупная задача и
|
||
окно в одну задачу без проверки на реальных файлах.
|
||
- **P4-2.** Адаптер пишется новыми модулями рядом со старыми (`cells`, `forms`, `header_cells`, `adapter`, `contract`,
|
||
`pipeline`); старые `statements/*` не правятся и удаляются целиком задачей 9. Цена: временное дублирование логики
|
||
поиска блоков и шапки.
|
||
- **P4-3.** `Cell.text` числа Excel — как в русской локали: группы по три через пробел, если в формате есть разделитель
|
||
групп; десятичная запятая; литералы секции (скобки) сохраняются; «-» добавляется только у односекционного формата;
|
||
`General`/неразобранный формат/xlsb — число без групп и экспоненты, целое без дробной части; дата — `dd.mm.yyyy`;
|
||
логическое — «ИСТИНА»/«ЛОЖЬ». Цена: у экзотических форматов текст может отличаться от экрана Excel; `value` и статус
|
||
от этого не зависят (у Excel значение берётся из `value`). Совпадает с примером docs
|
||
`result-statement-excel.json` (`"586 260,26"`).
|
||
- **P4-4.** Показываемое число вне `numeric(18,2)` больше не делает формат «неразобранным»: адаптер отдаёт `Decimal`, а
|
||
`VALUE_OUT_OF_RANGE` и `amount = null` выдаёт пакет (спека «Шаг 4»). Цена: правка поведения #42 для чисел больше 16
|
||
целых цифр (на реальных файлах их нет).
|
||
- **P4-5.** Сетка кандидата: строки блока целиком, включая пустые; объединённая ячейка — только в позиции левой верхней,
|
||
прочие позиции `None`; ширина — наибольшая среди листов кандидата. Цена: подпись, объединённая на два столбца, видна
|
||
пакету только в левом.
|
||
- **P4-6.** Шапка ищется в строках блока выше первой строки с кодом его столбца кодов (столбец — `code_column` пакета:
|
||
то же правило (а)/(б) шага 2 с защитой от блока «Коды», своего правила у адаптера нет), без кодов — во всём блоке: так титульный блок «Коды» (ОКОПФ 12267) не мешает, а подписи под формой (ИНН и ФИО в блоке
|
||
подписей) не попадают в шапку. Цена: метка шапки ниже начала таблицы не найдётся.
|
||
- **P4-7.** Шаблоны словаря пакета адаптер применяет к тексту, нормализованному функцией пакета по таблице «Словари»
|
||
раздела API плана 1: метки шапки, заголовки, ОКУД, `auditor`, `legal_form` — `normalize_label` (= прежний
|
||
`normalize_text` tabular-parser, P1-22); подпись «Код» и относительные периоды — `normalize_caption`; маркер
|
||
продолжения — `is_continuation`. Цена: нет — смена нормализации в пакете меняет и `type_hint`, и адаптер.
|
||
- **P4-8.** Склейка: открытая форма — последний кандидат с `type_hint` `BALANCE`/`INCOME`; лист с маркером сверяется только
|
||
по типу; лист без маркера (сильное продолжение — столбец кодов по правилу шага 2 или своя строка заголовка; слабое —
|
||
столбец кодов ослабленным поиском `code_column(…, relaxed=<тип формы>)`, минимум `min_codes_continuation` кодов,
|
||
сразу после листа формы) — `sheet_joins` с числом колонок сумм `len(amount_columns(...))` пакета («столбцы правее
|
||
столбца кодов, где в строках с кодами есть число»); открытой формы нет — отдельный кандидат без предупреждения (пакет даст `NO_ORGANIZATION_NO_INN`).
|
||
Цена: «Изменение, %» с числами считается колонкой сумм с обеих сторон сравнения; лист с иной шапкой, но тем же числом
|
||
колонок, присоединится и будет сопоставлен пакетом по датам.
|
||
- **P4-9.** `SHEET_OTHER` для листов «прочее» выдаёт книга сама (документа и кандидата нет), а не пакет по кандидату без
|
||
`type_hint`. Цена: два места порождения одного кода (у пакета — для кандидатов, не нашедших ни типа, ни кодов).
|
||
- **P4-10.** `XLSB_NO_FORMATS` — у каждого кандидата книги xlsb (P1-5); `NUMBER_FORMAT_UNPARSED` — одно на кандидата с
|
||
перечнем форматов всех его листов. Цена: у пояснений xlsb тоже есть `XLSB_NO_FORMATS` (`info`).
|
||
- **P4-11.** Книга вызывает `normalize_candidate` по одному кандидату, а не `normalize`: так проверяется дедлайн между
|
||
кандидатами (`header.form_name` пакет ставит сам из `evidence.title`, пара «кандидат → документ» не нужна).
|
||
Исключение пакета на кандидате — `RECOGNITION_FAILED` всей книги, как сейчас (`TABLE_PARSE_ERROR` — код таблицы ИС).
|
||
Цена: одна сломанная форма роняет книгу с ОСВ.
|
||
- **P4-12.** Коды ОСВ — по источнику предупреждения, не по тексту: `OSV_HIERARCHY_BY_<СТРАТЕГИЯ>`, `OSV_HEADER`,
|
||
`OSV_ROWS`, `OSV_INN_ROWS`, `OSV_NO_DATA_ROWS`, `OSV_BALANCE_IDENTITY`, `OSV_PARENT_SUM`, `OSV_TOTAL`,
|
||
`OSV_XLSB_NO_OUTLINE`; тексты сообщений прежние, кроме иерархии. Цена: внутри `OSV_HEADER` и
|
||
`OSV_ROWS` разные случаи различаются только текстом.
|
||
- **P4-13.** Заметки читателя: несовпадение формата — `FILE_TYPE_MISMATCH` файла; «xls без кодовой страницы» —
|
||
`XLS_NO_CODEPAGE` файла (`info`, код пакета, P1-23) в любой книге xls — касается и форм, и ОСВ; «xlsb без outline» — в
|
||
каждый документ ОСВ книги (`OSV_XLSB_NO_OUTLINE`), в книге без ОСВ только логируется (иерархия строк — только у ОСВ).
|
||
Цена: предупреждение о кодировке видно в карточке файла, а не у листа ОСВ.
|
||
- **P4-14.** Предупреждение «форма не разобрана: книга завершилась ошибкой листа ОСВ» снято: в v2.0 для него нет кода, а
|
||
текст ошибки и так называет лист ОСВ. Цена: аналитик не видит списка листов форм, оставшихся неразобранными.
|
||
- **P4-15.** Статус документа ОСВ — прежние условия листа: контрольные суммы §1.4 сошлись и строки ИНН без нарушений
|
||
(`glue.ok`, #35). Цена: формулировка спеки «когда контрольные суммы сошлись» читается шире.
|
||
- **P4-16.** Нет `api/arm_statements/VERSION` — сервис не стартует (`RuntimeError`), как при ошибке словаря; в golden
|
||
`service.statements_version` — volatile-поле. Цена: копия без `VERSION` не запустится даже локально.
|
||
- **P4-17.** Новые синтетические случаи (форма xlsb, книга из пояснений, конфликты продолжений) — в памяти в юнит-тестах
|
||
(`sheet_from_rows`, `write_xlsb`), новых файлов в `fixtures/statements-xlsx/synthetic` нет; PR fixtures — только golden.
|
||
- **P4-18.** Манифест получает необязательное поле `error_warnings` (точное множество кодов `error` документа);
|
||
проверка уникальности кодов строк снята (повторы законны, Р-4). Цена: при правке пакета, меняющей набор
|
||
предупреждений, манифест правится вместе с golden.
|
||
- **P4-19.** Тест покрытия словарей tabular-parser больше не охватывает словари форм — они в пакете. Цена: «мёртвый»
|
||
синоним заголовка или метки шапки на реальных файлах не ловится (O-8).
|
||
- **P4-20.** В tabular-parser — свой change `unified-normalization-tp` с дельтами `statement-parsing`,
|
||
`tabular-recognition-service`, `osv-parsing` (по образцу P0-5); требование «Копия контракта идентична источнику» правит
|
||
план 0, здесь не трогается.
|
||
- **P4-21.** Golden — отдельные issue, ветка и PR в RSHB/arm-ka-fixtures (один PR на репозиторий); сливается сразу за PR
|
||
tabular-parser. Цена: короткое окно, когда `make check` `main` одного из них красный на golden.
|
||
- **P4-22.** Две формы на одном листе дают два документа с одинаковым `locator` (спека уникальности `locator` не требует;
|
||
`ref` строк различаются).
|
||
|
||
### Рулинги контроллера при исполнении (R-n)
|
||
|
||
- **R-1.** docs-worktree отсоединён на origin/main (docs #117 слит), а не на feat/116 (П-1/П-5 плана). Цена ошибки: нет.
|
||
- **R-2.** Предупреждение tp #47 «xls без кодовой страницы…» — `XLS_NO_CODEPAGE` пакета (info, файл, без locator); нового
|
||
`OSV_*`-кода нет; текст сообщения reader'а tp #47 сохранён; передаётся флагом `BookRead.no_codepage`. Цена: правка
|
||
`book.py` и golden.
|
||
- **R-4.** Задача 1 (markdown-дельты) без отдельного task-ревью — проверена `openspec validate --strict` и финальным
|
||
ревью ветки. Цена: неточность формулировки дельты.
|
||
- **R-5.** Проверка диапазона numeric(18,2) в `display_amount` не возвращается (P4-4); диапазон и `VALUE_OUT_OF_RANGE`
|
||
выдаёт пакет; старый конвейер удалён задачей 9. Цена: нет после удаления.
|
||
- **R-6.** Для |raw| ≥ 1e15 текст/значение числа без округления до 15 значащих цифр (иначе число из 17 цифр теряет
|
||
цифры); пробел групп — обычный U+0020 как в примере docs `result-statement-excel.json`. Цена: расхождение с NBSP
|
||
русского Excel, на value/статус не влияет.
|
||
- **R-7.** Ячейки из одних цифр не считаются подзаголовком даты `report_date` (иначе год из тройки «число|месяц|год»
|
||
принимается за подзаголовок), как в прежнем `header.py`. Цена: подзаголовок из голого числа «2025» не распознаётся.
|
||
- **R-8.** Ячейка в строке-шапке таблицы (в строке есть подпись «Код» по словарю пакета `code_caption`) не заголовок
|
||
формы: подпись колонки «Пояснения» (`'^пояснени'` в `form_titles` пакета) делала лист-продолжение НПЗ ещё одной формой
|
||
DISCLOSURE. Дефект адаптера (спека называет «Пояснения» подписью колонки), пакет не правится. Решение владельца:
|
||
оставить, посмотреть позже. Цена: компактная шапка, где заголовок формы и «Код» в одной строке, потеряет заголовок.
|
||
- **R-9.** Предупреждения адаптера при кандидате без документа — предупреждения файла со своим `locator`; порядок
|
||
`metrics.warnings` — сначала reader, затем по номеру первого листа; `XLS_NO_CODEPAGE` и `OSV_XLSB_NO_OUTLINE` — флаги
|
||
`BookRead` (`no_codepage`, `no_outline`). Цена: правка `book.py` и golden.
|
||
- **R-10.** НПЗ, ОФР, строки «Реализация иного имущества, прав требования, прочих работ и услуг» (F34, F44): значения 0
|
||
с форматом `[=0]"-"` — только прочерки, суммы нет; по спеке «строка без кода и без содержимого — не строка данных»,
|
||
пакет её не выдаёт; значение прочерка не превращаем в 0 (сломало бы различие null/0 и соотношения §7).
|
||
`test_npz_documents` не требует detail/parent_ref 2340. Вопрос владельцу O-8.2: сценарий спеки «Реализация иного
|
||
имущества… с суммами» не соответствует файлу. Цена: строки из прочерков не видны аналитику.
|
||
- **R-11.** Задача 9 (удаление по списку) без отдельного task-ревью — покрыта финальным ревью ветки.
|
||
- **R-12.** Остановка Р-31 по Межениновской (посторонние формулы аналитика в столбцах U–W) снята владельцем 18.10.2026:
|
||
правило в пакете (docs #123, Р-44; arm-statements #6, 3390f92) — столбец без подписи правее последней подписанной
|
||
колонки с числами не колонка сумм, предупреждение `NUMBERS_OUTSIDE_TABLE` (info). Копия пакета обновлена коммитом
|
||
f5d071f. Цена: нет.
|
||
- **R-13.** Ложная склейка/дедлайн: `plan_sheets` получает `deadline` (финальное ревью I-1, регрессия таймаута против
|
||
main). Цена: нет.
|