7.9 KiB
Raw Blame History

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. Модель и инфраструктура

  • 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.
  • 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_result_object; make_entries_from_result(session, file, job, result_json) создан в Task 9 (R-2); 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_one, цикл по файлам — в эндпоинте; дедуп, 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 (R-3: EntryDraft.counterparty_id проставляет цикл маппинга; пропуск TOTAL, parent по row_number, account_id), counterparty_check(header, owner_inn, owner_names_normalized), document_status(...); repositories/counterparties.py:CounterpartyIndex + resolve(inn, name) -> 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/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 с), проверка документа и 9 строк (10 строк снимка минус TOTAL); без профиля — 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.