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_kindenum (platform|org), partial unique indexes for slug, RLS visibility union — all added in Phase 1exercise_rendersgets a newcontent_file_idFK to thecontent_filesregistry — every render row references a file-layer row; backfill is deterministic- Terminology shift: this doc says "global / org / custom"; the new spec uses
ownership_kindenum valuesplatform | org | patient_specific. "Custom plans" (per-patient) was a treatment-plan concept that now applies tosessions/programs, not toexercises(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
- Browse library: Admin searches for "shoulder" → sees 50+ shoulder exercises
- Create treatment plan: Builds 8-week program by adding library exercises
- Assign to patient: Patient gets treatment plan → telerehab app shows video demos
- Patient does exercises: Logs sets/reps → system tracks adherence
- Custom exercises (optional): Clone "Wall Slide" → customize instructions for your clinic's technique
- 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.
| Table | Purpose |
|---|---|
exercises | Main entity — video, metadata, taxonomy links. Global (organization_id IS NULL) or org-scoped. |
exercise_categories | Categories (stretching, strengthening, balance). Hierarchical via parent_id. Dual-scope. |
exercise_body_regions | Body part tags (shoulder, knee, lumbar spine). Grouped by body_area. Platform-only per D5. |
exercise_movement_patterns | Biomechanical pattern (push / pull / squat / hinge / rotation / lunge / carry / gait / hold). Platform-only per D5. |
exercise_recovery_phases | Rehab phase (acute / subacute / strength / return_to_activity / maintenance). Platform-only per D5. |
exercise_conditions | Diagnosis lookup (Lombalgie, Cervicalgie, Post-ACL, …) with optional ICD-10 mapping. Dual-scope per D5 / B5. |
exercise_skill_prerequisites | Patient-capability gates (balance_static, single_leg_stance, grip_strength, …). Platform-only per D5. |
exercise_equipment | Equipment catalog (resistance band, yoga mat, dumbbell). Dual-scope. |
exercise_tags | Polymorphic M:M junction — links exercises to every tag axis above via tag_type ENUM. |
exercise_prerequisites | Self-M:M between exercises ("Bird Dog before Side Plank") for program-builder ordering. |
exercise_instructions | Ordered text/image instruction steps per exercise. |
exercise_contraindications | Clinical warnings/restrictions per exercise. Links to exercise_conditions via condition_id FK (replaces the old freetext condition_name, per B5). |
pose_engines | Reference table — pose-engine vendor catalog (mediapipe.holistic at F9.1 Phase 2; future engines slot in here). |
pose_landmarks | Reference table — per-engine landmark catalog (~543 rows for MediaPipe holistic). |
exercise_pose_configs | Per-exercise pose-tracking config (1:1 with exercises per D8). Camera setup, engine, rep-success rule, asset-version pin, status. |
exercise_pose_config_history | Immutable prior snapshots of pose configs for Class IIa scoring reproducibility. |
exercise_pose_landmarks | M2M between a pose config and the landmark subset it tracks. |
exercise_pose_metrics | Typed metric definitions (angle / rom / symmetry / distance / velocity / position / hold_time) with targets, tolerances, weights, source landmark refs. |
exercise_pose_feedback_rules | Severity-tiered triggers (`warning |
pose_data_quality_overrides | Specialist 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.
| Axis | Question it answers | Scope |
|---|---|---|
body_region | Where on the body? | Platform-only (locked) |
movement_pattern | How does the body move? | Platform-only (locked) |
recovery_phase | When in recovery is this appropriate? | Platform-only (locked) |
conditions | What diagnosis is this for? | Dual-scope (platform + org) |
skill_prerequisites | Can the patient safely do it? | Platform-only (locked) |
equipment | What does the patient need at home? | Dual-scope (platform + org) |
categories | Organisational 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 vocabularymovement_patterns— pose-engine rep-counting heuristics map directly to these values; redefinition breaks engine integrationrecovery_phase— small clinical-standard enum; redefinition undermines program-builder reasoningskill_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 & RelaxationBody 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 BodyMovement Patterns
push | pull | squat | hinge | rotation | lunge | carry | gait | holdAn 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 | maintenanceExercises 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_enduranceThe 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
TheraBandDual-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=25Video 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):
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 durationWhy 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 physicianSeverity levels:
warning— exercise can be performed with modifications or cautioncontraindicated— 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 setup —
camera_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 threshold —
min_landmark_confidence NUMERIC(default 0.5) per B2 - Asset version pin — see § Asset re-filming invalidation below
- Status —
draft | published | invalidated - Class IIa provenance —
tagged_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:
| Type | Measures | Example exercise |
|---|---|---|
angle | Angle between three landmarks (joint flexion) | Knee flexion in squat |
rom | Range of motion (max − min over rep) | Cervical rotation ROM |
symmetry | Difference between two sibling metrics (L vs R) | L–R rotation symmetry |
distance | Distance between two landmarks | Hand-to-knee touch |
velocity | Rate of change of position/angle | Jumping, ballistic |
position | Absolute position relative to reference (axis-aligned) | Hip drop in plank |
hold_time | Duration above/below threshold | Static 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 type | What counts as a rep | params JSONB shape |
|---|---|---|
angle_cycle | Patient reaches target angle, returns to start | {start_angle, target_angle, return_angle, tolerance, side?} |
position_cycle | Body part reaches target position, returns | {landmark_id, target_xyz, return_xyz, tolerance} |
velocity_peak | Peak velocity detected on dominant axis | {landmark_id, axis, peak_threshold, min_amplitude} |
composite | Multiple 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:
| Event | Pose config |
|---|---|
Asset unchanged (same asset_version) | Reused as-is, no re-validation |
| Asset re-encoded without re-filming | Should 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 threshold | New 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:
POST /v1/exercises/{id}/cloneWhat 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_idset to current orgcloned_from_idset to source exercise IDstatusreset todraft- 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.idFK intreatment_plan_session_exercisesusesON DELETE RESTRICTas 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_exerciseswith 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) toPOST /v1/media/eventson the Telemetry API. Server-side aggregation writesmedia_session_metrics+media_buffering_events(Postgres, monthly partitioned). See ../../telemetry/media-events.md for the event spec. Consent:analyticsper-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 producespose_session_metrics+pose_rep_metrics(Postgres) + a replay blob in S3 ats3://restartix-telemetry/{org_id}/{session_id}.bin.gz. Consent:biometricper-purpose flag. - Errors: off-the-shelf (Sentry-equivalent) when needed; not part of telemetry. The earlier
POST /v1/errors/reportendpoint has been removed.
With S3/CDN
- Exercise videos:
{org_id}/exercises/{exercise_id}/video/{filename}(org) orglobal/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 filtersPOST /v1/exercises— Create exercise (admin: org, superadmin: global)GET /v1/exercises/{id}— Full details with instructions, tags, contraindications, pose configPOST /v1/exercises/{id}/clone— Clone to org library (carries pose config)POST /v1/exercises/{id}/video— Upload videoGET/POST/PATCH /v1/admin/exercises/{id}/pose-config— Pose-config authoringPOST /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
- 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 NULLvsIS NOT NULLpattern. Four taxonomy axes lock to platform-only viaCHECK (organization_id IS NULL)per D5 because cohort analytics and pose-engine integration require comparable vocabulary across clinics. - 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.) - Exercises are content, not prescription — No sets/reps/duration on
exercises. That's program / session config. - 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.
- Soft delete for safety — Referenced exercises can't be hard-deleted (RESTRICT FK + soft delete).
- Multi-tenant by design — All tables have org-scoped RLS. Global exercises get a special
organization_id IS NULLSELECT policy. - Class IIa-ready provenance from day one — Tag-association and pose-config sub-rows carry
tagged_by_principal_id,tagged_at, andclinical_basisper D3. The cost asymmetry (cheap pre-prod, catastrophic post-prod) makes day-one shipping the right call even under Class I posture. - 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.