"""Схемы API первичных файлов (spec primary-files, documents-api; design D-4, D-11). `UploadResult` — ответ загрузки (частичный успех: принятые и отклонённые файлы). `FileOut`/`JobBrief`/`DocumentBrief` — карточка файла для чтения (Task 9 Среза 2c): статус файла, текущее задание с причиной ошибки и предупреждениями, документы файла. """ import datetime import uuid from typing import Literal from pydantic import BaseModel, ConfigDict from src.models.enums import ( DocumentCheckResult, DocumentStatus, DocumentType, FileStatus, FileType, PeriodQuarter, RecognitionStatus, ) from src.schemas.issues import IssueCountsOut 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 FileWarningOut(BaseModel): """Предупреждение файла от сервиса распознавания (`metrics.warnings`): код, уровень, текст и место в файле. Старые строковые предупреждения (контракт 1.x) отдаются с кодом `LEGACY_WARNING` уровня `info`.""" code: str severity: Literal["error", "info"] message: str locator: str | None 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[FileWarningOut] 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 comments_count: int = 0 # число комментариев документа (D-11); в ORM-модели поля нет — задаётся при сборке issue_counts: IssueCountsOut = IssueCountsOut() # P3-23; так же задаётся при сборке 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 consolidation_id: uuid.UUID | None # v5.18: консолидация, в контексте которой загружен файл; None — без контекста job: JobBrief | None documents: list[DocumentBrief]