Default routine log reps from targets
This commit is contained in:
@@ -159,7 +159,7 @@ async def log_seeded_routine_workout(
|
|||||||
'performed_at': performed_at,
|
'performed_at': performed_at,
|
||||||
'routine_name': routine_name.strip(),
|
'routine_name': routine_name.strip(),
|
||||||
'notes': notes.strip() or None,
|
'notes': notes.strip() or None,
|
||||||
'exercises': await _parse_structured_routine_form(request, exercise_count),
|
'exercises': await _parse_structured_routine_form(request, routine, exercise_count),
|
||||||
}
|
}
|
||||||
request.app.state.store.add_workout(payload)
|
request.app.state.store.add_workout(payload)
|
||||||
return RedirectResponse(url='/', status_code=status.HTTP_303_SEE_OTHER)
|
return RedirectResponse(url='/', status_code=status.HTTP_303_SEE_OTHER)
|
||||||
@@ -272,6 +272,7 @@ def _build_routine_log_sheet(routine: dict) -> list[dict]:
|
|||||||
'exercise_name': exercise['exercise_name'],
|
'exercise_name': exercise['exercise_name'],
|
||||||
'target_sets': target_sets,
|
'target_sets': target_sets,
|
||||||
'target_reps': exercise.get('target_reps'),
|
'target_reps': exercise.get('target_reps'),
|
||||||
|
'default_reps': default_reps,
|
||||||
'notes': exercise.get('notes'),
|
'notes': exercise.get('notes'),
|
||||||
'sets': [
|
'sets': [
|
||||||
{
|
{
|
||||||
@@ -328,8 +329,9 @@ def _routine_action_label(routine_name: str) -> str:
|
|||||||
return day_aliases.get(label, label)
|
return day_aliases.get(label, label)
|
||||||
|
|
||||||
|
|
||||||
async def _parse_structured_routine_form(request: Request, exercise_count: int) -> list[dict]:
|
async def _parse_structured_routine_form(request: Request, routine: dict, exercise_count: int) -> list[dict]:
|
||||||
form = await request.form()
|
form = await request.form()
|
||||||
|
routine_exercises = routine.get('exercises', [])
|
||||||
exercises = []
|
exercises = []
|
||||||
for exercise_index in range(exercise_count):
|
for exercise_index in range(exercise_count):
|
||||||
exercise_name = str(form.get(f'exercise_{exercise_index}_name', '')).strip()
|
exercise_name = str(form.get(f'exercise_{exercise_index}_name', '')).strip()
|
||||||
@@ -339,12 +341,18 @@ async def _parse_structured_routine_form(request: Request, exercise_count: int)
|
|||||||
set_count = int(str(form.get(f'exercise_{exercise_index}_set_count', '0')).strip())
|
set_count = int(str(form.get(f'exercise_{exercise_index}_set_count', '0')).strip())
|
||||||
except ValueError as exc:
|
except ValueError as exc:
|
||||||
raise HTTPException(status_code=400, detail='Invalid set count in routine log form.') from exc
|
raise HTTPException(status_code=400, detail='Invalid set count in routine log form.') from exc
|
||||||
|
target_reps = None
|
||||||
|
if exercise_index < len(routine_exercises):
|
||||||
|
target_reps = routine_exercises[exercise_index].get('target_reps')
|
||||||
|
default_reps_raw = _default_reps_value(target_reps)
|
||||||
sets = []
|
sets = []
|
||||||
for set_index in range(set_count):
|
for set_index in range(set_count):
|
||||||
reps_raw = str(form.get(f'exercise_{exercise_index}_set_{set_index}_reps', '')).strip()
|
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()
|
weight_raw = str(form.get(f'exercise_{exercise_index}_set_{set_index}_weight_lbs', '')).strip()
|
||||||
if not reps_raw and not weight_raw:
|
if not reps_raw and not weight_raw:
|
||||||
continue
|
continue
|
||||||
|
if not reps_raw and weight_raw and default_reps_raw.isdigit():
|
||||||
|
reps_raw = default_reps_raw
|
||||||
if not reps_raw or not weight_raw:
|
if not reps_raw or not weight_raw:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=400,
|
status_code=400,
|
||||||
|
|||||||
@@ -53,6 +53,7 @@
|
|||||||
{% for exercise in routine_sheet %}
|
{% for exercise in routine_sheet %}
|
||||||
<section class="evidence-slab">
|
<section class="evidence-slab">
|
||||||
<input type="hidden" name="exercise_{{ exercise.exercise_index }}_set_count" value="{{ exercise.target_sets }}">
|
<input type="hidden" name="exercise_{{ exercise.exercise_index }}_set_count" value="{{ exercise.target_sets }}">
|
||||||
|
<input type="hidden" name="exercise_{{ exercise.exercise_index }}_default_reps" value="{{ exercise.default_reps }}">
|
||||||
<div class="case-legend">
|
<div class="case-legend">
|
||||||
<span class="section-label">Exercise {{ loop.index }}</span>
|
<span class="section-label">Exercise {{ loop.index }}</span>
|
||||||
<span class="pill">{{ exercise.target_sets }} sets</span>
|
<span class="pill">{{ exercise.target_sets }} sets</span>
|
||||||
|
|||||||
@@ -464,6 +464,47 @@ def test_seeded_routine_log_submission_creates_structured_workout(tmp_path: Path
|
|||||||
assert payload[0]['exercises'][1]['exercise_name'] == 'Upright Row'
|
assert payload[0]['exercises'][1]['exercise_name'] == 'Upright Row'
|
||||||
|
|
||||||
|
|
||||||
|
def test_seeded_routine_log_uses_numeric_target_reps_when_reps_left_blank(tmp_path: Path) -> None:
|
||||||
|
client = make_client(tmp_path)
|
||||||
|
|
||||||
|
summary = client.get('/api/dashboard/summary').json()
|
||||||
|
shoulders = next(routine for routine in summary['routines'] if routine['name'] == 'Thursday: Shoulders')
|
||||||
|
|
||||||
|
response = client.post(
|
||||||
|
'/workouts/routine-log',
|
||||||
|
data={
|
||||||
|
'user_id': 'john',
|
||||||
|
'routine_id': shoulders['id'],
|
||||||
|
'performed_at': '2026-05-28T12:00:00',
|
||||||
|
'routine_name': shoulders['name'],
|
||||||
|
'notes': 'Use default target reps when left blank.',
|
||||||
|
'exercise_count': '1',
|
||||||
|
'exercise_0_name': 'Shoulder Press',
|
||||||
|
'exercise_0_set_count': '3',
|
||||||
|
'exercise_0_set_0_reps': '',
|
||||||
|
'exercise_0_set_0_weight_lbs': '45',
|
||||||
|
'exercise_0_set_1_reps': '',
|
||||||
|
'exercise_0_set_1_weight_lbs': '45',
|
||||||
|
'exercise_0_set_2_reps': '',
|
||||||
|
'exercise_0_set_2_weight_lbs': '45',
|
||||||
|
},
|
||||||
|
follow_redirects=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert response.status_code == 303
|
||||||
|
assert response.headers['location'] == '/'
|
||||||
|
|
||||||
|
workout_history = client.get('/api/history/john/workouts')
|
||||||
|
assert workout_history.status_code == 200
|
||||||
|
payload = workout_history.json()
|
||||||
|
assert payload[0]['routine_name'] == 'Thursday: Shoulders'
|
||||||
|
assert payload[0]['exercises'][0]['sets'] == [
|
||||||
|
{'reps': 10, 'weight_lbs': 45.0},
|
||||||
|
{'reps': 10, 'weight_lbs': 45.0},
|
||||||
|
{'reps': 10, 'weight_lbs': 45.0},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
def test_existing_state_gets_seeded_routine_backfill(tmp_path: Path) -> None:
|
def test_existing_state_gets_seeded_routine_backfill(tmp_path: Path) -> None:
|
||||||
state_path = tmp_path / 'state.json'
|
state_path = tmp_path / 'state.json'
|
||||||
state_path.write_text(
|
state_path.write_text(
|
||||||
|
|||||||
Reference in New Issue
Block a user