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.

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.