diff options
| author | Tyler Hoang <tyler@tylerhoang.xyz> | 2026-05-08 01:58:48 -0700 |
|---|---|---|
| committer | Tyler Hoang <tyler@tylerhoang.xyz> | 2026-05-08 01:58:48 -0700 |
| commit | 2e0a94e88c847a5ed8dc6ad5fa49715cd631bdfe (patch) | |
| tree | 0c27fc5a8d8cbba60e571bb6690a13c0c0060ff4 /routers/menus.py | |
Initial commit — Commis personal chef app
AI-powered local chef tool: pantry tracking, meal logging, rotating weekly
menu generation, and grocery list optimization via Ollama (llama3).
FastAPI backend, SQLite, vanilla JS frontend.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Diffstat (limited to 'routers/menus.py')
| -rw-r--r-- | routers/menus.py | 138 |
1 files changed, 138 insertions, 0 deletions
diff --git a/routers/menus.py b/routers/menus.py new file mode 100644 index 0000000..49c4654 --- /dev/null +++ b/routers/menus.py @@ -0,0 +1,138 @@ +from fastapi import APIRouter, Depends, HTTPException +from sqlalchemy.orm import Session +from datetime import datetime, timedelta, date +import json + +from database import get_db +from schemas import MenuPlanRead +from models import MenuPlan, Recipe +from services import pantry_service, ai_service +from config import settings + +router = APIRouter(prefix="/api/menus", tags=["menus"]) + + +@router.get("/current") +async def get_current_menu(db: Session = Depends(get_db)): + """Get menu plan for current week.""" + today = datetime.utcnow().date() + monday = today - timedelta(days=today.weekday()) + + menu = db.query(MenuPlan).filter(MenuPlan.week_start == monday).first() + if not menu: + raise HTTPException(status_code=404, detail="No menu plan for current week") + return MenuPlanRead.from_orm(menu) + + +@router.post("/generate") +async def generate_menu(request: dict = None, db: Session = Depends(get_db)): + """Generate a weekly menu using AI.""" + # Parse request body + week_start = None + if request and isinstance(request, dict): + week_start = request.get("week_start") + + # Determine week_start date + if week_start: + try: + if isinstance(week_start, str): + week_start = datetime.fromisoformat(week_start).date() + else: + week_start = week_start + except Exception: + raise HTTPException(status_code=400, detail="Invalid week_start date") + else: + today = datetime.utcnow().date() + week_start = today - timedelta(days=today.weekday()) + + # Build pantry context and generate menu + try: + pantry_context = pantry_service.build_pantry_context(db) + ai_result = await ai_service.generate_weekly_menu(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)}") + + # Save recipes and build plan dict + week_plan = ai_result.get("week_plan", {}) + plan_dict = {} + + for day, meals in week_plan.items(): + plan_dict[day] = {} + for meal_type, meal_data in meals.items(): + if isinstance(meal_data, dict) and "name" in meal_data: + meal_name = meal_data["name"] + + # Check if recipe exists by name + existing_recipe = db.query(Recipe).filter(Recipe.name == meal_name).first() + if existing_recipe: + recipe_id = existing_recipe.id + else: + # Create new recipe + new_recipe = Recipe( + name=meal_name, + meal_type=meal_type, + ingredients=json.dumps(meal_data.get("ingredients", [])), + instructions=meal_data.get("instructions", ""), + estimated_time_minutes=meal_data.get("time_minutes", 30), + servings=meal_data.get("serves", 2), + source="ai", + ) + db.add(new_recipe) + db.flush() + recipe_id = new_recipe.id + + plan_dict[day][meal_type] = recipe_id + + # Upsert MenuPlan + existing_plan = db.query(MenuPlan).filter(MenuPlan.week_start == week_start).first() + if existing_plan: + existing_plan.plan = json.dumps(plan_dict) + existing_plan.generated_at = datetime.utcnow() + existing_plan.model_used = settings.model_name + menu = existing_plan + else: + menu = MenuPlan( + week_start=week_start, + plan=json.dumps(plan_dict), + generated_at=datetime.utcnow(), + model_used=settings.model_name, + ) + db.add(menu) + + db.commit() + db.refresh(menu) + + return { + "menu_plan": MenuPlanRead.from_orm(menu), + "week_plan": week_plan, + "notes": ai_result.get("notes", ""), + } + + +@router.get("") +async def list_menus(db: Session = Depends(get_db)): + """List all menu plans.""" + menus = db.query(MenuPlan).order_by(MenuPlan.week_start.desc()).all() + return [MenuPlanRead.from_orm(m) for m in menus] + + +@router.get("/{week_start}") +async def get_menu(week_start: date, db: Session = Depends(get_db)): + """Get menu plan for a specific week.""" + menu = db.query(MenuPlan).filter(MenuPlan.week_start == week_start).first() + if not menu: + raise HTTPException(status_code=404, detail="Menu plan not found") + return MenuPlanRead.from_orm(menu) + + +@router.delete("/{id}") +async def delete_menu(id: int, db: Session = Depends(get_db)): + """Delete a menu plan.""" + menu = db.query(MenuPlan).filter(MenuPlan.id == id).first() + if not menu: + raise HTTPException(status_code=404, detail="Menu plan not found") + db.delete(menu) + db.commit() + return {"status": "deleted"} |
