Learn session forecasts from training blocks and clarify progress UI
This commit is contained in:
@@ -0,0 +1,125 @@
|
||||
# Exercise forecasts
|
||||
|
||||
The forecast predicts joint **load × reps** outcomes for an exercise's next
|
||||
sessions. E1RM is derived from those sets with fractional loads preserved. The
|
||||
graph and table use the same forecast. Equivalent loads at other rep counts
|
||||
are a separate, collapsed reference table, not predicted workout choices.
|
||||
|
||||
## Why this model
|
||||
|
||||
A small, recency-weighted transition model fits the data and the user workflow:
|
||||
repeated sets, rep gains, difficult sessions, then load increases and rep resets.
|
||||
It is a nonparametric learner (weighted neighbours), trained on demand on the
|
||||
current person/exercise block. It needs only NumPy; no external training service,
|
||||
cross-person data pooling or large ML dependency is involved.
|
||||
|
||||
We compared it with repeating the last set, an anchored full-block linear E1RM
|
||||
trend, and a damped recent E1RM trend. A larger neural/boosted model was not
|
||||
trained: the per-exercise histories are small, and this model already improves
|
||||
the held-out metrics while preserving interpretable load/rep transitions.
|
||||
|
||||
The evaluation follows [rolling-origin validation](https://otexts.com/fpp3/tscv.html):
|
||||
fit only on observations strictly before the held-out session. Random train/test
|
||||
splits can give [optimistic results for lagged time series](https://scikit-learn.org/stable/auto_examples/applications/plot_time_series_lagged_features.html).
|
||||
[Damping](https://otexts.com/fpp3/holt.html) motivates restraining long-horizon growth;
|
||||
our load-transition damping is a conservative scenario assumption, not a learned
|
||||
physiological law.
|
||||
|
||||
## Training breaks and data handling
|
||||
|
||||
- Take the best E1RM set per date, including deterministic ties. Duplicate sets
|
||||
do not increase the session count.
|
||||
- Split training blocks online when a gap exceeds three recent usual intervals,
|
||||
with a 28-day minimum, 84-day maximum and 42-day sparse-history fallback.
|
||||
The threshold uses only preceding gaps; cadence restarts after a break.
|
||||
- Fit the latest block only. Require four valid sessions after a break. Older
|
||||
blocks remain visible in the chart but cannot drive current forecasts.
|
||||
- History-range controls change the chart and equivalent-rep map, not the
|
||||
training window. A custom end date is also a strict training cutoff.
|
||||
- Positive loads and integer reps 1–20 are supported. Missing, invalid and
|
||||
unsupported readings do not train the model. The UI explains when a new
|
||||
forecast is unavailable. This E1RM model is not appropriate for timed carries,
|
||||
zero-load bodyweight work, or other metrics merely stored as reps.
|
||||
- Old histories are labelled historical projections, not upcoming workouts.
|
||||
|
||||
## Model details
|
||||
|
||||
The last 32 current-block sessions supply joint load/rep transitions. A kernel
|
||||
weights their similarity to the current reps, relative load and stall streak;
|
||||
recency has an eight-session half-life. An explicit repeat-set prior keeps sparse
|
||||
evidence conservative. A stall means failing to improve the rep best at the
|
||||
current load, including drops/recoveries rather than counting every rebound as
|
||||
new progress.
|
||||
|
||||
The usual pre-increase reps and an actually observed load increment are inferred
|
||||
from the recent 24 sessions. Load increases are allowed at the inferred threshold;
|
||||
their reset reps come from actual transitions. No fixed 8–12 prescription is
|
||||
hardcoded. If there are no observed increases, the model cannot invent them.
|
||||
|
||||
The next set is the most likely **joint** outcome. For longer horizons, 512
|
||||
deterministically seeded trajectories produce scenario sets at the median load
|
||||
and median reps conditional on that load. This avoids jumping between dissimilar
|
||||
sets just because their E1RMs match. Each row describes a representative outcome
|
||||
at that date, not one prescribed sequence. Dates use the median recent session
|
||||
gap. A horizon extending between workout dates carries forward the previous
|
||||
scenario without inventing another workout.
|
||||
|
||||
The band starts at the observed value and uses simulated 10th/90th E1RM quantiles,
|
||||
broadened to at least the 80th percentile of recent absolute one-session backtest
|
||||
errors. These are plausible scenario ranges, **not calibrated 80% confidence
|
||||
intervals**. Long-horizon growth probabilities are damped after 12 sessions.
|
||||
Intervals and one-year scenarios have not been prospectively validated.
|
||||
|
||||
## Production audit, 6 September 2026
|
||||
|
||||
Read-only SSH/SQL inspection of Peter's 3,050 recorded sets covered 132 exercises.
|
||||
There were 3,015 distinct valid exercise/date observations and 53 detected breaks;
|
||||
91 latest blocks had at least four usable sessions. The rest are withheld with
|
||||
an explanation. Old but sufficiently populated blocks can produce historical
|
||||
projections; this count is not a count of currently active exercises.
|
||||
|
||||
Every eligible historical block was tested, starting with eight training sessions.
|
||||
Holdouts never cross a detected break. The first four to eight post-break sessions
|
||||
therefore have less validated support. All methods use the same block segmentation,
|
||||
fractional loads and estimated session cadence. Linear/damped baselines keep the
|
||||
latest rep target, as the previous product did. These are retrospective development
|
||||
results, not independent prospective validation or a guarantee for every exercise.
|
||||
|
||||
| Horizon | Holdouts | Model | Load MAE (kg) | Rep MAE | E1RM MAE (kg) |
|
||||
| --- | ---: | --- | ---: | ---: | ---: |
|
||||
| Next session | 1,929 | Learned transitions | 0.312 | 0.692 | 1.893 |
|
||||
| Next session | 1,929 | Repeat last set | 0.506 | 0.800 | 2.395 |
|
||||
| Next session | 1,929 | Damped trend | 0.374 | 0.800 | 2.133 |
|
||||
| Next session | 1,929 | Linear trend | 0.422 | 0.800 | 2.118 |
|
||||
| 4 sessions | 1,636 | Learned transitions | 1.022 | 1.084 | 3.243 |
|
||||
| 4 sessions | 1,636 | Repeat last set | 1.884 | 1.145 | 4.159 |
|
||||
| 4 sessions | 1,636 | Damped trend | 1.074 | 1.145 | 3.436 |
|
||||
| 4 sessions | 1,636 | Linear trend | 1.201 | 1.145 | 3.581 |
|
||||
| 8 sessions | 1,308 | Learned transitions | 1.782 | 1.174 | 4.114 |
|
||||
| 8 sessions | 1,308 | Repeat last set | 3.545 | 1.158 | 5.906 |
|
||||
| 8 sessions | 1,308 | Damped trend | 1.903 | 1.158 | 4.518 |
|
||||
| 8 sessions | 1,308 | Linear trend | 2.056 | 1.158 | 4.845 |
|
||||
|
||||
The learned model improves aggregate load and E1RM errors at all tested horizons.
|
||||
At eight sessions it does **not** beat persistence on reps; difficult sessions
|
||||
remain unpredictable. The UI includes exercise-specific recent error metrics.
|
||||
|
||||
For 30 degrees Smith Press, the latest block has 39 sessions. The current load
|
||||
block contains seven sessions at 93.5 kg, progressing 4→5→6→6→6→6→7 reps.
|
||||
Three sessions did not improve the rep best. The inferred threshold is nine reps
|
||||
and the load increment is 0.5 kg. The next forecast is 93.5 kg × 7, around 13 Sep
|
||||
2026 at an eight-day cadence. Its E1RM is 113.2 kg; the old SQL rounded the load
|
||||
to 94 kg and then the E1RM to 114 kg.
|
||||
|
||||
## Reproduce
|
||||
|
||||
```powershell
|
||||
.venv/Scripts/python -m unittest discover -s tests -v
|
||||
.venv/Scripts/python scripts/audit_forecasts.py --person Peter
|
||||
.venv/Scripts/python scripts/preview_forecasts.py
|
||||
```
|
||||
|
||||
The audit and local preview keep raw production records in process memory.
|
||||
Neither modifies the database. Cached forecasts are bounded, keyed by the full
|
||||
training rows and horizon, and copied before rendering; new records invalidate
|
||||
the key naturally.
|
||||
Reference in New Issue
Block a user