# 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.