diff --git a/docs/forecasting.md b/docs/forecasting.md index ac59808..7e9ac05 100644 --- a/docs/forecasting.md +++ b/docs/forecasting.md @@ -51,10 +51,16 @@ 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; +The usual pre-increase reps and most common load increment are inferred +from the recent 24 sessions. The model preserves the observed mix of load +increments from its transition window rather than replacing every increase with +the most common one. Jumps over 15% of their original source load are excluded +from progression evidence. 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. +Rep-building transitions are excluded at the ceiling, so clipping a rep gain +cannot turn it into artificial evidence of a stall. Actual repeats and the +explicit repeat-set prior remain in the model. The next set is the most likely **joint** outcome. For longer horizons, 512 deterministically seeded trajectories produce scenario sets at the median load @@ -87,15 +93,15 @@ results, not independent prospective validation or a guarantee for every exercis | 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 | Learned transitions | 0.272 | 0.687 | 1.863 | | 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 | Learned transitions | 0.953 | 1.030 | 3.112 | | 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 | Learned transitions | 1.716 | 1.173 | 4.151 | | 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 | @@ -104,6 +110,21 @@ 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. +After correcting the collapsed load increments and artificial ceiling repeats, +next-session and four-session errors improved over the original transition model. +Eight-session load error improved from 1.782 to 1.716 kg, but E1RM error rose +slightly from 4.114 to 4.151 kg. This adjustment is not an accuracy gain at every +horizon. All 23 unit tests pass, including mixed increments, ceiling transitions, +outlier increases and unchanged plateau behaviour. + +Peter's Stiff Leg Deadlift (TNG) eight-week scenario changes from 181 kg × 7 to +183 kg × 6, and its 26-week scenario from 183.5 kg × 7 to 188 kg × 6. These are +joint load/rep outcomes: a higher load does not necessarily mean a higher E1RM +at every horizon. DB Bulgarian Split Squat's 26-week historical scenario changes +from 39 kg × 10 to 39.5 kg × 11; its history ends on 11 January 2024. The current +Smith machine Bulgarian split squat has only two sessions and remains withheld +until four valid sessions are available. + 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 diff --git a/forecasting.py b/forecasting.py index 1e0d380..689041e 100644 --- a/forecasting.py +++ b/forecasting.py @@ -134,11 +134,19 @@ class TransitionModel: probabilities = np.exp(-distance) * 0.5 ** (self.age / 8) increase = self.dw > 0.01 decrease = self.dw < -0.01 + # Preserve the observed mix of increments; the mode alone can turn + # alternating small/large plate jumps into permanently tiny steps. + ordinary_increase = self.dw <= self.source_w * 0.15 + probabilities[:, increase & ~ordinary_increase] = 0 probabilities[:, increase] *= (reps >= self.profile['ceiling'])[:, None] + # A rep-building transition is not evidence of stalling at the ceiling. + # Exclude it there instead of clipping its gain into an artificial repeat. + rep_gain = (abs(self.dw) < 0.01) & (self.dr > 0) + probabilities[:, rep_gain] *= (reps < self.profile['ceiling'])[:, None] # Calendar horizons beyond ~12 sessions are scenarios. Damp the rate of # further load advances rather than extrapolating beginner gains forever. probabilities[:, increase] *= 0.98 ** max(0, step - 12) - delta_w = np.where(increase, self.profile['increment'], self.dw) + delta_w = self.dw delta_w = np.maximum(delta_w, -weights[:, None] * 0.15) out_w = np.maximum(weights[:, None] + delta_w, 0.5) out_r = np.clip(reps[:, None] + self.dr, 1, 20) diff --git a/templates/partials/exercise_history.html b/templates/partials/exercise_history.html index 1cf7001..0cb03c7 100644 --- a/templates/partials/exercise_history.html +++ b/templates/partials/exercise_history.html @@ -45,7 +45,7 @@
At the current load: {{ forecast.profile.block_sessions }} sessions, from {{ forecast.profile.block_start_reps }} to a best of {{ forecast.profile.block_best_reps }} reps. {{ forecast.profile.block_stalls }} sessions did not improve that rep best.
{% if forecast.profile.increment and forecast.profile.ceiling_evidence >= 2 %} -Your recent load increases usually follow {{ forecast.profile.ceiling }} reps, using {{ '%g'|format(forecast.profile.increment) }} kg steps. The model learns repeats, rep gains, difficult sessions and rep resets from this exercise.
+Your recent load increases usually follow {{ forecast.profile.ceiling }} reps. The most common step is {{ '%g'|format(forecast.profile.increment) }} kg; forecasts use the recorded mix of steps. The model learns repeats, rep gains, difficult sessions and rep resets from this exercise.
{% else %}There is not enough repeated loading history to infer a reliable rep threshold. Repeated sets remain a possible outcome.
{% endif %} {% if forecast.validation %} {% set validation = forecast.validation.transitions %} diff --git a/templates/partials/sparkline.html b/templates/partials/sparkline.html index 4aac544..5646af4 100644 --- a/templates/partials/sparkline.html +++ b/templates/partials/sparkline.html @@ -110,7 +110,7 @@ M{% for value, position in lower_points %}{{ (((position * vb_width) + margin) |