dough-calc/bread.py

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)