Skip to Content
Living documentation — last reviewed 2026-05-28
FeaturesMetric SetsMetric Sets — Behavior

Metric Sets — Behavior

State machine

Stateless tables. The lifecycle is “create or record” → “read”:

metric_definitions: seeded → (no UI to mutate today) metric_sets: createSet (validate exactly-one-owner, validate definitions exist) → exists metric_set_definitions: insert per definitionId, with sortOrder member_metrics: recordMemberMetric → row appended (append-only; reads pick latest by recorded_at DESC)

Invariants

InvariantEnforcement
Each metric_set has exactly one owner among (member, workout, org)DB CHECK metric_sets_owner_exclusive_chk
metric_definitions.slug is globally uniqueDB unique on slug
Set ↔ definition junction has unique (setId, definitionId) pairComposite PK
Coach cannot create an org-owned set for a different orgService check on dto.organizationId !== orgId
Only staff can create metric setsisStaffRole check

Golden paths

Coach attaches a metric set to a workout

  1. In the workout builder, coach picks “Use metric set” → modal opens.
  2. UI lists this org’s org-owned + workout-owned sets.
  3. Coach creates a new workout-owned set: POST /metric-sets with { name, workoutId, definitionIds: [...] }.
  4. Service: validates exactly-one-owner, validates definition IDs all exist, inserts the set + N junction rows in a transaction.

Assigning a workout to a member resolves the set

  1. When a workout is assigned and contains % 1RM cues, the UI calls GET /metric-sets/:setId/resolve?memberId=....
  2. Service joins the set’s definitions with the member’s latest member_metrics value per definition.
  3. The UI uses value * percent / 100 to compute the prescribed weight.

Coach records a member’s 1RM back squat

  1. Coach navigates to member → Performance metrics tab.
  2. Records { definitionId, value, unit } via POST /members/:memberId/metrics.
  3. Row appended; recorded_at defaults to now.
  4. Future resolves use this as the latest value.

Edge cases

ScenarioBehavior
Member has multiple values for the same definitionResolver picks the row with greatest recorded_at (index supports this)
Set references a definition that’s deletedJunction has ON DELETE RESTRICT — definition delete blocked while referenced
Cross-org workout owns the setPossible: workouts have their own org scoping; the set inherits via FK to a workout in a specific org
Two coaches record the same metric at the same instantBoth rows persist; resolver returns whichever has the later timestamp
Set owner type changed via DBShould be forbidden — metric_sets_owner_exclusive_chk enforces 1-of-3

Side effects

ActionWrites
createSetmetric_sets insert; metric_set_definitions insert per def
recordMemberMetricmember_metrics insert
resolvereads only

No events emitted.

Permissions

ActionAuth
List definitions / list sets / resolveActive org membership (any role)
Create set / record member metricStaff role