arm-core/api/src/schemas/files.py
Andrey Epifancev 20983fb3d8 feat(files): API файлов, документов и строк ОСВ; подтверждение документа
GET /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_012vJkmBGKKqMoEMzvsXG3y2
2026-10-05 23:34:35 +04:00

97 lines
3.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Схемы API первичных файлов (spec primary-files, documents-api; design D-4, D-11).
`UploadResult` — ответ загрузки (частичный успех: принятые и отклонённые файлы). `FileOut`/`JobBrief`/`DocumentBrief` —
карточка файла для чтения (Task 9 Среза 2c): статус файла, текущее задание с причиной ошибки и предупреждениями,
документы файла.
"""
import datetime
import uuid
from pydantic import BaseModel, ConfigDict
from src.models.enums import (
DocumentCheckResult,
DocumentStatus,
DocumentType,
FileStatus,
FileType,
PeriodQuarter,
RecognitionStatus,
)
class AcceptedOut(BaseModel):
"""Принятый файл: `stored_file.id`, `recognition_job.id` и очищенное имя."""
file_id: uuid.UUID
job_id: uuid.UUID
name: str
class RejectedOut(BaseModel):
"""Отклонённый файл: имя, код (`UNSUPPORTED_FILE`, `FILE_TOO_LARGE`, `DUPLICATE_FILE`, `STORAGE_UNAVAILABLE`),
пояснение; `file_id` — существующий файл при `DUPLICATE_FILE`."""
name: str
code: str
message: str
file_id: uuid.UUID | None = None
class UploadResult(BaseModel):
"""Ответ `POST /legal-entities/{id}/files`: 201, если принят хотя бы один файл, иначе 422."""
accepted: list[AcceptedOut]
rejected: list[RejectedOut]
class JobBrief(BaseModel):
"""Текущее задание распознавания файла: статус, причина ошибки сервиса (`error_code`/`error_message`) и маппинга
(`mapping_error` — `metrics.mapping_error`: файл в `ERROR` после маппинга при задании `done`), предупреждения
сервиса (`metrics.warnings`) и маппинга (`metrics.mapping_warnings`)."""
id: uuid.UUID
status: RecognitionStatus
error_code: str | None
error_message: str | None
mapping_error: str | None
warnings: list[str]
mapping_warnings: list[str]
finished_at: datetime.datetime | None
class DocumentBrief(BaseModel):
"""Документ файла в карточке файла."""
model_config = ConfigDict(from_attributes=True)
id: uuid.UUID
document_type: DocumentType
osv_account_code: str | None
period_year: int | None
period_quarter: PeriodQuarter | None
is_cumulative: bool | None
status: DocumentStatus
period_check: DocumentCheckResult | None
counterparty_check: DocumentCheckResult | None
locator: str | None
class FileOut(BaseModel):
"""Карточка файла: поля `stored_file`, текущее задание и документы."""
id: uuid.UUID
legal_entity_id: uuid.UUID
original_name: str | None
file_type: FileType
status: FileStatus
version_no: int
size_bytes: int | None
content_hash: str | None
uploaded_at: datetime.datetime | None
uploaded_by: uuid.UUID | None
superseded_by_file_id: uuid.UUID | None
job: JobBrief | None
documents: list[DocumentBrief]