"""Health check and metric endpoints for API v1.""" from fastapi import APIRouter, Depends, HTTPException, status from fastapi.responses import FileResponse import os from sqlalchemy.orm import Session from sqlalchemy import text from typing import List, Optional import datetime from backend.database import get_db from backend.models.daily_health_metrics import DailyHealthMetrics from backend.models.user import User from backend.models.workout_session import WorkoutSession from backend.schemas.daily_health_metrics import DailyHealthMetricsResponse from backend import auth router = APIRouter(prefix="/api/health", tags=["health"]) @router.get("", status_code=status.HTTP_200_OK) def health_check(db: Session = Depends(get_db)): """Check if the application and database are healthy.""" try: # Check database connection db.execute(text("SELECT 1")) return { "status": "healthy", "database": "connected" } except Exception as e: raise HTTPException(status_code=503, detail=f"Unhealthy: {str(e)}") @router.get("/stats", response_model=List[DailyHealthMetricsResponse]) def get_health_stats( period: str = "week", ref_date: Optional[datetime.date] = None, db: Session = Depends(get_db), current_user: User = Depends(auth.get_current_user) ): """Retrieve health statistics for various periods.""" if ref_date is None: ref_date = datetime.date.today() if period == "day": return db.query(DailyHealthMetrics).filter( DailyHealthMetrics.user_id == current_user.id, DailyHealthMetrics.date == ref_date ).all() elif period == "week": start_date = ref_date - datetime.timedelta(days=6) return db.query(DailyHealthMetrics).filter( DailyHealthMetrics.user_id == current_user.id, DailyHealthMetrics.date.between(start_date, ref_date) ).order_by(DailyHealthMetrics.date.asc()).all() elif period == "month": start_date = ref_date - datetime.timedelta(days=29) raw_data = db.query(DailyHealthMetrics).filter( DailyHealthMetrics.user_id == current_user.id, DailyHealthMetrics.date.between(start_date, ref_date) ).order_by(DailyHealthMetrics.date.asc()).all() aggregated = [] import itertools key_func = lambda x: x.date.isocalendar()[1] for key, group in itertools.groupby(raw_data, key_func): g = list(group) first = g[0] agg = { "id": key, "user_id": current_user.id, "date": first.date, "step_count": sum(x.step_count for x in g) // len(g), "step_count_total": sum(x.step_count for x in g), "calories_burned": sum(x.calories_burned for x in g) / len(g), "distance_meters": sum(x.distance_meters for x in g) / len(g), "sleep_duration_minutes": sum(x.sleep_duration_minutes for x in g) // len(g), "deep_sleep_minutes": sum(x.deep_sleep_minutes for x in g) // len(g), "light_sleep_minutes": sum(getattr(x, 'light_sleep_minutes', 0) for x in g) // len(g), "rem_sleep_minutes": sum(getattr(x, 'rem_sleep_minutes', 0) for x in g) // len(g), "awake_duration_minutes": sum(x.awake_duration_minutes for x in g) // len(g), "pai_score": sum(x.pai_score for x in g) / len(g), "avg_heart_rate": sum(x.avg_heart_rate or 0 for x in g) // len(g), "avg_spo2": sum(x.avg_spo2 or 0 for x in g) / len(g), "min_heart_rate": min((x.min_heart_rate for x in g if x.min_heart_rate), default=0), "max_heart_rate": max((x.max_heart_rate for x in g if x.max_heart_rate), default=0) } aggregated.append(agg) return aggregated elif period == "year": start_date = datetime.date.today() - datetime.timedelta(days=365) raw_data = db.query(DailyHealthMetrics).filter( DailyHealthMetrics.user_id == current_user.id, DailyHealthMetrics.date >= start_date ).order_by(DailyHealthMetrics.date.asc()).all() aggregated = [] import itertools key_func = lambda x: x.date.month for key, group in itertools.groupby(raw_data, key_func): g = list(group) first = g[0] agg = { "id": key, "user_id": current_user.id, "date": first.date.replace(day=1), "step_count": sum(x.step_count for x in g) // len(g), "step_count_total": sum(x.step_count for x in g), "calories_burned": sum(x.calories_burned for x in g) / len(g), "distance_meters": sum(x.distance_meters for x in g) / len(g), "sleep_duration_minutes": sum(x.sleep_duration_minutes for x in g) // len(g), "deep_sleep_minutes": sum(x.deep_sleep_minutes for x in g) // len(g), "light_sleep_minutes": sum(getattr(x, 'light_sleep_minutes', 0) for x in g) // len(g), "rem_sleep_minutes": sum(getattr(x, 'rem_sleep_minutes', 0) for x in g) // len(g), "awake_duration_minutes": sum(x.awake_duration_minutes for x in g) // len(g), "pai_score": sum(x.pai_score for x in g) / len(g), "avg_heart_rate": sum(x.avg_heart_rate or 0 for x in g) // len(g), "avg_spo2": sum(x.avg_spo2 or 0 for x in g) / len(g), "min_heart_rate": min((x.min_heart_rate for x in g if x.min_heart_rate), default=0), "max_heart_rate": max((x.max_heart_rate for x in g if x.max_heart_rate), default=0) } aggregated.append(agg) return aggregated return [] @router.get("/workouts") def get_workouts( limit: int = 50, offset: int = 0, activity_type: Optional[str] = None, db: Session = Depends(get_db), current_user: User = Depends(auth.get_current_user) ): """Retrieve workout sessions for the current user.""" query = db.query(WorkoutSession).filter( WorkoutSession.user_id == current_user.id ) if activity_type: query = query.filter(WorkoutSession.activity_type == activity_type) workouts = query.order_by( WorkoutSession.start_time.desc() ).offset(offset).limit(limit).all() # Manually serialize to avoid Pydantic issues return [ { "id": w.id, "user_id": w.user_id, "start_time": w.start_time.isoformat() if w.start_time else None, "end_time": w.end_time.isoformat() if w.end_time else None, "activity_type": w.activity_type, "activity_type_id": w.activity_type_id, "duration_seconds": w.duration_seconds or 0, "distance_meters": w.distance_meters or 0.0, "calories": w.calories or 0.0, "avg_pace": w.avg_pace, "max_pace": w.max_pace, "min_pace": w.min_pace, "avg_hr": w.avg_hr, "max_hr": w.max_hr, "min_hr": w.min_hr, "notes": w.notes } for w in workouts ] @router.get("/workouts/summary") def get_workout_summary( db: Session = Depends(get_db), current_user: User = Depends(auth.get_current_user) ): """Get summary statistics for workouts.""" from sqlalchemy import func # Total counts total = db.query(func.count(WorkoutSession.id)).filter( WorkoutSession.user_id == current_user.id ).scalar() # By activity type by_type = db.query( WorkoutSession.activity_type, func.count(WorkoutSession.id).label('count'), func.sum(WorkoutSession.duration_seconds).label('total_duration'), func.sum(WorkoutSession.calories).label('total_calories'), func.sum(WorkoutSession.distance_meters).label('total_distance') ).filter( WorkoutSession.user_id == current_user.id ).group_by(WorkoutSession.activity_type).all() return { "total_workouts": total, "by_activity_type": [ { "activity_type": row[0], "count": row[1], "total_duration_seconds": row[2] or 0, "total_calories": row[3] or 0, "total_distance_meters": row[4] or 0 } for row in by_type ] }