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
97 lines
3.2 KiB
Python
97 lines
3.2 KiB
Python
"""Схемы 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]
|