92 lines
3.0 KiB
Python
92 lines
3.0 KiB
Python
"""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)
|
|
|