The math
This page is the contract: every number Forge shows you, computed exactly as described here, deterministically, from your own sets. No machine learning, no cloud calls, no adjustments you can't see. When something is an approximation, it says so.
One rule applies everywhere first: warm-up sets never count. Not toward volume, not toward PRs, not toward any statistic. A "working set" below always means a completed, non-warm-up set with recorded reps.
Estimated one-rep max (e1RM)
The Epley formula:
e1RM = weight × (1 + reps / 30) // reps = 1 → e1RM = weight Applied per set; an exercise's session e1RM is the best set of that session. Epley is an estimate — most accurate under ~10 reps and increasingly optimistic beyond that. Forge uses it consistently everywhere, so trends in e1RM are meaningful even where the absolute number is soft. Bodyweight sets (no load) get no e1RM; they chart by reps instead.
Personal records
Three kinds, detected per exercise the moment you finish a workout:
- Weight PR — heaviest weight ever lifted on that exercise.
- e1RM PR — highest estimated 1RM ever, so 80 kg × 10 can out-PR 90 kg × 1.
- Reps PR — for unloaded bodyweight exercises, most reps in a set.
PRs are recomputed chronologically whenever history changes — editing an old workout, backdating an import. A fixed typo can never leave a phantom trophy behind.
Streaks & gap nudges
- A week counts as trained if it contains ≥ 1 finished workout (weeks start Monday).
- The streak is consecutive trained weeks ending at this week — or last week, so the streak isn't "broken" before your first session of the current week.
- Gap nudges only watch muscle groups in your rotation (trained in the last 60 days), fire at 9+ quiet days, show at most 2, and stay silent if you haven't trained at all in 14 days — the streak UI covers absence. The catch-all "Other" group never nudges.
Progression & deload suggestions
For a routine exercise with a rep range (e.g. 6–10):
all working sets of last session ≥ rep_max → suggest top_weight + increment (default 2.5)
else, with deload hints on:
last 3 sessions share the same top weight
AND none completed rep_max on every set → stalled
suggestion = round(stalled_weight × 0.9 to increment) The same stall detector drives the Stalled lifts card. Without a rep target, a lift instead counts as stalled when three sessions share a top weight and the session e1RM hasn't moved.
Program schemes
Programs prescribe sets as percentages of a per-lift
training max (TM), rounded to your plate step, never below one step. 5/3/1:
weeks of 65/75/85%, 70/80/90%, 75/85/95% (last set AMRAP), then a 40/50/60% deload.
Linear block: 3×10 @ 70%, 3×8 @ 75%, 3×6 @ 80%, 3×4 @ 85%. State advances only on
finished sessions: lift pointer → week → cycle, and each completed cycle adds
every lift's increment to its TM. The suggested starting TM is 90% of best e1RM.
The scheme tables are served verbatim at /api/programs/schemes.
Warm-up ramp generator
targets ≈ 40% × 10 reps · 60% × 6 · 80% × 3 (of the suggested or top working weight) Rounded to your plate step; regenerated every session as the target moves.
Form & fatigue
The lifting version of the endurance world's performance-management chart. Daily training load = that day's total working volume. Two exponentially-weighted averages run over it:
fitness += (load − fitness) / 42 // long-term, slow
fatigue += (load − fatigue) / 7 // short-term, fast
form = fitness − fatigue Status, with form% = form / fitness:
- Fresh — form% > +10%: fatigue is low relative to your base; a good stretch to push.
- Productive — between the two: normal training.
- Overreaching — form% < −30%: recent load far exceeds your base; plan an easier day.
Gate: 14 days since your first workout. The chart shows the last 90 days.
Recovery sweet spot
baseline = mean(best e1RM of previous 3 sessions of the lift)
performance = session e1RM / baseline
bucketed by rest days before the session: 1 · 2 · 3 · 4+ (gaps ≤ 10 days) The card shows the average deviation per bucket — "+2.3% after 2 rest days" — pooled across your lifts. Gate: a bucket needs ≥ 3 samples, at least two buckets must qualify, and ≥ 12 samples total.
Strength standards
Your best-ever e1RM ÷ current bodyweight, scored against commonly-cited population thresholds. Barbell lifts only (incl. trap/EZ bar) — machine leverage varies too much for the comparison to be honest. You hold a level once you've met its threshold:
| Lift | Untrained | Novice | Intermediate | Advanced | Elite |
|---|---|---|---|---|---|
| Bench Press | 0.50×BW | 0.75× | 1.00× | 1.50× | 2.00× |
| Squat | 0.75× | 1.00× | 1.50× | 2.00× | 2.50× |
| Deadlift | 1.00× | 1.25× | 1.75× | 2.25× | 2.75× |
| Overhead Press | 0.35× | 0.55× | 0.80× | 1.05× | 1.30× |
| Barbell Row | 0.50× | 0.75× | 1.00× | 1.25× | 1.50× |
Gate: a bodyweight measurement and at least one matching barbell lift.
Detraining
for each gap ≥ 14 days on a lift:
loss %/week = (1 − e1RM_after / e1RM_before) × 100 / (gap_days / 7) Averaged across all such events; values outside ±10%/week are discarded as exercise or form changes rather than detraining. Gate: ≥ 3 events.
Trajectory
An ordinary least-squares line through your weekly best e1RM over the last 12 weeks. The card shows the slope and the next round-number milestone (next multiple of 10) with its projected date. Gates: ≥ 4 weekly points spanning ≥ 4 weeks, slope ≥ 0.05/week, and the milestone must land within 26 weeks — beyond that, extrapolation is astrology, so the card says "holding steady" instead.
Time of day
index = session e1RM / that lift's mean e1RM × 100 // 100 = your normal Sessions bucket into morning (< 12:00), afternoon (< 17:00) and evening by your local clock, over the last 180 days. Only lifts trained in ≥ 2 different slots contribute — a lift you only ever do in the evening says nothing comparative. Gate: ≥ 3 contributing sessions per bucket for that bucket's index.
Pacing
- Measured rest — gaps between consecutive set-completion timestamps within the same exercise. Gaps under 15 s (drop sets, rapid taps) and over 10 min (interruptions) are discarded so they can't poison the average.
- Density — working volume ÷ session minutes, for sessions ≥ 1 minute.
Balance, split, blocks
- Push/pull balance — working sets, last 30 days: press = Chest + Shoulders, pull = Back. Arms and Core sit out — they mix both patterns. Flagged outside the 0.67–1.5 press:pull band.
- Muscle split — working sets per muscle group, last 30 days, with an 8-week per-group trend.
- Blocks — the last 28 days vs the 28 before: volume, workouts, per-group sets, and per-lift best e1RM. Shown only when both blocks contain training.
Muscle map regions
Primary and secondary regions resolve from a pattern table over the exercise name —
"…row" lights lats/traps with biceps and rear delts faded; "…curl" lights biceps unless
it's a leg curl; every one of the 278 catalog names has been reviewed against the table.
Unmatched names fall back to lighting their whole muscle group: coarse, but never wrong.
The table lives in frontend/src/lib/muscles.ts if you want to argue with it —
and the anatomy artwork is MIT-licensed from react-native-body-highlighter.
Where the code lives
Almost everything on this page is implemented in
backend/api/stats.py, backend/serializers.py and
backend/api/workouts.py — a few hundred lines of commented Python, no
dependencies beyond the standard library. Reading it is encouraged; catching a
discrepancy between the code and this page is a bug report we want.