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"
| Recipe | Ingredient | Qty |
"
- f"{rows_html}
"
+ f"
"
+ f""
+ f"| Ingredient | "
+ f"Qty | "
+ f"At Lucky | "
+ f"Price | "
+ f"
"
+ f"{rows_html}"
+ f"
"
+ f"
Estimated total: ${total_cost:.2f}
"
+ f"
"
)
else:
email_html = f"No meals were approved for week of {run.week_start_date}.
"