summaryrefslogtreecommitdiff
path: root/routers/grocery.py
diff options
context:
space:
mode:
Diffstat (limited to 'routers/grocery.py')
-rw-r--r--routers/grocery.py186
1 files changed, 186 insertions, 0 deletions
diff --git a/routers/grocery.py b/routers/grocery.py
new file mode 100644
index 0000000..cde2068
--- /dev/null
+++ b/routers/grocery.py
@@ -0,0 +1,186 @@
+from fastapi import APIRouter, Depends, HTTPException, Body
+from sqlalchemy.orm import Session
+from datetime import datetime, timedelta
+import json
+
+from database import get_db
+from schemas import GroceryListRead
+from models import GroceryList, MenuPlan, Recipe, Ingredient
+from services import pantry_service, ai_service
+
+router = APIRouter(prefix="/api/grocery", tags=["grocery"])
+ai_router = APIRouter(prefix="/api/ai", tags=["ai"])
+
+
+@router.get("")
+async def list_grocery_lists(db: Session = Depends(get_db)):
+ """List all grocery lists."""
+ lists = db.query(GroceryList).order_by(GroceryList.generated_at.desc()).all()
+ return [GroceryListRead.from_orm(l) for l in lists]
+
+
+@router.get("/current")
+async def get_current_grocery_list(db: Session = Depends(get_db)):
+ """Get grocery list for current week."""
+ today = datetime.utcnow().date()
+ monday = today - timedelta(days=today.weekday())
+
+ grocery_list = db.query(GroceryList).filter(GroceryList.generated_for == monday).first()
+ if not grocery_list:
+ raise HTTPException(status_code=404, detail="No grocery list for current week")
+ return GroceryListRead.from_orm(grocery_list)
+
+
+@router.get("/{id}")
+async def get_grocery_list(id: int, db: Session = Depends(get_db)):
+ """Get a grocery list by ID."""
+ grocery_list = db.query(GroceryList).filter(GroceryList.id == id).first()
+ if not grocery_list:
+ raise HTTPException(status_code=404, detail="Grocery list not found")
+ return GroceryListRead.from_orm(grocery_list)
+
+
+@router.post("/generate")
+async def generate_grocery_list(db: Session = Depends(get_db)):
+ """Generate a grocery list for the current week."""
+ # Get current week's Monday
+ today = datetime.utcnow().date()
+ monday = today - timedelta(days=today.weekday())
+
+ # Fetch current MenuPlan
+ menu_plan = db.query(MenuPlan).filter(MenuPlan.week_start == monday).first()
+ if not menu_plan:
+ raise HTTPException(status_code=404, detail="No menu plan for current week. Generate a menu first.")
+
+ # Parse the plan JSON
+ try:
+ plan_dict = json.loads(menu_plan.plan)
+ except json.JSONDecodeError:
+ raise HTTPException(status_code=500, detail="Invalid menu plan data")
+
+ # Collect all recipe IDs and fetch recipes
+ recipe_ids = set()
+ for day_meals in plan_dict.values():
+ if isinstance(day_meals, dict):
+ for recipe_id in day_meals.values():
+ if isinstance(recipe_id, int):
+ recipe_ids.add(recipe_id)
+
+ recipes = db.query(Recipe).filter(Recipe.id.in_(recipe_ids)).all() if recipe_ids else []
+ recipe_map = {r.id: r for r in recipes}
+
+ # Build menu_plan_for_ai
+ menu_plan_for_ai = {}
+ for day, day_meals in plan_dict.items():
+ menu_plan_for_ai[day] = {}
+ for meal_type, recipe_id in day_meals.items():
+ recipe = recipe_map.get(recipe_id)
+ if recipe:
+ try:
+ ingredients = json.loads(recipe.ingredients)
+ except (json.JSONDecodeError, TypeError):
+ ingredients = []
+ menu_plan_for_ai[day][meal_type] = {
+ "name": recipe.name,
+ "ingredients": ingredients,
+ }
+
+ # Build pantry context and generate grocery list
+ try:
+ pantry_context = pantry_service.build_pantry_context(db)
+ ai_result = await ai_service.generate_grocery_list(menu_plan_for_ai, pantry_context)
+ except ValueError as e:
+ raise HTTPException(status_code=503, detail=str(e))
+ except (ConnectionError, Exception) as e:
+ raise HTTPException(status_code=503, detail=f"Ollama service error: {str(e)}")
+
+ # Upsert GroceryList
+ existing_list = db.query(GroceryList).filter(GroceryList.generated_for == monday).first()
+ if existing_list:
+ existing_list.items = json.dumps(ai_result.get("items", []))
+ existing_list.total_estimate = ai_result.get("total_estimate", 0.0)
+ existing_list.notes = ai_result.get("shopping_notes", "")
+ existing_list.generated_at = datetime.utcnow()
+ grocery_list = existing_list
+ else:
+ grocery_list = GroceryList(
+ generated_for=monday,
+ items=json.dumps(ai_result.get("items", [])),
+ total_estimate=ai_result.get("total_estimate", 0.0),
+ notes=ai_result.get("shopping_notes", ""),
+ generated_at=datetime.utcnow(),
+ )
+ db.add(grocery_list)
+
+ db.commit()
+ db.refresh(grocery_list)
+
+ return {
+ "grocery_list": GroceryListRead.from_orm(grocery_list),
+ "items": ai_result.get("items", []),
+ "shopping_notes": ai_result.get("shopping_notes", ""),
+ }
+
+
+@router.put("/{id}/purchased")
+async def mark_purchased(id: int, db: Session = Depends(get_db)):
+ """Mark a grocery list as purchased and update pantry."""
+ grocery_list = db.query(GroceryList).filter(GroceryList.id == id).first()
+ if not grocery_list:
+ raise HTTPException(status_code=404, detail="Grocery list not found")
+
+ # Mark as purchased
+ grocery_list.is_purchased = True
+
+ # Parse items
+ try:
+ items = json.loads(grocery_list.items)
+ except json.JSONDecodeError:
+ items = []
+
+ # Update pantry for each item
+ for item in items:
+ if isinstance(item, dict):
+ name = item.get("name")
+ quantity = item.get("quantity", 0)
+ unit = item.get("unit", "")
+
+ if name:
+ # Check if ingredient exists
+ existing_ingredient = db.query(Ingredient).filter(Ingredient.name == name).first()
+ if existing_ingredient:
+ existing_ingredient.quantity += quantity
+ existing_ingredient.updated_at = datetime.utcnow()
+ else:
+ # Create new ingredient
+ new_ingredient = Ingredient(
+ name=name,
+ quantity=quantity,
+ unit=unit,
+ category=item.get("store_section"),
+ )
+ db.add(new_ingredient)
+
+ db.commit()
+ db.refresh(grocery_list)
+ return GroceryListRead.from_orm(grocery_list)
+
+
+@ai_router.post("/suggest-recipe")
+async def suggest_recipe_endpoint(db: Session = Depends(get_db)):
+ """Suggest a recipe based on current pantry."""
+ try:
+ context = pantry_service.build_pantry_context(db)
+ result = await ai_service.suggest_recipe(context)
+ return result
+ except ValueError as e:
+ raise HTTPException(status_code=503, detail=str(e))
+ except (ConnectionError, Exception) as e:
+ raise HTTPException(status_code=503, detail=f"Ollama service error: {str(e)}")
+
+
+@ai_router.get("/models")
+async def list_models():
+ """List available Ollama models."""
+ models = await ai_service.get_available_models()
+ return {"models": models}