from datetime import date, timedelta from fastapi import APIRouter, Depends from sqlalchemy import select, func from sqlalchemy.ext.asyncio import AsyncSession from backend.app.core.auth import get_current_rider from backend.app.core.database import get_session from backend.app.models.activity import Activity, ActivityMetrics from backend.app.models.fitness import PowerCurve from backend.app.models.rider import Rider from backend.app.schemas.rider import RiderUpdate, RiderResponse, FitnessHistoryResponse from backend.app.services.fitness import get_fitness_history router = APIRouter() @router.get("/profile", response_model=RiderResponse) async def get_rider(rider: Rider = Depends(get_current_rider)): return rider @router.put("/profile", response_model=RiderResponse) async def update_rider( data: RiderUpdate, rider: Rider = Depends(get_current_rider), session: AsyncSession = Depends(get_session), ): update_data = data.model_dump(exclude_unset=True) ftp_changed = "ftp" in update_data and update_data["ftp"] != rider.ftp for key, value in update_data.items(): setattr(rider, key, value) # Recalculate TSS/IF for all activities when FTP changes if ftp_changed and rider.ftp: query = ( select(ActivityMetrics) .join(Activity, Activity.id == ActivityMetrics.activity_id) .where(Activity.rider_id == rider.id) .where(ActivityMetrics.normalized_power.isnot(None)) ) result = await session.execute(query) for metrics in result.scalars().all(): np_val = metrics.normalized_power duration_q = await session.execute( select(Activity.duration).where(Activity.id == metrics.activity_id) ) duration = duration_q.scalar() if np_val and duration: metrics.intensity_factor = round(np_val / rider.ftp, 2) metrics.tss = round( (duration * np_val * (np_val / rider.ftp)) / (rider.ftp * 3600) * 100, 1, ) await session.commit() await session.refresh(rider) return rider @router.get("/fitness", response_model=list[FitnessHistoryResponse]) async def get_fitness( days: int = 90, rider: Rider = Depends(get_current_rider), session: AsyncSession = Depends(get_session), ): entries = await get_fitness_history(rider.id, session, days=days) return entries @router.get("/weekly-stats") async def get_weekly_stats( weeks: int = 8, rider: Rider = Depends(get_current_rider), session: AsyncSession = Depends(get_session), ): today = date.today() # Start from Monday of current week start_of_week = today - timedelta(days=today.weekday()) start_date = start_of_week - timedelta(weeks=weeks - 1) week_col = func.date_trunc('week', Activity.date).label("week") query = ( select( week_col, func.count(Activity.id).label("rides"), func.sum(Activity.duration).label("duration"), func.sum(Activity.distance).label("distance"), func.sum(ActivityMetrics.tss).label("tss"), ) .select_from(Activity) .outerjoin(ActivityMetrics, ActivityMetrics.activity_id == Activity.id) .where(Activity.rider_id == rider.id) .where(Activity.date >= start_date) .group_by(week_col) .order_by(week_col) ) result = await session.execute(query) return [ { "week": row.week.strftime("%Y-%m-%d") if row.week else None, "rides": row.rides, "duration": row.duration or 0, "distance": round(float(row.distance or 0) / 1000, 1), "tss": round(float(row.tss or 0), 0), } for row in result ] @router.get("/personal-records") async def get_personal_records( rider: Rider = Depends(get_current_rider), session: AsyncSession = Depends(get_session), ): """Best power at each standard duration across all activities.""" query = ( select(PowerCurve.curve_data, Activity.date, Activity.name, Activity.id) .join(Activity, Activity.id == PowerCurve.activity_id) .where(Activity.rider_id == rider.id) ) result = await session.execute(query) # Merge all curves: keep best power + source activity for each duration records: dict[int, dict] = {} for row in result: curve_data = row.curve_data for dur_str, power in curve_data.items(): dur = int(dur_str) if dur not in records or power > records[dur]["power"]: records[dur] = { "duration": dur, "power": power, "activity_id": str(row.id), "activity_name": row.name or "Ride", "date": row.date.isoformat(), } # Sort by duration return sorted(records.values(), key=lambda r: r["duration"])