Andrey Epifancev 47e6372cbe chore(openspec): архив unified-statement-normalization (#116)
Спеки применены: 5 новых capability (arm-core, arm-web, scan-gateway,
statement-normalization, tabular-recognition-service) с Purpose, изменения
quality-checks и recognition-contract. Ссылки в 05-domain-rules, DBML v5.19,
entities, dev/checks.md и dev/qa-statements-v2.md ведут в архив.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 09:25:43 +04:00

152 lines
19 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

## Why
Формы ББ/ОФР сегодня нормализуются двумя разными конвейерами: scan-gateway (PDF → ИС Сканирование) и tabular-parser
(Excel). У них разные правила раскладки таблицы, отбора строк, разбора сумм, проверок §7 и статуса; ядро не доверяет
ни одному из них и пересчитывает проверки и статус заново по косвенным признакам, дважды дедуплицирует строки и держит
третью и четвёртую копии словарей (§7 и «код → наименование»). Пользователь видит последствия как отдельные «ошибки
распознавания» — AURORA-1406, 1462, 1511, 1517, 1519, 1522, 1523, 1524, 1634: строки молча пропадают, повтор кода
теряет сумму, «чистая» ячейка с потерянной цифрой не подсвечивается, предупреждения сервисов нигде не видны.
Дизайн `design.md` (финальная версия, решения владельца Р-1…Р-19 от 17.10.2026, три раунда ревью) приводит оба
сервиса к одной цепочке шагов 1–9 с единым выходом каждого шага и общим кодом шагов 2–8; ядро хранит результат как
пришёл и пересчитывает его тем же общим кодом только после правок аналитика.
Срез: вне плана срезов `09-delivery-plan.md` — переработка Среза 2b (Excel-формы), Среза 3a (OCR-нормализатор) и
Среза 3b (формы в ядре). Issue RSHB/arm-ka-docs#116.
## What Changes
- **Общий код нормализации** — новый репозиторий `arm-statements`, пакет `arm_statements/` (Р-1): типы шага 1
(`FormCandidate`, `Cell`), шаги 2–8 (раскладка, отбор строк, значения, код и наименование, сборка документа,
самопроверки §7, статус и `needs_review`), словари §7 (полный и упрощённый наборы), периодов, заголовков форм,
подписей колонок, упорядоченные перечни кодов форм по редакциям и вариантам, диапазоны кодов. Словаря «код →
наименование» в пакете нет (Р-3). В tabular-parser, scan-gateway и arm-core пакет лежит копией, которую раскладывает
и проверяет скрипт `sync-statements` репозитория docs (по образцу `sync-web.sh`).
- **Строки не выбрасываются молча:** статусы строк `ok` | `unreadable` | `missing` | `detail`, `row_index`, `ref`,
сырой текст кода и ячейки (`text`) доезжают до БД и экрана; повтор кода — законный случай, все строки сохраняются
(Р-4, Р-8). **BREAKING** для потребителей: `line_code` становится nullable.
- **Качество структурно и с привязкой к месту:** `needs_review` у значения, строки и полей шапки; «подтверждение
проверкой» §7 вместо порога уверенности (Р-5, Р-7, Р-11, Р-12, Р-16, Р-17); `document.status` у каждого документа;
предупреждения — структуры `{code, severity, message, line_refs, column_index, field}` вместо строк.
- **Контракт `recognition-result.schema.json` v2.0** (MAJOR, Р-9) и конверт `recognition-result-message.schema.json`
v2.0 (структурные `metrics.warnings`); запрос `recognition-request` остаётся v1.0. Путь ОСВ в tabular-parser
меняется только в части структуры предупреждений и `document.status`. На окне перехода схемы 1.x лежат рядом
(`*.v1.schema.json`, примеры `examples/v1/`); тест идентичности копии у ядра и сервисов сверяет файл копии с файлом
docs своего MAJOR, если он есть, иначе с основным (схемы и примеры; правило — спека `recognition-contract`).
- **Упорядоченные перечни кодов форм** — данные `contracts/statement-code-orders.yaml` (66н / ФСБУ 4, полная /
упрощённая, законные повторы кода — Р-20); в пакет `arm_statements` — байтовой копией.
- **arm-core:** DBML v5.19 и одна миграция (статусы и порядок строк, `needs_review`, `is_confirmed`, `source_text`,
`period_kind`, `form_variant`, таблица `document_issue`, снятие уникальности `(meta_id, line_code)` и — для ББ/ОФР —
`document (stored_file_id, document_type, osv_account_code)`, `recognition_job.statements_version`,
`related_group.crm_id` по arm-core#112); «агрегат формы по коду» вместо «одна строка на код»; приём 1.x и 2.0 на
время перехода; новое условие `RECOGNIZED`; PATCH `…/statement` с `lines`, `columns`, `values[].confirmed`; 409
`LINES_UNRESOLVED` и `DUPLICATE_FORM` при подтверждении.
- **arm-web:** экран проверки формы — строки со статусами на своих местах, расшифровки под переключателем, подсветка
только по `needs_review`, предупреждения и нарушения у места, назначение кода, «Это расшифровка», подтверждение кода
и ячейки, дата колонки.
- **Документы:** `05-domain-rules.md` — §11 становится общей главой нормализации ББ/ОФР с подразделами по
источникам, §12 — заголовок-редирект; §7 — «посчитанное» соотношение, `abs_terms` и знак, упрощённый набор; §9.3,
§9.5 — статус, маппинг и подтверждение формы по v2.0. `07-open-questions.md` — закрыты OQ-36, OQ-141 (Р-8) и
вопросы, снятые решениями дизайна; новые вопросы — склейка половинок формы (Р-15) и открытое в дизайне.
`frontend-api-guide.md` — новые поля формы и PATCH (после реализации).
Читают и пишут контракт: `arm-core` (читает результат и конверт, публикует запрос), `tabular-parser` и `scan-gateway`
(читают запрос, пишут результат и конверт). Дочерние change'и (заводятся при планировании, по одному на репозиторий):
`arm-statements` (новый), RSHB/arm-core, RSHB/arm-tabular-parser, RSHB/arm-scan-gateway, RSHB/arm-web; связанная
задача — RSHB/arm-core#112 (`related_group.crm_id` входит в ту же миграцию, Р-10).
### Порядок выкладки — «сначала ядро» (обязателен)
0. arm-core, tabular-parser и scan-gateway: тест идентичности копии контракта по новому правилу — схема копии
сверяется с `<имя>.v<M>.schema.json` своего MAJOR, если такой файл есть в docs, иначе с основным; пример — с
`examples/v<M>/<имя>.json`, если он есть, иначе с `examples/<имя>.json`; файлы другого MAJOR не сверяются. Правило
проходит и до слияния шага 2 (файлов `.v1` ещё нет), и после; без него `make check` всех трёх краснеет между шагами 2
и 3 (ядро) или 2 и 5 (сервисы).
1. `arm-statements` — пакет с тестами; `sync-statements` в docs; копия пакета подключается к хостам (линтеры,
`testpaths`, `statements-check`) — код сервисов её ещё не вызывает.
2. docs: контракт v2.0, DBML v5.19, правила (этот change).
3. arm-core: **сначала очистка форм ББ/ОФР на контуре** (Р-24: владелец или платформенная команда по его заявке,
после резервной копии БД — скрипт ядра `scripts/purge_statements.py`: `--dry-run` с отчётом, затем `--apply`; удаляются
документы `BALANCE` / `INCOME`, их `financial_statement_*`, комментарии и привязки к версиям «в работе», а файлы, где
кроме форм нет ОСВ, — целиком с заданиями; смешанные файлы «ОСВ + формы» — по решению владельца по отчёту; при
формах в верифицированных версиях скрипт отказывает; ОСВ, консолидации, справочники не трогаются — спека `arm-core`,
«Очистка форм перед выкладкой»), затем миграция v5.19 (только колонки, индексы и значения по умолчанию; §7 для
старых форм не считается), маппер 2.0 рядом с маппером 1.x, приём ack и конвертов 1.x и 2.0, PATCH, экранные поля
API. Формы пересоздаются повторной загрузкой файлов. До этого шага ни один сервис не шлёт 2.0: первый же
`line_code = null` дал бы файлу `ERROR`.
4. arm-web: экран по новым полям (поля 1.x-документов заполнены по умолчанию — экран работает и на них).
5. tabular-parser и scan-gateway переходят на 2.0 в любом порядке относительно друг друга.
6. После перехода обоих сервисов маппер 1.x удаляется из ядра отдельным PR (не в этом change).
Копии схем в сервисах (`api/contracts/`, тест идентичности в `make check`) после слияния контракта v2.0 в docs
сверяются с `*.v1.schema.json` и `examples/v1/` (правило шага 0; у запроса файла `.v1` нет — с основным); каждый сервис
обновляет копию до v2.0 в своём PR шага 5, ядро держит копии обоих MAJOR с PR шага 3. Схемы `*.v1.schema.json` и
`examples/v1/` удаляются вместе с маппером 1.x (шаг 6): сначала PR ядра убирает свои копии `.v1`, затем docs.
**Чего ждать на реальных сканах.** Значения строк `detail` в `needs_review` и в статус не входят (Р-23), законные
повторы кода и мягкие строки ОФР документ не держат (Р-20, Р-21). Поэтому ББ и ОФР РБ («Бухгалтерская отчетность 1
кв.25») и Счастливочной («БО 1 кв 2025») могут стать `done`: по прогону ревью все их неуверенные значения строк с кодом
покрыты сошедшимися соотношениями, неуверенные коды подтверждаются перечнем `fsbu4`, а держали их только ячейки
расшифровок. aep_3 и aep_6 остаются `needs_check` (строки `unreadable` / `missing` с суммами, несошедшиеся
соотношения). Фактическая доля `done` фиксируется в PR scan-gateway.
## Non-goals
- Разбор ОСВ (`classify.py`, `to_money` ОСВ, правила §1) не меняется; путь ОСВ в tabular-parser меняется только в
структуре предупреждений и `document.status`.
- Переключатель «как в файле» (порядок строк по `row_index`) на экране — не в этой работе (Р-14).
- Склейка двух половинок формы из одного файла в ядре не предоставляется — файл перезагружают; вопрос в
`07-open-questions.md` (Р-15). Склейка страниц из разных файлов (OQ-140) — тоже нет.
- Восстановление строк по наименованию (OQ-149) и сопоставление формы без колонки кодов по именам строк (OQ-139) — нет.
- Запрос `recognition-request` остаётся v1.0; subject'ы NATS, `Nats-Msg-Id`, ack и повторы не меняются по механике.
- Межформенная проверка `1370 − 1370(нач. года) = 2400` остаётся в ядре и мягкой; формулы §7 по редакциям (OQ-146) и
знаки 2410/2420 (OQ-12) не пересматриваются.
- Замена пакета-копии на зависимость из Nexus платформы — не сейчас (платформа Python-пакеты не собирает, Р-1).
- Приведение единиц измерения сверх числового формата ячейки (OQ-138) — нет.
## Capabilities
### New Capabilities
- `statement-normalization`: общая цепочка нормализации ББ/ОФР (шаги 1–8) — пакет `arm_statements`, его типы,
правила и словари, `sync-statements`.
- `tabular-recognition-service`: адаптер Excel шага 1 в tabular-parser и что остаётся в сервисе (чтение книги, листы
ОСВ, сборка конверта v2.0).
- `scan-gateway`: адаптер json ИС шага 1 в scan-gateway и что остаётся в сервисе (горячие папки, склейка набора json,
`raw/`, сборка конверта v2.0).
- `arm-core`: хранение результата v2.0 (DBML v5.19), агрегат по коду, `document_issue`, условие `RECOGNIZED`, PATCH
формы, порядок 409 подтверждения, приём 1.x и 2.0.
- `arm-web`: экран проверки формы ББ/ОФР по новым полям.
### Modified Capabilities
- `recognition-contract`: файл результата и конверт — v2.0; правила перехода (ядро принимает 1.x и 2.0, «сначала
ядро»); статус и структурные предупреждения у всех документов, включая ОСВ; тест идентичности копии — по MAJOR.
- `quality-checks`: `make check` охватывает копию `arm_statements` (тесты, `mypy`), `make check-full` — `statements-check`
(`sync-statements --check`).
## Impact
- Новый репозиторий `arm-statements`; скрипт `sync-statements` и цель Makefile в docs (вызывается из `make check-full`
потребителей, не из `make check`; коды 0 / 10 / 20 / 2).
- docs: `contracts/statement-code-orders.yaml`, схемы `*.v1.schema.json` и `examples/v1/` на окне перехода;
`scripts/validate_contracts.py` проверяет оба MAJOR и перечни кодов, `make venv` ставит PyYAML.
- tabular-parser: `parser/statements/*` (кроме разбора книги и адаптера шага 1) уходит в общий код; словари
`statement_checks.yaml`, `statement_periods.yaml`, `statement_headers.yaml` переезжают в пакет; путь ОСВ — структура
предупреждений и `document.status`; манифест `api/tests/statements_manifest.yaml` обновляется: `failed_relations`
страниц 2 баланса (`unagrande-co/2024-FY/Б.Б. Лист 2.xls`, `unagrande-group/2024-FY/ББ. Лист 2 (2).xls`,
`unagrande-group/2025-Q3/ББ. Лист 2 3кв.25.xls`) получают `1600 = 1700` («посчитанное» соотношение, пустая цель),
статусы `done` не меняются.
- scan-gateway: `normalizer/ocr/text|dates|amounts` становятся общими разборами, `layout|rows|header|document` — шагами
2–6 общего кода; `api/rules/statement_lines.yaml` удаляется, `statement_labels.yaml` и `ocr_lookalikes.yaml` уходят в
словари пакета.
- arm-core: очистка форм ББ/ОФР перед выкладкой (`scripts/purge_statements.py`, Р-24); миграция v5.19
(`financial_statement_*`, `document`, `document_issue`, `recognition_job`, `related_group`; только колонки, индексы и
значения по умолчанию, §7 по старым формам не считается — на неочищенной базе их нарушения до первой правки не
показываются); `JobBrief.warnings` — структуры, `DocumentOut.issues` (в том числе ОСВ); `result_mapping`, `statement_mapping`,
`statements`, `documents`, `auto_confirm`, схемы API формы; признак строки правки — `row_index IS NULL`;
`api/rules/statement_checks.yaml` удаляется (копия словаря §7), `statement_lines.yaml` остаётся только словарём имён
(несколько имён на код, загрузчик повтор кода не запрещает); PATCH шапки принимает `organization_name`.
- arm-web: `widgets/DocumentReview` (экран [41]), типы API.
- Решение v5.13 «`needs_review` считается, не хранится» пересматривается (design, Decisions № 17).
- Риск окна выкладки: пока сервис шлёт 1.x, документы его конвейера получают статус по прежнему правилу D-6 и поля
v2.0 по умолчанию — экран и подтверждение работают, новых сигналов качества нет.