Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,14 @@ notes. This file records important changes to *this package*.
is unrecoverable once stored, while the refusal costs one retry with an
explicit unit.

* `get_workout_for_date` returns the day's `description` as `day_description`.
A routine's per-day notes are where rep ranges, machine substitutions and
form cues live, and the tool that answers "what am I doing today" was
returning the planned numbers without the terms they were written under — a
caller reporting the plan quoted a bare rep count where the routine had
specified a range. Unset descriptions come back as `null`, matching
`day_name`.

## 0.2.0

* `add_exercise_with_sets` returns the created ids, as its docstring always
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@ The training-plan tree is the largest group and splits in two: `routines_read` (
| `set_slot_entry_config(slot_entry_id, kind, value, iteration?, operation?, step?, repeat?, weight_unit?, requirements?)` | Add a per-iteration config record. `weight_unit` applies to `kind='weight'`/`'max_weight'` and is recorded on the slot entry. `requirements` gates the step on what was logged — any of `repetitions`, `weight`, `rir`, `rest` |
| `update_slot_entry_config(kind, config_id, value?, iteration?, ..., requirements?)` / `delete_slot_entry_config(kind, config_id)` | Patch / delete a config record (use to bump weight on progression). `requirements=[]` clears an existing gate |
| `add_exercise_with_sets(day_id, exercise_id, sets, reps, weight?, slot_order?, weight_unit?, rir?, entry_type?)` | Convenience: slot + entry + sets/reps configs in one call. Omit `weight` to prescribe sets without a load. `weight_unit` is `kg` or `lb`; omit it and the trainee's profile unit is used, as in `log_set` |
| `get_workout_for_date(routine_id, workout_date?)` | What the routine prescribes on a date (default today): one entry per planned SET, with exercise name, `slot_entry_id`, reps, weight and RiR. Feed its ids into `log_set` |
| `get_workout_for_date(routine_id, workout_date?)` | What the routine prescribes on a date (default today): one entry per planned SET, with exercise name, `slot_entry_id`, reps, weight and RiR, plus the day's own `day_description` notes. Feed its ids into `log_set` |

### Workout logs

Expand Down
13 changes: 13 additions & 0 deletions src/wger_mcp/tools/routines.py
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,12 @@ async def get_workout_for_date(
repetitions, weight and RiR. Feed routine_id, slot_entry_id and
iteration straight into log_set so the logged set attaches to the plan.

day_description carries the routine's own notes for that day — rep
ranges, machine substitutions, form cues — as the trainee wrote them.
The planned numbers say what to do; the description says on what terms,
and a caller that reports the plan without it quotes a bare rep count
where the routine specified a range.

This is the one call that answers "what am I doing today" and "what is
in this program". Walking days, slots, entries and their configs costs
dozens of requests and returns far more than anyone needs.
Expand Down Expand Up @@ -407,6 +413,12 @@ async def get_workout_for_date(
# A day need not be named, and Unset would not survive the
# tool boundary as JSON.
"day_name": None if isinstance(day.name, Unset) else day.name,
# Where a routine keeps its per-day coaching notes: rep ranges,
# machine substitutions, form cues. Without it a caller has the
# numbers but not the terms they were written under.
"day_description": (
None if isinstance(day.description, Unset) else day.description
),
"is_rest_day": (day.is_rest is True) or not planned,
"planned": planned,
}
Expand All @@ -419,6 +431,7 @@ async def get_workout_for_date(
"label": None,
"day_id": None,
"day_name": None,
"day_description": None,
"is_rest_day": True,
"planned": [],
"note": "no scheduled day on this date - it may fall outside the routine's range",
Expand Down
17 changes: 16 additions & 1 deletion tests/test_routine_logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,13 @@ def _sequence(
"iteration": 3,
"date": day_date.isoformat(),
"label": "Week 3",
"day": {"id": 11, "routine": 7, "name": "Push", "is_rest": is_rest},
"day": {
"id": 11,
"routine": 7,
"name": "Push",
"is_rest": is_rest,
"description": "Working reps = lower end of range: bench 6-8.",
},
"slots": slots,
}
)
Expand Down Expand Up @@ -172,6 +178,9 @@ async def test_returns_slot_entry_ids_for_today(monkeypatch: pytest.MonkeyPatch)

assert out["iteration"] == 3
assert out["day_name"] == "Push"
# The day's notes carry the terms the numbers were written under - a rep
# range here - so a caller reporting the plan can quote them.
assert out["day_description"] == "Working reps = lower end of range: bench 6-8."
assert out["is_rest_day"] is False
assert len(out["planned"]) == 1
entry = out["planned"][0]
Expand Down Expand Up @@ -222,11 +231,14 @@ async def test_unnamed_day_still_answers(monkeypatch: pytest.MonkeyPatch) -> Non
mcp = _register(routines)
sequence = _sequence()
sequence[0].day.name = UNSET
sequence[0].day.description = UNSET
monkeypatch.setattr(routines.routine_date_sequence_gym_list, "asyncio", _Capture(sequence))
_mock_names(monkeypatch)
out = _result(await mcp.call_tool("get_workout_for_date", {"routine_id": "7"}))

assert out["day_name"] is None
# Same treatment as the name: Unset must not survive the tool boundary.
assert out["day_description"] is None
assert len(out["planned"]) == 1


Expand All @@ -245,6 +257,9 @@ async def test_date_outside_the_routine_is_not_an_error(monkeypatch: pytest.Monk
assert out["planned"] == []
assert out["iteration"] is None
assert "note" in out
# Every key the scheduled-day answer carries is present here too, so a
# caller reading the payload does not have to branch on which it got.
assert out["day_description"] is None


@pytest.mark.asyncio
Expand Down
Loading