Skip to content

Tracking & Analytics

Overview

Treatment plan tracking operates across two systems:

  • Core API (PostgreSQL) — Structured clinical data: session completions, exercise logs, pain levels, form responses
  • Telemetry Service (ClickHouse) — High-frequency analytics: video watch events, pose tracking data, bandwidth monitoring

Data Flow

┌─────────────────────────────────────────────────────┐
│                    PATIENT DEVICE                    │
│                                                      │
│  ┌──────────────┐  ┌──────────────┐  ┌───────────┐ │
│  │ Exercise Log  │  │ Video Events │  │ Pose Data │ │
│  │ (clinical)   │  │ (analytics)  │  │ (realtime)│ │
│  └──────┬───────┘  └──────┬───────┘  └─────┬─────┘ │
└─────────┼─────────────────┼─────────────────┼───────┘
          │                 │                 │
          ▼                 ▼                 ▼
    ┌───────────┐    ┌────────────┐    ┌────────────┐
    │  Core API │    │  Telemetry │    │  Telemetry │
    │ (Postgres)│    │ (ClickHouse)│    │ (ClickHouse)│
    └───────────┘    └────────────┘    └────────────┘
          │                 │                 │
          │   Clinical      │   Analytics     │   Realtime
          │   Reports       │   Dashboards    │   Feedback
          ▼                 ▼                 ▼
    ┌───────────────────────────────────────────────┐
    │              SPECIALIST DASHBOARD              │
    │  Pain trends, adherence, exercise performance  │
    └───────────────────────────────────────────────┘

Core API Tracking (Clinical Data)

What's Stored

TableDataPurpose
patient_treatment_planssessions_completed, sessions_skipped, statusPlan-level progress
patient_session_completionsstarted_at, completed_at, pain levels, difficultySession-level outcomes
patient_exercise_logsprescribed vs actual (sets/reps), video watch %, pose accuracyExercise-level performance
forms (instances)Post-session questionnaire responsesDetailed patient-reported outcomes

Key Queries

Patient progress over time:

sql
SELECT
    psc.session_number,
    psc.completed_at,
    psc.pain_level_before,
    psc.pain_level_after,
    psc.perceived_difficulty,
    psc.exercises_completed,
    psc.exercises_total,
    psc.duration_seconds
FROM patient_session_completions psc
WHERE psc.patient_treatment_plan_id = 42
  AND psc.status = 'completed'
ORDER BY psc.completed_at;

Exercise performance comparison:

sql
SELECT
    e.name AS exercise_name,
    pel.prescribed_sets,
    pel.prescribed_reps,
    pel.actual_sets,
    pel.actual_reps,
    pel.video_watch_percentage,
    pel.pose_accuracy_score,
    pel.skipped
FROM patient_exercise_logs pel
JOIN exercises e ON e.id = pel.exercise_id
WHERE pel.session_completion_id = 789
ORDER BY pel.sort_order;

Adherence rate per week:

sql
SELECT
    EXTRACT(WEEK FROM psc.started_at) AS week_number,
    COUNT(*) FILTER (WHERE psc.status = 'completed') AS completed,
    COUNT(*) FILTER (WHERE psc.status = 'skipped') AS skipped,
    ptp.frequency_per_week AS expected
FROM patient_session_completions psc
JOIN patient_treatment_plans ptp ON ptp.id = psc.patient_treatment_plan_id
WHERE psc.patient_treatment_plan_id = 42
GROUP BY week_number, ptp.frequency_per_week
ORDER BY week_number;

Telemetry Service Tracking (Analytics Data)

The frontend sends high-frequency data directly to the Telemetry API. Three separate endpoints handle different data types — each with its own ClickHouse table and consent requirements.

For the full Telemetry API specification, see ../../telemetry/api.md.

Video Watch Events — POST /v1/media/events

When a patient watches an exercise video during a treatment plan session, the frontend sends the standard media event lifecycle. See ../../telemetry/media-events.md for the full event specification.

Event flow per exercise video:

Patient starts exercise video
  → session_start (TTFB, load time, connection info)
  → heartbeat every 10s (position, buffering, bitrate, quality, dropped frames)
  → buffering_start/buffering_end (per-stall detail)
  → quality_change (ABR bitrate/resolution switches)
  → milestone (25%, 50%, 75%, 95% watched)
  → session_end (final stats, completion status)

Example — session_start:

json
POST /v1/media/events
{
  "event": "session_start",
  "session_id": "550e8400-e29b-41d4-a716-446655440000",
  "media_id": "hashed-exercise-id",
  "media_type": "video",
  "timestamp": "2026-02-16T09:03:00Z",
  "data": {
    "total_duration_seconds": 42.0,
    "ttfb_ms": 340,
    "video_load_time_ms": 1200,
    "cdn_response_time_ms": 280,
    "connection_type": "wifi",
    "effective_bandwidth": 12.5,
    "rtt_ms": 45
  }
}

Example — heartbeat (every 10s during playback):

json
{
  "event": "heartbeat",
  "session_id": "550e8400-...",
  "timestamp": "2026-02-16T09:03:30Z",
  "data": {
    "position_seconds": 29.5,
    "watched_duration_seconds": 29.5,
    "completion_percent": 70.2,
    "buffering_count": 0,
    "buffering_duration_ms": 0,
    "current_bitrate": 2500000,
    "current_resolution": "720p",
    "dropped_frames": 2,
    "total_frames": 750
  }
}

Stored in ClickHouse (media_sessions + media_buffering_events):

  • Average video watch percentage per exercise
  • Video completion rates and milestone reach
  • Quality distribution and ABR switch patterns
  • Buffering frequency and duration (bandwidth issues)

Consent requirement: Level 2+ (analytics consent — media session data). For error-only troubleshooting at Level 1, use POST /v1/errors/report.

Pose Tracking — POST /v1/pose/frames

Pose tracking uses a dedicated endpoint (NOT /v1/media/events). The frontend buffers MediaPipe landmark frames and sends them in batches every 1-2 seconds.

json
POST /v1/pose/frames
{
  "session_id": "550e8400-...",
  "media_id": "hashed-exercise-id",
  "frames": [
    {
      "frame_number": 450,
      "elapsed_ms": 15000,
      "landmark_x": [0.52, 0.51, 0.50],
      "landmark_y": [0.32, 0.35, 0.38],
      "landmark_z": [-0.1, -0.09, -0.08],
      "landmark_visibility": [0.99, 0.98, 0.95],
      "pose_confidence": 0.96,
      "landmarks_detected": 33,
      "rep_count": 5,
      "form_score": 0.85,
      "rom_degrees": 42.5,
      "active_exercise_phase": "concentric",
      "camera_resolution": "640x480",
      "processing_time_ms": 12
    }
  ]
}

Stored in ClickHouse (pose_tracking_frames):

  • Pose accuracy trends over time (form_score, rom_degrees)
  • Rep counting accuracy and exercise phase detection
  • Most common form issues per exercise
  • Device capability distribution (camera resolution, processing time)

Consent requirement: Level 2+ (analytics consent — pose data is considered analytics, not essential). Additionally requires biometric consent from the patient (see Privacy & Compliance below).

Session Lifecycle Analytics — POST /v1/analytics/track

Treatment plan session starts and completions are tracked as analytics events (separate from media events and clinical data in Core API PostgreSQL).

Session started:

json
POST /v1/analytics/track
{
  "event_name": "treatment_plan.session_started",
  "event_category": "treatment_plan",
  "timestamp": "2026-02-16T09:00:00Z",
  "resource_type": "patient_session_completion",
  "resource_id": "789",
  "properties": {
    "patient_treatment_plan_id": 42,
    "session_number": 6,
    "exercises_total": 4
  }
}

Session completed:

json
POST /v1/analytics/track
{
  "event_name": "treatment_plan.session_completed",
  "event_category": "treatment_plan",
  "timestamp": "2026-02-16T09:30:00Z",
  "resource_type": "patient_session_completion",
  "resource_id": "789",
  "properties": {
    "patient_treatment_plan_id": 42,
    "session_number": 6,
    "exercises_completed": 4,
    "exercises_skipped": 0,
    "duration_seconds": 1800
  }
}

Stored in ClickHouse (analytics_events):

  • Session completion rates and adherence patterns
  • Average session duration by plan type
  • Time-of-day and day-of-week usage patterns

Consent requirement: Level 2+ (analytics consent)

Bandwidth Monitoring

Specialists and admins can query aggregated video performance data:

GET /v1/media/bandwidth/stats?period=7d&group_by=country_code

This enables:

  • Identifying patients in regions with poor connectivity
  • Proactive quality-of-service monitoring
  • Switching to lower quality streams for high-buffering regions

Analytics Dashboards

Patient Dashboard (Patient View)

Available via GET /v1/patient-treatment-plans/{id}/progress:

┌────────────────────────────────────────────────┐
│  My Progress                                   │
│                                                │
│  Sessions: 6/12 (50%)                          │
│  ████████████░░░░░░░░░░░░                      │
│                                                │
│  Pain Trend:                                   │
│  8 ┤                                           │
│  6 ┤  ●──●                                     │
│  4 ┤      ╲──●──●                              │
│  2 ┤            ╲──●──●                        │
│  0 ┤──────────────────────                     │
│    S1  S2  S3  S4  S5  S6                      │
│                                                │
│  This Week: 2/3 sessions done                  │
│  Streak: 3 days 🔥                             │
└────────────────────────────────────────────────┘

Specialist Dashboard (Clinical View)

Available via GET /v1/patient-treatment-plans/{id}/analytics:

┌────────────────────────────────────────────────┐
│  Patient: Jane Doe                             │
│  Plan: Post-ACL Rehab Weeks 1-4               │
│                                                │
│  Adherence: 85% (6/7 expected sessions)        │
│  Avg Session Duration: 28 min                  │
│  Avg Video Engagement: 88%                     │
│  Avg Pose Accuracy: 82%                        │
│                                                │
│  Exercise Performance:                         │
│  ┌────────────────────┬─────┬──────┬──────┐   │
│  │ Exercise           │ Done│ Skip │ Score│   │
│  ├────────────────────┼─────┼──────┼──────┤   │
│  │ Quad Set           │ 6/6 │ 0    │ 92%  │   │
│  │ Heel Slides        │ 6/6 │ 0    │ 88%  │   │
│  │ Straight Leg Raise │ 5/6 │ 1    │ 78%  │   │
│  │ Prone Hang         │ 6/6 │ 0    │ 85%  │   │
│  └────────────────────┴─────┴──────┴──────┘   │
│                                                │
│  Pain Evolution:                               │
│  Before: 7 → 6 → 6 → 5 → 5 → 4 (↓ 43%)     │
│  After:  5 → 4 → 4 → 3 → 3 → 3 (↓ 40%)     │
│                                                │
│  Weekly Adherence:                             │
│  Week 1: ███████████████ 100% (3/3)           │
│  Week 2: ██████████░░░░░  67% (2/3)           │
└────────────────────────────────────────────────┘

Organization Dashboard

Organization-level aggregates (for the stats described in needs-on-day-1.md):

  • Pain reduction: Average pain improvement across all completed plans
  • Adherence rates: Session completion rates across all active plans
  • Top exercises: Most prescribed, best/worst performance
  • Revenue per plan: Treatment plan → service plan billing correlation
  • Plan completion rates: How many plans reach "completed" vs "expired" or "cancelled"

Data Retention

Data TypeStorageRetention
Patient session completionsCore API (PostgreSQL)7 years (HIPAA)
Patient exercise logsCore API (PostgreSQL)7 years (HIPAA)
Post-session form responsesCore API (PostgreSQL)7 years (HIPAA)
Video watch eventsTelemetry (ClickHouse)2 years (TTL)
Pose tracking eventsTelemetry (ClickHouse)6 months (TTL)
Bandwidth samplesTelemetry (ClickHouse)90 days (TTL)

Privacy & Compliance

HIPAA

  • All clinical data (pain levels, exercise performance) stored in HIPAA-compliant PostgreSQL
  • Audit trail via Core API audit_log (every session start/complete/skip is logged)
  • Patient exercise logs are append-mostly (corrections update, not delete)

GDPR

  • Patient can request export of all treatment plan data (standard GDPR export flow)
  • Patient can request deletion (soft delete, then hard delete after retention period)
  • Telemetry analytics use hashed actor IDs (no PII in ClickHouse)
  • Pose tracking requires explicit biometric consent from the patient
  • Consent can be triggered via automation: treatment_plan.assignedrequire_form (biometric consent form)
  • No pose data captured until consent is given
  • Consent revocable at any time (disables camera tracking)