Activity retrospective

Activity retrospective

After a key session you want more than “nice ride”. This recipe makes the assistant pull one activity apart — intervals, splits, time-in-zone, decoupling — and turn it into two takeaways, while being honest about Strava-imported activities that come back nearly empty. Split rows are source-labelled and may include aligned average heart rate, power, cadence, and positive elevation gain when coverage is sufficient; missing channels stay omitted.

For logged carbohydrate evidence, missing-log coverage, and source-labelled grams-per-hour calculations without nutrition targets, use the dedicated Fueling review recipe.

When to use this

  • After an interval session, to check whether the hard parts held their targets.
  • After a long endurance ride or run, to see aerobic durability (drift, decoupling).
  • As a post-race debrief.

The recipe

You can name an activity by ID or just describe it — the assistant will look it up.

Compare repeated workouts

When you already know the ordered activity IDs for the same workout, use the read-only compute_workout_progression analyzer instead of asking the assistant to match sessions by title or date. Supply at least two IDs in the order you want compared, and pass an exact planned event_id only when the activity’s prescription is not already linked. The analyzer reports prescribed/completed structure, target-family adherence, stability, duration/recovery deltas, source-labelled feel/RPE, and optional wellness fields. It keeps mixed sports, missing intervals/prescriptions, unsafe lap sources, and missing readiness explicit; it does not assign a progression score, recommend extra intensity/repetitions/recovery changes, or write the calendar.

{
  "activities": [
    {"activity_id": "ride-2026-05-01", "label": "first session"},
    {"activity_id": "ride-2026-05-15", "label": "repeat session"}
  ],
  "include_readiness": true
}

Treat comparison.comparable_pair_count, row/pair status, reasons, _meta.source_tools, and _meta.insufficient_sample as part of the evidence. A not_comparable or insufficient_evidence result is a data boundary, not a coaching verdict. For example, a mixed-sport pair or a missing prescription should be reported as unavailable evidence rather than silently paired.

This analyzer is read-only: it does not choose the next workout, increase intensity, shorten recovery, add repetitions, assign a progression/readiness score, or write calendar events. Do not use workout names to discover the sequence. For relative dates like “last Sunday,” first resolve the athlete-local date window with get_activities, choose the matching activity IDs, and then pass those explicit IDs to the analyzer.

Give me a detailed retrospective of my most recent hard ride. Use icuvisor
with my intervals.icu data.

1. If I gave an activity ID, use it. Otherwise list my recent activities or
   the requested athlete-local date window, pick the one I described, and tell
   me which activity ID you chose.
2. Get the activity details: sport, local start time, duration, distance,
   load, tags, activity fueling (`carbs_ingested_g`, `carbs_used_g`),
   and source/device. If I ask about an activity custom field such as VO2Max,
   request its field code explicitly with `custom_fields` instead of assuming
   it is present in default activity rows.
3. Get the intervals or laps with get_activity_intervals, check
   `_meta.interval_source`, `_meta.auto_lap_suspected`, and
   `_meta.interval_source_caveat`, and get the per-km or per-mile splits.
   Treat split `provenance` and `distance_basis` as part of the evidence: a
   device lap or workout interval is not automatically an exact fixed-distance
   row. Use split `_meta.data_availability` for pauses, missing channels, and
   insufficient coverage; do not fetch or average raw streams in chat.
4. Get the time-in-zone for the session.
5. Get the extended metrics, and report only the ones actually present
   (decoupling, IF, VI, normalized power, interval `dfa_alpha1`, RPE, feel).
   If I ask for AlphaHRV, route only verified interval `dfa_alpha1` from
   upstream `average_dfa_a1`; do not join daily wellness `hrv`/`hrvSDNN`,
   infer readiness, or treat `device_name` as DFA provenance. If I mean an
   athlete-defined field, pass its exact code in `custom_fields`; preserve
   that code and read `_meta.custom_field_provenance` and
   `_meta.data_availability` without inventing a unit, device, algorithm, or
   physiological interpretation.
6. If `get_activities`/`get_activity_details` returns `hypoxic_training_caveat`,
   or `get_extended_metrics` returns `_meta.hypoxic_training_caveat`, quote its
   provenance and wording. If I explicitly said the session used an altitude
   tent/chamber or reduced oxygen exposure, mention that CTL/ATL/Form still use
   logged `training_load` and do not add a hypoxia multiplier.

Then give me:
- What kind of session this was and how it went overall.
- A table with one row per work interval: target vs actual power, average
  HR, duration, any interval `custom_fields` that are relevant to the prompt
  (for example manually-entered lactate), and whether it held. If the activity
  has only laps and no structured intervals, say so and summarize the laps instead;
  if there is one unknown/collapsed interval row, say it is not proof of no
  intervals and use `compute_activity_segment_stats` over explicit time or
  distance bounds before judging sprint or anaerobic execution.
- How the hard parts held up — pacing, fade, heart-rate drift, decoupling.
- Two concrete takeaways for next time.

Rules: if this activity was imported from Strava and its fields are blank,
tell me that up front and analyze only what is actually there. Quote a
decoupling, NP, IF, or VI figure only when a tool returned it, and name the
source tool inline. Do not compute or estimate metrics intervals.icu did not
provide. For hypoxic training, require explicit provenance from the user or from
activity name/notes/tags/custom fields; do not treat altitude, elevation gain, or
SpO2 alone as proof. Keep the answer under about 400 words, leading with the
interval table.

Split routing and caveats

get_activity_splits keeps the existing manual_intervals and virtual_streams sources while adding provenance, distance_basis, and optional metric fields. Virtual rows require cumulative distance/time coverage from zero and include pause time; structured, manual-added, unknown, and device rows retain their upstream distance basis. A pool Swim request may omit split_unit to use 100m only when the matching Swim sport setting says SECS_100M and the distance stream explicitly proves meters. Yards, unknown pace units, missing settings, or ambiguous distance metadata safely fall back to km/mi (or return a user error for explicit 100m).

What icuvisor does

StepToolWhy
1get_activitiesFinds the activity and its ID when you describe it; terse rows include upstream tags when intervals.icu returns them.
2get_activity_detailsSport, timing, load, tags, activity fueling grams, device, Strava-import flag.
3get_activity_intervals and get_activity_splitsPer-rep and per-distance breakdown. Check interval provenance/caveats before treating rows as reps; a single unknown/collapsed row can be an averaged import, not proof of no interval work. get_activity_intervals also includes scalar interval custom_fields such as lactate when upstream returns them.
4get_activity_histogramTime-in-zone distribution for the session.
5get_extended_metricsDecoupling, IF, VI — only those upstream actually fitted.

For a specific surge, climb, distance-bounded split, or sprint/anaerobic workout that appears as one averaged interval row, compute_activity_segment_stats computes mean/median/p90, NP/IF, drift, or decoupling over an explicit time or distance range. For relative requests like “last 10 km”, first use get_activity_details to get the activity distance, convert it to meters, and pass explicit bounds such as start_distance_m = total_distance_m - 10000 and end_distance_m = total_distance_m. Do not fetch raw streams and average them in chat.

For “which sustained climbs were in this activity?” or a comparison of its climb segments, route to get_climb_segments rather than reducing raw streams in chat. Use bounded parameters, for example {"activity_id":"a123","min_grade_percent":3,"min_elevation_gain_m":30,"max_gap_distance_m":100,"max_bridged_elevation_loss_m":5}. The read-only analyzer returns concise distance, gain, grade, and available time/HR/power metrics; it reports missing or noisy altitude evidence instead of inventing climbs and does not make coaching or physiology claims.

Hypoxic-training caveat

Some athletes do workouts in reduced-oxygen environments such as altitude tents, altitude chambers, or other explicitly logged hypoxic setups. icuvisor surfaces a hypoxic_training_caveat on activity rows, and _meta.hypoxic_training_caveat from get_extended_metrics, only when explicit evidence is present in the user request or exposed activity name/notes/tags/custom fields. Do not infer hypoxic stress from altitude, elevation gain, or a low SpO2 value by itself.

When the caveat appears, keep the interpretation conservative: CTL, ATL, Form, and projections are based on logged training_load; icuvisor does not change TSS or apply a hypoxia multiplier. If load is power-based, it may under-represent extra physiological strain from reduced oxygen. If load is HR-based, it may capture some acute cardiovascular response, but it is still not a complete hypoxic-stress model. Use HR, RPE, feel, and recovery trends as supporting context only.

A good answer looks like

Session: Tuesday VO2 ride, 1h 12m, 38.4 km, load 95, tags vo2 and trainer, recorded on a Wahoo head unit (get_activity_details) — direct upload, full data. Fueling fields show 72 g carbs ingested and 138 g used.

The work: 5 x 4 min intervals (get_activity_intervals). Targets held well for reps 1-4 (308-314 W, target 310 W) but rep 5 faded to 291 W. Recoveries were on target.

Aerobic response: Pw:HR decoupling 6.1% across the work intervals (get_extended_metrics) — acceptable for a VO2 session. Time-in-zone was 41% Z5+ during the work blocks (get_activity_histogram).

Takeaways: (1) The rep-5 fade suggests the session was at the right edge of doable — hold this format rather than adding a sixth rep. (2) Decoupling stayed controlled, so durability is not the limiter here; the ceiling is repeatability at VO2 power.

Variations

  • Lactate test: “Analyze this interval session and include the lactate values I entered on each rep. Use custom_fields.lactate from get_activity_intervals when present.”
  • Activity custom field: “For this ride, include my activity custom field vo2max_est. Pass custom_fields: ["vo2max_est"] to get_activity_details, preserve the exact code and source metadata, and state when the value is absent, null, or malformed. If I ask whether it is improving over time, switch to analyze_correlation with metric_x: "custom:vo2max_est" plus the same custom_fields selection over a date range; do not assign a unit or physiology from the name alone.”
  • AlphaHRV/DFA request: “Use get_extended_metrics and report interval dfa_alpha1 only when sourced from average_dfa_a1. If it is absent, null, malformed, or the interval source is restricted, report the _meta.data_availability reason. Do not rename an activity custom field alpha_hrv, join wellness HRV, or infer readiness, threshold, medical, or training conclusions.”
  • Race debrief: “…this was a race — focus on pacing discipline and where I lost time.”
  • First-vs-last distance comparison: “Compare the first 10 km with the last 10 km of this run. Use get_activity_details for total distance, then call compute_activity_segment_stats with explicit distance bounds (0..10000 m and total_distance_m-10000..total_distance_m) for average velocity_smooth, watts if available, and heart_rate. Convert velocity to pace in the final answer; do not reduce raw streams in chat.”
  • Compare two sessions: “Compare activity A and activity B — same workout, two weeks apart. Did the hard parts improve?”
  • A Strava import specifically: “Analyze my most recent Strava-imported activity. Identify genuine Strava imports by the source field — a Garmin or Wahoo device means a native upload, not a Strava import — state the blank-field policy first, and confirm the blank payload with get_activity_details rather than inferring it from the list.”
  • Leave a note: add “If write tools are enabled, append a one-line summary as a comment on the activity.” This uses add_activity_message.

Why this prompt works

  • Look-it-up step. Letting the assistant resolve the activity with get_activities — including athlete-local date windows for phrases like “last Sunday” — means you never paste raw data or raw IDs, which is what overruns the context window.
  • “Report only the ones actually present.” Extended metrics vary by device and sport. This line stops the assistant inventing a decoupling figure for a run with no power.
  • Strava callout. Strava-imported activities return blank fields by policy. Naming this up front turns a confusing “your power was 0 W” into an honest “this was a Strava import, so power is unavailable”.