diff --git a/backend/app/services/orchestrator/steps.py b/backend/app/services/orchestrator/steps.py index 1d979ec..04cfc96 100644 --- a/backend/app/services/orchestrator/steps.py +++ b/backend/app/services/orchestrator/steps.py @@ -321,6 +321,8 @@ def step_finalize(run: "WeeklyRun", db: "Session") -> None: logger.info("step_finalize: already done for %s", run.week_start_date) return + import uuid as _uuid_fin + plan = ( db.query(MealPlan) .filter( @@ -345,20 +347,77 @@ def step_finalize(run: "WeeklyRun", db: "Session") -> None: .all() ) + # Preload ingredient names for approved items + _fin_ids: set = set() + for _item in approved_items: + if _item.recipe: + for _ing in (_item.recipe.ingredients or []): + if "ingredient_id" in _ing: + _fin_ids.add(_ing["ingredient_id"]) + _fin_names: dict = {} + if _fin_ids: + _fin_rows = db.query(Ingredient).filter( + Ingredient.id.in_([_uuid_fin.UUID(str(i)) for i in _fin_ids]) + ).all() + _fin_names = {str(r.id): r.name for r in _fin_rows} + if approved_items: - rows_html = "".join( - f"{html.escape(item.recipe.name)}" - f"{html.escape(str(ing.get('name', '')))}" - f"{html.escape(str(ing.get('qty', '')))} {html.escape(str(ing.get('unit', '')))}" - for item in approved_items - if item.recipe - for ing in (item.recipe.ingredients or []) - ) + total_cost = 0.0 + rows_html = "" + seen_ingredients: set = set() + for item in approved_items: + if not item.recipe: + continue + for ing in (item.recipe.ingredients or []): + ing_id = str(ing.get("ingredient_id", "")) + ing_name = _fin_names.get(ing_id, "") + if not ing_name or ing_name in seen_ingredients: + continue + seen_ingredients.add(ing_name) + qty = ing.get("qty", "") + unit = ing.get("unit", "") + + # Find Lucky CA match + match = ( + db.query(IngredientGroceryMatch) + .join(Ingredient, IngredientGroceryMatch.ingredient_id == Ingredient.id) + .filter(Ingredient.id == _uuid_fin.UUID(ing_id)) + .order_by(IngredientGroceryMatch.confidence.desc()) + .first() + ) + if match and match.grocery_item: + lucky_name = html.escape(match.grocery_item.name or "") + price = float(match.grocery_item.price or 0) + total_cost += price + price_str = f"${price:.2f}" + else: + lucky_name = "—" + price_str = "—" + + rows_html += ( + f"" + f"{html.escape(ing_name)}" + f"{html.escape(str(qty))} {html.escape(str(unit))}" + f"{lucky_name}" + f"{price_str}" + f"" + ) + email_html = ( + f"
" f"

Shopping list — week of {run.week_start_date}

" f"

{len(approved_items)} meal(s) approved.

" - f"" - f"{rows_html}
RecipeIngredientQty
" + f"" + f"" + f"" + f"" + f"" + f"" + f"" + f"{rows_html}" + f"
IngredientQtyAt LuckyPrice
" + f"

Estimated total: ${total_cost:.2f}

" + f"
" ) else: email_html = f"

No meals were approved for week of {run.week_start_date}.

"