from datetime import date as date_type from datetime import datetime from typing import Any import litellm from fastapi import APIRouter, Depends, File, Form, HTTPException, Query, UploadFile from pydantic import BaseModel from sqlmodel import Session, select from app.ai.nutrition import NutritionalInfo, analyze_nutrition_from_image, nutrition_module from app.api import deps from app.models.food import FoodLog # Added FoodItem router = APIRouter() class AnalyzeRequest(BaseModel): description: str class FoodLogCreate(BaseModel): name: str calories: float protein: float carbs: float fats: float @router.post("/analyze", response_model=NutritionalInfo) def analyze_food( request: AnalyzeRequest, current_user: deps.CurrentUser, ) -> Any: """ Analyze food description and return nutritional info using DSPy. """ try: result = nutrition_module(description=request.description) return result.nutritional_info except Exception as e: raise HTTPException(status_code=500, detail=str(e)) @router.post("/analyze/image", response_model=NutritionalInfo) async def analyze_food_image( current_user: deps.CurrentUser, file: UploadFile = File(...), description: str = Form(""), ) -> Any: """ Analyze food image and return nutritional info. """ try: contents = await file.read() return analyze_nutrition_from_image(contents, description) except litellm.exceptions.BadRequestError as e: raise HTTPException(status_code=400, detail=f"Invalid image or request: {str(e)}") except Exception as e: raise HTTPException(status_code=500, detail=str(e)) @router.post("/log", response_model=FoodLog) def log_food( *, session: Session = Depends(deps.get_session), nutrition_info: FoodLogCreate, current_user: deps.CurrentUser, ) -> Any: """ Save food log to database. """ food_log = FoodLog( user_id=current_user.id, name=nutrition_info.name, calories=nutrition_info.calories, protein=nutrition_info.protein, carbs=nutrition_info.carbs, fats=nutrition_info.fats, ) session.add(food_log) session.commit() session.refresh(food_log) return food_log @router.get("/logs", response_model=list[FoodLog]) def read_logs( current_user: deps.CurrentUser, session: Session = Depends(deps.get_session), skip: int = 0, limit: int = 100, ) -> Any: """ Get food logs for current user. """ statement = ( select(FoodLog) .where(FoodLog.user_id == current_user.id) .order_by(FoodLog.timestamp.desc()) .offset(skip) .limit(limit) ) return session.exec(statement).all() class NutritionSummary(BaseModel): date: str total_calories: float total_protein: float total_carbs: float total_fats: float log_count: int target_calories: float | None target_protein: float | None target_carbs: float | None target_fat: float | None @router.get("/summary", response_model=NutritionSummary) def get_nutrition_summary( current_user: deps.CurrentUser, session: Session = Depends(deps.get_session), date: str = Query(default=None, description="Date in YYYY-MM-DD format, defaults to today"), ) -> Any: """ Get aggregated macro totals for a given day. """ if date: target_date = datetime.strptime(date, "%Y-%m-%d").date() else: target_date = date_type.today() start = datetime(target_date.year, target_date.month, target_date.day, 0, 0, 0) end = datetime(target_date.year, target_date.month, target_date.day, 23, 59, 59) statement = ( select(FoodLog) .where(FoodLog.user_id == current_user.id) .where(FoodLog.timestamp >= start) .where(FoodLog.timestamp <= end) ) logs = session.exec(statement).all() return NutritionSummary( date=target_date.isoformat(), total_calories=sum(log.calories for log in logs), total_protein=sum(log.protein for log in logs), total_carbs=sum(log.carbs for log in logs), total_fats=sum(log.fats for log in logs), log_count=len(logs), target_calories=getattr(current_user, "target_calories", None), target_protein=getattr(current_user, "target_protein", None), target_carbs=getattr(current_user, "target_carbs", None), target_fat=getattr(current_user, "target_fat", None), )