Skip to content

Releases: munzzyy/liftmath

liftmath 2.4.0

Choose a tag to compare

@munzzyy munzzyy released this 02 Aug 19:32

Hardening pass: garbage input now gets a clean error: ... message instead of a
traceback, and the finite-inventory solver can no longer be asked to enumerate
forever.

  • records crashed with a UnicodeEncodeError on a Windows console. Two dozen
    fields in the bundled data carry diacritics (Polish lifter and meet names,
    the women's hammer-throw notes) that a cp1252 console can't encode, so the
    table died partway through on an ordinary query. The CLI now writes UTF-8
    and replaces anything the terminal can't draw.

  • Piping CLI output into head or less, or hitting Ctrl-C mid-render, dumped
    a BrokenPipeError or KeyboardInterrupt traceback. Both exit quietly now.

  • import: one row with a date the parser doesn't recognize used to throw away
    the whole file. Those rows come through without a date, stay out of the
    per-day and per-week views, and get counted in a summary line.

  • CI runs on Windows and macOS, not only Linux, which is what the
    OS Independent classifier has been claiming all along.

  • Web app: the footer links back to the source, the license, and the issue
    tracker, and a JS-blocked visitor gets an explanation instead of an empty
    page.

  • Web app: the unit toggle, plate setup, bodyweight, sex and open tab survive a
    reload. Still device-only, still no network calls.

  • 1rm: --weight nan crashed with an IndexError, and --weight inf printed
    an "inf" consensus. Both are rejected now.

  • plates: --plates 0 crashed with a ZeroDivisionError, a negative --bar
    was accepted ("Load 135lb on a -45lb bar"), and a non-finite --target
    crashed the greedy loop. All rejected now.

  • standards: a negative lb total dumped a raw traceback, and a negative kg
    total printed negative Wilks/DOTS/IPF GL scores. Totals must be finite and
    greater than zero.

  • Plate inventories are capped at 99 plates per size and 5M search
    combinations, in both the CLI and the web app. --inventory 45x100000000
    used to hang until killed (the web equivalent froze the tab); it errors
    immediately now. Realistic inventories come nowhere near the caps.

  • Web app: tapping a kg-only preset chip (women's bar, metric-no-45) while in
    lb mode reinterpreted the target box as kg, so 225 lb became a plate stack
    for 225 kg (~496 lb). The result pane did relabel to kg, but the number
    changed meaning. The box value now converts to kg when you switch in and
    back to lb when you switch out.

  • standards: IPF GL points for a woman under ~17.7kg bodyweight came out
    negative (or, right at that boundary, absurdly large) instead of leveling
    off - the women's classic coefficient table has B > A, so the formula's own
    denominator inverts sign below that point. The IPF's own formula document
    states a domain floor for this (40kg men, 35kg women); bodyweight is now
    clamped to that floor before evaluating, same treatment Wilks/DOTS already
    got for their own out-of-range bodyweights. Unreachable for any real adult
    lifter, but the function had no floor of its own, so a bad unit conversion
    upstream could've silently returned a nonsense score.

  • New liftmath import: reads a Strong or Hevy CSV workout export (format
    auto-detected, or pass --source) and reports best estimated 1RM per
    exercise and total tonnage per week - the two things a single logged set
    can't tell you on its own. Column layout is read by name, not a fixed
    schema: Strong's own export has drifted across app versions and differs
    between iOS (comma-delimited, no weight-unit column) and Android
    (semicolon-delimited, with one); Hevy's is comma-delimited and always
    records weight in kg. --unit covers both what a unit-less Strong export
    is assumed to already be in, and what a Hevy export gets converted to.

69 new Python tests, 30 new JS tests.

Releases before this file existed are documented on the

liftmath 2.3.0 - track & field records, deeper lifting data

Choose a tag to compare

@munzzyy munzzyy released this 11 Jul 07:34

The records lookup grows a lot.

New: a Track tab (and --sport track on the CLI) with 136 curated records across three levels - World Athletics world records, US collegiate records, and US national high-school records. Every entry cites its source, pending-ratification marks are flagged, and times work the way runners write them: give it "4:12.3" and it tells you what percent of record pace that is and how far off you are. High-school throws use lighter implements, so each of those records says so - the levels aren't directly comparable, on purpose.

Powerlifting records now come in both weight-class schemes - the traditional all-time classes and the IPF's current ones - and every record carries the lifter's country plus Dots/IPF GL points where those actually describe the record lift.

Strongman and grip get a depth pass on the same citation-per-entry standard: circus dumbbell, both sandbag-over-bar variants, Inver Stone press, under-90/under-105 axle, the women's u64-u82 class records, full bodyweight-class tables for the flagship grip events, and USAWA's V-bar and Weaver stick straight from their official record book.

1,577 records bundled (up from 864), snapshot dated 2026-07-11, still fully offline.

liftmath 2.2.0 - world records

Choose a tag to compare

@munzzyy munzzyy released this 11 Jul 06:47

A records lookup next to the three calculators.

  • liftmath records on the CLI and a Records tab in the web app
  • search by lift or event, sex, weight class (or bodyweight - it picks the class for you), and equipment
  • powerlifting records are computed from the OpenPowerlifting public-domain database (~4M sanctioned meet results), in two scopes: all-time and drug-tested only
  • strongman and grip records are hand-curated with a citation on every entry, since neither sport has an open database anywhere
  • give it your own lift and it shows you the percentage of the record
  • works offline like everything else: the dataset is a dated snapshot bundled with the app, and tools/build_records.py regenerates it from a fresh CSV

864 records as of the 2026-07-11 snapshot.

v2.1.0: lb ↔ kg

Choose a tag to compare

@munzzyy munzzyy released this 09 Jul 16:40

A small converter, not a fourth toolliftmath convert turns pounds into kilograms and back, using the exact international pound (1 lb = 0.45359237 kg).

  • Library: lbs_to_kg / kg_to_lbs
  • CLI: liftmath convert --weight 225 --unit lb225lb = 102.06kg
  • Web app: a Convert tab that follows the same unit toggle and theme as the other three

Python package: 96 tests ✓, zero deps · Web: 82 JS tests ✓

See the README for details

v2.0.0: focus on 3 tools

Choose a tag to compare

@munzzyy munzzyy released this 08 Jul 19:39

liftmath is now a simple gym calculator — one-rep max, plate loading, and strength scores.

Trimmed from ~26 calculators down to 3 that you actually reach for:

  • 1RM: 6 formulas + median consensus
  • Plates: greedy loading + finite-inventory solver
  • Strength score: Wilks (2020 + original), DOTS, IPF GL

Python package: 75 tests ✓, zero deps, Python 3.10+

Web app: mobile-first PWA, fully offline, installable, dark by default

See the README for details

v1.5.0: the gym-math release

Choose a tag to compare

@munzzyy munzzyy released this 08 Jul 08:42

The gym-math release: eight new calculators, every constant traced to a source you can check.

  • prilepin and inol - Prilepin's intensity zones and Hristov's INOL, both quoted verbatim from the 2005 paper that every calculator site copies from (we read the actual PDF; one popular site's variant bands turned out to be fabricated, and the tests pin the paper's own worked examples: 0.9, 0.86, 1.04).
  • attempts - meet attempt selection. Opener 91% of your goal third, second 96%: those are the averages of lifters who went 9 for 9 at IPF Worlds (Travis, Zourdos & Bazyler 2021), shown alongside the coach-consensus range so you see the data point and the range real handlers use.
  • skinfold - Jackson-Pollock 3-site and 7-site body density plus the Siri equation, sites named explicitly in the output. Sources disagree about which three sites the men's short version uses, so we ship only the combination we could verify and say so.
  • tonnage - volume load per session, the "how much weight actually moved" number that complements the session-RPE load math already here.
  • pr - e1RM PR detection off the existing six-formula consensus. Log a set, find out if it beat your best.
  • clubs - the 1000-lb club and plate milestones, labeled what they are: gym culture, no governing body, still fun to chase.
  • gainrate - realistic muscle-gain rates from McDonald's yearly model and the Aragon/Helms monthly model, with the source status of each stated plainly (the internet's favorite "20-25 lb in year one" figure could not be traced to a current primary source, so it isn't here).

One candidate didn't make it: the Casey Butt muscular-potential formula. The primary source is offline and every reachable reproduction is secondhand, so it waits until the real text can be checked. No invented numbers.

All eight ship in the web app too, in 32 UI languages with the math parity-pinned between Python and JavaScript like everything else here.

v1.4.0: strength tiers

Choose a tag to compare

@munzzyy munzzyy released this 08 Jul 06:56

The "am I strong?" release.

liftmath tier --sex m --bodyweight 82 --total 500 now places a squat/bench/deadlift total against Strength Level's published powerlifting-total standards (5th/20th/50th/80th/95th percentile bands), interpolated to your bodyweight, kg or lb. standards --tier folds the same answer into the existing strength-score report, and the web app's Standards tab grew a matching section.

The caveats ship with the feature instead of hiding in a footnote: the source data is Strength Level's user population (people who log their lifts, which skews strong), between-bracket values are linearly interpolated, and out-of-range bodyweights get clamped and flagged rather than silently extrapolated. The numbers come from the published TOTAL tables, not per-lift tables summed together - summing overestimates the low tiers by up to a third, so we didn't.

Also in this release:

  • a version-sync CI guard, so CITATION.cff can't quietly disagree with pyproject.toml again (it had been stuck on 1.0.0 for two releases before anyone noticed)
  • CI actions bumped off deprecated Node 20 runners, pinned by SHA as usual

435 Python tests, 372 JS parity tests, all guards green. First release with an outside contributor's work in it too: rep-fraction entries for pendlay rows, landmine presses, nordic curls and friends, from #4.

v1.3.0 — clearer, more accurate, and it explains itself

Choose a tag to compare

@munzzyy munzzyy released this 08 Jul 04:34

Accuracy

  • The calorie tool now uses the Mifflin-St Jeor equation when you give it age, height, and sex. That is the best-validated general-population formula. The old bodyweight-times-a-factor number stays as a quick fallback.
  • Fixed the bulk-and-cut citation (it had two papers tangled together) and corrected the fast-gain lean:fat split to 61/39, which is what the Garthe 2013 figures actually work out to. The numbers are directional, and the code now says so.
  • Body-fat estimates now tell you they get less reliable at very low or very high body fat.

Plain English

  • Every bit of jargon has a plain-English explanation now: 1RM, RIR, RPE, MEV/MAV/MRV, TDEE, DOTS, Wilks, and 27 more. There is a glossary command, a glossary in the web app, and a small "?" next to the confusing words.
  • The calculators explain their output instead of just printing a number: what it means, how to use it, what to watch out for.

New

  • Type in the reps you hit on a 5/3/1 AMRAP set and it works out your next training max.
  • Miss the bottom of your rep range two sessions running and it tells you to deload instead of grinding harder.

Mobile

  • Fixed a layout bug that pushed the page sideways on a phone, plus the usual tap-target and zoom-on-focus fixes, checked across phone sizes.

Nothing was removed. Every command and flag still works the way it did.

Live: https://munzzyy.github.io/liftmath/

Eight themes and a fixed minus button

Choose a tag to compare

@munzzyy munzzyy released this 07 Jul 20:24

Eight UI themes and a fixed minus button.

  • The web app has a theme picker now: Light, Dark, Iron (OLED black), Chalk, Rust, Forest, Contrast (a max-contrast accessibility mode), and Neon, plus Auto to follow your system. Your choice is saved on-device and applies before first paint, so a saved theme never flashes the default. Every theme clears WCAG AA contrast.
  • Fixed: the added-weight stepper on the weighted pull-up/dip calculator refused to go below zero. Negative added weight is how you model assisted sets (a band or assist machine) and the math has supported it all along - the minus button now follows it down.
  • The picker's two new labels are translated in all 32 languages. The math is untouched and still parity-pinned between Python and JS.

v1.1.0 — weighted pull-up 1RM, symmetry, program templates, 32 languages

Choose a tag to compare

@munzzyy munzzyy released this 07 Jul 07:32

New:

  • Weighted pull-up / dip 1RM (bw-onerm) — a 1RM for weighted calisthenics, run on total system load, giving back how much you could strap on for one rep.
  • Plate inventory (plates --inventory) — solve loading against the plates you actually own, with an honest nearest-reachable answer when the exact target can't be built.
  • Lift-ratio symmetry (symmetry) — squat/bench/deadlift against the ratios a balanced lifter hits, flagging what's lagging.
  • Program templates — a training max (tm) feeding 5/3/1 (program531), GZCLP (gzclp), and nSuns (nsuns). Where a program's canon is genuinely unspecified, it asks for the input instead of guessing.
  • 32 languages in the web app, with a switcher and right-to-left layout for Arabic, Hebrew, and Persian. Only the interface is translated — the math and its cited sources are identical underneath.
  • A rebuilt web UI.

Still pure standard-library Python: no dependencies, no accounts, no network. Every number traces to a named source, and the browser JavaScript stays pinned to the Python.

Try it: https://munzzyy.github.io/liftmath/