Skip to content

feat(training-history): date filtering, pagination, and detail tiers#2

Open
dcaslin wants to merge 1 commit into
Alex-Keyes:mainfrom
dcaslin:feat/training-history-pagination
Open

feat(training-history): date filtering, pagination, and detail tiers#2
dcaslin wants to merge 1 commit into
Alex-Keyes:mainfrom
dcaslin:feat/training-history-pagination

Conversation

@dcaslin

@dcaslin dcaslin commented Jun 13, 2026

Copy link
Copy Markdown

Problem

get_training_history returns the user's entire workout history in one JSON
blob. On a representative account that's 231 workouts / ~3.4 MB, of which
98.9% is the per-set records data (sets average ~14.6 KB/workout, dominated
by redundant fields like previous_*, archived_*, valueEmpty). MCP clients
(e.g. Claude Desktop) reject it with "result too large", so the tool is
effectively unusable on any account with real history.

The upstream /programs/history endpoint accepts only timezone_offset and
returns everything grouped by date — there are no server-side filtering or
pagination knobs. So all shaping is done client-side in the MCP server.

What changed

get_training_history gains optional parameters (all backward-compatible — a
bare call still works, now bounded and useful):

Param Purpose
start_date / end_date Inclusive YYYY-MM-DD date-range filter
detail summary (default) or full
page / page_size Walk large histories in chunks (has_more flag)
  • summary (default): per workout — date, title, program, week/day,
    exercise_count, exercise names, and computed total_volume. ~500 bytes each.
  • full: adds records with each set slimmed to the meaningful fields
    (weight, reps, target, rpe, skipped, weight_unit), dropping the
    redundant bloat. Roughly halves full-mode size with no analytical loss.
  • Guardrail: page_size is capped per tier (summary ≤ 100, full ≤ 25) so no
    single response can blow past the client limit regardless of arguments.
  • Envelope: every response carries pagination (incl. has_more),
    filters, and a hint telling the model how to fetch more.

A bare get_training_history() now returns the summary of the 50 most recent
workouts
instead of the full 3.4 MB dump.

Architecture

All transforms live in a new pure module src/boostcamp_mcp/history.py
(flatten → date-filter → newest-first sort → detail-shape → paginate →
envelope). server.py validates params, calls the API once, delegates to
history.shape_history, and returns JSON. The pure module is fully unit-tested
with no network.

Test plan

  • uv run pytest — 22 unit tests covering set slimming, volume math
    (skipped/empty sets), summary/full shaping, date-filter boundaries,
    newest-first ordering, pagination edges, page_size caps, and validation.
  • Live smoke test against a real account:
    • default summary (5 workouts): 2.5 KB response (was 3.4 MB), has_more: true
    • detail="full" (3 workouts): ~12 KB with slimmed sets
    • start_date="2026-04-07": 20 workouts, correct filtering
    • bad date → clean Error: start_date must be YYYY-MM-DD message
  • Server object loads cleanly under FastMCP.

Design and implementation notes: docs/superpowers/specs/2026-06-12-training-history-pagination-design.md.

🤖 Generated with Claude Code

@dcaslin dcaslin force-pushed the feat/training-history-pagination branch from 4113924 to d5ba09a Compare June 13, 2026 02:33
get_training_history returned the user's entire history in one blob (~3.4 MB
on a real account, 98.9% of it per-set records data), which MCP clients reject
with "result too large". The upstream /programs/history endpoint only accepts
timezone_offset, so all shaping is done client-side.

Adds optional, backward-compatible params:
- start_date / end_date: inclusive YYYY-MM-DD date-range filter
- detail: "summary" (default) or "full"
- page / page_size: walk large histories in chunks (has_more flag)

Summary mode returns per-workout date/program/exercises/total_volume (~500B
each); full mode adds records with each set slimmed to the meaningful fields
(weight, reps, target, rpe, skipped, weight_unit). page_size is capped per
tier (summary <= 100, full <= 25) as a truncation guardrail, and every
response carries pagination, filters, and a hint for fetching more.

Superset records (which carry no name/sets of their own, only a `supersets`
child list) are flattened so each child exercise is listed individually and
its sets count toward total_volume — previously these surfaced as a null
exercise and their volume was silently dropped. In full detail the children
keep a `superset` id so the grouping stays visible. Volume_unit is read from
any set declaring a weight_unit so bodyweight/skipped-only days report a unit
instead of null.

Transforms live in a new pure module src/boostcamp_mcp/history.py and are
covered by 27 unit tests (no network). A bare call now returns the summary of
the 50 most recent workouts (~2.5 KB) instead of the full dump.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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