Skip to content

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

routine-engine

A deterministic strength-training program generator. Give it an exercise catalogue and a few facts about an athlete; it returns a training week that progresses sensibly and rotates on schedule.

Extracted from a fitness app built for East Africa, where an athlete might have a full gym or a concrete floor and the same program has to serve both.

npm install && npm test

The idea

Program by movement pattern, not by muscle or by exercise name. A session template asks for "a heavy horizontal push", and the engine fills that slot from whatever the athlete's equipment allows. Barbell bench, dumbbell press, or a push-up — the structure is identical, so one plan serves a commercial gym and a bare room without branching.

import { buildWeek } from 'routine-engine';

const week = buildWeek(catalogue, {
  goal: 'strength',
  daysPerWeek: 4,
  level: 'intermediate',
  allowedEquipment: ['Body Weight', 'Dumbbell'],
  seed: 'athlete-2f9c',
}, 0);

Splits scale with what the athlete can actually commit to: two or three days is full body every session, because a muscle trained once a week at that frequency is barely trained. Splits only start paying off at four days.

Four decisions worth explaining

Exercises rotate per four-week block, not weekly

This is the one people get wrong. Rotating exercises every week feels varied and makes progressive overload impossible to see — you cannot add weight to a lift you are not repeating. So a block holds its exercises for four weeks:

week in block volume
1 base
2 base
3 +1 set
4 deload — one set fewer

The deload is scheduled rather than left to willpower. It is the week people skip and the reason they stall.

Selection is a pure function

Every pick is derived from (seed, block, session, slot) via a small mulberry32 PRNG. Nothing is stored, nothing drifts. The same plan renders the same session on any device, on any run, with no network — and an athlete can look at next week and have it still be that when next week arrives.

Two athletes with identical settings get different exercises, because the seed differs. The same athlete regenerating gets exactly what they had.

Difficulty is a hard filter, not a scoring penalty

This started as a penalty in the scoring function and produced a beginner program containing sets of one-arm chin-ups.

The reason is that entire patterns can be uniformly advanced. Bodyweight vertical pulling is genuinely hard — there is no beginner option without equipment — so ranking by score just reorders a list of things the athlete cannot do, and the least-bad one still wins. A ceiling of "at most one level above the athlete" has to be a filter that removes candidates, after which an unfillable slot is substituted or dropped. An impossible workout is worse than a shorter one.

Nothing gets prescribed without form cues

An exercise whose instructions array is empty is never programmed, at any slot. Catalogues are patchy, and a movement the app cannot explain is one an athlete has to guess at. It stays browsable; it just never gets prescribed.

Catalogue data is dirty, and the engine assumes it

Real exercise datasets are messy in consistent, predictable ways. patternOf() exists to absorb that, and every rule in it comes from a dataset that actually behaved this way:

  • Stretches are flagged compound and carry a real muscle target, so they outscore genuine lifts. Only category separates them — and some slip through even that, so yoga shapes are also caught by name.
  • One muscle covers opposite movements. A rear-delt row and an overhead press share the target "Delts". Only force tells them apart.
  • Isolation hides among compounds. Lateral raises target the same muscle as presses; they are not presses.
  • Core work hides among hinges. A glute-targeted flutter kick is not a deadlift. A hinge either pulls, or is a named bridge/thrust/deadlift movement.
  • Sport skills are filed as strength. A boxing hook is not a shoulder press, and it was filling the vertical-press slot on bodyweight plans.

The tests encode each of these against a fixture built to contain exactly these traps.

Substitution

When a slot cannot be filled, the engine tries the paired pattern before giving up — press for press, pull for pull. A bodyweight beginner has no vertical press available at all, and a coach in that position gives them another press rather than skipping pressing for the day. Filled slots report substituted: true.

Nothing substitutes for a squat or a hinge. Those are not interchangeable with anything and pretending otherwise would be worse than an absent slot.

Coverage is a property of your catalogue

With a rich catalogue every slot fills. With a thin one some cannot, and the engine drops the slot rather than inventing or crashing — a bodyweight-only athlete has no biceps isolation available, because that movement essentially does not exist without equipment.

The test suite asserts both: full coverage across every goal × days × level for catalogues that can supply the patterns, and graceful degradation for one that cannot — where what survives must still be unique, correctly prescribed, and never missing the heavy work.

API

buildWeek(catalogue, plan, week): Session[]
buildSession(catalogue, plan, week, dayIndex): Session
weekFor(startedAt, now?): number
estimatedMinutes(session): number
patternOf(exercise): Pattern | null

Your catalogue needs id, name, target, equipment, force, mechanic, category, difficulty, and instructions. popularityRank is optional and nudges more recognisable movements up the ranking. See src/types.ts.

Not included

Load and rep prescription per set, 1RM estimation, fatigue modelling, and exercise-specific technique guidance are all out of scope. This picks what to train and how much volume; what you lift on the day is yours to decide.

License

MIT

About

Deterministic strength-training program generator. Programs by movement pattern, rotates on four-week blocks, works from any exercise catalogue.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages