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
| Table | Data | Purpose |
|---|---|---|
patient_treatment_plans | sessions_completed, sessions_skipped, status | Plan-level progress |
patient_session_completions | started_at, completed_at, pain levels, difficulty | Session-level outcomes |
patient_exercise_logs | prescribed vs actual (sets/reps), video watch %, pose accuracy | Exercise-level performance |
forms (instances) | Post-session questionnaire responses | Detailed patient-reported outcomes |
Key Queries
Patient progress over time:
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:
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:
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:
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):
{
"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.
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:
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:
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_codeThis 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 Type | Storage | Retention |
|---|---|---|
| Patient session completions | Core API (PostgreSQL) | 7 years (HIPAA) |
| Patient exercise logs | Core API (PostgreSQL) | 7 years (HIPAA) |
| Post-session form responses | Core API (PostgreSQL) | 7 years (HIPAA) |
| Video watch events | Telemetry (ClickHouse) | 2 years (TTL) |
| Pose tracking events | Telemetry (ClickHouse) | 6 months (TTL) |
| Bandwidth samples | Telemetry (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)
Biometric Consent
- Pose tracking requires explicit biometric consent from the patient
- Consent can be triggered via automation:
treatment_plan.assigned→require_form(biometric consent form) - No pose data captured until consent is given
- Consent revocable at any time (disables camera tracking)