Apex Benchmark
Compares personal athletic scores against elite standards (NFL Combine, Olympic lifting) and prescribes scaled training protocols.
The problem
Gym progress is hard to contextualize without external reference points. You can track PRs in a notebook, but โI squatted 225โ is meaningless without a frame of reference. Apex Benchmark maps your personal athletic scores โ 40-yard dash, vertical jump, back squat, VO2 max โ against tiered elite standards (NFL Combine, Olympic lifting norms) and immediately prescribes a scaled training protocol matched to your gap.
Architecture
A FastAPI monolith with a Jinja2 + Alpine.js frontend โ no separate SPA build step. The backend owns four concerns: a metric registry (what tests exist), a benchmark table (elite / average / below-average thresholds by gender), a protocol table (scaled workouts keyed to each metric), and an evaluation service that scores a submission and determines tier.
Postgres is the store; SQLAlchemy ORM with Alembic-managed migrations keeps schema history in version control. The whole stack runs in a two-stage Docker image โ builder installs deps into a venv, runtime copies only the venv and source, keeping the final image lean.
Evaluation engine
Two scoring functions handle the directional asymmetry of athletic tests: timed metrics (lower is better โ 40-yard dash) use elite_time / user_time, output metrics (higher is better โ vertical jump, squat, VO2 max) use user_value / elite_value. Both return a ratio where 1.0 means you matched the elite baseline. Tier assignment (elite / average / below average) then selects which protocol to prescribe.
Hard decisions
Alembic over auto-migration. Each schema change produces a versioned migration file that can be reviewed, rolled back, and applied identically across environments. Slower to set up, far safer in production.
Alpine.js over a full SPA.The evaluation form is the only interactive surface. Pulling in a React build pipeline for one form is overhead that doesn't pay off โ Alpine handles the fetch, state binding, and conditional rendering in the same Jinja template.
Bodyweight multiplier for squat benchmarks. Storing absolute lb values would make cross-user comparison meaningless. The relative multiplier (e.g., 2.0ร bodyweight = elite male) is the standard used by strength coaches and transfers across weight classes.
What I'd do next
Add a user submission log so each evaluation is persisted and trends are visible over time โ the data model supports it, the UI doesn't yet. Expand the benchmark table with age-group rows (VO2 max norms differ significantly by decade). Add Cognito or Supabase auth so the history belongs to a specific user rather than floating in an anonymous session.