Forge
Insights

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.

Reading time —

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.

TM headroom

Every 5/3/1 AMRAP set is a measurement. Headroom compares its e1RM to the training max that prescribed it:

headroom % = (e1RM of the AMRAP set ÷ TM at that session − 1) × 100

The AMRAP set is the heaviest completed working set of the session's program lift. Week, cycle, and TM at session time are reconstructed by replaying the program's state machine backwards from its current state — exact unless week or pointer were hand-edited between sessions. Reading the number: a healthy fresh TM (set at 90% of max) shows about +10%; bare-minimum AMRAP reps land near 0%; negative means the TM has overtaken you — deload it. Gate: at least one finished AMRAP session on a 5/3/1 program. Deload weeks and linear-block programs carry no AMRAP and are excluded.

Cycle over cycle

The same lift on the same program week, one cycle apart — since the program fixes every other variable, reps held (or gained) at the cycle's higher prescribed weight is the cleanest progress signal available. Shown per week as the sequence of weight×reps across cycles with the e1RM delta from first to latest. Gate: AMRAP data in at least two different cycles for that lift.

Cycle report

When a cycle completes, its close-out: every AMRAP top set of that cycle per lift, and a verdict on the training-max bump it triggered.

earned  = best AMRAP e1RM of the cycle ≥ the new TM
margin  = (best e1RM / new TM − 1) × 100

An earned bump was demonstrated before it was granted; a "not shown" bump means the program advanced on schedule but the evidence isn't there yet — worth watching, not necessarily worth reverting. Accessories are reported as top-working-weight movement across the cycle's date span. Gate: at least one fully completed cycle on an AMRAP scheme.

AMRAP beat target

beat = ⌊30 × (best e1RM / top-set weight − 1)⌋ + 1   (Epley, inverted)

The smallest rep count on the prescribed AMRAP set that sets a new estimated-1RM best — shown on the session preview and inside a live program session. Recomputed from finished workouts only, so the running session never hides its own target. Hidden without history or when the answer is 20+ reps (that's noise, not a target).

Measurement trend

trend₀ = first reading
trendᵢ = trendᵢ₋₁ + (1 − 0.9^gap_days) × (readingᵢ − trendᵢ₋₁)

A time-aware exponential smoother in the Hacker's Diet tradition: daily weigh-ins step 10% toward each reading, and a gap of n days takes exactly the step n daily readings at that value would have — sparse and dense logging agree. The weekly rate is the least-squares slope of the trend over the last 28 days (needs 5+ days of span); the 28-day change reads the trend, never the raw scale noise. BMI, when a height is on file, uses the trend weight — not today's reading.

Progression velocity

For rep-range (double-progression) work only: how many sessions each exercise needs to earn a weight increase.

top(session) = heaviest completed working set
an increase = top(session) > top(previous session)
velocity   = mean sessions between increases

Also reported: sessions spent at the current top weight and the last session's weakest working-set reps against the rep-range ceiling — together they show how close the next increase is. Gate: at least 3 sessions and 2 increases per exercise; sets without a rep range never count.

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:

LiftUntrainedNoviceIntermediateAdvancedElite
Bench Press0.50×BW0.75×1.00×1.50×2.00×
Squat0.75×1.00×1.50×2.00×2.50×
Deadlift1.00×1.25×1.75×2.25×2.75×
Overhead Press0.35×0.55×0.80×1.05×1.30×
Barbell Row0.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.