"""Bread ingredients model and calculation logic.""" from pydantic import BaseModel, Field, computed_field class SourdoughRatio(BaseModel): """ Represents the flour:water ratio in the sourdough starter. Example: - Ratio(1, 1) = 50% hydration (equal parts) - Ratio(1, 0.8) = 44.4% hydration - Ratio(1, 1.2) = 54.5% hydration """ flour_parts: float = Field(gt=0, description="Flour parts in starter (typically 1)") water_parts: float = Field(gt=0, description="Water parts in starter") @computed_field @property def hydration_percentage(self) -> float: """Calculate hydration % of the starter.""" return (self.water_parts / self.flour_parts) * 100 class BreadRecipe(BaseModel): """ Input parameters for bread recipe calculation. All percentages follow baker's percentage (relative to total flour weight). """ # What the user provides total_flour: float = Field(gt=1, description="Total flour weight in grams") hydration: float = Field( ge=50, le=100, description="Total hydration % (water/flour)" ) sourdough_percentage: float = Field( ge=10, le=100, description="Sourdough as % of total flour weight" ) sourdough_ratio: SourdoughRatio = Field( description="Flour:water ratio in starter" ) salt: float = Field(ge=0, le=2, description="Salt as % of total flour") # What Pydantic calculates automatically @computed_field @property def sourdough_weight(self) -> float: """Calculate total sourdough weight in grams.""" return round(self.total_flour * (self.sourdough_percentage / 100), 2) @computed_field @property def water_in_sourdough(self) -> float: """Calculate water content in the sourdough.""" ratio = self.sourdough_ratio total_parts = ratio.flour_parts + ratio.water_parts water_fraction = self.sourdough_ratio.water_parts / total_parts return round(self.sourdough_weight * water_fraction, 2) @computed_field @property def flour_in_sourdough(self) -> float: """Calculate flour content in the sourdough.""" return round(self.sourdough_weight - self.water_in_sourdough, 2) @computed_field @property def total_water(self) -> float: """Calculate total water needed in the recipe.""" return round(self.total_flour * (self.hydration / 100), 2) @computed_field @property def water_to_add(self) -> float: """Calculate water to add (excluding water in sourdough).""" return round(self.total_water - self.water_in_sourdough, 2) @computed_field @property def flour_to_add(self) -> float: """Calculate flour to add (excluding flour in sourdough).""" return round(self.total_flour - self.flour_in_sourdough, 2) @computed_field @property def salt_weight(self) -> float: """Calculate salt weight in grams.""" return round(self.total_flour * (self.salt / 100), 2)