"""Pydantic schemas for Station and StationRecipeAssignment.""" from src.backend.models.api.tipi import UtcDateTime from typing import Optional from pydantic import BaseModel, ConfigDict, Field, field_validator def _normalize_worker_url(value: Optional[str]) -> Optional[str]: """Vuoto/spazi -> None (worker centrale); altrimenti http(s) senza '/' finale (#45).""" if value is None: return None value = value.strip() if not value: return None if not value.lower().startswith(("http://", "https://")): raise ValueError("vision_worker_url must start with http:// or https://") return value.rstrip("/") class StationCreate(BaseModel): code: str = Field(..., min_length=1, max_length=100) name: str = Field(..., min_length=1, max_length=255) location: Optional[str] = Field(default=None, max_length=255) notes: Optional[str] = None active: bool = True # worker di visione della stazione (#45); vuoto = centrale vision_worker_url: Optional[str] = Field(default=None, max_length=500) _check_worker_url = field_validator("vision_worker_url")(_normalize_worker_url) class StationUpdate(BaseModel): name: Optional[str] = Field(default=None, min_length=1, max_length=255) location: Optional[str] = Field(default=None, max_length=255) notes: Optional[str] = None active: Optional[bool] = None # worker di visione della stazione (#45); "" -> None riporta al centrale vision_worker_url: Optional[str] = Field(default=None, max_length=500) _check_worker_url = field_validator("vision_worker_url")(_normalize_worker_url) class StationResponse(BaseModel): model_config = ConfigDict(from_attributes=True) id: int code: str name: str location: Optional[str] notes: Optional[str] active: bool vision_worker_url: Optional[str] = None # #45 created_by: int created_at: UtcDateTime class StationRecipeAssignmentCreate(BaseModel): recipe_id: int = Field(..., gt=0) class StationRecipeAssignmentResponse(BaseModel): model_config = ConfigDict(from_attributes=True) id: int station_id: int recipe_id: int assigned_by: int assigned_at: UtcDateTime class RecipeSummary(BaseModel): model_config = ConfigDict(from_attributes=True) id: int code: str name: str active: bool image_path: Optional[str] = None description: Optional[str] = None # The selection screen has to know before the operator starts: what a recipe # demands is the difference between an enabled Avvia and a disabled one. requires_lot: bool = False requires_serial: bool = False class StationWithRecipesResponse(StationResponse): recipes: list[RecipeSummary] = Field(default_factory=list) class StationResetResponse(BaseModel): """Outcome of clearing every recipe assignment of a station.""" station_id: int removed: int