Add date range picker and download button to the dashboard page, backed by GET /dashboard/export endpoint that returns a StreamingResponse CSV file. Completes Phase 4 (final V2 improvement). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
81 lines
2.5 KiB
Python
81 lines
2.5 KiB
Python
"""Export service for generating CSV-ready workout history data.
|
|
|
|
Queries workout logs joined with sessions, days, and exercises
|
|
to produce flat rows suitable for CSV export.
|
|
"""
|
|
|
|
from datetime import date
|
|
|
|
import structlog
|
|
from sqlmodel import Session, select
|
|
|
|
from app.models.exercise import Exercise
|
|
from app.models.workout_log import WorkoutLog
|
|
from app.models.workout_session import WorkoutSession
|
|
|
|
logger = structlog.get_logger(__name__)
|
|
|
|
|
|
class ExportService:
|
|
"""Builds export-ready workout history rows.
|
|
|
|
Args:
|
|
session: An active SQLModel Session.
|
|
"""
|
|
|
|
def __init__(self, session: Session) -> None:
|
|
self._session = session
|
|
|
|
def get_export_rows(
|
|
self, user_id: int, start_date: date, end_date: date,
|
|
) -> list[dict]:
|
|
"""Get flat workout log rows for CSV export.
|
|
|
|
Args:
|
|
user_id: The user whose data to export.
|
|
start_date: Inclusive start of date range.
|
|
end_date: Inclusive end of date range.
|
|
|
|
Returns:
|
|
List of dicts with keys: date, workout_type, exercise,
|
|
set_number, reps, weight, felt_easy.
|
|
"""
|
|
sessions = self._session.exec(
|
|
select(WorkoutSession)
|
|
.where(
|
|
WorkoutSession.user_id == user_id,
|
|
WorkoutSession.date >= start_date,
|
|
WorkoutSession.date <= end_date,
|
|
)
|
|
.order_by(WorkoutSession.date.asc())
|
|
).all()
|
|
|
|
if not sessions:
|
|
return []
|
|
|
|
# Pre-load exercises for name lookup
|
|
exercises = self._session.exec(select(Exercise)).all()
|
|
exercise_map = {e.id: e for e in exercises}
|
|
|
|
rows = []
|
|
for ws in sessions:
|
|
logs = self._session.exec(
|
|
select(WorkoutLog)
|
|
.where(WorkoutLog.session_id == ws.id)
|
|
.order_by(WorkoutLog.exercise_id, WorkoutLog.set_number)
|
|
).all()
|
|
|
|
for log_entry in logs:
|
|
exercise = exercise_map.get(log_entry.exercise_id)
|
|
rows.append({
|
|
"date": ws.date.isoformat(),
|
|
"workout_type": exercise.workout_day if exercise else "Unknown",
|
|
"exercise": exercise.name if exercise else "Unknown",
|
|
"set_number": log_entry.set_number,
|
|
"reps": log_entry.reps_completed,
|
|
"weight": log_entry.weight_used,
|
|
"felt_easy": "Yes" if log_entry.felt_easy else "No",
|
|
})
|
|
|
|
return rows
|