Add structured routine logging sheets to Doris Barbell
Some checks failed
secret-guardrails / gitleaks (push) Has been cancelled
secret-guardrails / artifact-secret-scan (push) Has been cancelled

This commit is contained in:
Fizzlepoof
2026-05-28 17:51:14 +00:00
parent 24c6a29273
commit 8a61169ab9
6 changed files with 296 additions and 2 deletions

View File

@@ -121,6 +121,47 @@ def quick_log_workout(
return RedirectResponse(url='/', status_code=status.HTTP_303_SEE_OTHER)
@router.get('/routines/{routine_id}/log')
def routine_log_sheet(request: Request, routine_id: str):
routine = _get_routine_or_404(request, routine_id)
return templates.TemplateResponse(
request=request,
name='routine_log.html',
context={
'title': f"Log {routine['name']}",
'routine': routine,
'routine_sheet': _build_routine_log_sheet(routine),
'routine_action_label': _routine_action_label(routine['name']),
'default_performed_at': '',
},
)
@router.post('/workouts/routine-log', status_code=303)
async def log_seeded_routine_workout(
request: Request,
user_id: str = Form(...),
routine_id: str = Form(...),
performed_at: str = Form(...),
routine_name: str = Form(...),
exercise_count: int = Form(...),
notes: str = Form(''),
):
routine = _get_routine_or_404(request, routine_id)
if routine['user_id'] != user_id:
raise HTTPException(status_code=400, detail='Routine does not belong to that user.')
payload = {
'user_id': user_id,
'performed_at': performed_at,
'routine_name': routine_name.strip(),
'notes': notes.strip() or None,
'exercises': await _parse_structured_routine_form(request, exercise_count),
}
request.app.state.store.add_workout(payload)
return RedirectResponse(url='/', status_code=status.HTTP_303_SEE_OTHER)
def _dashboard_context(request: Request) -> dict:
store = request.app.state.store
summary = store.dashboard_summary()
@@ -208,3 +249,95 @@ def _parse_workout_exercises(raw_lines: str) -> list[dict]:
if not exercises:
raise HTTPException(status_code=400, detail='At least one workout exercise is required.')
return exercises
def _get_routine_or_404(request: Request, routine_id: str) -> dict:
routine = request.app.state.store.get_routine(routine_id)
if routine is None:
raise HTTPException(status_code=404, detail='Unknown routine')
return routine
def _build_routine_log_sheet(routine: dict) -> list[dict]:
sheet = []
for exercise_index, exercise in enumerate(routine.get('exercises', [])):
target_sets = max(int(exercise.get('target_sets', 1) or 1), 1)
default_reps = _default_reps_value(exercise.get('target_reps'))
sheet.append(
{
'exercise_index': exercise_index,
'exercise_name': exercise['exercise_name'],
'target_sets': target_sets,
'target_reps': exercise.get('target_reps'),
'notes': exercise.get('notes'),
'sets': [
{
'exercise_index': exercise_index,
'index': set_index,
'default_reps': default_reps,
}
for set_index in range(target_sets)
],
}
)
return sheet
def _default_reps_value(target_reps: str | None) -> str:
if target_reps is None:
return ''
cleaned = str(target_reps).strip()
return cleaned if cleaned.isdigit() else ''
def _routine_action_label(routine_name: str) -> str:
if ': ' in routine_name:
label = routine_name.split(': ', 1)[1].strip().lower()
else:
label = routine_name.strip().lower()
day_aliases = {
'shoulders': 'shoulder day',
'arms': 'arm day',
'legs': 'leg day',
}
return day_aliases.get(label, label)
async def _parse_structured_routine_form(request: Request, exercise_count: int) -> list[dict]:
form = await request.form()
exercises = []
for exercise_index in range(exercise_count):
exercise_name = str(form.get(f'exercise_{exercise_index}_name', '')).strip()
if not exercise_name:
continue
try:
set_count = int(str(form.get(f'exercise_{exercise_index}_set_count', '0')).strip())
except ValueError as exc:
raise HTTPException(status_code=400, detail='Invalid set count in routine log form.') from exc
sets = []
for set_index in range(set_count):
reps_raw = str(form.get(f'exercise_{exercise_index}_set_{set_index}_reps', '')).strip()
weight_raw = str(form.get(f'exercise_{exercise_index}_set_{set_index}_weight_lbs', '')).strip()
if not reps_raw and not weight_raw:
continue
if not reps_raw or not weight_raw:
raise HTTPException(
status_code=400,
detail=f'Exercise {exercise_name} set {set_index + 1} needs both reps and weight.',
)
try:
reps = int(reps_raw)
weight_lbs = float(weight_raw)
except ValueError as exc:
raise HTTPException(
status_code=400,
detail=f'Exercise {exercise_name} set {set_index + 1} has invalid reps or weight.',
) from exc
sets.append({'reps': reps, 'weight_lbs': weight_lbs})
if not sets:
continue
exercise_notes = str(form.get(f'exercise_{exercise_index}_notes', '')).strip() or None
exercises.append({'exercise_name': exercise_name, 'notes': exercise_notes, 'sets': sets})
if not exercises:
raise HTTPException(status_code=400, detail='Log at least one completed exercise set.')
return exercises