Skip to content

Exercise Library Feature

Dual-ownership refactor folded into Phase 1 of programs-and-assignments

The "Dual-Scope Model: Global + Organization" section below describes the intended model — the table today is platform-only (organization_id absent from migration 000023_exercises). The dual-ownership implementation (formerly F9.1 Phase 2) is folded into Phase 1 of programs-and-assignments and standardized as the catalog-ownership pattern (P49) in patterns.md.

Specifically:

  • exercises.organization_id (nullable), exercises.ownership_kind enum (platform | org), partial unique indexes for slug, RLS visibility union — all added in Phase 1
  • exercise_renders gets a new content_file_id FK to the content_files registry — every render row references a file-layer row; backfill is deterministic
  • Terminology shift: this doc says "global / org / custom"; the new spec uses ownership_kind enum values platform | org | patient_specific. "Custom plans" (per-patient) was a treatment-plan concept that now applies to sessions / programs, not to exercises (exercises stay catalog content, two-tier only)

When in doubt about exercises ownership / slug / RLS, follow programs-and-assignments — the architecture doc wins.

F9.1 Phase 2 — taxonomy + pose-tracking ship together

The clinical/biomechanical taxonomy and the per-exercise pose-tracking config are designed together as one feature with sub-phases (2026-05-25). This doc covers the product surface for both. The authoritative design lives in exercise-taxonomy-pose-tracking.md — all 21 architectural decisions (D1–D21), 5 deferred items (DF1–DF5), and 8 brainstorm resolutions (B1–B8) are locked there. Schema details live in data-model.md Area 9. Rationale ("why") lives in decisions.md — six new entries from Why Class IIa provenance columns on every tag-association row from day one? through Why platform-canonical conditions with optional ICD-10 mapping?. Glossary terms are in glossary.md → Exercise taxonomy & pose tracking.

When this doc disagrees with the design doc, the design doc wins.

Video library of exercises with taxonomy, instructions, equipment requirements, and per-exercise pose-tracking config that drives the AI pipeline.

What this enables

Video exercise catalog: Browse hundreds of PT exercises by body region (shoulder, knee, lumbar spine), movement pattern (squat / hinge / rotation), recovery phase (acute / strength / maintenance), and condition (Lombalgie, Post-ACL, etc.) with video demos and step-by-step instructions.

Shared vs. custom: Use platform exercises ("Bird Dog", "Wall Slide") or clone and customize them for your clinic protocols.

Assign to programs: Build telerehab programs from library exercises — "Day 1: 3× Shoulder External Rotation, 2× Wall Slide" — with skill-prerequisite gating ("don't prescribe Side Plank to a patient who can't hold Plank yet") and exercise ordering ("Bird Dog before Side Plank").

Track patient adherence: See which exercises patients complete, how many reps, if they're struggling.

Pose-tracking for execution feedback: Per-exercise pose configuration drives the patient-side AI pipeline — landmark subset, per-frame metrics (angle / ROM / symmetry / hold-time), real-time feedback rules (warning / critical / stop), and rep-success criteria. Authored by clinicians in Console; consumed by the patient Portal during exercise execution.

Clinical taxonomy: Seven independent axes — categories, body regions, movement patterns, recovery phases, conditions, equipment, and skill prerequisites — each answering a distinct clinical question. See Taxonomy below.

How it works

  1. Browse library: Admin searches for "shoulder" → sees 50+ shoulder exercises
  2. Create treatment plan: Builds 8-week program by adding library exercises
  3. Assign to patient: Patient gets treatment plan → telerehab app shows video demos
  4. Patient does exercises: Logs sets/reps → system tracks adherence
  5. Custom exercises (optional): Clone "Wall Slide" → customize instructions for your clinic's technique
  6. Analytics: See which exercises patients struggle with, which get best results

Technical Reference

Overview

The exercise library is a global + organization-scoped video exercise database for telerehabilitation. Organizations browse, search, and assign exercises to treatment plans. Exercises contain a video demonstration, text/image instructions, and full clinical taxonomy metadata.

Core Concept

Exercise Library = What exercises exist (content domain)
Programs/Protocols = How exercises are prescribed (clinical domain)  → See ../programs-and-assignments/
Telemetry        = How exercises are tracked (Layer 2 ingest pipeline)  → See ../../telemetry/

An exercise defines:

  • What it is (name, description, difficulty, taxonomy)
  • How to perform it (video + ordered instruction steps)
  • What's needed (equipment, contraindications)

An exercise does NOT define:

  • Sets, reps, or duration (that's configured per-exercise in treatment plan sessions)
  • Who should do it (that's on treatment plans → patient enrollment)
  • Tracking data (that's in patient_exercise_logs + the Layer 2 Telemetry ingest path)

Dual-Scope Model: Global + Organization

┌─────────────────────────────────────────┐
│         GLOBAL LIBRARY                  │
│  (organization_id IS NULL)              │
│  Platform-curated by superadmins        │
├─────────────────────────────────────────┤
│  exercises                              │
│  ├── Shoulder External Rotation         │
│  ├── Wall Slide                         │
│  ├── Bird Dog                           │
│  └── Clamshell                          │
└─────────────────────────────────────────┘
          ↓ visible to all orgs
┌─────────────────────────────────────────┐
│         ORG LIBRARY                     │
│  (organization_id = org.id)             │
│  Created by org admins                  │
├─────────────────────────────────────────┤
│  exercises                              │
│  ├── Custom Knee Protocol Step 1        │
│  ├── Post-ACL Warm-up (cloned)         │
│  └── Balance Board Series               │
└─────────────────────────────────────────┘

Visibility rules:

  • All authenticated users see global exercises (published)
  • Org staff see global + their org's exercises
  • Patients see published exercises only (through their treatment plans)
  • Only superadmins can create/modify global exercises
  • Org admins can create/modify their org's exercises
  • Org admins can clone global exercises into their org library for customization

Key Tables

Full schema in data-model.md Area 9.

TablePurpose
exercisesMain entity — video, metadata, taxonomy links. Global (organization_id IS NULL) or org-scoped.
exercise_categoriesCategories (stretching, strengthening, balance). Hierarchical via parent_id. Dual-scope.
exercise_body_regionsBody part tags (shoulder, knee, lumbar spine). Grouped by body_area. Platform-only per D5.
exercise_movement_patternsBiomechanical pattern (push / pull / squat / hinge / rotation / lunge / carry / gait / hold). Platform-only per D5.
exercise_recovery_phasesRehab phase (acute / subacute / strength / return_to_activity / maintenance). Platform-only per D5.
exercise_conditionsDiagnosis lookup (Lombalgie, Cervicalgie, Post-ACL, …) with optional ICD-10 mapping. Dual-scope per D5 / B5.
exercise_skill_prerequisitesPatient-capability gates (balance_static, single_leg_stance, grip_strength, …). Platform-only per D5.
exercise_equipmentEquipment catalog (resistance band, yoga mat, dumbbell). Dual-scope.
exercise_tagsPolymorphic M:M junction — links exercises to every tag axis above via tag_type ENUM.
exercise_prerequisitesSelf-M:M between exercises ("Bird Dog before Side Plank") for program-builder ordering.
exercise_instructionsOrdered text/image instruction steps per exercise.
exercise_contraindicationsClinical warnings/restrictions per exercise. Links to exercise_conditions via condition_id FK (replaces the old freetext condition_name, per B5).
pose_enginesReference table — pose-engine vendor catalog (mediapipe.holistic at F9.1 Phase 2; future engines slot in here).
pose_landmarksReference table — per-engine landmark catalog (~543 rows for MediaPipe holistic).
exercise_pose_configsPer-exercise pose-tracking config (1:1 with exercises per D8). Camera setup, engine, rep-success rule, asset-version pin, status.
exercise_pose_config_historyImmutable prior snapshots of pose configs for Class IIa scoring reproducibility.
exercise_pose_landmarksM2M between a pose config and the landmark subset it tracks.
exercise_pose_metricsTyped metric definitions (angle / rom / symmetry / distance / velocity / position / hold_time) with targets, tolerances, weights, source landmark refs.
exercise_pose_feedback_rulesSeverity-tiered triggers (`warning
pose_data_quality_overridesSpecialist clinical override flagging a session's pose data as unreliable. Audited per B3.

Class IIa provenance. Every tag-association row (exercise_tags, exercise_prerequisites, exercise_contraindications, exercise_instructions) and every pose-config sub-row (exercise_pose_landmarks, exercise_pose_metrics, exercise_pose_feedback_rules) carries tagged_by_principal_id, tagged_at, and clinical_basis (nullable now, NOT NULL at the Class IIa elevation). The columns ship pre-prod because backfill across a populated catalog is operationally infeasible — see Why Class IIa provenance columns on every tag-association row from day one?.

Per-tag deprecation. Every tag entity carries deprecated_at TIMESTAMPTZ + replaced_by_id (self-FK), paired with a DB trigger that rejects in-place edits to a tag's canonical fields. To "rename" a tag, the author deprecates the old row and creates a new one — see Why per-tag deprecation over full vocabulary versioning?.

Taxonomy

Exercises are tagged across seven independent axes. Each axis answers a distinct clinical question.

AxisQuestion it answersScope
body_regionWhere on the body?Platform-only (locked)
movement_patternHow does the body move?Platform-only (locked)
recovery_phaseWhen in recovery is this appropriate?Platform-only (locked)
conditionsWhat diagnosis is this for?Dual-scope (platform + org)
skill_prerequisitesCan the patient safely do it?Platform-only (locked)
equipmentWhat does the patient need at home?Dual-scope (platform + org)
categoriesOrganisational grouping (clinic protocols)Dual-scope (platform + org)

Plus the locked exercises.difficulty enum (beginner | intermediate | advanced) that captures intrinsic exercise hardness — distinct from recovery_phase, skill_prerequisites, and the per-rep perceived_difficulty patient self-report on patient_session_completions. See B6 in the design doc for why no effort_tier was added.

Locked vs extensible scopes

Per D5 and Why platform-locked vocabulary for body_regions, movement_patterns, recovery_phase, and skill_prerequisites?, four axes lock to platform-only via CHECK (organization_id IS NULL):

  • body_regions — cohort analytics across clinics require comparable vocabulary
  • movement_patterns — pose-engine rep-counting heuristics map directly to these values; redefinition breaks engine integration
  • recovery_phase — small clinical-standard enum; redefinition undermines program-builder reasoning
  • skill_prerequisites — used in algorithmic program suggestion; vocabulary lock matters for comparable recommendations

The three extensible axes (categories, equipment, conditions) follow the standard P49 dual-scope pattern — clinics can extend with org-private rows.

Escape hatches for clinics on locked axes: clinics rename platform tags in their UI via the translations JSONB (P21); platform vocabulary additions can be requested via a clinical reviewer (process overhead, not a schema lock-out).

Categories (Hierarchical)

Stretching
├── Static Stretching
├── Dynamic Stretching
└── PNF Stretching
Strengthening
├── Isometric
├── Isotonic
└── Plyometric
Balance & Proprioception
Mobility
Breathing & Relaxation

Body Regions (Grouped by Area)

Upper Body
├── Shoulder
├── Elbow
├── Wrist / Hand
└── Cervical Spine

Core
├── Lumbar Spine
├── Thoracic Spine
└── Abdominals

Lower Body
├── Hip
├── Knee
├── Ankle / Foot
└── Glutes

Full Body

Movement Patterns

push | pull | squat | hinge | rotation | lunge | carry | gait | hold

An exercise can carry multiple patterns (e.g., a deadlift is hinge and arguably pull). hold is the isometric pattern. See movement pattern in the glossary.

Recovery Phases

acute | subacute | strength | return_to_activity | maintenance

Exercises can span multiple phases (acute → strength is common). See recovery phase in the glossary.

Conditions

A platform-curated lookup of common rehab diagnoses with optional ICD-10 mapping (per B5). Clinicians work with names; ICD-10 codes ride along for EHR / insurance interop where present.

Lombalgie (M54.5)
Cervicalgie (M54.2)
Meniscopatie (M23.x)
Post-ACL reconstruction (Z98.89)
Rotator cuff impingement (M75.4)
Coxartroza (M16.x)
Proteză totală genunchi (Z96.651)
Epicondilita laterală (M77.1)

F9.1 Phase 2 seeds ~30–50 most common Romanian rehab conditions; codes populated where known. The exercise_conditions.icd10_code column is nullable — rows without a code don't break, they just can't be billed under that interop path.

Conditions are dual-scope: clinics can add org-private rows for local condition names not in the platform catalog (e.g., a clinic-specific protocol name).

Skill Prerequisites

What the patient must already be capable of for an exercise to be safe and effective:

balance_static | balance_dynamic | single_leg_stance | floor_to_stand
grip_strength | bilateral_coordination | weight_bearing_tolerance | core_endurance

The program-builder uses these for algorithmic safety gating ("don't prescribe an exercise requiring single_leg_stance to a patient whose current capability profile lacks it"). Distinct from exercise_prerequisites (which is the self-M:M between specific exercises — "Bird Dog before Side Plank"); skill_prerequisites are about patient capability, exercise_prerequisites are about exercise ordering.

Equipment

No Equipment (bodyweight)
Resistance Band
Yoga Mat
Dumbbell
Swiss Ball
Foam Roller
Balance Board
TheraBand

Dual-scope — clinics can add proprietary equipment (e.g., a brand-specific brace).

Filtering

The GET /v1/exercises endpoint supports multi-taxonomy filtering across every axis above. See api.md.

GET /v1/exercises?category_id={uuid}&body_region_id={uuid}&movement_pattern_id={uuid}
                  &recovery_phase_id={uuid}&condition_id={uuid}&skill_prerequisite_id={uuid}
                  &equipment_id={uuid}&difficulty=beginner&status=published&q=plank
                  &sort=-created_at&page=1&limit=25

Video Storage

Section superseded by the compositional model

The "single video_url column per exercise" model below predates the platform's move to composed videos per prescription. Exercises no longer have one canonical video — they have a bundle of raw filming primitives in S3, from which the media service renders one MP4 per (exercise, prescription, language) tuple. Bunny video IDs are cached in exercise_renders (landing with F9.1), not stored as a column on exercises.

For the current model, see composition.md and P56 Exercise Video Composition Pipeline.

Exercises store video via a CDN-agnostic design (historical model — see above):

sql
video_url               TEXT        -- CDN URL (Bunny Stream, S3, etc.)
video_provider          TEXT        -- 'bunny_stream' | 's3'
video_thumbnail_url     TEXT        -- poster frame
video_duration_seconds  INT         -- cached duration

Why CDN-agnostic: The platform supports Bunny Stream (EU-first CDN with built-in HLS transcoding) or S3 + CloudFront. The video_provider field allows the upload/playback layer to adapt without schema changes.

See video-upload.md for the full upload flow and adaptive streaming design.

Instructions Model

Each exercise has ordered, manually written instruction steps. Whoever creates the exercise (the platform team for global exercises, or clinic staff for clinic exercises) writes each step by hand through the admin UI. Instructions are typed by role:

Exercise: Shoulder External Rotation
├── [preparation] "Lie on your side with a towel roll under your arm"
├── [step] "Rotate your forearm upward, keeping elbow at 90°"
├── [form_cue] "Keep your elbow pinned to your side"
├── [breathing] "Exhale as you rotate up, inhale on return"
└── [safety] "Stop if you feel sharp pain in the shoulder joint"

Instruction types: preparation, step, form_cue, breathing, safety

Each instruction can have an optional image (S3 upload) for visual demonstration. When cloning an exercise, all instructions (including images) are copied and can be freely edited.

Contraindications

Each exercise can have clinical contraindications — warnings tied to specific medical conditions. Stored in exercise_contraindications.

Condition references are FKs, not freetext

The historical model used a freetext condition_name column. F9.1 Phase 2 replaces it with condition_id UUID NOT NULL FK → exercise_conditions(id) per B5. This enables "exercises indicated for diagnosis X" queries, eliminates spelling variants of the same condition, and unlocks the optional ICD-10 mapping for insurance/EHR interop. Existing freetext rows are migrated to FK references during the F9.1 Phase 2 schema migration.

Exercise: Shoulder External Rotation
├── [warning]          condition_id → "Shoulder Impingement"      — reduce range of motion, stop if sharp pain
└── [contraindicated]  condition_id → "Acute Rotator Cuff Tear"   — do not perform until cleared by physician

Severity levels:

  • warning — exercise can be performed with modifications or caution
  • contraindicated — exercise should not be performed by patients with this condition

Who manages contraindications:

  • Platform exercises — contraindications are pre-written and maintained by the RestartiX team. Clinics cannot edit them directly (clone the exercise first).
  • Clinic exercises — the clinic's specialists and admins manage contraindications. When cloning from the platform library, all contraindications are copied and can then be freely edited, added, or removed.

Display, not enforcement (Class I posture): Contraindications are shown as clinical guidance when a specialist prescribes an exercise. The platform displays the warning; the prescribing specialist makes the clinical judgment to override or honour it. This is the Class I MDR posture the platform ships under today — informational data, specialist-mediated decisions.

Class IIa provenance. Every contraindication row carries tagged_by_principal_id, tagged_at, and clinical_basis columns so the audit trail captures who authored each contraindication and on what clinical rationale. The columns ship pre-prod for the cost-asymmetry reason explained in Why Class IIa provenance columns on every tag-association row from day one?.

Specialist signoff workflow deferred (DF5)

A formal review_status enum + reviewer/approver workflow for contraindication authoring is deferred — no placeholder columns ship at F9.1 Phase 2. Class I posture handles this informally (internal clinical review). Class IIa elevation will add the workflow as a single additive migration when regulatory counsel signs off on the process design. See DF5 in the design doc for why columns aren't placeholder-shipped today.

Trigger to revisit: Class IIa preparation kickoff, OR first contraindication-related incident.

Pose tracking

Class I MDR posture today; Class IIa-upgrade-ready

The platform is a registered Class I MDR device per Medical Device Readiness: it displays informational data; the specialist makes clinical decisions. The pose engine below (rep count, ROM, form feedback) is unbuilt and outside the current declared intended purpose, which states the device has no measuring function — it ships at the Class IIa step, and the schema here exists so that step is a migration rather than a rewrite. See telemetry → Aggregation engine. The schema designed below carries the Class IIa-grade provenance columns (tagged_by_principal_id, tagged_at, clinical_basis) from day one so the upgrade path is a single column NOT-NULL migration — see Why Class IIa provenance columns on every tag-association row from day one?.

Per-exercise pose-tracking configuration drives the patient-side AI pipeline that scores execution during a session. F9.1 Phase 2 ships the authoring side (Console UI for clinicians, API CRUD); the consumption side (patient Portal pose-tracking UX) and the aggregator (POST /v1/pose/frames → per-rep metrics) ship as separate F-tier work on the telemetry service.

Pose config (per exercise)

Each exercise has one optional Pose config row (exercise_pose_configs), 1:1 with the exercise per D8. A different filming or camera setup is a different exercise (a new asset_version), not a second config. Multiple-config-per-exercise has an easy escape hatch if a concrete clinical case ever appears.

A pose config carries:

  • Engine + tracking master switch (tracking_enabled BOOLEAN, engine_id FK → pose_engines) — see Pose engine
  • Camera setupcamera_angle (frontal | lateral | 45_degree | posterior), camera_distance_cm_min/max, lighting_requirement (frontal | ambient | strong | any), in_frame_requirements TEXT[] (e.g., ['fata_integral_vizibila', 'umeri_in_cadru'])
  • Landmark subset — see § Landmark subset below
  • Metrics list — see § Metrics authoring below
  • Feedback rules — see § Feedback rules below
  • Rep success criteria — see § Rep success criteria below
  • Tracking confidence thresholdmin_landmark_confidence NUMERIC (default 0.5) per B2
  • Asset version pin — see § Asset re-filming invalidation below
  • Statusdraft | published | invalidated
  • Class IIa provenancetagged_by_principal_id, tagged_at, clinical_basis

Pose engines

The platform-curated pose_engines reference table is the catalog of pose-detection models available. F9.1 Phase 2 seeds with mediapipe.holistic only; future engines (different vendors, newer MediaPipe variants) slot in as new rows without schema changes. Each engine has its own landmark catalog versioned via landmark_catalog_version. See pose engine in the glossary.

Landmark subset (authoring)

Per D17, each pose config tracks a subset of the engine's landmark catalog (MediaPipe holistic exposes ~543: 33 pose + 21 left hand + 21 right hand + 468 face). Most exercises use 3–10 landmarks. Fewer landmarks = more stable tracking, less CPU on the patient's device.

The authoring UI presents a body-silhouette picker grouped by pose_landmarks.body_part_category (head / torso / left_arm / right_arm / left_leg / right_leg / hands / face). Clinicians pick the joints relevant to the exercise; the rest are excluded from per-frame inference.

Metrics authoring

Per D18 and B7, each pose config carries a list of typed metrics (exercise_pose_metrics). Seven initial metric types:

TypeMeasuresExample exercise
angleAngle between three landmarks (joint flexion)Knee flexion in squat
romRange of motion (max − min over rep)Cervical rotation ROM
symmetryDifference between two sibling metrics (L vs R)L–R rotation symmetry
distanceDistance between two landmarksHand-to-knee touch
velocityRate of change of position/angleJumping, ballistic
positionAbsolute position relative to reference (axis-aligned)Hip drop in plank
hold_timeDuration above/below thresholdStatic plank hold

Each metric carries target_min / target_max, tolerance, weight_pct (per-config weights sum to 100; validated app-side), axis (x | y | z | xy | xz | yz | xyz), and either landmark_refs UUID[] (source landmarks for direct measurement) or derived_from_metric_ids UUID[] (for composite/derived metrics like symmetry that reference sibling metric IDs, not landmarks).

Block-removal validation (per B4): the Console blocks removal of a landmark or metric that's referenced by another metric. The error names the dependency ("Landmark X is used by metric Y; drop the metric first"). No silent auto-disable, no cascade-delete.

Feedback rules

Per D10 / D19, form errors and live warnings collapse into one schema (exercise_pose_feedback_rules) differentiated by severity:

  • warning — informational ("Shoulders dropping slightly")
  • critical — correct urgently ("Knee tracking inward — pause and reset stance")
  • stop — halt rep ("Severe form breakdown — end set")

Each rule carries a condition_expression (engine-parseable DSL — grammar deferred per DF1; the column ships as TEXT with a condition_format ENUM discriminator) and a translatable patient_message (RO/EN at launch).

Bare version at F9.1 Phase 2. No rate-limiting columns (min_interval_seconds, trigger_once_per_rep) ship. Default engine behavior: trigger on every frame the condition holds; client de-dupes within a short window. Rate-limiting columns land as additive nullable columns when real "too noisy" complaints surface — see D10.

The authoring UI shows two stacked sections in the editor (warning rules grouped, critical/stop rules grouped) for readability — that's a presentation choice; the data is one table.

Rep success criteria

Per D20 / B8, what counts as a successful rep is structured (not freetext) — rep_success_rule_type ENUM + rep_success_rule_params JSONB on the pose config row.

Rule typeWhat counts as a repparams JSONB shape
angle_cyclePatient reaches target angle, returns to start{start_angle, target_angle, return_angle, tolerance, side?}
position_cycleBody part reaches target position, returns{landmark_id, target_xyz, return_xyz, tolerance}
velocity_peakPeak velocity detected on dominant axis{landmark_id, axis, peak_threshold, min_amplitude}
compositeMultiple sub-rules ANDed (tree structure)`{operator: 'AND'

Composite rules use a nested JSONB tree; refactor to a tree-structured table only happens if real composite rules get hairy. Params are app-side validated per type discriminator.

Asset re-filming invalidation

Pose-tracking thresholds (landmark positions, axis-aligned metrics) encode the camera framing of a specific filming. If an exercise is re-filmed (new shoot, new camera angle, new patient distance), thresholds that worked for the old asset no longer apply. Per D9 and Why pose config pins to asset_version?, the schema enforces this structurally:

EventPose config
Asset unchanged (same asset_version)Reused as-is, no re-validation
Asset re-encoded without re-filmingShould NOT bump asset_version (composer contract) → config reused
Asset re-filmed (new shoot / new framing)MUST bump asset_version → config invalidates, re-authoring required
Clinician tweaks a thresholdNew exercise_pose_config_history row; config stays VALID (versioning, not invalidation)

The DB trigger on exercises.asset_version UPDATE flips the matching exercise_pose_configs.status to 'invalidated' and reverts tracking_enabled to FALSE. The Console pose-config authoring surface surfaces an "Asset re-filmed — pose config invalidated, re-author" UI banner. Patients in-flight on a session continue with the old asset version cached; future sessions wait until the clinician re-authors.

Clone behavior. When an exercise is cloned (P49 dual-scope, e.g., an org cloning a platform exercise), the pose config is copied alongside. The cloned config gets a new id, points at the cloned exercise, inherits the original's pinned_asset_version, and the Class IIa columns reflect the cloning principal (tagged_by_principal_id = the cloner, clinical_basis = "cloned from {original_id}").

Pose config history

Every edit to a pose config (any field change, every status transition, every invalidation, every clone) writes a full-row snapshot to exercise_pose_config_history per D8. Append-only, no UPDATE/DELETE policies — required for Class IIa reproducibility ("given session_run X at time T, what pose config was active?"). The Console exposes a read-only history viewer per pose config.

Pose data quality override (specialist clinical judgment)

Per B3, a specialist can mark a session's pose data as unreliable and have it excluded from scoring. The pose_data_quality_overrides table stores the override at one of two granularities:

  • session_run — entire session's pose data flagged (e.g., patient's camera kept dropping out)
  • session_exercise_event — one specific exercise within a session flagged (e.g., the patient adjusted the camera mid-set)

Effect: stats queries (per-patient, cohort) and the program promotion threshold (D13) exclude overridden data via WHERE NOT EXISTS. Raw pose data stays in the DB (audit intact) but doesn't feed scoring.

Every override insert is audit-logged with full overridden_by_principal_id + override_reason — Class IIa requirement.

Override CRUD API ships at F9.1 Phase 2; Console UI deferred

The POST /v1/admin/sessions/{run_id}/pose-override endpoint (and the table) ship at F9.1 Phase 2. The Console UI for specialists to invoke the override is deferred — initial workflow is "support request to platform team" (the same DF4-style escape hatch the platform uses for low-frequency operations). Build trigger: first clinic explicit feature request, OR specialist workflow demand pattern.

Cloning

Org admins can clone exercises from the global library (or from their own library) to create customized variants:

http
POST /v1/exercises/{id}/clone

What gets cloned:

  • All exercise fields (name, description, difficulty, asset metadata)
  • All tags across every axis (categories, body_regions, movement_patterns, recovery_phases, conditions, skill_prerequisites, equipment)
  • All instructions (with images)
  • All contraindications
  • All exercise prerequisites (the self-M:M chain)
  • The pose config (if any) — copied alongside per D9 clone behavior, inheriting the source's pinned_asset_version. Class IIa columns record the cloning principal (tagged_by_principal_id = the cloner, clinical_basis = "cloned from {original_id}").

What changes:

  • New UUID
  • organization_id set to current org
  • cloned_from_id set to source exercise ID
  • status reset to draft
  • Pose config gets a new UUID + points at the cloned exercise; landmark subset, metrics, and feedback rules are cloned as sub-rows.

The clone is fully independent — editing the source does not affect the clone.

Soft Delete

Exercises use soft delete (deleted_at timestamp) because they may be referenced by active treatment plans. A soft-deleted exercise:

  • Is hidden from library search/browse
  • Remains visible in existing treatment plans (with a "discontinued" indicator)
  • Cannot be added to new plans
  • The exercises.id FK in treatment_plan_session_exercises uses ON DELETE RESTRICT as a safety net

Exercise Status

draft → published → archived
  • draft: Only visible to admins/superadmins. Work in progress.
  • published: Visible to all authorized users. Can be used in treatment plans.
  • archived: Hidden from new plan creation but remains in existing plans.

Integration Points

With Treatment Plans Feature

  • Exercises are added to treatment_plan_session_exercises with per-plan configuration (sets, reps, duration, rest)
  • Exercise data (video, instructions) is always "live" — updates to an exercise are immediately visible in all plans
  • Plan version snapshots capture exercise IDs (not exercise content), so the current exercise state is always shown

With Telemetry API (Layer 2 feature)

  • Video engagement: When a patient watches an exercise video, the Patient Portal sends the standard media event lifecycle (session_start, heartbeat, buffering_start/end, milestone, session_end) to POST /v1/media/events on the Telemetry API. Server-side aggregation writes media_session_metrics + media_buffering_events (Postgres, monthly partitioned). See ../../telemetry/media-events.md for the event spec. Consent: analytics per-purpose flag.
  • Pose tracking: If enabled, MediaPipe landmark frames are batched (binary float32 + gzip, 1-sec batches) and sent to POST /v1/pose/frames (separate endpoint). Server-side aggregation at session_end produces pose_session_metrics + pose_rep_metrics (Postgres) + a replay blob in S3 at s3://restartix-telemetry/{org_id}/{session_id}.bin.gz. Consent: biometric per-purpose flag.
  • Errors: off-the-shelf (Sentry-equivalent) when needed; not part of telemetry. The earlier POST /v1/errors/report endpoint has been removed.

With S3/CDN

  • Exercise videos: {org_id}/exercises/{exercise_id}/video/{filename} (org) or global/exercises/{exercise_id}/video/{filename} (global)
  • Instruction images: {org_id}/exercises/{exercise_id}/instructions/{sort_order}/{filename}
  • Thumbnails: {org_id}/exercises/{exercise_id}/thumbnail/{filename}

API Endpoints

See api.md for full API documentation.

Key endpoints:

  • GET /v1/exercises — Browse/search with multi-axis taxonomy filters
  • POST /v1/exercises — Create exercise (admin: org, superadmin: global)
  • GET /v1/exercises/{id} — Full details with instructions, tags, contraindications, pose config
  • POST /v1/exercises/{id}/clone — Clone to org library (carries pose config)
  • POST /v1/exercises/{id}/video — Upload video
  • GET/POST/PATCH /v1/admin/exercises/{id}/pose-config — Pose-config authoring
  • POST /v1/admin/sessions/{run_id}/pose-override — Pose data quality override (API ships; Console UI deferred)
  • GET /v1/pose/engines, GET /v1/pose/engines/{id}/landmarks — Pose engine + landmark catalog reads

Design Principles

  1. Dual-scope by default; selective locking for analytics-critical axes — Global library is platform-curated, org libraries are private. Most tables use the standard organization_id IS NULL vs IS NOT NULL pattern. Four taxonomy axes lock to platform-only via CHECK (organization_id IS NULL) per D5 because cohort analytics and pose-engine integration require comparable vocabulary across clinics.
  2. CDN-agnostic — Video storage abstracted behind video_url + video_provider. No vendor lock-in. (See composition.md for the current compositional model that supersedes single-video-per-exercise.)
  3. Exercises are content, not prescription — No sets/reps/duration on exercises. That's program / session config.
  4. Mutable for fields, deprecate-and-replace for vocabulary — Exercise rows update in place; tag entities follow the "never modify in place" discipline per D4. Pose configs are mutable but history-snapshotted for Class IIa scoring reproducibility.
  5. Soft delete for safety — Referenced exercises can't be hard-deleted (RESTRICT FK + soft delete).
  6. Multi-tenant by design — All tables have org-scoped RLS. Global exercises get a special organization_id IS NULL SELECT policy.
  7. Class IIa-ready provenance from day one — Tag-association and pose-config sub-rows carry tagged_by_principal_id, tagged_at, and clinical_basis per D3. The cost asymmetry (cheap pre-prod, catastrophic post-prod) makes day-one shipping the right call even under Class I posture.
  8. Pose-tracking config is structurally relational, not JSONB — Per D6. Enables per-row Class IIa provenance, FK validation, indexed queries, and an authorable Console UI.