feat(wellness): training-readiness signal tool (target 0.3.0) #1

Closed
opened 2026-07-20 13:29:07 +00:00 by Chris Farhood · 1 comment
Owner

Summary

Add a get_training_readiness MCP tool that synthesizes an athlete's recent
wellness signals into a single readiness read, deferred from the 0.2.0
nutrition/wellness work.

Scope

  • Pull recent wellness history (HRV/rMSSD, resting HR, sleep, subjective
    soreness/fatigue/stress) via the existing /athlete/{id}/wellness path.
  • Compute a readiness assessment against the athlete's own rolling baseline
    (e.g. 7-day lnRMSSD mean +/- smallest worthwhile change), not fixed thresholds.
  • Surface a plain-language readiness call plus the contributing signals.

Grounding (document formulas in docstrings)

  • HRV-guided readiness: 7-day rolling lnRMSSD vs baseline +/- SWC.
  • Combine with RHR trend and subjective inputs; flag divergence.

Notes

  • Read-only; composes with the 0.2.0 update_wellness write tool and the
    computed Form (TSB) already surfaced in wellness output.
  • Deferred deliberately because it is an opinionated calculator; 0.2.0 kept to
    the data-plane write tool + formatting fixes.

Target release: 0.3.0

## Summary Add a `get_training_readiness` MCP tool that synthesizes an athlete's recent wellness signals into a single readiness read, deferred from the 0.2.0 nutrition/wellness work. ## Scope - Pull recent wellness history (HRV/rMSSD, resting HR, sleep, subjective soreness/fatigue/stress) via the existing `/athlete/{id}/wellness` path. - Compute a readiness assessment against the athlete's own rolling baseline (e.g. 7-day lnRMSSD mean +/- smallest worthwhile change), not fixed thresholds. - Surface a plain-language readiness call plus the contributing signals. ## Grounding (document formulas in docstrings) - HRV-guided readiness: 7-day rolling lnRMSSD vs baseline +/- SWC. - Combine with RHR trend and subjective inputs; flag divergence. ## Notes - Read-only; composes with the 0.2.0 `update_wellness` write tool and the computed Form (TSB) already surfaced in wellness output. - Deferred deliberately because it is an opinionated calculator; 0.2.0 kept to the data-plane write tool + formatting fixes. Target release: **0.3.0**
Chris Farhood added this to the 0.3.0 milestone 2026-07-20 19:26:40 +00:00
Author
Owner

Implementation plan (0.3.0) — intended delivery model: Claude Opus 4.8

Branch: feat/coaching-context; one conventional feat: commit; lands LAST (after #2, whose sport-settings context enriches the output).

Design: computation separated from I/O

  • utils/readiness.py — pure functions, stdlib statistics only, no HTTP. Fully unit-testable on fixtures.
  • get_training_readiness(days: int = 45) in tools/wellness.py — fetches GET /athlete/{id}/wellness for the window via the existing path, hands the records to the compute layer, renders.

Computation (formulas cited in docstrings — no invented thresholds)

  1. HRV signal (primary; Plews & Laursen rolling-average method): ln-transform daily rMSSD (hrv field); 7-day rolling mean vs the preceding ~30-day baseline mean; normal band = baseline +/- SWC where SWC = 0.5 x SD of baseline lnRMSSD. Below band -> stress flag; above-with-rising-RHR noted as possible parasympathetic saturation (flag, don't diagnose).
  2. Resting HR: 7-day mean vs 30-day baseline; > ~+5% flag.
  3. Sleep: last night + 7-day mean vs baseline (sleepSecs, sleepScore/quality when present).
  4. Subjective: soreness/fatigue/stress/mood/motivation vs each field's own baseline (ICU app semantics: some scales invert; treat per-field, render neutrally).
  5. Load context: Form (TSB = ctl - atl) from the newest record; monotony (mean/SD of daily load over 7d) only if ctlLoad/atlLoad present.
  6. Verdict: green/amber/red from flag count+severity, with the contributing signals listed and an explicit data-sufficiency caveat (e.g. '<20 HRV days -> baseline unreliable; verdict withheld' -> report signals without a verdict rather than fabricate).

Honesty constraints (project convention)

  • Missing fields degrade gracefully: each signal reports 'no data' rather than defaulting.
  • The tool output must state what the verdict is based on and what was missing. No pseudo-precision (no fake 0-100 score) — banded verdict + evidence lines.

Tests (tests/test_readiness.py)

  • Deterministic fixtures: stable-HRV athlete (green), suppressed-HRV + elevated-RHR (red), sparse data (verdict withheld), missing HRV entirely (signals-only), inverted-subjective handling, band-edge cases for SWC.
  • Compute layer tested directly (no HTTP mocks needed); one integration-style test through the tool with make_intervals_request patched.

Estimate

~400 LOC incl. tests. Closes the loop with #2 (zones/thresholds context) and 0.2.0's update_wellness (the data this reads).

## Implementation plan (0.3.0) — intended delivery model: Claude Opus 4.8 **Branch:** `feat/coaching-context`; one conventional `feat:` commit; lands LAST (after #2, whose sport-settings context enriches the output). ### Design: computation separated from I/O - **`utils/readiness.py`** — pure functions, stdlib `statistics` only, no HTTP. Fully unit-testable on fixtures. - **`get_training_readiness(days: int = 45)`** in `tools/wellness.py` — fetches `GET /athlete/{id}/wellness` for the window via the existing path, hands the records to the compute layer, renders. ### Computation (formulas cited in docstrings — no invented thresholds) 1. **HRV signal** (primary; Plews & Laursen rolling-average method): ln-transform daily rMSSD (`hrv` field); 7-day rolling mean vs the preceding ~30-day baseline mean; normal band = baseline +/- SWC where SWC = 0.5 x SD of baseline lnRMSSD. Below band -> stress flag; above-with-rising-RHR noted as possible parasympathetic saturation (flag, don't diagnose). 2. **Resting HR**: 7-day mean vs 30-day baseline; > ~+5% flag. 3. **Sleep**: last night + 7-day mean vs baseline (sleepSecs, sleepScore/quality when present). 4. **Subjective**: soreness/fatigue/stress/mood/motivation vs each field's own baseline (ICU app semantics: some scales invert; treat per-field, render neutrally). 5. **Load context**: Form (TSB = ctl - atl) from the newest record; monotony (mean/SD of daily load over 7d) only if ctlLoad/atlLoad present. 6. **Verdict**: green/amber/red from flag count+severity, with the contributing signals listed and an explicit data-sufficiency caveat (e.g. '<20 HRV days -> baseline unreliable; verdict withheld' -> report signals without a verdict rather than fabricate). ### Honesty constraints (project convention) - Missing fields degrade gracefully: each signal reports 'no data' rather than defaulting. - The tool output must state what the verdict is based on and what was missing. No pseudo-precision (no fake 0-100 score) — banded verdict + evidence lines. ### Tests (`tests/test_readiness.py`) - Deterministic fixtures: stable-HRV athlete (green), suppressed-HRV + elevated-RHR (red), sparse data (verdict withheld), missing HRV entirely (signals-only), inverted-subjective handling, band-edge cases for SWC. - Compute layer tested directly (no HTTP mocks needed); one integration-style test through the tool with `make_intervals_request` patched. ### Estimate ~400 LOC incl. tests. Closes the loop with #2 (zones/thresholds context) and 0.2.0's `update_wellness` (the data this reads).
Sign in to join this conversation.
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: farhoodlabs/intervalsicu-mcp#1