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