Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
713 changes: 617 additions & 96 deletions backend/routes/beerxml.py

Large diffs are not rendered by default.

8 changes: 6 additions & 2 deletions backend/services/ai/flowchart_ai_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -370,9 +370,13 @@ def _apply_changes_to_recipe(
f"Ingredient '{ingredient_name}' not found for {change_type}"
)

elif change_type in ["batch_size_converted", "temperature_converted"]:
elif change_type in [
"batch_size_converted",
"batch_size_normalized",
"temperature_converted",
]:
# Recipe-level unit conversions with guards for missing values
if change_type == "batch_size_converted":
if change_type in ["batch_size_converted", "batch_size_normalized"]:
new_value = change.get("new_value")
new_unit = change.get("new_unit")
if new_value is not None:
Expand Down
85 changes: 84 additions & 1 deletion backend/services/ai/optimization_strategies.py
Original file line number Diff line number Diff line change
Expand Up @@ -1606,7 +1606,11 @@ def execute(self, parameters: Dict[str, Any] = None) -> List[Dict[str, Any]]:
# Convert batch size
batch_size = self.recipe.get("batch_size")
batch_unit = self.recipe.get("batch_size_unit", "gal")
if batch_size and batch_unit.lower() in ["gal", "gallon", "gallons"]:
if batch_size is not None and batch_unit.lower() in [
"gal",
"gallon",
"gallons",
]:
new_size = UnitConverter.convert_volume(batch_size, batch_unit, "l")
changes.append(
{
Expand Down Expand Up @@ -1673,12 +1677,59 @@ def execute(self, parameters: Dict[str, Any] = None) -> List[Dict[str, Any]]:
Example:
- 1587.57g -> 1600g (nearest 25g for grains)
- 28.3g -> 30g (nearest 5g for hops)
- 18.927L -> 19L (nearest 0.5L for batch size)
"""
changes = []
parameters = parameters or {}

grain_increment = parameters.get("grain_increment", 25) # 25g
hop_increment = parameters.get("hop_increment", 5) # 5g
batch_size_increment = parameters.get("batch_size_increment", 0.5) # 0.5L
if batch_size_increment <= 0:
logger.warning(
"Invalid batch_size_increment %s; falling back to 0.5L",
batch_size_increment,
)
batch_size_increment = 0.5

# Normalize batch size if present
batch_size = self.recipe.get("batch_size")
batch_unit = self.recipe.get("batch_size_unit", "")
logger.info(
f"[BATCH_SIZE_NORMALIZATION] Input batch size: {batch_size} {batch_unit}, increment: {batch_size_increment}"
)
if batch_size is not None and batch_unit.lower() in [
"l",
"liter",
"liters",
"litre",
"litres",
]:
# Round to nearest increment
normalized_batch_size = (
round(batch_size / batch_size_increment) * batch_size_increment
)
logger.info(
f"[BATCH_SIZE_NORMALIZATION] Calculated normalized batch size: {normalized_batch_size:.6f} l"
)

if abs(batch_size - normalized_batch_size) > 0.01:
logger.info(
f"[BATCH_SIZE_NORMALIZATION] Creating normalization change: {batch_size:.6f} -> {normalized_batch_size:.6f}"
)
changes.append(
{
"type": "batch_size_normalized",
"old_value": batch_size,
"new_value": normalized_batch_size,
"unit": "l",
"reason": f"Normalized batch size from {batch_size:.3f}L to {normalized_batch_size:.1f}L",
}
)
else:
logger.info(
f"[BATCH_SIZE_NORMALIZATION] No normalization needed (difference < 0.01)"
)

ingredients = self.recipe.get("ingredients", [])
for ingredient in ingredients:
Expand Down Expand Up @@ -1749,12 +1800,44 @@ def execute(self, parameters: Dict[str, Any] = None) -> List[Dict[str, Any]]:
Example:
- 3.858 lbs -> 3.875 lbs (nearest 0.125 lb = nearest 2 oz)
- 1.058 oz -> 1.0 oz (nearest 0.25 oz)
- 5.28 gal -> 5.5 gal (nearest 0.5 gal for batch size)
"""
changes = []
parameters = parameters or {}

grain_increment = parameters.get("grain_increment", 0.125) # 1/8 lb = 2 oz
hop_increment = parameters.get("hop_increment", 0.25) # 1/4 oz
batch_size_increment = parameters.get("batch_size_increment", 0.5) # 0.5 gal
if batch_size_increment <= 0:
logger.warning(
"Invalid batch_size_increment %s; falling back to 0.5 gal",
batch_size_increment,
)
batch_size_increment = 0.5

# Normalize batch size if present
batch_size = self.recipe.get("batch_size")
batch_unit = self.recipe.get("batch_size_unit", "")
if batch_size is not None and batch_unit.lower() in [
"gal",
"gallon",
"gallons",
]:
# Round to nearest increment
normalized_batch_size = (
round(batch_size / batch_size_increment) * batch_size_increment
)

if abs(batch_size - normalized_batch_size) > 0.01:
changes.append(
{
"type": "batch_size_normalized",
"old_value": batch_size,
"new_value": normalized_batch_size,
"unit": "gal",
"reason": f"Normalized batch size from {batch_size:.2f} gal to {normalized_batch_size:.1f} gal",
}
)

ingredients = self.recipe.get("ingredients", [])
for ingredient in ingredients:
Expand Down
15 changes: 14 additions & 1 deletion backend/services/ai/recipe_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,7 @@ def _apply_single_change(self, change: Dict[str, Any]):
self._modify_recipe_parameter(change)
elif change_type in ["ingredient_converted", "ingredient_normalized"]:
self._convert_or_normalize_ingredient(change)
elif change_type == "batch_size_converted":
elif change_type in ["batch_size_converted", "batch_size_normalized"]:
self._convert_batch_size(change)
elif change_type == "temperature_converted":
self._convert_temperature(change)
Expand Down Expand Up @@ -367,6 +367,13 @@ def _convert_or_normalize_ingredient(self, change: Dict[str, Any]):

def _convert_batch_size(self, change: Dict[str, Any]):
"""Convert batch size to new unit."""
change_type = change.get("type", "unknown")
old_batch_size = self.recipe.get("batch_size")
old_batch_unit = self.recipe.get("batch_size_unit")
logger.info(
f"[BATCH_SIZE_APPLY] Applying {change_type}: current recipe batch_size={old_batch_size} {old_batch_unit}"
)

new_value = change.get("new_value")
new_unit = change.get("new_unit")

Expand All @@ -376,10 +383,16 @@ def _convert_batch_size(self, change: Dict[str, Any]):
f"Invalid batch size value: {new_value} - must be positive numeric"
)
return
logger.info(f"[BATCH_SIZE_APPLY] Setting batch_size to {new_value}")
self.recipe["batch_size"] = new_value
if new_unit is not None:
logger.info(f"[BATCH_SIZE_APPLY] Setting batch_size_unit to {new_unit}")
self.recipe["batch_size_unit"] = new_unit

logger.info(
f"[BATCH_SIZE_APPLY] After apply: batch_size={self.recipe.get('batch_size')} {self.recipe.get('batch_size_unit')}"
)

def _convert_temperature(self, change: Dict[str, Any]):
"""Convert temperature to new unit."""
parameter = change.get("parameter", "mash_temperature")
Expand Down
2 changes: 2 additions & 0 deletions backend/services/ai/workflows/unit_conversion.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ nodes:
parameters:
grain_increment: 25 # Round to nearest 25g (e.g., 1587.57g -> 1600g)
hop_increment: 5 # Round to nearest 5g (e.g., 28.3g -> 30g)
batch_size_increment: 0.5 # Round batch size to nearest 0.5L (e.g., 18.927L -> 19L)
Comment thread
coderabbitai[bot] marked this conversation as resolved.
large_grain_unit: "kg" # Use kg for amounts >= 1000g
small_grain_unit: "g" # Use g for amounts < 1000g
hop_unit: "g" # Always use g for hops
Expand All @@ -79,6 +80,7 @@ nodes:
parameters:
grain_increment: 0.125 # Round to nearest 1/8 lb = 2 oz (e.g., 3.858 lbs -> 3.875 lbs)
hop_increment: 0.25 # Round to nearest 0.25 oz (e.g., 1.058 oz -> 1.0 oz)
batch_size_increment: 0.5 # Round batch size to nearest 0.5 gal (e.g., 5.28 gal -> 5.5 gal)
large_grain_unit: "lb" # Use lb for amounts >= 8 oz
small_grain_unit: "oz" # Use oz for amounts < 8 oz
hop_unit: "oz" # Always use oz for hops
Expand Down
Loading