diff --git a/CHANGELOG.md b/CHANGELOG.md index f9c8720..e064e2a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,14 @@ notes. This file records important changes to *this package*. ## Unreleased +* `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 diff --git a/README.md b/README.md index c21dad1..5448027 100644 --- a/README.md +++ b/README.md @@ -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 | -| `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 diff --git a/src/wger_mcp/tools/routines.py b/src/wger_mcp/tools/routines.py index 23cbbf9..ff901e4 100644 --- a/src/wger_mcp/tools/routines.py +++ b/src/wger_mcp/tools/routines.py @@ -363,6 +363,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. @@ -406,6 +412,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, } diff --git a/tests/test_routine_logging.py b/tests/test_routine_logging.py index f25b98a..da51872 100644 --- a/tests/test_routine_logging.py +++ b/tests/test_routine_logging.py @@ -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, } ) @@ -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] @@ -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