Andrey Epifancev 4d30718689 docs: дельты OpenSpec, design.md, README и CLAUDE.md под контракт 2.0 (#54)
I-3: дельты osv-parsing (листы ОСВ, иерархия, golden, XLS_NO_CODEPAGE) и
перечень словарей api/rules; пункт об архивации (Purpose спек) в tasks.md.
I-4: README, CLAUDE.md и docstring hierarchy.py совпадают с кодом, пример
вывода CLI переснят. M-6: tasks.md, proposal.md, design.md с решениями P4/R.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BJKMLHdxMwSUeYcdkwqX4W
2026-10-18 22:08:30 +04:00

17 KiB
Raw Blame History

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). Цена: нет.