feat(backend): implement unit conversion for cost calculation

- Add UnitConverter (normalization, within-family, density tables)
- Update cost.py to convert recipe qty to grocery price unit
- Update generate.py _load_match_index to fetch ingredient name + unit
- Fix orchestrator email/shopping-list cost loops to use conversion
- Fix missing Ingredient import in generate.py
- Add 19 unit tests
This commit is contained in:
2026-05-24 13:46:50 -07:00
parent 3885d7d0dc
commit fd8ba3c4d2
6 changed files with 429 additions and 20 deletions
+25 -5
View File
@@ -23,6 +23,7 @@ from app.services.email import get_email_backend
from app.services.orchestrator.alerts import send_admin_alert
from app.services.planner.generate import generate_meal_plan
from app.services.scraper_service import ScraperService
from app.utils.units import convert_qty
from app.services.feedback_analyzer import FeedbackAnalyzer
from app.services.recipe_discovery import RecipeDiscoveryService
@@ -254,7 +255,16 @@ def step_email(run: "WeeklyRun", db: "Session") -> None:
.first()
)
if match and match.grocery_item and match.grocery_item.current_price:
est_cost_total += float(match.grocery_item.current_price)
qty = ing.get("qty", 1.0)
unit = ing.get("unit", "")
gunit = match.grocery_item.unit or ""
converted = convert_qty(
qty,
unit,
gunit,
ingredient_name_lower=ing_name,
)
est_cost_total += float(match.grocery_item.current_price) * float(converted)
recipe_servings = (item.recipe.servings or 4) if item.recipe else 4
est_cost_per_serving = est_cost_total / recipe_servings
@@ -408,10 +418,20 @@ def step_finalize(run: "WeeklyRun", db: "Session") -> None:
.first()
)
if match and match.grocery_item:
lucky_name = html.escape(match.grocery_item.name or "")
price = float(match.grocery_item.current_price or 0)
total_cost += price
price_str = f"${price:.2f}"
g_item = match.grocery_item
lucky_name = html.escape(g_item.name or "")
price = float(g_item.current_price or 0)
qty = ing.get("qty", 1.0)
unit = ing.get("unit", "")
gunit = g_item.unit or ""
converted = convert_qty(
qty,
unit,
gunit,
ingredient_name_lower=ing_name,
)
total_cost += price * float(converted)
price_str = f"${price * float(converted):.2f}"
else:
lucky_name = ""
price_str = ""