"""Конверт сообщения на `recognition.result`: `recognition-result-message.schema.json` v1.0. `processing` — ack «взял в работу» (до чтения файла из S3); `done | needs_check | error` — итог. Условная обязательность — буквально из `allOf` схемы: для `done | needs_check` обязательны `result_s3` и `result_schema_version`; для `error` — `error`. Других правил нет (например, `error` при `done` схема не запрещает). """ from enum import StrEnum from typing import Annotated, Self from pydantic import Field, model_validator from src.contract.common import ( ContractModel, ContractUUID, LaxErrorCode, Metrics, OptionalNotNull, ResultSchemaVersion, S3Ref, SchemaVersion, ServiceInfo, ) class MessageStatus(StrEnum): PROCESSING = "processing" DONE = "done" NEEDS_CHECK = "needs_check" ERROR = "error" LaxMessageStatus = Annotated[MessageStatus, Field(strict=False)] class MessageError(ContractModel): """`error {code, message}` конверта: оба поля обязательны.""" code: LaxErrorCode message: str class RecognitionResultMessage(ContractModel): schema_version: SchemaVersion job_id: ContractUUID # recognition_job.id file_id: ContractUUID # stored_file.id status: LaxMessageStatus service: ServiceInfo result_s3: OptionalNotNull[S3Ref] = None # results/{file_id}/{job_id}/result.json result_schema_version: OptionalNotNull[ResultSchemaVersion] = None # schema_version файла результата error: OptionalNotNull[MessageError] = None raw_s3_key: str | None = None # scan-gateway: raw/{job_id}.json; в БД не регистрируется metrics: OptionalNotNull[Metrics] = None @model_validator(mode="after") def _check_status_requirements(self) -> Self: if self.status in (MessageStatus.DONE, MessageStatus.NEEDS_CHECK): missing = [name for name in ("result_s3", "result_schema_version") if getattr(self, name) is None] if missing: raise ValueError(f"при status = {self.status.value} обязательны поля: {', '.join(missing)}") elif self.status is MessageStatus.ERROR and self.error is None: raise ValueError("при status = error обязательно поле error") return self