Skip to content

feat(logs): let a logged set attach to its routine - #3

Open
wromansky wants to merge 5 commits into
wger-project:masterfrom
wromansky:feat/link-logs-to-routine
Open

feat(logs): let a logged set attach to its routine#3
wromansky wants to merge 5 commits into
wger-project:masterfrom
wromansky:feat/link-logs-to-routine

Conversation

@wromansky

@wromansky wromansky commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

The gap

log_set can only record freestanding work. It sends exercise, repetitions, weight, date and rir — never routine, slot_entry or iteration, even though POST /api/v2/workoutlog/ accepts all three. Every set logged through this server comes back with routine: null.

That breaks the loop the rest of the server is well set up for: build a routine, follow it, log against it, adjust it. volume_trend and compare_periods cannot tell planned work from ad-hoc work, and "how am I tracking against my program" has no answer.

Found while wiring this server to a self-hosted wger for a coaching agent: it could create routines and it could log sets, but the two were unrelated records.

The change

log_set gains optional routine_id, slot_entry_id and iteration. Omitting them reproduces the previous payload byte for byte. Passing slot_entry_id without routine_id is rejected up front rather than posting a log wger cannot place.

New tool get_workout_for_date(routine_id, workout_date=today), because supplying those ids means knowing which slot entry today's exercise belongs to — which the existing tools only expose by walking routine → days → slots → entries and matching by hand. It uses wger's own /routine/{id}/date-sequence-gym/ route.

A date the routine does not cover is reported as a rest day with a note rather than an error — asking "what am I doing today" on an off day is normal use.

Three follow-up commits, each from watching it used

Exercise names. The tool first returned exercise ids, so an assistant asked "what is in my routine" answered by calling list_slot_entry_configs per entry — which fans out to ten config endpoints each — plus a full exerciseinfo record per exercise, purely to recover names. Dozens of requests, and enough payload to exhaust a 32k-token context. Names live on translations, not on the exercise (/exercise/{id}/ is 306 bytes of ids and no name), and neither plan serializer carries them, so the tool now resolves them itself: one exercise-translation lookup per distinct exercise, concurrent, cached for the process, preferring DEFAULT_LANGUAGE via its numeric language id. The cost is on the wire, not in the caller's context — the response gains one short string per exercise. A failed lookup leaves exercise_name null rather than failing the plan.

weight_unit is rendered as kg/lb, not a raw id. An assistant reported a set stored as 14 kg to its user as "14 lb" — it had been told the user trains in pounds, saw an opaque 1, and asserted. 14 kg is 31 lb, so the number was wrong by more than double in a context where the user acts on it. Unknown units fall back to the raw value (wger also has Body Weight, Plates, km/h, mph).

Docstring correction: gym mode enumerates sets, so a 3-set exercise appears three times. The original text said one entry per exercise, which invites a caller to read the list as the exercise list and under-count the work.

Tests

6 tests in tests/test_routine_logging.py: slot-entry ids surface for today, an uncovered date and a rest day both report no planned work, the linkage reaches the POST payload, an unlinked call is unchanged, and slot_entry_id without routine_id is refused without touching the network. Names and the rendered unit are asserted alongside.

Full suite passes (77) and ruff check is clean. The parsing was written against the running serializers — WorkoutDayDataGymModeSerializerSlotDataSerializerSetConfigDataSerializer — not against assumptions.

Note

Independent of #2, #4 and #5. Touches log_set alongside #6, which adds weight_unit to the same function; they merge cleanly in either order, but whichever lands second will want a trivial rebase.

log_set could only record freestanding work. It sends exercise, repetitions,
weight, date and rir — never routine, slot_entry or iteration, though
POST /api/v2/workoutlog/ accepts all three. Sets logged through this server
therefore came back with routine=null, and no routine report could attribute
them.

That breaks the loop the server is otherwise well set up for: build a routine,
follow it, log against it, adjust it. Volume and comparison analytics cannot
tell planned work from ad-hoc work, and "how am I tracking against my program"
has no answer.

log_set gains optional routine_id, slot_entry_id and iteration. Omitting them
keeps the previous payload byte for byte, so nothing changes for existing
callers. Passing slot_entry_id without routine_id is rejected up front rather
than posting a log that wger cannot place.

Supplying those ids meant knowing which slot entry today's exercise belongs
to, which the existing tools only expose by walking routine -> days -> slots ->
entries and matching by hand. get_workout_for_date does it in one call, using
wger's own date-sequence-gym route: it returns the day, its iteration, and one
entry per planned exercise carrying slot_entry_id alongside the planned sets,
repetitions, weight and RiR. Feed those straight into log_set.

A date the routine does not cover is reported as a rest day rather than an
error, since asking "what am I doing today" on an off day is normal use.

Adds 6 tests: slot-entry ids surface for today, an uncovered date and a rest
day both report no planned work, the linkage reaches the payload, an unlinked
call is unchanged, and slot_entry_id without routine_id is refused.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
wromansky and others added 3 commits August 12, 2026 21:54
get_workout_for_date returned exercise ids, so anything describing a plan to a
human had to look each one up. Watching an assistant answer "what is in my
routine", that meant list_slot_entry_configs per entry — which fans out to ten
config endpoints each — followed by a full exerciseinfo record per exercise,
just to recover names. Dozens of requests, and enough payload to exhaust a
32k-token context.

Names live on translations rather than on the exercise, and neither plan
serializer carries them, so the tool now resolves them itself: one
exercise-translation lookup per distinct exercise, run concurrently, cached
for the process since exercise names are static. The configured
DEFAULT_LANGUAGE is mapped to wger's numeric language id (also cached) and
preferred, with any other translation used rather than showing a bare id.

Cost lands on the wire, not in the caller's context: the response gains one
short string per exercise. A failed lookup leaves exercise_name null rather
than failing the plan.

The docstring now says plainly that this is the call for "what am I doing
today" and "what is in this program", because the expensive walk is the
obvious path when the cheap one looks incomplete.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
wger's gym mode enumerates sets, so an exercise prescribed for three sets
appears three times. The docstring said one entry per exercise, which invites
a caller to read the list as the exercise list and under-count the work.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
get_workout_for_date passed wger's raw weight-unit id through. A plan is read
by people and by language models, and "weight_unit: 1" invites both to guess.

Observed: an assistant reported a planned set stored as 14 kg to its user as
"14 lb" — it had been told the user trains in pounds, saw an opaque 1, and
assumed. 14 kg is 31 lb, so the number it gave was wrong by more than double
in a context where the user acts on it.

Rendered as its code instead, falling back to the raw value for units this
mapping does not know (wger also has Body Weight, Plates, km/h, mph).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@PawelHaracz

Copy link
Copy Markdown
Contributor

@wromansky please resolve conflicts

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants