"""Message payloads for the RabbitMQ pipeline (pydantic v2). Validated on consume; a body that fails validation goes straight to the DLQ with failure_class=infra. Every message carries `correlation_id` (also placed in RabbitMQ header `x-correlation-id` by the publisher/consumer) so logs and traces trace api → rabbit → workers under one id. """ from __future__ import annotations import uuid from typing import Self from pydantic import BaseModel, Field class PipelineMessage(BaseModel): """Base: every pipeline message has these.""" correlation_id: uuid.UUID document_id: uuid.UUID user_id: uuid.UUID attempt: int = Field(default=0, ge=0) def next_attempt(self) -> Self: return self.model_copy(update={"attempt": self.attempt + 1}) class DocumentUploaded(PipelineMessage): """api → contracts.x[extract] → worker-extract. `s3_key` points at the raw uploaded blob (`users/{uid}/docs/{did}.{ext}`). """ s3_key: str filename: str mime: str class DocumentExtracted(PipelineMessage): """worker-extract → contracts.x[analyze] → worker-analyze. `extracted_s3_key` points at the extracted plaintext (`users/{uid}/docs/{did}.txt`). """ extracted_s3_key: str char_count: int ocr_used: bool