Skip to content

HealthKit integration: import Apple Watch sleep stages for ML training #1

@ng

Description

@ng

Feature request

Use Apple Watch sleep stage data from HealthKit as labeled ground truth to train the on-device sleep stage classifier.

Background

The pod's piezo sensors produce raw vitals (HR, HRV, breathing rate) but we need labeled sleep stages to train a Core ML model. Apple Watch already classifies sleep into stages (wake/REM/core/deep) and writes them to HealthKit. By pairing Watch labels with pod sensor data, we get free training data from every user who wears a Watch to bed.

Implementation

Phase 1: Read HealthKit sleep stages

import HealthKit

let sleepType = HKCategoryType(.sleepAnalysis)
let store = HKHealthStore()

// Request read permission
store.requestAuthorization(toShare: nil, read: [sleepType]) { ... }

// Query sleep stages for a date range
let predicate = HKQuery.predicateForSamples(
    withStart: bedtime, end: waketime, options: .strictStartDate
)
let query = HKSampleQuery(sampleType: sleepType, predicate: predicate, ...) { _, samples, _ in
    for sample in samples as? [HKCategorySample] ?? [] {
        // sample.value maps to:
        //   HKCategoryValueSleepAnalysis.awake = 2
        //   HKCategoryValueSleepAnalysis.asleepREM = 5
        //   HKCategoryValueSleepAnalysis.asleepCore = 4
        //   HKCategoryValueSleepAnalysis.asleepDeep = 3
        //   HKCategoryValueSleepAnalysis.asleepUnspecified = 1
        let stage = sample.value
        let start = sample.startDate
        let end = sample.endDate
    }
}

Phase 2: Pair with pod data

For each HealthKit sleep stage epoch:

  1. Find pod vitals records within the same time window
  2. Create a training sample: (hr, hrv, br, hr_delta, hrv_delta, br_delta) → stage_label
  3. Store paired data locally in a SQLite DB or JSON file
struct TrainingSample: Codable {
    let timestamp: Date
    let heartRate: Double
    let hrv: Double?
    let breathingRate: Double?
    let hrDelta: Double      // change from previous epoch
    let hrvDelta: Double
    let brDelta: Double
    let label: Int           // 0=wake, 1=light/core, 2=deep, 3=REM
    let source: String       // "apple_watch"
}

Phase 3: On-device training / fine-tuning

Once enough paired data exists (est. 10-20 nights):

  1. Export training samples as CSV or use Create ML directly
  2. Train a tabular classifier or small 1D-CNN
  3. Convert to .mlmodel → bundle in app or download OTA
  4. SleepAnalyzer switches from rule-based to model-based

Alternatively, use the paired data to fine-tune the rule-based thresholds in SleepAnalyzer.swift — no ML training needed, just adjust the HR ratio cutoffs based on real labeled data.

Phase 4: Pod-only classification

After training, the pod can classify sleep stages independently:

  • Users without Apple Watch get sleep staging
  • Users with Watch get validated staging (compare pod vs Watch)
  • Show confidence scores: "Pod agrees with Watch 87% of the time"

Requirements

  • NSHealthShareUsageDescription in Info.plist
  • HealthKit capability in Xcode signing
  • User opt-in: "Allow Sleepypod to read sleep data from Apple Health?"
  • Privacy: all training data stays on-device

Data quality notes

  • Apple Watch sleep staging accuracy: ~80% agreement with polysomnography (Walch et al., PNAS 2019)
  • Pod HR/HRV data needs calibration first (see core#172)
  • Minimum viable training set: ~10 nights × ~480 epochs/night = ~4,800 samples
  • Apple Watch sleep stages are available from watchOS 9+ / iOS 16+

References

  • Walch et al., "Sleep stage prediction with raw acceleration and photoplethysmography," PNAS 2019
  • Apple HealthKit documentation: HKCategoryValueSleepAnalysis
  • Fonseca et al., "Cardiorespiratory sleep stage classification," Journal of Sleep Research, 2018

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions