Exercises
What this is
The exercises domain owns the movement library that workouts pull from. It is a two-tier system: a global canonical library of ~600 movements (rows with organization_id IS NULL) that platform admins seed and enrich, plus per-org customs (organization_id = <org>) and overrides (a JSONB diff a coach applies on top of a canonical row to localize its cues, video, name, etc., without forking). A hybrid lexical + semantic search service (/exercises/search) fuses three signals — full-text, pg_trgm similarity, vector cosine on a 1024-dim Voyage embedding — using Reciprocal Rank Fusion. Platform admins drive seeding and alias enrichment via BullMQ jobs.
Who uses it
| Persona | Role |
|---|---|
| Platform admin | Seeds canonical movements from R2 JSON; enqueues Hebrew/Russian alias enrichment via Anthropic Haiku. |
| Owner / Coach | Builds the org’s view: PATCH overrides on canonical rows, create org-customs, delete customs. Consumes the library inside the workout builder. |
| Member | Reads the merged effective view through workouts they’re assigned (exercise name, cues, video). |
| Spotter agent | Searches via /exercises/search to resolve a workout’s movements before persisting. |
Persona impact
- Coach sees one merged list: canonical rows pre-loaded with their cues/videos/translations, plus their org’s customs at the top.
- Owner can localize a canonical movement (Hebrew name, swap video, tweak primary muscles) without forking the row — improvements to the canonical library still flow through for any field the coach hasn’t overridden.
- Spotter agent can semantically resolve “barbell hip thrust” against the library even when the gym uses “glute bridge with bar” as the canonical name.
Capabilities
- List org-only exercises (
GET /exercises, legacy). - List the merged effective library (
GET /exercises/library) — paginated canonical + custom + override-merged, with filters:q,category,movementPattern,equipment[],discipline[],difficulty,source(all/canonical/custom/customized). - Get a single exercise (
GET /exercises/:id) — accepts canonical or org-owned ids. - Get a single effective exercise (
GET /exercises/library/:id) — merged shape. - Create org-custom (
POST /exercises), update (PATCH /exercises/:id), soft-delete (DELETE /exercises/:id). - Upsert org-custom by case-insensitive name (
POST /exercises/upsert). - Upsert per-org override on a canonical row (
PUT /exercises/:id/override) and reset (DELETE /exercises/:id/override). - Hybrid search (
GET /exercises/search) — lexical/semantic/hybrid mode with RRF. - Platform-admin: enqueue canonical seed jobs (
POST /admin/exercises/canonical/seed) and alias enrichment (POST /admin/exercises/canonical/enrich); inspect jobs (GET /admin/exercises/canonical/jobs).
Relationship to other features
- workouts —
workout_movements.exerciseIdmust resolve to canonical (organization_id IS NULL) or this org’s customs. Validated on bothcreateandsetSections. - workout-results —
workout_set_results.exerciseIdandpersonal_records.exerciseIdreference exercises. Per-movement PR detection runs against the exerciseId. - Spotter agent —
/exercises/searchis the primary movement-resolution endpoint. Search results carry asourceflag (canonical / org / customized) so the agent can prefer org rows.
Current status
Shipped. M0 (canonical library) is live with ~600 movements seeded from a curated JSON in R2. Slug-based dedup, HNSW vector index, pg_trgm name index, and tsvector full-text index are in place. Override system shipped. Video-moderation columns (video_status, video_candidates, vote counts) exist on the schema but the full feedback loop is “M0+ deferred” per the in-code comment (libs/db/src/lib/schema/workouts.ts:72).
Known gaps / open Linear issues
- Video feedback loop — schema present (
video_statusCHECK accepts'auto'/'verified'/'demoted'/'manual', vote columns, lock-by-user column) but read/write endpoints not exposed yet. TODO: verify Linear ticket. - Exercise org fork — schema supports
forkedFromIdfor full forking, but the current implementation prefers the override pattern.forkedFromIdis read in search results but no public write endpoint creates a fork. - Aliases enrichment — Haiku enrichment runs in
canonical-enrich.service.tsfor Hebrew + Russian; canonical seeds happen viacanonical-seed.processor.ts. Jobs require platform-admin role. - Search relevance tuning ongoing —
apps/api/src/exercises/exercise-search.int.spec.tscovers RRF fusion; relevance-evaluation work likely tracked elsewhere.