# 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.