"""Pydantic schemas for Recipe and RecipeVersion operations.""" from src.backend.models.api.tipi import UtcDateTime from typing import Optional, TYPE_CHECKING from pydantic import BaseModel, ConfigDict, Field if TYPE_CHECKING: from src.backend.models.api.task import TaskResponse class RecipeCreate(BaseModel): """Schema for creating a new recipe.""" code: str = Field(..., min_length=1, max_length=100) name: str = Field(..., min_length=1, max_length=255) description: Optional[str] = None image_path: Optional[str] = Field(None, max_length=500) measurement_interval_minutes: Optional[int] = Field(None, ge=1, le=1440) # Rules of the recipe: what the operator must supply, and how a value may be # entered. Manual input defaults to forbidden - the caliper is the instrument. requires_lot: bool = False requires_serial: bool = False allow_manual_input: bool = False # Optional task-level fields for the initial technical drawing file_path: Optional[str] = Field(None, max_length=500) file_type: Optional[str] = Field(None, pattern="^(image|pdf)$") annotations_json: Optional[dict] = None class RecipeDuplicate(BaseModel): """Schema for duplicating a recipe (#52): only the new code and name.""" code: str = Field(..., min_length=1, max_length=100) name: str = Field(..., min_length=1, max_length=255) class RecipeUpdate(BaseModel): """Schema for updating a recipe (creates new version).""" name: Optional[str] = Field(None, min_length=1, max_length=255) description: Optional[str] = None image_path: Optional[str] = Field(None, max_length=500) measurement_interval_minutes: Optional[int] = Field(None, ge=1, le=1440) requires_lot: Optional[bool] = None requires_serial: Optional[bool] = None allow_manual_input: Optional[bool] = None change_notes: Optional[str] = None # Task-level fields: saved to the first task of the new version file_path: Optional[str] = Field(None, max_length=500) file_type: Optional[str] = Field(None, pattern="^(image|pdf)$") annotations_json: Optional[dict] = None class RecipeVersionResponse(BaseModel): """Schema for recipe version response.""" model_config = ConfigDict(from_attributes=True) id: int recipe_id: int version_number: int is_current: bool created_by: int created_at: UtcDateTime change_notes: Optional[str] = None tasks: list["TaskResponse"] = [] class RecipeResponse(BaseModel): """Schema for recipe response.""" model_config = ConfigDict(from_attributes=True) id: int code: str name: str description: Optional[str] = None image_path: Optional[str] = None measurement_interval_minutes: Optional[int] = None # The operator's screen reads these to know whether to ask for lot and serial # before starting, and whether to offer the keypad at all. requires_lot: bool = False requires_serial: bool = False allow_manual_input: bool = False created_by: int created_at: UtcDateTime active: bool current_version: Optional[RecipeVersionResponse] = None class RecipeListResponse(BaseModel): """Schema for paginated recipe list.""" items: list[RecipeResponse] total: int page: int per_page: int pages: int # Forward reference imports for model_rebuild from src.backend.models.api.task import TaskResponse # noqa: E402 RecipeVersionResponse.model_rebuild() RecipeResponse.model_rebuild()