Trainora I · The model

What the numbers are, and how far you should trust them

This page is the arithmetic, written out. It is longer than a marketing page has any business being, and that is on purpose: a figure you cannot interrogate is a figure you will abandon the first week it disagrees with you.

Step one · one session becomes one number

Every recorded session is reduced to a single scalar called its load. The formula is unglamorous:

load = minutes × (relative intensity)²

Relative intensity is expressed against your threshold, where 1.0 means threshold effort — the pace or power or heart rate you could hold for roughly an hour, not your maximum. The square is what makes an hour at threshold cost several times an hour easy, and it is the standard quadratic weighting from the open literature rather than something invented here.

Getting that intensity out of a recorded workout is the hard part, and there are three ways to do it. Trainora tries them in order of how much they can be trusted, and records on the session which one was used, so that when your data quality changes mid-season — you buy a power meter, or stop wearing a strap — the two kinds of session sit side by side in the same chart and you can see which is which.

Power, when there is a threshold power on file
The power series is first passed through a thirty-second trailing rolling mean, so a five-second spike cannot dominate but repeated surges still cost more than steady riding at the same average. Then the fourth-power mean of the smoothed series, then the fourth root of that, divided by your threshold. Standard open method. Highest confidence available.
Heart rate, when max, resting and threshold are all on file
It works in heart-rate reserve, so a threshold heart rate on its own has no denominator and the method simply is not offered — this is the requirement people trip over. Each sample becomes a fraction of reserve, normalised so that your threshold reads 1.0; samples below resting are treated as sensor artefacts, not as recovery below zero. The session's intensity is then the root-mean-square of those values, time-weighted by the interval each sample represents. Not the plain mean: an interval session and a steady session with the same average heart rate are not the same stress, and averaging first erases exactly the difference that matters.
Perceived effort, when you tell it how hard it felt
Effort over an anchor of 7 — "hard", the top of what is sustainable for a long effort — treated as threshold. That anchor is a modelling choice, stated rather than hidden, and it is deliberately conservative. It is also why this method can never rise above low confidence. It is the only estimator available for pool swims and for strength work.

If none of the three applies — no power, no heart rate, no reported effort — the session gets no load at all. It is not recorded as a zero. A zero would quietly deflate your fitness for weeks; instead the app asks you for an effort rating.

Two things then knock confidence down a step each. If the threshold the estimate is measured against is itself a population estimate rather than something you entered or measured, the load standing on it cannot be high-confidence. And if usable samples cover less than eighty per cent of the session's duration, it drops too — a ninety-minute ride with ten minutes of power data is not a power-based session, whatever the arithmetic says. The app reports that coverage figure, so it can tell you why a number is uncertain rather than only that it is.

Step two · days, including the empty ones

Loads are totalled per calendar day into a dense series with zero-load days included. This sounds like a detail and is not. A gap is information: a rest day has to appear in the series so that it can decay your fitness. Skipping empty days would mean a fortnight off looked identical to a fortnight of training compressed into fewer entries.

Step three · two moving averages

The daily series feeds two exponentially weighted moving averages, the Banister impulse-response model. Each day, for each series:

value += α × (today's load − value), where α = 1 − e−1/τ

Fitness
τ = 42 days · α ≈ 0.02353What your body has absorbed over weeks and months. It rises slowly and falls slowly. Half-life about 29 days.
Fatigue
τ = 7 days · α ≈ 0.13312What you are still carrying from the recent past. Half-life about 4.9 days.
Form
fitness − fatigueDerived on read, never stored on its own. Storing it separately is how three numbers that must agree slowly stop agreeing.

Why fatigue moves faster. The two α values differ by a factor of about 5.7, and that single ratio is the whole behaviour of the chart. A hard session pushes fatigue up by roughly an eighth of the gap between it and that session's load, and fitness up by roughly a fortieth. Take a week off and fatigue has shed half of itself in five days while fitness has shed barely a tenth — which is precisely the taper effect, falling out of the arithmetic rather than being bolted on. It also means form runs negative through any honest block of hard training. That is not a warning. It is what building looks like.

WEEK 1WEEK 5WEEK 9 FATIGUEFITNESS

Drawn by running the recurrence above over a synthetic twelve-week history — three sessions a week, three weeks up and one down. Not a screenshot, and not anybody's real training.

Notice the shape of the fourth week in each block. Fatigue falls off a cliff; fitness barely dips. That is the same τ ratio again, seen from the other side.

Recomputed, never patched

The whole series is recomputed from your full history every time it is needed, rather than updated incrementally from yesterday's stored value. Incremental updates drift, and a drifted number is unreproducible — which makes any bug in it impossible to pin down. Same history in, byte-identical numbers out, on every device and on every launch. There is no random seeding anywhere in the engine and no aggregation that depends on hash ordering, which is a boring engineering property that happens to be the difference between a chart you can argue with and one you cannot.

Step four · how certain it is

Confidence is not decorative and it is not a vibe. It is a function of how many days of your recorded training the model has actually seen — counted from the first day with a load on it, never from the first day of the computation window. That distinction matters more than it looks: counting the window would report high confidence for somebody with three sessions, simply because the app chose to compute over 180 days. That is exactly the flattering number the whole engine exists to avoid.

0–6 days
Still learningBelow this floor no number is rendered at all. The screen shows a dash, and the reason line reads: Still learning your response — this session is a starting point, not a verdict.
7–20
Low confidenceA figure appears, labelled as low.
21–41
Moderate confidence
42+
High confidenceThe ceiling. The panel then says so plainly: It has all the history it needs.

Tap any figure in the app and you get four things: what it is, how it is worked out, how sure it is, and what it cannot tell you. The confidence section is computed rather than written — it says how many days you are standing on and roughly how many more would raise it a step. "Low confidence" on its own is the category's failure restated politely; the useful version is an instruction.

The first fortnight, honestly

So: you install the app on a Monday. For the first six days there are no numbers. On day seven a fitness figure appears marked low, and it will look absurdly small, because an exponential average starting from zero takes about six weeks to approach anything like the level your training actually supports. You have to know that to read the early chart without being misled, and the app tells you — but you should hear it here too.

Meanwhile the plan is not waiting for the model. The first four-week block is generated from what you told the app during setup: your sport, the days you have, and a race if there is one. It uses zones only — no paces, no heart-rate targets, because printing "4:15/km" for somebody with no measured threshold is the single fastest way to have a person train at the wrong intensity while believing the app measured it. Three sessions a week by default, more than four refused, the long session on the last training day of your week, one key session, and nothing hard in the two days before a race you have entered.

If you import history — Trainora reads .tcx and .gpx exported from a watch or another platform — the ladder starts from that history's first day rather than from installation day, and the picture fills in immediately. That is the fastest way past the awkward fortnight, with one catch: without max, resting and threshold heart rate on file, imported sessions carry no load at all, and the app says so before you import rather than after.

The week-level numbers

Above the day there is a week. Trainora buckets daily loads by your own calendar's week boundaries — Monday for a French athlete, Sunday for an American one — and computes three things from complete weeks only:

Monotony
Mean daily load divided by the standard deviation of daily load across the week. The same stress every day with no recovery contrast is the classic pattern that precedes breaking down. Returns nothing at all rather than a spectacular number when there is no variation to divide by.
Strain
Weekly load multiplied by monotony.
Load ramp
The latest complete week against the average of up to four complete weeks before it. Above roughly 1.5 is the range usually described as where injury risk rises. Your very first week is not a hundred-per-cent ramp; it is an unknown, and it is reported as one.

Partial weeks are excluded from all three, on both sides. A history that stops on a Wednesday would otherwise report a collapse in load that is an artefact of where the data ends.

Zones

Five, at the level the app shows by default: recovery, easy, steady, threshold, hard. The boundaries are fractions of your threshold — 0.60, 0.75, 0.88 and 1.00 — upper-exclusive, so every value falls in exactly one zone with no gaps and no overlaps. Heart rate and power scale with effort; pace does the opposite, since a faster pace is a smaller number, so its fraction is inverted before classification. More granular models exist in the literature and do not earn a place on the default screen.

A note on vocabulary

Trainora's words are load, fitness, fatigue, form and intensity. It deliberately does not use the trademarked terminology of the commercial platforms in this space for the same quantities. The underlying models — Banister's impulse response, exponentially weighted load, the fourth-power treatment of a power series — are open literature, and this is a naming decision rather than a technical one. If you have used those other products, the concepts will map across without difficulty; the numbers will not be identical, because the estimators are not identical.

Next: a week, worked — the same model seen from the other end, as a block of sessions with a reason attached to each one.

Also in this notebook: the front page, what the watch does, where this is weakest (which is the proper companion to this page), questions, the changelog, support, privacy and terms.

© 2026 Oshi Lab · contact@oshi-messenger.com