diff options
Diffstat (limited to 'routers/grocery.py')
| -rw-r--r-- | routers/grocery.py | 186 |
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} |
