Skip to content

Commit

Permalink
Support audiograms
Browse files Browse the repository at this point in the history
  • Loading branch information
christophhagen committed Apr 21, 2024
1 parent 50d7b60 commit 5dd5245
Show file tree
Hide file tree
Showing 7 changed files with 95 additions and 22 deletions.
13 changes: 12 additions & 1 deletion Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ There are many sample tables in the database, and all appear to be linked by the
|---|---|
`account_owner_samples` | ❌
`allergy_record_samples` | ❌
`binary_samples` | ✅
`binary_samples` | ✅ (Heartbeat series, audiograms)
`category_samples` | ✅
`clinical_note_record_samples` | ❌
`clinical_record_samples` | ❌
Expand Down Expand Up @@ -309,6 +309,17 @@ The meaning of the last seven bytes is currently unknown.
Most of these values are zeroes, but occasionally there are values like `0xa770270`, `0x57ef26`, `0xd7ab25`, `0xd65b15`, or `0x573e26`.
These values have no apparent meaning, and can occur with both `true` or `false` for `isPrecededByGap`.

### Audiograms

Stored `HKAudiogramSample`s can also be queried.

```swift
let samples: [HKAudiogramSample] = try db.audiograms(from: start, to: end)
```

Like heartbeat series data, the binary data associated with each sample is stored in `binary_samples`.
The `payload` column consists of an array of `HKAudiogramSensitivityPoint`s, encoded with an `NSKeyedArchiver`.

### Workouts

Workouts can be queried from the database similar to other samples:
Expand Down
36 changes: 23 additions & 13 deletions Sources/HealthDB/Database/HKDatabaseStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import CoreLocation
import HealthKit
import HealthKitExtensions

typealias ObjectData = (dataId: Int, startDate: Date, endDate: Date, uuid: UUID, device: HKDevice?, metadata: [String : Any])

public final class HKDatabaseStore {

private let fileUrl: URL
Expand Down Expand Up @@ -103,7 +105,7 @@ public final class HKDatabaseStore {
/**
Extract common object properties from a row.
*/
private func objectData(from row: Row) throws -> (dataId: Int, startDate: Date, endDate: Date, uuid: UUID, device: HKDevice?, metadata: [String : Any]) {
private func objectData(from row: Row) throws -> ObjectData {
let dataId = row[samples.table[samples.dataId]]
let startDate = Date(timeIntervalSinceReferenceDate: row[samples.table[samples.startDate]])
let endDate = Date(timeIntervalSinceReferenceDate: row[samples.table[samples.endDate]])
Expand Down Expand Up @@ -1077,7 +1079,25 @@ public final class HKDatabaseStore {

// MARK: Heartbeat Series

/**
Query for heartbeat series samples within a date interval.
*/
public func heartBeatSeries(from start: Date = .distantPast, to end: Date = .distantFuture) throws -> [HeartbeatSeries] {
try binarySamples(type: SampleType.Other.heartbeatSeries.rawValue, from: start, to: end)
}

// MARK: Audiograms

/**
Query for audiogram samples within a date interval.
*/
public func audiograms(from start: Date = .distantPast, to end: Date = .distantFuture) throws -> [HKAudiogramSample] {
try binarySamples(type: SampleType.Other.audiogram.rawValue, from: start, to: end)
}

// MARK: Binary samples

private func binarySamples<T: BinarySample>(type: Int, from start: Date, to end: Date) throws -> [T] {
let query = samples.table
.select(samples.table[*],
objects.table[objects.provenance],
Expand All @@ -1087,25 +1107,15 @@ public final class HKDatabaseStore {
on: samples.table[samples.dataId] == objects.table[objects.dataId])
.join(.leftOuter, binarySamples.table,
on: samples.table[samples.dataId] == binarySamples.table[binarySamples.dataId])
.filter(samples.table[samples.dataType] == SampleType.Other.heartbeatSeries.rawValue &&
.filter(samples.table[samples.dataType] == type &&
samples.table[samples.startDate] <= end.timeIntervalSinceReferenceDate &&
samples.table[samples.endDate] >= start.timeIntervalSinceReferenceDate)

return try database.prepare(query).compactMap { row in
let object = try objectData(from: row)
guard let data = try binarySamples.payload(for: object.dataId, in: database) else {
return nil
}
guard let samples = HeartbeatSeries.samples(from: data) else {
return nil
}
return .init(
samples: samples,
startDate: object.startDate,
endDate: object.endDate,
uuid: object.uuid,
metadata: object.metadata.withoutUUID(),
device: object.device)
return T.from(object: object, data: data)
}
}

Expand Down

This file was deleted.

17 changes: 17 additions & 0 deletions Sources/HealthDB/Database/HealthDatabase+Series.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,21 @@ extension HealthDatabase {
.map(T.init(quantitySample:))
}

// MARK: Heartbeat Series

/**
Query for heartbeat series samples within a date interval.
*/
public func heartBeatSeries(from start: Date = .distantPast, to end: Date = .distantFuture) throws -> [HeartbeatSeries] {
try store.heartBeatSeries(from: start, to: end)
}

// MARK: Audiograms

/**
Query for audiogram samples within a date interval.
*/
public func audiograms(from start: Date = .distantPast, to end: Date = .distantFuture) throws -> [HKAudiogramSample] {
try store.audiograms(from: start, to: end)
}
}
22 changes: 22 additions & 0 deletions Sources/HealthDB/Model/Audiogram/HKAudiogram+BinarySample.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import Foundation
import HealthKit

extension HKAudiogramSample: BinarySample {

static func from(object: ObjectData, data: Data) -> Self? {
let points: [HKAudiogramSensitivityPoint]
do {
let decoder = try NSKeyedUnarchiver(forReadingFrom: data)
decoder.requiresSecureCoding = false

guard let values = decoder.decodeObject(of: NSArray.self, forKey: NSKeyedArchiveRootObjectKey) as? [HKAudiogramSensitivityPoint] else {
return nil
}
points = values
} catch {
return nil
}

return .init(sensitivityPoints: points, start: object.startDate, end: object.endDate, metadata: object.metadata)
}
}
6 changes: 6 additions & 0 deletions Sources/HealthDB/Model/BinarySample.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import Foundation

protocol BinarySample {

static func from(object: ObjectData, data: Data) -> Self?
}
15 changes: 15 additions & 0 deletions Sources/HealthDB/Model/HeartbeatSamples/HeartbeatSeries.swift
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,18 @@ extension HeartbeatSeries {
}
}

extension HeartbeatSeries: BinarySample {

static func from(object: ObjectData, data: Data) -> HeartbeatSeries? {
guard let samples = HeartbeatSeries.samples(from: data) else {
return nil
}
return .init(
samples: samples,
startDate: object.startDate,
endDate: object.endDate,
uuid: object.uuid,
metadata: object.metadata.withoutUUID(),
device: object.device)
}
}

0 comments on commit 5dd5245

Please sign in to comment.