How fitness projection works

How fitness projection works

get_fitness_projection answers questions like “where will my form be on race day if I keep ramping load?” It is tempting to read its output as a forecast. It is not one — it is a deterministic scenario model, and understanding that distinction is what keeps an AI assistant honest about it.

A scenario, not a forecast

A forecast claims what will happen. A scenario answers if this, then that: given a starting point and a set of assumptions, here is how the numbers move. icuvisor projects fitness with a closed deterministic_ctl_atl_tsb model — the same exponentially-weighted CTL/ATL/TSB maths intervals.icu itself uses — run forward from today.

It is deterministic on purpose. The same inputs always produce the same curve, so the assistant can explain why a projection looks the way it does. Free-form “physiology” models, where the model invents its own progression, are rejected: they would be unreproducible and impossible to audit.

Where the curve starts

The projection seeds CTL, ATL, and TSB from the athlete-local start_date returned by get_fitness. The starting point is your real, current fitness — not an estimate — so a projection is only ever as current as your logged data.

Logged load is the model input

CTL, ATL, Form, and get_fitness_projection all move from logged training_load. icuvisor does not adjust TSS, invent a hypoxia multiplier, or separately model reduced-oxygen exposure. Treat hypoxic-training caveats only when explicit provenance exists — for example, the user says the session was in an altitude tent/chamber or an activity name, note, tag, or selected custom field clearly says hypoxic/reduced oxygen. Altitude, elevation gain, or SpO2 by itself is context, not proof that the session should be interpreted as hypoxic training.

If a hypoxic session used power-based TSS/load, the logged load may under-represent extra physiological strain from reduced oxygen. HR-based load may capture some acute cardiovascular response, but it is not a complete hypoxic-stress model. Use HR, RPE, feel, and recovery trends as supporting context; do not inflate CTL/ATL/Form unless the logged training_load itself was changed upstream.

Per-sport fitness trends are unavailable from weekly buckets

For runners, cyclists, swimmers, and triathletes, a single combined load can hide discipline differences. But the visible byCategory[].training_load values are upstream weekly bucket totals, not daily samples. get_fitness therefore does not distribute them across days or compute per-sport CTL/ATL/TSB. With include_per_sport_load_trends: true, it preserves the 84-day summary lookback but returns explicit unavailable metadata and the reason weekly_summary_cannot_be_distributed_daily.

Use the combined upstream weekly CTL/ATL/TSB anchors for global fitness/form context. For discipline context, get_training_summary can expose factual weekly sport/category totals when present. Preserve its inclusive anchor-window and partial-week caveats, and do not relabel those totals as per-sport CTL, ATL, TSB, fatigue, daily load, or precise physiology.

The assumptions are part of the answer

Because the result depends entirely on its assumptions, the tool reports them back to you. _meta.assumptions records the scenario it ran: horizon length, weekly ramp percentage, recovery-week cadence and load, the number of explicit planned loads supplied, and the CTL/ATL time constants. _meta.boundaries records the limits: the horizon is capped at 180 days, no hidden upstream periodization fields are read, and explicit planned_daily_loads replace the modelled ramp only on the dates they cover. Plan-health reviews should quote those assumptions instead of collapsing them into an opaque score.

Treat those fields as the fine print of the projection. If a scenario assumed a 5%-per-week ramp and you would never train that way, the curve is answering a different question than the one you asked — change the assumption and run it again.

Planned targets are inputs, not evidence

Annual-plan tools can summarize explicit weekly TARGET loads already on your calendar or produce a read-only season proposal. Their projection bridge contains only weekly targets with an explicit load; it does not infer an athlete’s preferred ramp, recovery cadence, taper, or intensity distribution from calendar text.

When those targets are supplied to get_fitness_projection, each week_start_date is an athlete-local ISO Monday. The tool distributes its training_load evenly across the seven dates of that week. A planned_daily_loads entry wins for its matching date and is not redistributed over the other days. Partial weeks remain partial rather than being reweighted.

That makes a calendar target a transparent scenario input, not proof that training happened or a prediction that it will. Compare the resulting curve with future completed load as it becomes available, and keep the assumptions in the answer.

Reading the output

By default the tool returns only the summary. Set include_full: true to get the daily projected CTL/ATL/TSB curve — see Terse by default for when that opt-in is worth the extra tokens. If wellness/readiness or race-event data is missing, the projection does not fill it in; the assistant should say what is missing and treat a user-supplied race date as a scenario anchor when no matching race event is found.