A flat week is noise. A slope that can't clear zero is a plateau.
Plateau pastes your lifting log and fits a rolling regression per exercise. If the slope's 90% confidence interval spans zero, the lift is stalled, and it says for how many weeks. Runs in your browser via Pyodide.
Your bench hasn't moved in three weeks. Or it has, and you genuinely cannot tell, because last Tuesday you slept four hours and ground out a triple that felt like a one-rep max. Same column of numbers, two honest readings, and staring harder at the spreadsheet settles nothing. That is the exact moment lifters either give up on a program that was working or keep hammering one that stopped.
Plateau answers the one question the tracker you already use won't: is this lift actually stuck, or did you just have a bad week? You paste a log (date, exercise, weight, reps) and every exercise comes back labeled trending up, trending down, plateaued for N weeks, or insufficient data. It is built for intermediate and advanced lifters who log every session somewhere already, a notes app, a spreadsheet, a tracker you can export, and don't want to migrate that history into yet another app or make an account to get one straight answer.
Two numbers, one comparable
The first problem is that no two sessions are the same shape. Three reps at 100 and
five reps at 95 are both good days, but you can't regress a trend line over a mixed
column of weights and reps. So each session collapses to a single estimated one-rep
max via the Epley formula: 1RM = weight × (1 +
reps / 30). A triple at 100 becomes 110, a set of five at 95 becomes about 111,
and now they live on the same axis. Every exercise turns into a clean time series of
one number: your estimated top-end strength, session by session.
The slope, and whether it's real
Here is the part that separates the tool from a graph. The naive move is to compare this week's best to last week's and call it. That is a single noisy sample against another single noisy sample, and it hands you false alarms and false comfort in equal measure: one lucky day reads as progress, one bad day reads as a stall.
Instead, Plateau fits a rolling linear regression over the recent window of each exercise's 1RM history and asks a sharper question: given the scatter in your own data, is the slope of that line statistically distinguishable from flat? It computes a 90% confidence interval on the slope. If that interval clears zero on the high side, you're trending up. On the low side, trending down. If the interval straddles zero, the honest statistical statement is that your strength is indistinguishable from a flat line, and that is a plateau, not a vibe. Then it counts back how many weeks the window has been reading flat and prints the number. Each lift also gets a chart: the 1RM scatter, the fitted trend line, and a shaded confidence band, so the call is legible and not just a label you have to trust.
The knob I had to pick
Ninety percent, not ninety-five. That choice is the whole personality of the tool and it is a genuine tradeoff, so it's worth being honest about. A wider interval (95%, 99%) is more conservative about declaring a real trend, which sounds safer but means the band spans zero more easily, so it calls plateau faster and on thinner evidence. A narrower interval demands a stronger signal before it admits movement. At 90% the tool leans toward telling you a stall is real rather than reassuring you that the trend is still alive. For someone deciding whether to change a program, a slightly itchy trigger on "this is stalled" is the more useful failure mode than one that keeps insisting you're still climbing. The Epley estimate has its own limit, too: it drifts at very high rep counts, so a log full of 15-rep sets is noisier input than heavy triples. And with fewer than four sessions for an exercise there's nothing to fit a window over, so it says insufficient data instead of guessing.
The whole statistical core is plain Python with zero runtime dependencies, and it
runs entirely in your browser through Pyodide. Nothing you
paste ever leaves the page, there is no server and no account, and the identical
Python package is tested with pytest in CI, so the regression that runs in the tab
is the one covered by unit tests.
Try it
Open the app, paste a few weeks of any lift
as date, exercise, weight, reps (one session per line, CSV or TSV), and read the
labels. Then paste your real export and go find the lift you suspected was stuck.
Either it's been flat for five weeks and the band around zero says so, or the slope
clears zero and you were about to quit on a program that was still working.
This post is part of the build log: every app my automated factory ships gets written up here, honestly. Browse everything at apps.charliekrug.com. Comments are open below.
Loading comments…