from __future__ import annotations from decimal import Decimal from typing import List, Optional from uuid import UUID from pydantic import BaseModel, Field, field_validator class RecipeIngredientRef(BaseModel): ingredient_id: UUID qty: float = Field(gt=0) unit: Optional[str] = Field(default=None, max_length=50) notes: Optional[str] = None class SideDishIngredient(BaseModel): name: str qty: float unit: Optional[str] = None class SideDish(BaseModel): name: str ingredients: List[SideDishIngredient] prep_notes: Optional[str] = None class RecipeBase(BaseModel): name: str = Field(min_length=1, max_length=300) description: Optional[str] = None image_url: Optional[str] = None prep_time_minutes: int = Field(ge=0) cook_time_minutes: int = Field(ge=0) servings: int = Field(gt=0) cuisine_tags: List[str] = Field(default_factory=list) dietary_tags: List[str] = Field(default_factory=list) protein_type: str = Field(min_length=1, max_length=50) spice_level: Optional[int] = Field(default=None, ge=0, le=5) calories_per_serving: Optional[int] = Field(default=None, ge=0) ingredients: List[RecipeIngredientRef] = Field(min_length=1) instructions: List[str] = Field(min_length=1) source_url: Optional[str] = None class RecipeCreate(RecipeBase): pass class RecipeUpdate(BaseModel): name: Optional[str] = None description: Optional[str] = None image_url: Optional[str] = None prep_time_minutes: Optional[int] = Field(default=None, ge=0) cook_time_minutes: Optional[int] = Field(default=None, ge=0) servings: Optional[int] = Field(default=None, gt=0) cuisine_tags: Optional[List[str]] = None dietary_tags: Optional[List[str]] = None protein_type: Optional[str] = None spice_level: Optional[int] = None calories_per_serving: Optional[int] = None ingredients: Optional[List[RecipeIngredientRef]] = None instructions: Optional[List[str]] = None side_dishes: Optional[List[SideDish]] = None class RecipeRead(RecipeBase): id: UUID external_source: Optional[str] = None external_id: Optional[str] = None discovery_reason: Optional[str] = None side_dishes: List[SideDish] = Field(default_factory=list) model_config = {"from_attributes": True} class ResolveIngredientRequest(BaseModel): text: str = Field(min_length=1) class ResolveIngredientCandidate(BaseModel): ingredient_id: UUID name: str score: float aisle: Optional[str] = None class ResolveIngredientResponse(BaseModel): parsed_qty: Optional[float] = None parsed_unit: Optional[str] = None parsed_text: str candidates: List[ResolveIngredientCandidate]