Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
69 lines
7.6 KiB
Markdown
69 lines
7.6 KiB
Markdown
# Tasks: Срез 2c — arm-core
|
||
|
||
Каждая задача ≤ половины дня, начинается с тестов, заканчивается зелёным `make check` и интеграционными тестами своей
|
||
области. Допущения в коде — `# ASSUMPTION(OQ-nn)`. DBML v5.15 — `../docs/model/data-model-v5.15.dbml` (ветка
|
||
`feat/16-slice-2c-osv-ingest` в `.wt/slice-2c/docs`).
|
||
|
||
## 1. Модель и инфраструктура
|
||
|
||
- [x] 1.1 **DBML v5.15, миграция 0003.** `OsvDataType.NU` (после BU), миграция `0003_dbml_v5_15` (`ALTER TYPE … ADD VALUE 'NU'
|
||
AFTER 'BU'`, `UPDATE app_user SET email = lower(email)`; downgrade пересоздаёт enum); `DBML_VERSION = "5.15"`, `Makefile`,
|
||
`dbml_parity.DBML_FILE_NAME`, `HEAD` в `test_migrations`, `test_enums`, `test_check_dbml_script` (фиктивная 5.16); нормализация
|
||
email в нижний регистр в seed (`_apply_users`) и в `users_admin.create_user/update_user`; тесты seed/admin на регистр.
|
||
Проверка: `make dbml-check` пуст; `make migration-cycle`.
|
||
- [x] 1.2 **S3 и фабрики.** `S3Storage.get_bytes(key) -> bytes` (+ закрытие response), `ObjectStorage`-протокол с `get_bytes`;
|
||
`FakeStorage.get_bytes`; `tests/factories.py`: `make_stored_file(session, *, entity, status, content_hash, job_status)`,
|
||
`make_document`, `make_entries_from_result(session, file, result_json)`; `CommittedJobs.cleanup()` удаляет `osv_entry`,
|
||
`document`. Настройки `UPLOAD_MAX_BYTES` (50 МиБ), `MAPPING_POLL_SECONDS` (5), `MAPPING_MAX_ATTEMPTS` (3) + `.env.example`/`.env.test`.
|
||
|
||
## 2. Загрузка и статусы
|
||
|
||
- [ ] 2.1 **Загрузка файлов.** `domain/file_types.py` (расширение → `FileType`/`pipeline`, проверка сигнатуры), `services/primary_files.py`
|
||
(`upload_many`, дедуп, S3, `stored_file`/`recognition_job`/`current_recognition_job_id`, аудит `FILE_UPLOAD`),
|
||
`schemas/files.py` (`UploadResult{accepted,rejected}`, `FileOut`, `JobBrief`, `DocumentBrief`), эндпоинт
|
||
`POST /legal-entities/{id}/files`, роутер. Тесты: сценарии спеки `primary-files` (принята, смесь, сигнатура, чужое ЮЛ, дубль);
|
||
`fixtures`-тест: реальный xls принят и `publish_pending` на `FakeNats` публикует запрос с ключом `uploads/{file_id}/{job_id}/…`.
|
||
- [ ] 2.2 **Статусы файла в обмене.** `recognition_exchange.py`: ack → `PROCESSING`; `error` → `ERROR`; вахтёр `timeout` → `ERROR`;
|
||
публикатор `dlq` → `ERROR` (все — только при `current_recognition_job_id = job`); `ResultMapper`/`NoopMapper` удалены,
|
||
`_map_result` убран, тесты inbox обновлены (`RecordingMapper` → проверка статуса файла). Тесты: сценарии спеки
|
||
`recognition-job-exchange` (ack → PROCESSING, error → ERROR, timeout → ERROR, done → файл остаётся PROCESSING).
|
||
|
||
## 3. Маппинг
|
||
|
||
- [ ] 3.1 **Домен маппинга.** `domain/osv_mapping.py`: `normalize_account_code`, `account_root`, `EntryDraft`, `build_entries(doc,
|
||
chart, resolve)` (пропуск TOTAL, parent по row_number, `account_id`), `counterparty_check(header, owner)`, `document_status(...)`;
|
||
`repositories/counterparties.py:CounterpartyIndex` + `resolve(row) -> uuid | None` (ИНН → имя → алиас, >1 → None).
|
||
Unit-тесты: нормализация (`60.1`, `76.7.1`, `76.АВ`, `МЦ.04` → root None), дерево и TOTAL на `result-osv-minimal.json`
|
||
и golden `ОСВ_66 за 2024`, резолв (ИНН/имя/алиас/неоднозначно), проверка шапки (ИНН, имя, алиас, пусто), статус документа.
|
||
- [ ] 3.2 **Цикл маппинга.** `services/result_mapping.py`: `pick_candidate` (FOR UPDATE SKIP LOCKED), проверка ключа (OQ-70), чтение
|
||
S3 вне транзакции, `map_file` (одна транзакция: очистка прежних строк файла, `document`×N с обработкой повтора счёта,
|
||
пакетная вставка `osv_entry`, `group_schema`, `is_cumulative` по Q1 (D-9), `metrics.mapping_warnings`, `PROCESSED`),
|
||
повторы `mapping_attempts` → `ERROR`, `run_mapping_loop` в lifespan (`ExchangeTasks`). Интеграционные тесты: сценарии спеки
|
||
`osv-result-mapping` на `FakeStorage` с golden JSON (`ОСВ_66`), ключ вне папки, сбой с повтором (monkeypatch), идемпотентность,
|
||
два листа одного счёта, MISMATCH шапки, нарастающая по Q1, RECOGNIZED.
|
||
- [ ] 3.3 **Автозамена.** В `map_file` после `PROCESSED`: поиск прежних файлов по документам, `SUPERSEDED`, `version_no`, аудит
|
||
`FILE_REPLACE` (системное событие). Тесты: сценарий спеки (CONFIRMED заменяется, `confirmed=true` в description), два
|
||
разных счёта не заменяют друг друга, `include_superseded`.
|
||
- [ ] 3.4 **Golden-прогон.** `tests/fixtures/test_mapping_golden.py` (`fixtures`): все `../fixtures/golden/expected_osv_parse/**/*.json`
|
||
→ файл + задание `done` + маппинг через `map_file`; инварианты спеки (строки без TOTAL, родители, суммы, `account_id`).
|
||
Время: ≤ 60 с на все 82.
|
||
|
||
## 4. API и удаление
|
||
|
||
- [ ] 4.1 **API чтения и подтверждение.** `repositories/files.py`, `services/documents.py`, `schemas/documents.py`, эндпоинты
|
||
`GET /legal-entities/{id}/files`, `GET /files/{id}`, `GET /documents/{id}`, `GET /documents/{id}/entries`,
|
||
`POST /documents/{id}/confirm`; policy через ЮЛ-владельца. Тесты: сценарии спеки `documents-api`; `test_openapi_routes`.
|
||
- [ ] 4.2 **Удаление файла.** `DELETE /files/{id}`: проверки `CONFIRMED`/`superseded_by`, удаление строк/документов/заданий/файла,
|
||
`s3_object.expires_at`, аудит `FILE_DELETE`. Тесты: сценарии спеки.
|
||
|
||
## 5. Сквозной прогон и документы
|
||
|
||
- [ ] 5.1 **E2E через compose.** `tests/e2e/test_osv_ingest_e2e.py` (маркеры `integration`, `e2e`): при поднятом профиле
|
||
`services` (реальный `tabular-parser` против общего NATS/MinIO) — загрузка `rb/2024-FY/ОСВ_66 за 2024.xls` через API ядра,
|
||
ожидание `PROCESSED` (≤ 60 с), проверка документа и 20 строк; без профиля — skip с причиной. `make e2e` цель; `check-full`
|
||
его включает (skip допустим).
|
||
- [ ] 5.2 **Документы.** README (загрузка: curl-пример, статусы файла и документа, цикл маппинга и настройки, API, удаление и замена,
|
||
ограничения), CLAUDE.md (DBML v5.15, `domain/osv_mapping.py`, `services/primary_files.py`, `services/result_mapping.py`,
|
||
три фоновых цикла), `qa-guide` в docs — сценарии загрузки (в docs-change); `make check-full` зелёный; `tasks.md` `[x]`.
|
||
- [ ] 5.3 После merge: `openspec archive slice-2c-osv-ingest`.
|