Read ONE completed activity in full detail: all stats (NP, IF, TSS, avg/max power & HR, cadence, elevation, kJ, sRPE/load), heart-rate drift at the same power (cycling only), under one of two names: `decouplingPct` with `decouplingBasis` for ONE sustained effort (Pw:Hr across its halves), or `hr_drift_across_reps` for a session with reps (effort 1 vs the last at the same watts — a direction, not a verdict: the <5% line belongs to decouplingPct and does not transfer). When neither is present, `decouplingNote` says why. `work_reps` lists each sustained rep in order with the power it was ridden at and its heart rate as `hr` (mean), `hrStart`, `hrEnd` and `hrMax` — this is the athlete's own heart-rate graph as numbers, so read it instead of asking them for a screenshot of one. Read the SHAPE, not the mean: a rep that climbs and settles and one that climbs and keeps climbing average the same, as do a rep the athlete eased off and one he did not. Rising hrEnd at unchanged watts across reps is drift; flat is an aerobically clean session whatever any percentage says. Also returned: the interval structure, the device LAP structure if the file had laps, and both notes (`note` = the athlete's, `coachNote` = your earlier read). WHICH structure to read the session by is stated by `structure_read_by`: when the athlete has set this session to Laps on the card, `structure`/`blocks` ARE the file's laps and the detection travels alongside as `structure_auto_detected` — read the version they are looking at and don't re-cut the session into intervals they rejected. Otherwise it is our detection, as `structure_detected`/`blocks_detected`, with any laps as `structure_laps` — and when both are present, NEVER report a prescribed element as not done, cut short, or skipped on the strength of its absence from the detection alone: the detection reads the trace and can miss real work, while the laps are the athlete's own thumb on the button, so check both and grade on the laps when they disagree. Each detected block carries its own CADENCE and TERRAIN — `92rpm` and `-62m/11.4km` mean the block lost 62 m over 11.4 km, i.e. a descent. Read them before attributing a power drop to fatigue: falling watts at a high cadence on a losing gradient is a rider out of gears, not a tiring one, and terrain is shown only where the net change exceeds 10 m. Use the id from list_activities. This is how you analyze how a session was actually executed (interval powers, rep consistency) before writing a coach read or adjusting the plan. Returns BOTH zone splits when the file supports them: `time_in_zone_min` is the stored measure and `time_in_zone_basis` names which quantity it is (%FTP, %threshold pace, or %maxHR) — read it before comparing this session's zone minutes with another's, because the same key is not the same quantity across activities. Two more preconditions travel with their numbers: `tss_basis` says whether a run's rTSS was grade-adjusted or read off raw pace (no altitude in the file), and `np_caveat` appears when the ride is too short for a 30-second rolling average to be stable. `hr_time_in_zone_min` is the heart-rate split, which the stored column never holds for a run — use it to tell a real physiological excursion from pace running ahead of a stale threshold mark. Its `ref` field names the maxHR the percentages are taken from; when that says it is a default rather than the athlete's measured maximum, say so instead of quoting the zones as precise. `description` is what the athlete (or the app that recorded the session) wrote on Strava / intervals.icu — on a gym session pushed from an app like Motra it holds every exercise, set, rep and weight, and since such a file carries neither power nor distance it is not a remark ABOUT the session, it IS the session: read it as the content and grade against it. list_activities flags its presence as `hasDescription`.