fix: step_finalize — resolve ingredient names and prices in shopping list email

Preloads Ingredient names from the DB (ingredients JSONB has no name field),
deduplicates by ingredient name, looks up top-confidence IngredientGroceryMatch
per ingredient, and renders a rich 4-column HTML table (Ingredient | Qty | Unit |
At Lucky | Price) with an estimated total.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-09 20:48:46 -07:00
co-authored by Claude Sonnet 4.6
parent a03152e51a
commit bc85b9b617
+68 -9
View File
@@ -321,6 +321,8 @@ def step_finalize(run: "WeeklyRun", db: "Session") -> None:
logger.info("step_finalize: already done for %s", run.week_start_date) logger.info("step_finalize: already done for %s", run.week_start_date)
return return
import uuid as _uuid_fin
plan = ( plan = (
db.query(MealPlan) db.query(MealPlan)
.filter( .filter(
@@ -345,20 +347,77 @@ def step_finalize(run: "WeeklyRun", db: "Session") -> None:
.all() .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: if approved_items:
rows_html = "".join( total_cost = 0.0
f"<tr><td>{html.escape(item.recipe.name)}</td>" rows_html = ""
f"<td>{html.escape(str(ing.get('name', '')))}</td>" seen_ingredients: set = set()
f"<td>{html.escape(str(ing.get('qty', '')))} {html.escape(str(ing.get('unit', '')))}</td></tr>" for item in approved_items:
for item in approved_items if not item.recipe:
if item.recipe continue
for ing in (item.recipe.ingredients or []) 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"<tr>"
f"<td style='padding:6px 8px'>{html.escape(ing_name)}</td>"
f"<td style='padding:6px 8px;color:#555'>{html.escape(str(qty))} {html.escape(str(unit))}</td>"
f"<td style='padding:6px 8px;color:#555'>{lucky_name}</td>"
f"<td style='padding:6px 8px;font-weight:bold'>{price_str}</td>"
f"</tr>"
)
email_html = ( email_html = (
f"<div style='font-family:sans-serif;max-width:600px;margin:0 auto'>"
f"<h2>Shopping list — week of {run.week_start_date}</h2>" f"<h2>Shopping list — week of {run.week_start_date}</h2>"
f"<p>{len(approved_items)} meal(s) approved.</p>" f"<p>{len(approved_items)} meal(s) approved.</p>"
f"<table><thead><tr><th>Recipe</th><th>Ingredient</th><th>Qty</th></tr></thead>" f"<table style='border-collapse:collapse;width:100%;font-size:14px'>"
f"<tbody>{rows_html}</tbody></table>" f"<thead><tr style='background:#f3f4f6'>"
f"<th style='text-align:left;padding:6px 8px;border-bottom:2px solid #e5e7eb'>Ingredient</th>"
f"<th style='text-align:left;padding:6px 8px;border-bottom:2px solid #e5e7eb'>Qty</th>"
f"<th style='text-align:left;padding:6px 8px;border-bottom:2px solid #e5e7eb'>At Lucky</th>"
f"<th style='text-align:left;padding:6px 8px;border-bottom:2px solid #e5e7eb'>Price</th>"
f"</tr></thead>"
f"<tbody>{rows_html}</tbody>"
f"</table>"
f"<p style='margin-top:16px;font-weight:bold'>Estimated total: ${total_cost:.2f}</p>"
f"</div>"
) )
else: else:
email_html = f"<p>No meals were approved for week of {run.week_start_date}.</p>" email_html = f"<p>No meals were approved for week of {run.week_start_date}.</p>"