โ† All projects
Full-stack

Apex Benchmark

Compares personal athletic scores against elite standards (NFL Combine, Olympic lifting) and prescribes scaled training protocols.

PythonFastAPISQLAlchemyAlembicPostgreSQLDockerAlpine.jsTailwind CSS

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.