Andrey Epifancev 239d5bc270 OpenSpec: change slice-2c-osv-ingest (загрузка файлов, цикл маппинга, документы — спека)
proposal, design (D-1…D-13), спеки primary-files, osv-result-mapping, documents-api (новые), recognition-job-exchange,
data-model (MODIFIED), audit-trail (ADDED), tasks (13 задач).

Issue: RSHB/arm-core#14

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-05 21:08:56 +04:00

78 lines
8.4 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.

# Proposal: Срез 2c — загрузка файлов ОСВ, очередь распознавания, маппинг результата в document/osv_entry
**Срез:** 2, подсрез 2c (`../docs/context/09-delivery-plan.md`; порядок по решению владельца 05.10.2026: 2a → 2c → 2b → 2d).
**Репозиторий:** arm-core. **Issue:** RSHB/arm-core#14. **Родительский change:** `../docs/openspec/changes/slice-2c-osv-ingest`
(DBML v5.15, правила по факту, OQ; RSHB/arm-ka-docs#16). Фикстуры не меняются (golden-снимки парсера из Среза 2a).
## Why
После Среза 2a парсер разбирает ОСВ, но результат некуда положить: в ядре нет загрузки файлов (`stored_file` нигде не
создаётся), inbox фиксирует конверт, а маппер — `NoopMapper`. Без 2c нет сквозного пути «файл → строки ОСВ в БД», на
котором стоят чек-лист (Срез 3), сводная ОСВ и ВГО (Срезы 4–5), и тестировщику нечего проверять на реальных данных.
## What Changes
- **DBML v5.15**: `osv_data_type` + `NU` (OQ-45); `app_user.email` хранится в нижнем регистре (OQ-59, нормализация при
записи, UNIQUE прежний); миграция `0003_dbml_v5_15` (enum + `UPDATE app_user SET email = lower(email)`); `DBML_VERSION`,
`Makefile`, `dbml_parity`, тесты миграций — на v5.15. Таблиц 43, enum'ов 27.
- **Загрузка файлов** `POST /api/v1/legal-entities/{id}/files` (multipart, несколько файлов, `DOCUMENT_EDIT`, policy
видимости ЮЛ): `s3_object` (`uploads/{file_id}/{job_id}/{имя}`, sha256) + `stored_file(UPLOADED)` +
`recognition_job(queued)` + `current_recognition_job_id` одной транзакцией; тип по расширению и сигнатуре
(xls/xlsx/xlsb → `tabular`, pdf/png/jpg → `image`); дедуп по sha256 в пределах ЮЛ (409); лимит `UPLOAD_MAX_BYTES`
50 МиБ; частичный успех по файлам; аудит `FILE_UPLOAD`.
- **Статусы файла в обмене**: ack → `PROCESSING`; `error`/`timeout`/`dlq` → `ERROR` с причиной (OQ-67, inbox).
- **Цикл маппинга** (`services/result_mapping.py`, фоновая задача lifespan, период `MAPPING_POLL_SECONDS`): задание
`done|needs_check` с файлом `PROCESSING` и `current_recognition_job_id = job` → `FOR UPDATE SKIP LOCKED` → проверка ключа
(OQ-70) → чтение результата из S3 вне транзакции → валидация v1.1 → одна транзакция: `document` + `osv_entry` +
`group_schema` + статус файла `PROCESSED`; ошибка чтения/валидации → `ERROR`; исключение → повтор до 3 раз
(`metrics.mapping_attempts`), затем `ERROR` (OQ-69 — маппинг отделён от inbox).
- **Маппинг** по §1.3/§3/§9.3: все строки кроме `TOTAL` (OQ-01) с `parent_entry_id`; нормализация кода счёта,
`account_root`, `account_id` по плану счетов; резолв контрагента ИНН → имя → алиас, несколько кандидатов → NULL;
`counterparty_check` по шапке; `period_check = UNKNOWN` до Среза 4; `is_cumulative` по данным Q1 (OQ-02); статус
документа `RECOGNIZED` | `NEEDS_CHECK` (OQ-05); второй лист с тем же счётом — предупреждение и `NEEDS_CHECK`.
- **Автозамена** (решение владельца 05.10.2026): после маппинга прежние файлы того же ЮЛ с документом того же типа, счёта
и периода → `SUPERSEDED` (`superseded_by_file_id`, `version_no`), аудит `FILE_REPLACE` (с предупреждением, если
заменён `CONFIRMED`). **Удаление** `DELETE /files/{id}` — пока нет `CONFIRMED` документов; аудит `FILE_DELETE`.
- **API чтения** (`DOCUMENT_VIEW`): список файлов ЮЛ с заданием и документами, карточка файла, документ с проверками и
схемой, строки документа деревом (до 2000 на страницу); **подтверждение** `POST /documents/{id}/confirm`
(`DOCUMENT_EDIT`, `DOCUMENT_CONFIRM`).
- **S3**: `S3Storage.get_bytes(key)`; `FakeStorage.get_bytes` в тестах.
- **Тесты**: unit (нормализация кода, резолв, проверки шапки), integration (загрузка/дедуп/статусы/цикл/автозамена/
удаление/API), `fixtures` — маппинг всех 82 golden-снимков парсера без NATS и сквозной прогон через compose с
реальным `tabular-parser` на `rb/2024-FY/ОСВ_66 за 2024.xls`.
- **Документы**: README (загрузка, статусы, маппинг, API), CLAUDE.md, спеки.
## Capabilities
### New Capabilities
- `primary-files`: загрузка файлов отчётности, дедуп, статусы файла, автозамена, удаление.
- `osv-result-mapping`: цикл маппинга результата распознавания в `document`/`osv_entry`, резолвы, проверки, статусы документа.
- `documents-api`: чтение файлов, документов и строк ОСВ, подтверждение документа.
### Modified Capabilities
- `recognition-job-exchange`: ack и финальные статусы задания меняют `stored_file.status`; результат `done|needs_check`
не маппится в inbox, а передаётся циклу маппинга (`NoopMapper` и протокол `ResultMapper` удаляются).
- `data-model`: схема равна DBML **v5.15**, цепочка миграций 0001 → 0003.
- `audit-trail`: события файлов и документов (`FILE_UPLOAD`, `FILE_REPLACE`, `FILE_DELETE`, `DOCUMENT_CONFIRM`).
## Impact
- **Таблицы DBML v5.15:** `s3_object`, `stored_file`, `recognition_job` (`group_schema`, `metrics`), `document`,
`osv_entry`, `chart_of_account` (чтение), `legal_entity`, `legal_entity_alias` (чтение), `app_user` (email в
нижнем регистре), `audit_event`. Полей вне DBML нет.
- **Правила:** §1.3 (маппинг строк, нормализация, `account_root`/`account_id`), §1.5 (`is_cumulative` по данным,
`period_check`), §3 п. 4 (резолв контрагента), §9.2–9.4 (статусы). Допущения: `ASSUMPTION(OQ-02)` (по данным — только
сравнение с Q1 того же года), `ASSUMPTION(OQ-55)` (события обмена не аудируются), `ASSUMPTION(OQ-100)` (ИНН из текста
строки не извлекается), `ASSUMPTION(OQ-103)` (буквенный первый сегмент → `account_id = NULL`).
- **Соседи:** `../docs` (DBML v5.15, §, OQ), `tabular-parser` — без изменений; compose — без изменений.
- **Seed:** `users.yaml` dev-email уже в нижнем регистре; `permissions`/матрица не меняются (`DOCUMENT_VIEW`/`DOCUMENT_EDIT` есть).
## Non-goals
- UI (панель «Первичные документы», окно загрузки, экран проверки ОСВ) — подсрез 2d.
- Маппинг `statementDocument` (ББ/ОФР), логические проверки §7, правка распознанных значений
(`RECOGNIZED_DATA_EDIT`), чек-лист — Срез 3 / подсрез 2b.
- Ручное сопоставление контрагентов (`OSV_COUNTERPARTY_MAP`), `period_check` по версии консолидации — Срез 4.
- Replay заданий (`dlq → queued`, OQ-73), аудит смены статусов задания (OQ-55), JetStream.
- Ограничение удаления по консолидациям (ANL-020) — Срез 4.