Files
SneakySwole/app/routes/workouts.py
Phillip Tarrant 4b117c6fa7 feat: add smart "Workout Now" recommendation to workout picker
Auto-recommends the next workout in the 4-day cycle based on the
user's last completed session (one with logged sets). Redesigns
the /workouts page with highlighted recommendation card and
renames the back button to "Change Workout".

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 13:05:39 -05:00

139 lines
4.6 KiB
Python

"""Workout day viewer routes.
Displays the warmup routine and main exercises for each workout day,
with the active profile's programming targets.
"""
from datetime import date
import structlog
from fastapi import APIRouter, Depends, Request
from fastapi.responses import HTMLResponse
from sqlmodel import Session, select
from app.database import get_db_session
from app.models.user import User
from app.models.user_exercise_program import UserExerciseProgram
from app.models.workout_day import WorkoutDay
from app.services.exercise_service import ExerciseService
from app.services.log_service import LogService
from app.services.progression_service import ProgressionService
from app.services.workout_session_service import WorkoutSessionService
from app.utils.auth import require_active_profile, get_active_profile_id
logger = structlog.get_logger(__name__)
router = APIRouter(prefix="/workouts", tags=["workouts"])
@router.get("", response_class=HTMLResponse)
async def workout_days_list(
request: Request,
session: Session = Depends(get_db_session),
profile: User = Depends(require_active_profile),
):
"""List all workout days with a recommendation for what to do next."""
exercise_service = ExerciseService(session)
days = exercise_service.list_workout_days()
ws_service = WorkoutSessionService(session)
last_session = ws_service.get_last_completed_session(profile.id)
recommended_day_id = None
last_workout_name = None
last_workout_date = None
if last_session:
last_day = session.get(WorkoutDay, last_session.workout_day_id)
if last_day:
last_workout_name = last_day.name
last_workout_date = last_session.date
next_day_number = (last_day.day_number % 4) + 1
for d in days:
if d.day_number == next_day_number:
recommended_day_id = d.id
break
else:
for d in days:
if d.day_number == 1:
recommended_day_id = d.id
break
templates = request.app.state.templates
return templates.TemplateResponse("pages/workout_days.html", {
"request": request,
"days": days,
"recommended_day_id": recommended_day_id,
"last_workout_name": last_workout_name,
"last_workout_date": last_workout_date,
})
@router.get("/{day_name}", response_class=HTMLResponse)
async def workout_day_detail(
day_name: str,
request: Request,
session: Session = Depends(get_db_session),
profile: User = Depends(require_active_profile),
):
"""Display a full workout day -- warmups + exercises with form cues."""
exercise_service = ExerciseService(session)
# Normalize day name for DB lookup (e.g., "push" -> "Push", "full-body" -> "Full Body")
day_display = day_name.replace("-", " ").title()
warmups = exercise_service.list_warmups()
exercises = exercise_service.list_exercises(workout_day=day_display)
# Get active profile's programming
active_profile_id = profile.id
programs = {}
existing_logs = {}
statement = select(UserExerciseProgram).where(
UserExerciseProgram.user_id == active_profile_id
)
for prog in session.exec(statement).all():
programs[prog.exercise_id] = prog
# Look up the workout day ID for logging forms
days = exercise_service.list_workout_days()
workout_day_id = 0
for d in days:
if d.name == day_display:
workout_day_id = d.id
break
# Get progression suggestions for each exercise
suggestions = {}
progression = ProgressionService(session)
for exercise in exercises:
suggestions[exercise.id] = progression.get_suggestion(
active_profile_id, exercise.id,
)
# Load existing logs for today's session (if any)
if workout_day_id:
ws_service = WorkoutSessionService(session)
ws = ws_service.get_or_create_session(
user_id=active_profile_id,
workout_day_id=workout_day_id,
session_date=date.today(),
)
log_service = LogService(session)
all_logs = log_service.list_logs_for_session(ws.id)
for log in all_logs:
existing_logs.setdefault(log.exercise_id, []).append(log)
templates = request.app.state.templates
return templates.TemplateResponse("pages/workout_day.html", {
"request": request,
"day_name": day_display,
"warmups": warmups,
"exercises": exercises,
"programs": programs,
"active_profile": profile,
"existing_logs": existing_logs,
"suggestions": suggestions,
"workout_day_id": workout_day_id,
})