- 05-domain-rules §1.0 п. 1: признак ОСВ в первых 30 строках листа; п. 4, §1.3: ИНН по значениям без дополнения нулём, дополнение при извлечении (R-24); ACCOUNT_RE с re.ASCII | re.DOTALL (M-1); §1.4: остаточные риски R-21 (RR-1…RR-4, слепые зоны); §1.6: лимиты чтения (R-23), логирование. - 06-test-data §3 п. 17: ИНН в тексте строки — ОСВ 76 Счастливочной за 1–3 кв. 2024 (6 + 7 + 7 строк). - 07-open-questions: OQ-106 (Итого = первый корень), OQ-107 (hierarchy=sums), OQ-108 (defusedxml, лимиты чтения), OQ-109 (размер объекта S3 до скачивания); OQ-105 ссылается на риски §1.4. - OQ-81 «частично закрыт» в 09-delivery-plan, tasks.md и proposal.md change. - contracts/examples/result-osv-needs-check.json — как пишет парсер (копия tabular-parser байт в байт). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
191 lines
22 KiB
Markdown
191 lines
22 KiB
Markdown
# 09. План работ вертикальными срезами
|
||
|
||
Принцип: каждый срез — рабочий end-to-end кусок (миграции + API + минимальный UI + тесты на реальных фикстурах),
|
||
который можно показать аналитику. Порядок выбран так, чтобы самое рискованное (парсер ОСВ, матчинг ВГО) проверялось
|
||
на реальных данных как можно раньше, а внешние зависимости (ИС Сканирование, РАИСА, NATS платформы) не блокировали.
|
||
|
||
Каждый срез = OpenSpec change **в каждом затронутом репозитории** (`openspec/changes/<slug>/` с `proposal.md`, `specs/`,
|
||
`tasks.md`), сквозная часть — change в `docs/`; задачи — issues в Gitea; PR — по одному на репозиторий. Jira-эпики
|
||
пользователя маппятся на срезы (пользователь ведёт соответствие сам).
|
||
|
||
## Срез 0 — Скелет (2–3 дня, три репозитория + docs)
|
||
|
||
- Три репозитория из нулевого коммита шаблона команды; соглашения шаблона — в `03-architecture.md` и в `CLAUDE.md`
|
||
каждого репозитория. **Первая задача агента — прочитать шаблон и дополнить документы.** (Сделано: раздел «Соглашения
|
||
шаблона команды» в `03-architecture.md`; в `tabular-parser`/`scan-gateway` фронт и БД вырезаются задачами среза.)
|
||
- OpenSpec change'и среза: `docs/openspec/changes/slice-0-skeleton` (сквозной: контракт, compose, проверки качества, документы) и
|
||
по одному `openspec/changes/slice-0-skeleton` в `arm-core`, `tabular-parser`, `scan-gateway`; issue #1 в каждом репозитории.
|
||
- `docker-compose` workspace-уровня (`docs/dev/docker-compose.yml`): PostgreSQL 16, NATS 2.10 **без JetStream** (как на
|
||
платформе; ack и повторная публикация — на уровне приложения), MinIO (S3, один бакет `arm-ka`), эмулятор ИС
|
||
Сканирование (часть `scan-gateway`, отдаёт `fixtures/ocr/aep_N.json`), три сервиса (профиль `services`, Python 3.11).
|
||
`Makefile` с общими целями `up/test/test-integration/lint/check/check-full` (+ `migrate/seed` в ядре) в каждом репо.
|
||
Порты, запуск профиля `services` и ручная сквозная проверка обмена `recognition_job` через NATS (сценарии `tabular` →
|
||
`NOT_IMPLEMENTED`, `image` → эмулятор ИС → `INVALID_DOCUMENT`, потеря ack → `dlq`) — `docs/dev/README.md`,
|
||
разделы «Профиль `services`» и «Проверка окружения».
|
||
- Контракт v1.0 — три схемы в `docs/contracts/`: `recognition-request`, `recognition-result-message` (конверты NATS) и
|
||
`recognition-result` (файл результата) + `examples/`. Общий пакет `arm-ka-contracts` **не публикуется**: каждый сервис
|
||
хранит копию схем в `api/contracts/` и pydantic-модели в `src/contract/`, идентичность с `docs/contracts` проверяется в `make check`
|
||
(решение D-1 change `slice-0-skeleton` в docs).
|
||
- Alembic: миграция 0001 — **все** таблицы и enum'ы DBML v5.13 (черновик из ORM, сверка с DBML скриптом
|
||
`api/scripts/check_dbml_vs_orm.py` — расхождение = ошибка CI).
|
||
- Seed (`arm-core/api/seed/`, идемпотентно): `regional_office` (ГО; примеры РФ — только `--dev`), `role` (7),
|
||
`permission` (16 осмысленных сочетаний объект × действие по §10), `role_permission` по §10, `chart_of_account`
|
||
(План счетов 94н + типовые субсчета 1С, OQ-47), `elimination_rule` v1 из §4 (только ББ), `required_document_rule`
|
||
для BASIC/EXTENDED (OQ-46).
|
||
- Auth: валидация JWT + dev-эмитент; `app_user` seed (по одному на роль); зависимость `require(permission)`.
|
||
- `audit_event` helper; `s3_object` клиент; outbox-паблишер (ack `processing`, повторная публикация без ack, `dlq`) и
|
||
подписка `recognition.result` (дедуп по `result_msg_id`, пустой маппер документов).
|
||
- Проверки качества вместо CI (в Gitea CI нет, пайплайн платформы вне репозиториев): `make check` (ruff, mypy, pytest,
|
||
контракт) и `make check-full` (+ интеграционные тесты против compose, цикл миграций, `check_dbml_vs_orm`) — `docs/dev/checks.md`.
|
||
- DoD: `GET /api/v1/me` возвращает пользователя и его эффективные права; все миграции применяются и откатываются;
|
||
`make check-full` зелёный в трёх репозиториях.
|
||
|
||
## Срез 1 — Справочники (ГК / ЮЛ / ГСК / ГСЗ / филиалы) + пользователи и роли — **завершён 05.10.2026**
|
||
|
||
Объём — **вариант Б** по решению владельца 04.10.2026: без UI справочника, с минимальным экраном администратора; цель —
|
||
быстрее дойти до Среза 2 (`tabular-parser`). Изначальный состав среза (CRUD + импорт + админка + policy + фикстуры +
|
||
UI справочника по макету) разделён: всё, кроме UI справочника, сделано; UI вынесен в отдельный срез.
|
||
|
||
**Сделано (по факту):**
|
||
|
||
- **API справочников** (`arm-core`, `/api/v1/directories/*`): ГК (`code`, `crm_id` nullable, закрепление за РФ, архив,
|
||
история SCD4), ЮЛ/ИП (ИНН 10/12, алиасы, перевод в другую ГК с `effective_date`, закрытие/вывод из ГК, история),
|
||
ГСК/ГСЗ и состав (SCD2; ЮЛ — одна ГСК и одна ГСЗ одновременно), филиалы (чтение). Каждая мутация пишет `audit_event`.
|
||
- **Импорт из выгрузки ЕЦП CRM** (`POST /directories/import`, право `DIRECTORY_ADMIN`): правила — `05-domain-rules.md` §13,
|
||
`dry_run` по умолчанию, `on_existing ∈ {skip, update}`, отчёт с кодами, `audit_event(DIRECTORY_IMPORT)` и файл в S3;
|
||
полная выгрузка (8400 строк) обрабатывается за ~1 с.
|
||
- **Policy видимости** (§10.1): по `regional_office.is_head_office`, без проверки кода роли; ЗО не фильтрует (OQ-86);
|
||
объект вне видимости — 404.
|
||
- **Админ-API и минимальный экран `/admin`** («Пользователи и роли»): пользователи, роли, матрица прав
|
||
(`USER_CHANGE`, `USER_ROLE_CHANGE`, `ROLE_PERMISSION_CHANGE`), защита `LAST_ADMIN`; вход — dev-форма (SSO РАИСА — позже).
|
||
- **DBML v5.14** (`related_group_member.kind`, partial unique `(legal_entity_id, kind)`, `audit_action` +
|
||
`LEGAL_ENTITY_CHANGE`/`USER_CHANGE`) и **миграция `0002`** в `arm-core`; `entities-v5.14.md`.
|
||
- **Фикстура** `fixtures/directories/ecp-export-russkiy-biskvit.xlsx` (21 строка ГК «Русский Бисквит»; `06-test-data.md` §6.1).
|
||
- **Документы:** §13 и §10.1 в `05-domain-rules.md` (сверены с кодом, задача 3.1 change `slice-1-directories`), OQ-31/32
|
||
закрыты, OQ-86…OQ-97 открыты.
|
||
- **PR:** arm-core #6, docs #7, fixtures #3 (issue-ы: arm-core #5, docs #6, fixtures #2).
|
||
|
||
**Перенесено в будущий срез «UI справочника»** (номер не присвоен; планируется после Среза 2):
|
||
|
||
- Экраны справочника по макету: «ГК и организации» [50] (список ГК | карточка с вкладками ЮЛ / ГСК / ГСЗ / Архив),
|
||
создание ГК/ЮЛ/ГСК/ГСЗ [51], «ЮЛ вне ГК» [61], вкладка ГСК/ГСЗ [77], изменение ЮЛ [78]–[79], перевод ЮЛ [79-01],
|
||
вывод из состава ГК [80], модалка импорта [38]. API для них готов с Среза 1 (см. `10-user-journey.md`).
|
||
- Пока UI нет, справочники заполняются импортом ЕЦП и вызовами API (`/docs`).
|
||
|
||
## Срез 2 — Загрузка файлов + табличный парсер (ОСВ, ББ, ОФР из Excel) — самый рискованный, делать рано
|
||
|
||
Решение владельца 05.10.2026: срез делится на четыре подсреза со своими change'ами и PR; сначала **только парсер ОСВ**.
|
||
Порядок: 2a → 2b → 2c → 2d. DoD среза в целом (без правил под конкретный файл, без координат в коде парсера) — в конце раздела.
|
||
|
||
### Срез 2a — Парсер ОСВ (`tabular-parser`) — реализован, ожидает merge
|
||
|
||
Change'и: `tabular-parser/openspec/changes/slice-2a-osv-parser` (issue arm-tabular-parser#5, PR #6), сквозной
|
||
`docs/openspec/changes/slice-2a-osv-parser` (arm-ka-docs#9), фикстуры arm-ka-fixtures#4. Копия схемы v1.1 в `arm-core` —
|
||
отдельный bounded-PR без логики после merge docs и tabular-parser.
|
||
|
||
**Сделано (по факту):**
|
||
|
||
- **Парсер ОСВ** `tabular-parser/api/src/parser/`: динамическое обнаружение структуры по содержимому (§1.0): листы ОСВ,
|
||
заголовок таблицы и колонки сумм по тексту (обязательны «обороты» и «сальдо на конец»), шапка и `group_schema`
|
||
(варианты 1 и 2), иерархия каскадом `outline → indent → account_pattern → sums` (+ `trivial` для пустой ОСВ),
|
||
классификация строк, `Decimal`, контрольные суммы §1.4, статусы и коды (`05-domain-rules.md` §1.6), лимиты
|
||
`PARSE_MAX_BYTES` (50 МиБ) и `PARSE_TIMEOUT_SECONDS` (120 с). Чтение XLSX/XLS/XLSB по сигнатуре.
|
||
- **Словари** `api/rules/{osv_headers,osv_groups,periods,data_types}.yaml` (данные, не код) с проверкой при старте и
|
||
тестом «каждая запись срабатывает хотя бы раз».
|
||
- **Контракт v1.1**: `rows[].extra` (нераспознанные колонки внутри таблицы); примеры `result-osv-minimal.json`,
|
||
`result-osv-needs-check.json`.
|
||
- **Результат на реальных данных:** все 66 файлов `fixtures/osv/**` — `done`, контрольные суммы сходятся, максимум 0,6 с
|
||
на файл; 16 синтетических файлов (`fixtures/osv/synthetic/`); 82 golden-снимка (`fixtures/golden/expected_osv_parse/`);
|
||
тест-линтер «нет координат в `src/parser/`»; CLI `python -m src.parser.cli <файл>`.
|
||
- **Нетривиальное решение (R-21):** сверка «сумма детей = родитель» по сальдо идёт по колонкам либо по нетто, если у
|
||
родителя сальдо свёрнуто (1С показывает счёт нетто, субсчета — развёрнуто); без него 32 из 66 файлов не сходились.
|
||
- **Допущения парсера, решаются в 2c:** `is_cumulative` только по тексту периода (OQ-02), `data_type = NU` как есть
|
||
(OQ-45), «Итого» выдаётся строкой `TOTAL` (OQ-01).
|
||
- **Документы:** `05-domain-rules.md` §1 приведён к факту (§1.6 — результат парсера), `06-test-data.md` §7 (синтетика,
|
||
golden, рукописный xlsb), OQ-98…OQ-105 открыты (финальное ревью добавило OQ-106…OQ-109), OQ-81 частично закрыт (в `tabular-parser` `ARM_KA_FIXTURES` поддержан; `scan-gateway` — follow-up).
|
||
- **Не сделано / риски:** xlsb проверен только рукописным синтетическим файлом (OQ-102); ИНН из текста строки не
|
||
извлекается (OQ-100); счета с буквой в первом сегменте не распознаются (OQ-103).
|
||
|
||
### Срез 2b — ББ/ОФР из Excel (`tabular-parser`)
|
||
|
||
- `tabular-parser`: ББ/ОФР из Excel по §12 в тот же `statementDocument`; поиск листов с формами по содержимому
|
||
(в 2a такие листы пропускаются с предупреждением `skipped: statement (slice 2b)`); словари синонимов `rules/*.yaml`;
|
||
контрольные формулы §7 для форм.
|
||
- Тесты: property-тесты инвариантов на **всех** файлах `fixtures/statements-xlsx/**` (формулы §7) + синтетика (ББ+ОФР на
|
||
одном листе, csv-подобный лист); снимки результата — рядом с golden ОСВ.
|
||
|
||
### Срез 2c — Загрузка файлов и маппинг результата в ядре (`arm-core`)
|
||
|
||
- `arm-core`: `stored_file` + `s3_object` + `recognition_job` (outbox) + дедуп по `content_hash` + замена файла (`SUPERSEDED`).
|
||
- Маппинг результата в ядре: `document` (OSV) + `osv_entry` (все строки, OQ-01) + резолв `account_id`, `account_root`,
|
||
`counterparty_id` по ИНН/имени/алиасу; `document.period_check / counterparty_check`; статусы. Решения, отложенные
|
||
парсером: OQ-01 (хранение «Итого»), OQ-02 (нарастающие по данным), OQ-45 (`NU`), OQ-67, OQ-69, OQ-70 (маппер и
|
||
`recognition.result`), OQ-100 (ИНН из текста имени контрагента).
|
||
- Маппинг ББ/ОФР в ядре из `statementDocument` — общий для Excel и OCR (Срез 3 его переиспользует).
|
||
- Копия схемы контракта v1.1 в `arm-core` (`OsvRow.extra`) — если не сделана bounded-PR раньше.
|
||
|
||
### Срез 2d — UI «Первичные документы»
|
||
|
||
- Панель «Первичные документы», окно загрузки, таблица ОСВ с деревом строк и подсветкой нераспознанных контрагентов.
|
||
|
||
**DoD среза 2 целиком:** все реальные файлы и вся синтетика проходят **без единого правила под конкретный файл или
|
||
координату**; в коде парсера нет литералов букв колонок/номеров строк/имён листов (проверяется тестом-линтером).
|
||
|
||
## Срез 3 — ББ/ОФР: scan-gateway, маппинг форм, логические проверки, подтверждение документа
|
||
|
||
- `scan-gateway`: горячие папки (по FNR02) с заглушкой-эмулятором ИС Сканирование для dev (возвращает `aep_*.json`
|
||
по имени файла); сырой ответ → S3 `raw/`; нормализация в контракт результата; таймаут 5 мин.
|
||
- Маппинг: `financial_statement_meta/column/line/value` из JSON; числа, скобки, confidence; ИНН/имя сверка.
|
||
- Логические проверки §7 (чистые функции `domain/checks.py`), акцепт КА, «Подтвердить данные» → `CONFIRMED`,
|
||
`RECOGNIZED_DATA_EDIT` с before/after.
|
||
- Чек-лист `required_document_rule × периметр − document CONFIRMED` (API + модалка + CSV).
|
||
- UI: экран «Проверка распознавания» (исходник слева — PDF.js, результат справа, блок «Логические ошибки»).
|
||
- Тесты: пары `aep_N.pdf/json`; машиночитаемые PDF АО РБ 2024 как dev-путь без OCR (опционально pdfplumber).
|
||
|
||
## Срез 4 — Консолидация: карточка, версии, периметр, периоды, сводная ОСВ
|
||
|
||
- `consolidation` / `consolidation_version` / `consolidation_member` / `consolidation_document`; статусы INITIATED →
|
||
DOCUMENTS_PROCESSING → DATA_CORRECTION; `consolidation_lock`.
|
||
- Приведение квартальных ОСВ к нарастающим (§2) как вычисляемое представление.
|
||
- Сводная ОСВ (§3) как запрос; признак контрагента; ручное сопоставление с алиасами и автоприменением.
|
||
- UI: список консолидаций, карточка, состав, сводная ОСВ с фильтрами и экспортом xlsx.
|
||
- Тест: сводная ОСВ группы РБ за 9 мес. 2024: строки «Юг-Финанс / ПЕКАРНЯ СЧАСТЛИВОЧНАЯ» → ГСК после одного ручного
|
||
сопоставления.
|
||
|
||
## Срез 5 — ВГО: автоматическое сопоставление, шахматка, корректировки
|
||
|
||
- `elimination_rule` как данные + версия в `consolidation_version`; матчинг §5 (чистые функции на pydantic-моделях);
|
||
шахматка §6; `correction` с дочерними строками, контроль 5 %, статусы DRAFT/APPLIED/REVOKED; `ADJUSTMENT`.
|
||
- UI: шахматка (ячейки с комментариями), таблица пар (авто/ручные/несопоставленные), таблица корректировок с
|
||
импортом/экспортом xlsx.
|
||
- Тест: пара 60.01 АО РБ ↔ 62.01 Счастливочная на 30.09.2024 = 106 311 116,46 (точный матч по правилу № 6); заём
|
||
42 млн 66.03 ↔ 58.03; сверка «в т. ч. взаимные обязательства» с `Консолидация_Группа РБ.xlsx` лист `01.10.2024`.
|
||
|
||
## Срез 6 — КФО: результат, гудвилл, пороги, сравнение с клиентом
|
||
|
||
- `kfo_result(_line, _line_company)`: сумма → авто-элиминация → корректировки → гудвилл → итог; единицы (OQ-26);
|
||
пороги §8; `goodwill`; `client_kfo_line` для `CONFIRM`.
|
||
- UI: КФО (колонки как в эталонном Excel), сводка расхождений, гудвилл, подтверждение КФО клиента, экспорт xlsx.
|
||
- Тест: КФО группы РБ на 01.10.2024 vs эталонный лист (допуск на ручные правки аналитика — зафиксировать расхождения
|
||
и показать аналитику).
|
||
|
||
## Срез 7 — Маршрут согласования, ЕХ, журнал, выгрузки
|
||
|
||
- Полная машина статусов §9.1 с `consolidation_status_history`, policy по ролям/филиалам; № и дата УОБ; новая версия.
|
||
- `VERIFIED` → `ex_snapshot/_member/_item` (payload по схеме `docs/contracts/ex-payload-v1.schema.json`); read-only.
|
||
- ЛК ЕХ: дерево ГК → ГСК → организации, просмотр payload, скачивание первички, выгрузка xlsx на лету.
|
||
- Журнал действий: экран с фильтрами по разделам/объектам/полям.
|
||
- Экспорт: настраиваемые колонки, `audit_event(EXPORT)`, S3 `exports/` 7 дней.
|
||
|
||
## Срез 8 — Нефункциональные: ретеншн, архивирование, производительность, безопасность
|
||
|
||
- Lifecycle S3 и удаление по ТЗ 8.4 (5 лет), архив в data lake (ссылка), объёмы `osv_entry` (индексы, partitioning по
|
||
году при необходимости), нагрузочный тест парсера (1000-строчные ОСВ × 20 компаний), OWASP-проверка, аудит доступа.
|
||
|
||
## Поперечные правила
|
||
|
||
- Любой срез, затрагивающий модель, начинается с diff к DBML (новая версия v5.14+ с записью в changelog) и миграции.
|
||
- Вычисляемые объекты — только `domain/` + `repositories/` запросы; никаких таблиц «для ускорения» без DECISION.
|
||
- Каждый алгоритм из `05-domain-rules.md` — отдельный модуль с docstring-ссылкой на § и тестами на фикстурах.
|
||
- Открытые вопросы — только через `07-open-questions.md` с `ASSUMPTION(OQ-nn)` в коде.
|