# Data Points

PostMessage events sent from KinesteX SDK for integration with native apps and external systems. Events are sent in real time, work safely offline, and provide comprehensive tracking for workouts, exercises, and health assessments.

## Receiving Data

Each platform has a specific pattern for receiving data from KinesteX.

**SDK Platforms (Swift, Kotlin, React Native, React-TS, Flutter):** Use the callback function provided by the SDK with typed message enums.

**HTML/JS:** Set up a message event listener manually since there's no SDK wrapper.

**Platform-Specific Data Handlers**

_Flutter_
```dart
void handleWebViewMessage(WebViewMessage message) {
  if (message is KinestexLaunched) {
    print("KinesteX launched at: ${message}");
  } else if (message is ExitKinestex) {
    print("Exited KinesteX at: ${message} seconds.");
  } else if (message is PlanUnlocked) {
    print("Plan Unlocked: ${message}");
  } else if (message is WorkoutOpened) {
    print("Workout Opened: ${message}");
  } else if (message is WorkoutStarted) {
    print("Workout Started: ${message}");
  } else if (message is ExerciseCompleted) {
    print("Exercise: ${message}");
  } else if (message.data['type'] == 'total_active_seconds') {
    // No dedicated class — arrives as a generic message
    print("Active seconds: ${message.data}");
  } else if (message is LeftCameraFrame) {
    print("User left camera frame at time: ${message}");
  } else if (message is ReturnedCameraFrame) {
    print("User returned to camera frame at time: ${message}");
  } else if (message is WorkoutOverview) {
    print("Workout Overview: ${message}");
  } else if (message is ExerciseOverview) {
    print("Exercise Overview: ${message}");
  } else if (message is WorkoutCompleted) {
    print("Workout Completed: ${message}");
  } else {
    print("Other data points: ${message.data}");
  }
}
```

_React Native_
```jsx
const handleMessage = (type: string, data: { [key: string]: any }) => {
  switch (type) {
    case "kinestex_launched":
      console.log('Launched at:', data);
      break;
    case "exit_kinestex":
      console.log('Exited, time spent:', data.time_spent);
      break;
    case "workout_overview":
      console.log('Workout stats:', data);
      break;
    case "exercise_completed":
      console.log('Exercise done:', data.exercise_title);
      break;
    case "workout_completed":
      console.log('Workout finished:', data);
      break;
    case "error_occurred":
      console.error('Error:', data);
      break;
    default:
      console.log('Other message type:', type, data);
      break;
  }
};
```

_React (TypeScript)_
```tsx
const handleMessage = (type: string, data: { [key: string]: any }) => {
  switch (type) {
    case "kinestex_launched":
      console.log('Launched at:', data);
      break;
    case "exit_kinestex":
      console.log('Exited, time spent:', data.time_spent);
      break;
    case "workout_overview":
      console.log('Workout stats:', data);
      break;
    case "exercise_completed":
      console.log('Exercise done:', data.exercise_title);
      break;
    case "workout_completed":
      console.log('Workout finished:', data);
      break;
    case "error_occurred":
      console.error('Error:', data);
      break;
    default:
      console.log('Other message type:', type, data);
      break;
  }
};
```

_Kotlin (Android)_
```kotlin
private fun handleWebViewMessage(message: WebViewMessage) {
    when (message) {
        is WebViewMessage.KinestexLaunched -> {
            println("KinesteX launched at: $message")
        }
        is WebViewMessage.FinishedWorkout -> {
            println("Finished Workout: $message")
        }
        is WebViewMessage.ErrorOccurred -> {
            println("Error Occurred: $message")
        }
        is WebViewMessage.ExerciseCompleted -> {
            println("Exercise Completed: $message")
        }
        is WebViewMessage.ExitKinestex -> {
            println("Exited KinesteX at: $message")
        }
        is WebViewMessage.WorkoutOpened -> {
            println("Workout Opened: $message")
        }
        is WebViewMessage.WorkoutStarted -> {
            println("Workout Started: $message")
        }
        is WebViewMessage.PlanUnlocked -> {
            println("Plan Unlocked: $message")
        }
        is WebViewMessage.WorkoutOverview -> {
            println("Workout Overview: $message")
        }
        is WebViewMessage.ExerciseOverview -> {
            println("Exercise Overview: $message")
        }
        is WebViewMessage.WorkoutCompleted -> {
            println("Workout Completed: $message")
        }
        // Camera Component Specific
        is WebViewMessage.Reps -> {
            println("Reps: $message")
        }
        is WebViewMessage.Mistake -> {
            println("Mistake: $message")
        }
        is WebViewMessage.CustomType -> {
            println("Any other message: $message")
        }
    }
}
```

_Swift (iOS)_
```swift
// onMessageReceived callback passes WebViewMessage enum
// Available message types:

// kinestex_launched([String: Any]) - KinesteX View launched
// finished_workout([String: Any]) - Workout completed
// error_occurred([String: Any]) - Errors (e.g., missing camera)
// exercise_completed([String: Any]) - Exercise finished
// exit_kinestex([String: Any]) - User exits KinesteX view
// workout_opened([String: Any]) - Workout description viewed
// workout_started([String: Any]) - Workout begins
// plan_unlocked([String: Any]) - Workout plan unlocked
// custom_type([String: Any]) - Unrecognized messages
// reps([String: Any]) - Successful repetitions
// mistake([String: Any]) - Detected mistakes
// left_camera_frame([String: Any]) - User left camera frame
// returned_camera_frame([String: Any]) - User returned to frame
// workout_overview([String: Any]) - Workout summary
// exercise_overview([String: Any]) - Exercise summary
// workout_completed([String: Any]) - Workout done, overview exited
```

_HTML / JavaScript_
```html
// HTML/JS requires manual event listener setup
window.addEventListener("message", (event) => {
  // Security: only accept messages from KinesteX
  if (event.origin !== "https://ai.kinestex.com") return;

  try {
    const message = JSON.parse(event.data);

    switch (message.type) {
      case "kinestex_launched":
        console.log("Launched:", message.data);
        break;
      case "exit_kinestex":
        console.log("Exited, time spent:", message.time_spent);
        break;
      case "workout_overview":
        console.log("Workout stats:", message.data);
        break;
      case "exercise_completed":
        console.log("Exercise done:", message.data);
        break;
      case "error_occurred":
        console.error("Error:", message.data || message.message);
        break;
      default:
        console.log("Message:", message.type, message);
    }
  } catch (e) {
    console.error("Failed to parse message:", e);
  }
});
```

## Application Lifecycle

Events for app startup, loading, and exit.

| Event | Data Fields | Description |
|-------|-------------|-------------|
| kinestex_launched | data: string ("dd mm yyyy hh:mm:ss") | KinesteX application is launched |
| kinestex_loaded | date: string (ISO format) | KinesteX fully loaded and ready |
| exit_kinestex | date: Date, time_spent: string ("hh:mm:ss") | User exits with total time spent |
| main_page_opened | date: string (ISO format) | Main/home page is opened |
| home_page_opened | date: string (ISO format) | Home Page integration screen opened. Fires exactly once per mount |
| streak_extended | data: object | User's daily streak was extended by completing a qualifying activity (workout, challenge, plan day, assessment) |

**streak_extended Data Structure:**
```
{
  current_streak: number,    // Current streak count (days)
  longest_streak: number,    // User's longest streak ever
  last_activity_date: string // ISO date of the activity that extended the streak
}
```

## Workout Events

Events for workout lifecycle and statistics.

| Event | Data Fields | Description |
|-------|-------------|-------------|
| workout_opened | title: string, id: string, date: string | Workout details page opened |
| workout_started | id: string, date: string | Workout session started |
| workout_started (alt) | workoutId: string | Alternative workout start format |
| workout_completed | workout: string, date: string | Workout finished, user exits overview |
| workout_ended | id: string, exit_type: string, date: string | Workout session ended (see exit_type values below) |
| workout_overview | data: object | Complete workout summary statistics |

**workout_ended exit_type values:**

| Value | Meaning |
|-------|---------|
| complete | User finished the entire workout including outro |
| exit | User abandoned the workout mid-session |
| outro | User exited from the outro/cooldown screen after completing all exercises |

**workout_overview Data Structure:**
```
{
  workout_title: string,            // Workout name
  workout_id: string,               // Unique workout ID
  target_duration_seconds: number,  // Target workout duration (seconds)
  workout_duration_seconds: number, // Total wall-clock session time
                                    // (includes rest, transitions, pauses).
                                    // For challenges/assessments this equals
                                    // total_time_spent (no wall-clock concept)
  total_time_spent: number,         // Active exercise time only (seconds)
  completed_reps_count: number,     // Total completed reps
  target_reps_count: number,        // Total target reps
  calories_burned: number,          // Calories (2 decimal places)
  completion_percentage: number,    // Completion % (2 decimals)
  total_mistakes: number,           // Total mistake count
  accuracy_score: number,           // Overall accuracy (0-100)
  efficiency_score: number,         // Efficiency metric (0-100)
  total_exercise: number,           // Number of exercises
  actual_hold_time_seconds: number, // Time in correct position
  target_hold_time_seconds: number  // Target hold time
}
```

**Note:** Use `workout_duration_seconds` to display or log the full session time (including rest periods). Use `total_time_spent` if you only need active exercise time.

**Handling Workout Overview**

_Swift (iOS)_
```swift
case .workout_overview(let data):
    if let calories = data["calories_burned"] as? Double,
       let completion = data["completion_percentage"] as? Double {
        print("Burned \(calories) cal, \(completion)% complete")
    }
```

_Kotlin (Android)_
```kotlin
is WebViewMessage.WorkoutOverview -> {
    // Payload fields arrive in message.data (Map<String, Any>)
    val calories = message.data["calories_burned"]
    val completion = message.data["completion_percentage"]
    Log.d("Workout", "Burned $calories cal, $completion% complete")
}
```

_React Native_
```jsx
case "workout_overview":
  const { calories_burned, completion_percentage, accuracy_score } = data;
  console.log(`Workout: ${completion_percentage}% complete`);
  console.log(`Calories: ${calories_burned}, Accuracy: ${accuracy_score}`);
  break;
```

_Flutter_
```dart
if (message is WorkoutOverview) {
  // Payload fields arrive in message.data (Map<String, dynamic>)
  print("Calories: ${message.data['calories_burned']}");
  print("Completion: ${message.data['completion_percentage']}%");
  print("Accuracy: ${message.data['accuracy_score']}");
}
```

_HTML / JavaScript_
```html
case "workout_overview":
  const stats = message.data;
  console.log("Workout:", stats.workout_title);
  console.log("Calories:", stats.calories_burned);
  console.log("Accuracy:", stats.accuracy_score);
  break;
```

_React (TypeScript)_
```tsx
case "workout_overview":
  const { calories_burned, completion_percentage, accuracy_score } = data;
  console.log(`Workout: ${completion_percentage}% complete`);
  console.log(`Calories: ${calories_burned}, Accuracy: ${accuracy_score}`);
  break;
```

## Exercise Events

Events for individual exercise tracking.

| Event | Description |
|-------|-------------|
| exercise_completed | Individual exercise completed |
| exercise_overview | All exercises summary (array) |

**exercise_completed Data Structure:**
```
{
  exercise_title: string,        // Exercise name
  time_spent: number,            // Seconds spent
  repeats: number,               // Reps completed
  total_reps: number,            // Required reps
  total_duration: number,        // Countdown time
  perfect_hold_position: number, // Time in perfect hold position (seconds).
                                 // 0 for non-hold exercises
  calories: number,              // Calories burned
  exercise_id: string,           // Exercise ID
  exercise_index: number,        // 1-based position of the completed exercise
  total_exercises: number,       // Total number of exercises in the workout
  mistakes: Array<{              // Mistakes made
    mistake: string,
    count: number
  }>,
  average_accuracy?: number      // Average accuracy (0-1, optional)
}
```

**exercise_overview Item Structure:**
```
{
  exercise_title: string,        // Exercise name
  exercise_id: string,           // Unique exercise ID
  time_spent: number,            // Time on exercise (seconds)
  perfect_hold_position: number, // Time in correct position (timer-based)
  repeats: number,               // Reps completed
  total_required_reps: number,   // Target reps
  total_required_time: number,   // Target time (seconds)
  calories: number,              // Calories (2 decimal places)
  mistakes: Array<{              // Detailed mistake breakdown
    mistake: string,
    count: number
  }>,
  mistake_count: number,         // Total mistakes for exercise
  accuracy_reps?: number[],      // Per-rep accuracy scores (optional)
  average_accuracy?: number      // Average accuracy 0-100 (optional)
}
```

## Camera & Frame Events

Events for camera tracking and frame detection.

| Event | Data Fields | Description |
|-------|-------------|-------------|
| left_camera_frame | date: string ("dd mm yyyy hh:mm:ss") | User left camera view |
| returned_camera_frame | date: string ("dd mm yyyy hh:mm:ss") | User returned to camera view |
| check_frame_completed | message: string ("Person stepped into frame") | Frame check completed |
| camera_selector_opened | message: array (available cameras) | Camera selector opened |
| camera_selected | id: string, label: string, isMirrorCamera: boolean | Camera selected by user |

## Plans & Programs

Events for workout plans and programs.

| Event | Data Fields | Description |
|-------|-------------|-------------|
| plan_unlocked | id: string, img: string, title: string, date: string | Plan unlocked/selected |
| plan_opened | id: string | Plan result screen rendered. Fires for both goal-based and personalized plans |
| plan_onboarding_plan_created | data: { plan_id: string, plan_type: string } | A new plan was created from onboarding/assessment. Does NOT fire on revisits to an existing personalized plan |
| plan_progression_saved | data: { planType: string, weekNumber: number, dayNumber: number, source?: string } | Plan day progression was saved successfully after a plan workout finished. `planType` is `"goal-based"` or `"personalized"`. `source: "offline_outbox"` is present only when the save was recovered after a lost connection — live saves omit it |
| plan_progression_failed | data: object | Plan progression save failed (network/server error) |
| personalized_plan_exit | workout: string, date: string | Exit from personalized plan |
| remind_me_later_clicked | - | User tapped "Remind me later" on the Assessment screen inside the plan-onboarding flow. Only fires from the plan-onboarding page |

**Note on plan tracking:** When you launch a plan workout directly via the SDK, you can pass `planId`, `planType`, and `progressWorkoutId` in the initial PostMessage configuration so the SDK associates the workout session with the correct plan. After the workout finishes, you'll receive either `plan_progression_saved` (success) or `plan_progression_failed` (error). See [Plan Context configuration](/docs/customization-parameters/plan-context) for details.

**Offline save recovery:** if a save fails (e.g. the user loses connection on the results screen), the result is stored locally and automatically re-sent when the connection returns or the app is reopened. `plan_progression_saved` (and `workout_session_saved`) can therefore arrive **after a delay** instead of only at save time — make your handler idempotent and treat `source` as optional.

## Challenge Events

Events for challenge mode.

| Event | Data Fields | Description |
|-------|-------------|-------------|
| challenge_started | exerciseId: string | Challenge exercise started |
| challenge_completed | repCount: number, mistakes: number | Challenge completed |
| challenge_exit | workout: string, date: string | Exit from challenge |

## Leaderboard Events

Events for leaderboard functionality.

| Event | Data Fields | Description |
|-------|-------------|-------------|
| highlighted_user | data: object | User's leaderboard position |

**highlighted_user Data Structure:**
```
{
  username: string,  // User's username
  score: number,     // User's score
  position: number   // Leaderboard position (1-based)
}
```

## Navigation Events

Events for app navigation.

| Event | Data Fields | Description |
|-------|-------------|-------------|
| kinestex_home_exit | workout: string, date: string | Exit from KinesteX home |
| navigation_back | data: { exercise_index: number, total_exercises: number } | User navigated back to a previous exercise during a workout |

## Feedback Events

Events dispatched when a user submits feedback.

| Event | Description |
|-------|-------------|
| feedback_submitted | User submitted training rating or per-exercise feedback |

**Training Feedback Payload (source: "training_feedback"):**
```
{
  type: "feedback_submitted",
  source: "training_feedback",
  rating: number,              // User rating (e.g., 1-5)
  is_like: boolean,            // Whether the user liked the workout
  description: string,         // Optional text feedback
  workout_id: string,          // Workout ID
  workout_title: string        // Workout name
}
```

**Per-Exercise Feedback Payload (source: "exercise_feedback"):**
```
{
  type: "feedback_submitted",
  source: "exercise_feedback",
  workout_id: string,          // Workout ID
  workout_title: string,       // Workout name
  feedbacks: Array<{           // Per-exercise feedback entries
    exercise_id: string,       // Exercise ID
    exercise_title: string,    // Exercise name
    is_like: boolean,          // Whether the user liked the exercise
    description: string        // Optional text feedback
  }>
}
```

## AI Trainer Events

Events emitted by the [AI Trainer Chat](/docs/ai-trainer-chat) view. On Swift/Kotlin/Flutter they arrive through the generic `custom_type` / `CustomType` case with the parsed JSON as a dictionary/map.

| Event | Data Fields | Description |
|-------|-------------|-------------|
| trainer_schedule_next_workout | scheduledFor: string, sessionType: string | User picked a date/time for their next session. `scheduledFor` is `YYYY-MM-DDTHH:MM` device-local time; `sessionType` is `"workout"` or `"assessment"` (treat missing as `"workout"`) |
| open_subscription_flow | source: string, date: string | A non-subscribed user tried to generate a workout — present your subscription flow and post `subscription_result` back (`"purchased"` or `"dismissed"`) |
| trainer_assessment_started | date: string | User began the in-app fitness assessment |
| trainer_assessment_completed | date: string, results: object, fitnessLevel: string | Assessment finished — per-exercise reps and level (`results.squats` / `results.pushups`) plus an overall `fitnessLevel` (`"beginner"` / `"intermediate"` / `"advanced"`) |
| trainer_assessment_skipped | date: string, reason: string | User chose to set their fitness level manually (`reason: "set_manually"`) |
| trainer_profile_updated | source: string, profile: object | The user's fitness profile changed — carries the full current profile; `source` is `"onboarding"`, `"assessment"`, or `"chat"`. Persist it on your side |
| workout_exit_request | - | The user exited a workout that was launched from the trainer |

Full payload examples and the subscription handshake: [AI Trainer Chat guide](/docs/ai-trainer-chat) and the [subscription gating guide](/docs/guides/guide-subscription-gating).

## Session & Upload Events

Events related to workout session saving and motion recording uploads. These events are only dispatched when `shouldSendStats: true` is passed in the SDK configuration.

| Event | Data Fields | Description |
|-------|-------------|-------------|
| workout_session_saved | data: object | Workout session successfully saved to backend. May arrive after a delay when the save was recovered by the offline outbox (same payload) — see the offline note under [Plans & Programs](/docs/data-points/plans-programs) |
| session_save_complete | - | Motion recording uploads finished successfully |
| motion_upload_progress | data: { completed: number, total: number } | Motion recording upload progress |
| motion_upload_error | data: { error: string } | Motion recording upload failed or timed out |
| workout_completion_overlay_dismissed | - | User dismissed the workout completion celebration overlay |

**workout_session_saved Data Structure:**
```
{
  session_id: number,            // Backend-assigned session ID
  workout_title: string,         // Name of the workout
  accuracy_score: number,        // Overall accuracy (0-100)
  efficiency_score: number,      // Efficiency score (0-100)
  completion_percentage: number,  // How much of the workout was completed (0-100)
  completed_reps_count: number,  // Total reps completed
  calories_burned: number        // Estimated calories burned
}
```

**motion_upload_progress Data Structure:**
```
{
  completed: number,  // Number of exercise recordings uploaded so far
  total: number       // Total number of exercise recordings to upload
}
```

## Error & Status Events

Events for errors, warnings, and active time tracking.

| Event | Data Fields | Description |
|-------|-------------|-------------|
| error_occurred | data: string | General error message |
| error_occurred | message: string | Alternative error format |
| error_occurred | data: string, error: any | Error with details |
| warning | data: string | Warning message |
| total_active_seconds | number | Active workout time (sent every 5s, pauses when user leaves camera frame) |
| ios_video_fallback_activated | reason, videoUrl, readyState, userAgent | iOS dropped to image-based playback after the full recovery ladder (silent retry → tap-to-play prompt → image mode) failed. The exercise still runs in a degraded-but-functional mode |

**ios_video_fallback_activated Payload:**
```
{
  type: "ios_video_fallback_activated",
  reason: string,              // which recovery stage failed (see below)
  videoUrl: string,            // URL of the affected video
  readyState: number | null,   // HTMLMediaElement readyState; null when no video element was available
  userAgent: string            // Device user agent string
}
```

`reason` is one of:
- `"stuck_after_tap"` — user tapped the prompt but the video still had no media data
- `"tap_prompt_timeout"` — the tap-to-play prompt was ignored (~5s) and auto-advanced to image mode
- `"tap_play_element_missing"` — the video element was gone when the user tapped
- `"tap_play_rejected_<ErrorName>"` — the gesture-driven play was rejected, e.g. `"tap_play_rejected_NotAllowedError"`

(The former single value `"video_stuck_at_metadata"` is no longer emitted — prefer treating any occurrence of this event as "iOS entered image mode" and branch on the reason prefixes only if needed. Handle `readyState` as nullable.)

Use this event for analytics or to surface a notice in your UI when iOS playback degrades. Only listen for it if you need visibility into iOS playback issues — no integrator action is required.

## Assessment Exit Events

Events dispatched when a user exits an assessment before completing it.

| Event | Data Fields | Description |
|-------|-------------|-------------|
| assessment_exit | exerciseId: string | User exited the assessment early (before results) |
| assessment_exit_results | exerciseId: string | User exited the assessment from the results screen |

**assessment_exit Payload:**
```
{
  type: "assessment_exit",
  data: {
    exerciseId: string  // The assessment exercise identifier
  }
}
```

**assessment_exit_results Payload:**
```
{
  type: "assessment_exit_results",
  data: {
    exerciseId: string  // The assessment exercise identifier
  }
}
```

Use `assessment_exit` to detect when users abandon an assessment mid-session, and `assessment_exit_results` to detect when they leave after viewing their results.

## Assessment Overview

AI-powered health assessments with two event types:
- **assessment_overview**: Sent when results page loads
- **assessment_completed**: Sent when user clicks restart or finish

**Common Fields (All Assessments):**
| Field | Type | Description |
|-------|------|-------------|
| type | string | "assessment_overview" or "assessment_completed" |
| assessmentType | string | Assessment identifier (e.g., "tug", "sls") |
| date | Date | Timestamp when completed |
| time | number | Total assessment time (seconds) |
| steps | number? | Estimated step count (walking assessments) |

**Assessment Types:**
- **Mobility**: TUG (tug), Gait Speed Test (gaitspeedtest)
- **Balance**: SLS (sls), SBSS (sbss), STSS (stss), Full Tandem (fulltandem)
- **Functional**: STS (sts), Five Times STS (fivetimessts), FRT (frt)
- **Range of Motion**: Shoulder ROM (romshoulder)
- **Games**: Balloon Pop (balloonpop), Color Chase (colorchase), Alien Squat Shooter (aliensquatshooter)

**Risk Levels:**
| Value | Meaning |
|-------|---------|
| low | Good performance, minimal fall/balance risk |
| moderate | Some limitations, may benefit from training |
| high | Significant limitations, balance training recommended |

## Mobility Assessments

**TUG (Timed Up and Go) - assessmentType: "tug"**

Stand-walk-turn-return-sit timing test.

| Field | Type | Description |
|-------|------|-------------|
| time | number | Total completion time (seconds) |
| steps | number | Estimated step count |
| standingUpTime | number | Time to stand from seated (seconds) |
| sittingDownTime | number | Time to sit at end (seconds) |
| walkingForwardTime | number | Time walking to 3m marker (seconds) |
| walkingBackwardTime | number | Time walking back (seconds) |
| turningTime | number | Time spent turning (seconds) |
| backBendingAngleSitting | number[] | Back angles during sitting countdown (degrees) |
| backBendingAngleStanding | number[] | Back angles during movement (degrees) |
| averageSpeedMs_tug | number | Average walking speed. Formula: 6m / time |
| avgBackBendingSitting | number? | Average back angle sitting (degrees) |
| avgBackBendingStanding | number? | Average back angle standing (degrees) |

---

**Gait Speed Test - assessmentType: "gaitspeedtest"**

Walking speed measurement over 4 meters.

| Field | Type | Description |
|-------|------|-------------|
| time | number | Total test time (seconds) |
| steps | number | Estimated step count |
| standingTime | number | Time in standing phase (seconds) |
| walkingTime | number | Active walking time (seconds) |
| averageGaitSpeed | number | Gait speed. Formula: 4m / time |

## Balance Assessments

**SLS (Single Leg Stand) - assessmentType: "sls"**

| Field | Type | Description |
|-------|------|-------------|
| rightTime | number | Duration on right leg (seconds) |
| leftTime | number | Duration on left leg (seconds) |
| symmetryScore_sls | number? | Leg symmetry % (0-100). Formula: (min/max) * 100 |
| riskLevel_sls | string | "low", "moderate", or "high" |

**Risk Calculation:**
- Low: Min time >=20s AND symmetry good (difference <=5s)
- Moderate: Min time >=10s AND average >=15s
- High: All other cases

---

**SBSS (Side-by-Side Stand) - assessmentType: "sbss"**

| Field | Type | Description |
|-------|------|-------------|
| timeInProperPosition_sbss | number | Time in correct stance (max 10s) |
| maxShoulderShift_sbss | number | Max lateral shoulder shift (% of shoulder width) |
| maxHipShift_sbss | number | Max lateral hip shift (% of hip width) |
| feetMoved_sbss | boolean | Whether feet moved |
| riskLevel_sbss | string | "low", "moderate", or "high" |

**Risk Calculation:**
- High: Feet moved OR (time <7s AND sway >=30%)
- Moderate: Time >=7s AND sway <30%
- Low: Time >=9.5s AND sway <15%

---

**STSS (Semi-Tandem Stand) - assessmentType: "stss"**

| Field | Type | Description |
|-------|------|-------------|
| timeInProperPosition_stss | number | Time in correct stance (max 10s) |
| maxShoulderShift_stss | number | Max shoulder shift (% of width) |
| maxHipShift_stss | number | Max hip shift (% of projection) |
| feetMoved_stss | boolean | Whether feet moved |
| riskLevel_stss | string | "low", "moderate", or "high" |

Risk calculation same as SBSS.

---

**Full Tandem Stand - assessmentType: "fulltandem"**

| Field | Type | Description |
|-------|------|-------------|
| time | number | Total test time (seconds) |
| timeInProperPosition_fulltandem | number | Time in heel-to-toe stance (max 10s) |
| maxShoulderShift_fulltandem | number | Max shoulder shift (%) |
| maxHipShift_fulltandem | number | Max hip shift (%) |
| feetMoved_fulltandem | boolean | Whether feet moved |
| testFailed_fulltandem | boolean | Whether test was terminated early |
| terminationReason_fulltandem | string? | Reason for early termination |
| riskLevel_fulltandem | string | "low", "moderate", or "high" |

**Risk Calculation:**
- Low: Time >=10s AND no feet movement
- Moderate: Time >=5s
- High: Time <5s

## Functional Assessments

**STS (30-Second Sit-to-Stand) - assessmentType: "sts"**

| Field | Type | Description |
|-------|------|-------------|
| reps | number | Total reps completed in 30 seconds |
| averageSittingTime | number | Average time sitting per rep (seconds) |
| averageStandingTime | number | Average time standing per rep (seconds) |
| avgTimePerRep_sts | number? | Average seconds per rep. Formula: 30 / reps |
| repTimeVariance_minRepTime | number? | Fastest rep time (seconds) |
| repTimeVariance_maxRepTime | number? | Slowest rep time (seconds) |
| repTimeVariance_minRepIndex | number? | Which rep was fastest (1-indexed) |
| repTimeVariance_maxRepIndex | number? | Which rep was slowest (1-indexed) |

---

**Five Times STS - assessmentType: "fivetimessts"**

| Field | Type | Description |
|-------|------|-------------|
| time | number | Total completion time for 5 reps (seconds) |
| averageSittingTime | number | Average time sitting (seconds) |
| averageStandingTime | number | Average time standing (seconds) |

---

**FRT (Functional Reach Test) - assessmentType: "frt"**

| Field | Type | Description |
|-------|------|-------------|
| reach | number | Maximum forward reach (cm) |
| maxHeelLift | number | Maximum heel lift detected (cm) |
| heelLiftCount | number | Count of heel lift violations |
| legLiftCount | number | Count of leg lift violations |
| testCompleted | boolean | Whether test finished normally |
| endReason | string? | Reason for early termination |
| riskLevel_frt | string? | "low", "moderate", or "high" |

**End Reason Values:**
- "Feet moved out of zone"
- "User left zone"
- "User turned forward"
- "Arm dropped completely"
- "Reference lost"
- "Unhandled state"

**Risk Calculation:**
- High: Test not completed OR reach <=15cm
- Moderate: Reach 15-25cm
- Low: Reach >25cm

## Range of Motion Assessments

**Shoulder ROM - assessmentType: "romshoulder"**

A quick range-of-motion check for the shoulders. The user stands facing the camera and lifts one arm at a time straight out to the side (shoulder abduction), as high as comfortably possible. The system tracks the peak abduction angle reached by each arm, compares left and right sides, and flags meaningful asymmetry.

Useful for tracking recovery, spotting asymmetry, and observing mobility improvements session over session.

| Field | Type | Description |
|-------|------|-------------|
| romMovement | string | Type of ROM movement assessed. Primary value: "shoulder_abduction" |
| romMaxLeft | number | Maximum ROM achieved on the left side (degrees, rounded to 0 decimals, range 0-180+) |
| romMaxRight | number | Maximum ROM achieved on the right side (degrees, rounded to 0 decimals, range 0-180+) |
| romMinLeft | number | Minimum ROM recorded on the left side (degrees, rounded to 0 decimals, range 0-180+) |
| romMinRight | number | Minimum ROM recorded on the right side (degrees, rounded to 0 decimals, range 0-180+) |
| romSymmetryDelta | number | Absolute difference between max left and max right (degrees). Formula: abs(romMaxLeft - romMaxRight) |
| romSymmetryFlagDegrees | number | Asymmetry threshold (degrees). Defaults to 10 if not provided |
| romAsymmetric | boolean | true if romSymmetryDelta > romSymmetryFlagDegrees, else false |

**Notes:**
- All angle measurements are in degrees.
- Numeric values are rounded to 0 decimal places.
- `romAsymmetric` is computed by comparing `romSymmetryDelta` against `romSymmetryFlagDegrees`.

## Game Assessments

**Balloon Pop - assessmentType: "balloonpop"**

| Field | Type | Description |
|-------|------|-------------|
| gameScore | number | Total balloons popped |
| averageReactionTime | string | Average time to pop (seconds) |
| maxIdleTime | string | Max time without action (seconds) |
| averageSpeed_balloonpop | number | Balloons/second. Formula: gameScore / 30 |
| masteryTitle_balloonpop | string | Achievement tier |

**Mastery Titles:**
| Score | Title |
|-------|-------|
| >=30 | pop_tastic_hero |
| >=25 | magic_popper |
| >=20 | super_duper_popper |
| >=15 | sparkly_popper |
| >=10 | giggly_popper |
| >=5 | bouncy_bubbler |
| <5 | tiny_popper |

---

**Color Chase - assessmentType: "colorchase"**

| Field | Type | Description |
|-------|------|-------------|
| gameScore | number | Total score achieved |
| levelReached | number | Highest level completed |
| totalDuration | string | Total game duration (seconds) |
| averageReactionTime | string | Average reaction time per tap (seconds) |
| maxIdleTime | string | Max time between taps (seconds) |
| masteryTitle_colorchase | string | Achievement tier |

**Mastery Titles:**
| Level | Title |
|-------|-------|
| >=10 | color_chase_legend |
| >=8 | magic_color_wizard |
| >=6 | super_color_star |
| >=4 | shiny_sequencer |
| >=2 | rainbow_chaser |
| >=1 | color_buddy |
| <1 | little_color_finder |

---

**Alien Squat Shooter - assessmentType: "aliensquatshooter"**

| Field | Type | Description |
|-------|------|-------------|
| gameScore | number | Total aliens destroyed |
| squatsPerformed | number | Number of squats completed |
| totalDuration | string | Total game duration (seconds) |
| averageSquatRate | string | Squats per second |
| maxTimeBetweenSquats | string | Max idle between squats (seconds) |
| averageAlienDestroyTime | string | Average time to destroy alien (seconds) |
| masteryTitle_aliensquatshooter | string | Achievement tier |

**Mastery Titles:**
| Score | Title |
|-------|-------|
| >=25 | alien_annihilator |
| >=20 | cosmic_blaster |
| >=15 | star_shooter |
| >=10 | galactic_gunner |
| >=5 | space_squatter |
| <5 | rookie_defender |

---

**Health Benefits (All Games)**

Included in payload for all game types.

```
healthBenefits: {
  heartDiseaseReduction: number,  // Estimated % reduction (max 15)
  diabetesReduction: number,      // Estimated % reduction (max 20)
  obesityReduction: number,       // Estimated % reduction (max 10)
  depressionReduction: number     // Estimated % reduction (max 15)
}
```

**Health Benefit Calculation:**
`reduction = min((gameScore / 100) * maxReduction, maxReduction)`

## Event Flow Examples

**Complete Workout Flow:**
1. `kinestex_launched` - Application starts
2. `workout_opened` - User views workout details
3. `workout_started` - Workout begins
4. `returned_camera_frame` / `left_camera_frame` - Frame tracking
5. Multiple `exercise_completed` - Each exercise finished
6. `workout_overview` - Summary statistics
7. `exercise_overview` - All exercises summary
8. `workout_completed` - User exits statistics
9. `exit_kinestex` - Application closed

**Challenge Flow:**
1. `challenge_started` - Challenge begins
2. `exercise_completed` - Exercise finished
3. `challenge_completed` - Challenge complete
4. `challenge_exit` - Exit from challenge

**Assessment Flow:**
1. `kinestex_launched` - Application starts
2. Assessment performed (user follows on-screen instructions)
3. `assessment_overview` - Results page loads with all metrics
4. `assessment_completed` - User clicks restart or finish
5. `assessment_exit_results` - User exits from results screen (or `assessment_exit` if they exit early)
6. `exit_kinestex` - Application closed

---
Source: https://www.kinestex.com/docs/data-points · Index: https://www.kinestex.com/llms.txt
