Срез 2c: загрузка файлов ОСВ, цикл маппинга результата в document/osv_entry, API документов, DBML v5.15 #15
Loading…
x
Reference in New Issue
Block a user
No description provided.
Delete Branch "feat/14-slice-2c-osv-ingest"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #14. Спека и реализация подсреза 2c (решения владельца 05.10.2026: автозамена при совпадении счёта/периода; маппинг отдельным циклом). 35 коммитов: 12 задач плана, финальное ревью (opus) с fix-wave, индексы по I-1.
Что в PR
0003_dbml_v5_15):osv_data_type + NU,app_user.emailв нижнем регистре (данные приводятся миграцией; нормализация в seed и admin API), индексыosv_entry(parent_entry_id),osv_entry(recognition_job_id),stored_file(current_recognition_job_id)(удаление документа 1000 строк: 8,6 с → 16 мс).POST /legal-entities/{id}/files(multipart, несколько файлов, частичный успех, тип по расширению и сигнатуре, лимитUPLOAD_MAX_BYTES50 МиБ, дедуп по sha256 в пределах ЮЛ под advisory-lock,s3_object+stored_file(UPLOADED)+recognition_job(queued)в outbox, аудитFILE_UPLOAD).services/file_status.py, единственная точка): ack →PROCESSING;error/timeout/dlq/INVALID_REQUEST→ERROR;ResultMapper/NoopMapperудалены, inbox не читает S3.services/result_mapping.py, третий фоновый цикл lifespan): кандидат поstored_file.status = PROCESSING+current_recognition_job_id, проверка ключа результата (OQ-70), чтение S3 вне транзакции, одна транзакция на файл (очистка →document×N → пакетная вставкаosv_entry→group_schema→PROCESSED→ автозаменаFILE_REPLACE), повторыMAPPING_MAX_ATTEMPTSс паузой, категории ошибок вmetrics.mapping_error.domain/osv_mapping.py,repositories/counterparties.py): все строки кромеTOTAL, нормализация кода счёта,account_root/account_id, резолв контрагента ИНН → имя → алиас (неоднозначность → NULL),counterparty_check,period_check = UNKNOWN(до Среза 4),is_cumulativeпо Q1, статусRECOGNIZED/NEEDS_CHECK, повтор счёта в файле → предупреждение +NEEDS_CHECK.POST /documents/{id}/confirm,DELETE /files/{id}(запретыFILE_HAS_CONFIRMED_DOCUMENTS,FILE_SUPERSEDES_OTHERS;s3_object.expires_at).ОСВ_60 за 20241033 строки — 0,32 с), e2e через compose с реальнымtabular-parser(make e2e, прошёл:ОСВ_66→PROCESSED,RECOGNIZED; без профиляservices— skip). Ledger —docs/superpowers/plans/2026-10-05-slice-2c-osv-ingest-ledger.md.Проверки (HEAD
6ccb991)make check-fullEXIT 0: ruff/format/mypy чисто; unit 901 passed; dbml-check пусто; check-infra ok; integration 445 passed (e2e skipped без профиля); docker-build ok; web-check чисто. Финальное ревью (opus): Critical нет; I-1 закрыт индексами в v5.15; M-1…M-4 исправлены.Rulings I made (design.md №14–53, ключевые)
done/needs_checkinbox переводит файлUPLOADED → PROCESSING(ack мог потеряться без JetStream); R-5:PROCESSINGтолько изUPLOADED.pg_advisory_xact_lock(ЮЛ, sha256).MAPPING_POLL_SECONDS(иначе 3 попытки за миллисекунды).NEEDS_CHECK.job_id/file_idрезультата ≠ заданию — неповторяемая ошибка; листы ББ/ОФР игнорируются; строки-сироты →NEEDS_CHECK.confirmперечитывает файл и документ подFOR UPDATE.Вопросы владельцу (в OQ docs, не блокируют)
NEEDS_CHECKдля «нарастающей по данным» (R-17).01.01,66.01,76.01,60.АВ…) →account_id = NULL.Deferred (follow-up issue при закрытии)
T2.3, T3.1–T3.6, T4.3–T4.5, T5.2, T5.3, T6.2, T6.3, T6.5, T7.3–T7.5, T7.8–T7.13, T8.2, T8.3, T8.5, T8.7, T9.1, T9.2, T9.4–T9.7, T10.1–T10.5, T11.3, T11.5, T11.6; OQ-110…112, 114. Формулировки — в ledger.
Зависит от: RSHB/arm-ka-docs#17 (DBML v5.15). Порядок merge: docs → arm-core.
🤖 Generated with Claude Code
https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
Срез 2c: загрузка файлов ОСВ, цикл маппинга результата, документы и строки — спека OpenSpecto Срез 2c: загрузка файлов ОСВ, цикл маппинга результата в document/osv_entry, API документов, DBML v5.15POST /api/v1/legal-entities/{id}/files (DOCUMENT_EDIT, видимость ЮЛ → 404): тип по расширению со сверкой сигнатуры (domain/file_types.py), лимит UPLOAD_MAX_BYTES → FILE_TOO_LARGE, дедуп по sha256 в пределах ЮЛ (кроме SUPERSEDED) с рекомендательной блокировкой (ЮЛ, sha256) до конца транзакции запроса, s3_object → put_object → stored_file(UPLOADED) → recognition_job(queued) → current_recognition_job_id, аудит FILE_UPLOAD. Каждый файл — SAVEPOINT: частичный успех (201/422), отказ S3 на файле — STORAGE_UNAVAILABLE только для него; без S3 — 503. safe_filename/CONTENT_TYPES/read_limited вынесены в common/filenames.py (импорт справочников их переиспользует). Схемы UploadResult/FileOut/JobBrief/DocumentBrief, PrimaryFilesService и зависимость для Task 9–10. tasks.md 2.1. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2services/result_mapping.py: pick_candidate (FOR UPDATE OF job SKIP LOCKED, файл PROCESSING и задание текущее), проверка ключа results/{file_id}/{job_id}/ и бакета, чтение S3 вне транзакции, map_file одной транзакцией (очистка документов/строк файла, документ на лист ОСВ, повтор счёта — предупреждение и NEEDS_CHECK первого листа, строки пакетами по 1000, резолв контрагента COUNTERPARTY+BU/SUM (R-3), нарастающая по Q1 (D-9), group_schema, metrics.mapping_warnings, PROCESSED через set_file_status_for_job). Неповторяемые ошибки — сразу ERROR с metrics.mapping_error; прочие — откат и mapping_attempts, на MAPPING_MAX_ATTEMPTS — ERROR. run_mapping_loop — третья задача ExchangeTasks при подключённом S3 (иначе предупреждение). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2GET /legal-entities/{id}/files (include_superseded, по убыванию uploaded_at), GET /files/{id}, GET /documents/{id} (group_schema листа, warnings — R-6), GET /documents/{id}/entries (limit ≤ 2000, по умолчанию 500), POST /documents/{id}/confirm (DOCUMENT_EDIT; блокировка файла, затем документа; 409 FILE_SUPERSEDED / ALREADY_CONFIRMED / DOCUMENT_NOT_CONFIRMABLE; аудит DOCUMENT_CONFIRM). Видимость — через ЮЛ-владельца, чужое — 404 как несуществующее. Список файлов без N+1: файлы, текущие задания и документы — пакетными запросами. JobBrief.mapping_error — причина ERROR файла по маппингу. Тесты: сценарии documents-api, гонка подтверждения с автозаменой (Review Focus 5), make_entries_from_result (R-2), реальная проверка уникальности документа (T2.2). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2DELETE /files/{file_id} (DOCUMENT_EDIT, policy): блокировки «задание → файл → документы», проверки после блокировок (409 FILE_HAS_CONFIRMED_DOCUMENTS, FILE_SUPERSEDES_OTHERS), удаление osv_entry/document/recognition_job/ stored_file (разрыв FK-цикла), expires_at у объектов S3 файла и результатов, аудит FILE_DELETE с before_value. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2M-3: s3_object.expires_at = now ставится только объектам с ролью uploads и ключом uploads/{file_id}/… или с ролью results и ключом results/{file_id}/…; ссылка задания на объект другого файла (inbox переиспользует s3_object при конфликте (bucket, key)) не трогается и ошибкой не считается. Фабрика make_stored_file кладёт загрузку в папку файла uploads/{file_id}/…, как upload_key в проде. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2