from typing import List, Optional from fastapi import APIRouter, Depends, HTTPException from app.api.deps import get_training_repository from app.core.database.repository import TrainingRepository from app.core.dto.training import ExerciseNameDTO from app.core.metrics import Record, summarize from app.schemas.exercises import ( ExerciseProgressResponse, RecordResponse, SessionMetricsResponse, SessionRowResponse, ) router = APIRouter() def record_response(record: Optional[Record]) -> Optional[RecordResponse]: return RecordResponse(value=record.value, date=record.date) if record else None @router.get("/", response_model=List[ExerciseNameDTO]) async def list_exercise_names( repository: TrainingRepository = Depends(get_training_repository), ): """Distinct exercise names with the number of times each was done.""" return await repository.list_exercise_names() @router.get("/progress/", response_model=ExerciseProgressResponse) async def exercise_progress( name: str, repository: TrainingRepository = Depends(get_training_repository), ): """The whole history of the exercise with progress metrics. The name is matched exactly, but case-insensitively: what was typed may differ in case. """ names_by_casefold = { item.name.casefold(): item.name for item in await repository.list_exercise_names() } exact_name = names_by_casefold.get(name.strip().casefold()) if exact_name is None: raise HTTPException(status_code=404, detail="Exercise not found") progress = summarize(await repository.exercise_history(exact_name)) return ExerciseProgressResponse( name=exact_name, weighted=progress.weighted, max_weight=progress.max_weight, best_primary=record_response(progress.best_primary), best_secondary=record_response(progress.best_secondary), first_primary=progress.first_primary, last_primary=progress.last_primary, primary_change=progress.primary_change, primary_change_percent=progress.primary_change_percent, rows=[ SessionRowResponse( date=row.session.date, trainer=row.session.trainer, splitted_weight=row.session.splitted_weight, approaches=row.session.approaches, metrics=SessionMetricsResponse.model_validate( row.metrics, from_attributes=True ), ) for row in progress.rows ], )