Skip to content

[intervals-mcp-server] Fix calendar tools and add planning projection to the coach report - #2

Merged
morritter merged 4 commits into
mainfrom
feature/coach-report-plan
Sep 29, 2026
Merged

morritter merged 4 commits into
mainfrom
feature/coach-report-plan

Conversation

@morritter

Copy link
Copy Markdown
Owner

Summary

  • get_event_by_id: called /athlete/{id}/event/{eventId} (always 404); now uses /events/{eventId} like update/delete.
  • get_events / get_event_by_id: show category, sport, planned load and planned time instead of Type: Other (N/A when Intervals.icu has no value); races are detected from RACE_A/B/C.
  • Event and note updates send only the fields that are set: no null description, no forced category: WORKOUT, no move to today when no date is given. workout_type is optional (renaming works), new optional category (e.g. RACE_A).
  • get_coach_report schema 1.1: projection mode for a future end_date:
    • period.mode, load.actual / load.projected, recovery.as_of
    • plan section: planned load, hours and sessions per ISO week and sport family, rest days, missing_load, projected CTL/ramp/tsb_min, races 12 weeks ahead
    • plan flags ramp_planned_high (> 6), tsb_planned_low (< −20 / < −30), planned_rest_days_low; every flag carries basis: actual|projection
    • load.src = api_incl_planned for values that include planned workouts; recomputed_without_planned only for days up to today

Compatibility

  • Reports without a future end_date are unchanged apart from schema_version: "1.1" and period.mode: "actual" (snapshot test against the 1.0 output).
  • add_or_update_event: name is now the first parameter and workout_type is optional. MCP clients pass named arguments; there are no positional callers in the repo.
  • get_coach_report fetches the athlete first also with an explicit end_date (one extra round trip), because the athlete's "today" decides the mode.

Intervals.icu API assumptions (verified with read-only live calls)

  • Events: category, type, icu_training_load (null for workouts without targets), moving_time (s), start_date_local.
  • GET /athlete/{id}/events/{eventId} returns the event.
  • category=RACE_A,RACE_B,RACE_C filters /events server-side.
  • Wellness records for future days contain ctl, atl, rampRate including planned workouts; rampRate = CTL(d) − CTL(d−7).

Testing

  • pytest: 202 passed.
  • ruff, mypy: no new findings (the two F401 in server.py and power_curves.py already exist on main).
  • pylint as in CI: no new findings; the existing ones in power_curves.py, gear.py, server.py and formatting.py are unchanged.
  • Manual, read-only against a live account:
    • get_events and get_event_by_id show sport, category, planned load and time.
    • get_coach_report(days=14, end_date="2026-10-04"): mode: projection, plan W40 load 817 with missing_load: 2, projected CTL 105.1 / ramp 2.4 (api_incl_planned), load.actual CTL does not rise with planned-but-not-done load, tsb_planned_low warning at −23.0.
    • get_coach_report(): mode: actual, keys unchanged.
  • Not done: a live write test of event updates (covered by unit tests only).

Known limitations

  • Once today has an activity, plan starts tomorrow; a second session planned for today is then missing from plan (it is still in the CTL projection).
  • Workouts without a computed load are also missing from the Intervals.icu projection, so ramp/TSB are optimistic; reported as missing_load.
  • ramp is checked at the end of each week only; planned_rest_days_low only for fully planned weeks.

🤖 Generated with Claude Code

morritter and others added 4 commits September 28, 2026 15:42
get_event_by_id called /athlete/{id}/event/{eventId}, which returns 404; the
Intervals.icu endpoint is /events/{eventId} like update and delete. Event
details now show category, sport, planned load and planned time, and detect
races from the RACE_A/B/C category.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The event list of the API has no workout object, so every event was shown as
"Type: Other". Summaries now use category, type, icu_training_load and
moving_time (N/A when missing), and detect races from RACE_A/B/C.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Updates sent description/moving_time/distance as null, always set category
WORKOUT and moved the event to today when no date was given, so renaming an
event cleared its description or turned a NOTE into a WORKOUT. None values are
now dropped, category/type/date/color are only sent on update when passed, and
workout_type is optional so an event can be renamed. add_or_update_event takes
an optional category (e.g. RACE_A).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A future end_date treated planned days as rest days (ACWR/deload/HRV false
alarms) and labelled CTL values that already included planned workouts as
recomputed_without_planned. In projection mode, sections from completed
training end at the last completed day, recovery refers to today, CTL/ATL/TSB
at the end date are reported as api_incl_planned with load.actual and
load.projected, and a plan section summarises planned workouts per week and
sport (planned load, rest days, missing loads, projected CTL/ramp/TSB, races).
New flags ramp_planned_high, tsb_planned_low and planned_rest_days_low; flags
carry basis actual/projection. Reports without a future end date are unchanged
apart from schema_version and period.mode (snapshot test).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@morritter
morritter merged commit 9d8bcb9 into main Sep 29, 2026
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.

1 participant