Skip to content

Commit

Permalink
VIT-7905: Introduce VitalResource electrocardiogram, afib_burden and …
Browse files Browse the repository at this point in the history
…heart_rate_alert cases
  • Loading branch information
andersio committed Dec 5, 2024
1 parent d6f0a58 commit 8db1321
Show file tree
Hide file tree
Showing 6 changed files with 123 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ public enum TimeSeriesData: Equatable, Encodable {
case steps([LocalQuantitySample])
case vo2Max([LocalQuantitySample])
case temperature([LocalQuantitySample])
case afibBurden([LocalQuantitySample])

public var payload: Encodable {
switch self {
Expand All @@ -91,7 +92,7 @@ public enum TimeSeriesData: Equatable, Encodable {
let .nutrition(.water(samples)), let .mindfulSession(samples),
let .caloriesActive(samples), let .caloriesBasal(samples), let .distance(samples),
let .floorsClimbed(samples), let .steps(samples), let .vo2Max(samples),
let .respiratoryRate(samples), let .temperature(samples):
let .respiratoryRate(samples), let .temperature(samples), let .afibBurden(samples):
return samples

case let .bloodPressure(samples):
Expand All @@ -107,7 +108,7 @@ public enum TimeSeriesData: Equatable, Encodable {
let .nutrition(.water(samples)), let .mindfulSession(samples),
let .caloriesActive(samples), let .caloriesBasal(samples), let .distance(samples),
let .floorsClimbed(samples), let .steps(samples), let .vo2Max(samples),
let .respiratoryRate(samples), let .temperature(samples):
let .respiratoryRate(samples), let .temperature(samples), let .afibBurden(samples):
return samples.count

case let .bloodPressure(samples):
Expand Down Expand Up @@ -149,6 +150,8 @@ public enum TimeSeriesData: Equatable, Encodable {
return "respiratory_rate"
case .temperature:
return "temperature"
case .afibBurden:
return "afib_burden"
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,22 @@ public enum VitalResource: Equatable, Hashable, Codable {
@_spi(VitalSDKInternals)
public var priority: Int {
switch self {
case .activity, .body, .workout, .menstrualCycle, .profile:
return 0
case .sleep, .individual(.vo2Max), .vitals(.bloodOxygen), .vitals(.bloodPressure),
.vitals(.glucose), .vitals(.heartRateVariability),
.nutrition(.water), .nutrition(.caffeine),
.vitals(.mindfulSession), .vitals(.temperature), .vitals(.respiratoryRate), .meal:
case .activity, .body, .profile:
return 1
case .sleep, .menstrualCycle, .meal:
return 4
case .workout, .individual(.vo2Max), .nutrition(.water), .nutrition(.caffeine):
return 8
case .electrocardiogram, .heartRateAlert, .afibBurden:
return 12
case .vitals(.bloodOxygen), .vitals(.bloodPressure),
.vitals(.glucose), .vitals(.heartRateVariability),
.vitals(.mindfulSession), .vitals(.temperature), .vitals(.respiratoryRate):
return 16
case .individual(.distanceWalkingRunning), .individual(.steps), .individual(.floorsClimbed):
return 2
return 32
case .vitals(.heartRate), .individual(.activeEnergyBurned), .individual(.basalEnergyBurned):
return 3
return 64
case .individual(.exerciseTime), .individual(.weight), .individual(.bodyFat):
return Int.max
}
Expand Down Expand Up @@ -76,6 +81,12 @@ public enum VitalResource: Equatable, Hashable, Codable {
return .respiratoryRate
case .meal:
return BackfillType.meal
case .afibBurden:
return .afibBurden
case .electrocardiogram:
return .electrocardiogram
case .heartRateAlert:
return .heartRateAlert
}
}

Expand Down Expand Up @@ -171,7 +182,10 @@ public enum VitalResource: Equatable, Hashable, Codable {
case individual(Individual)
case nutrition(Nutrition)
case meal

case electrocardiogram
case heartRateAlert
case afibBurden

public static var all: [VitalResource] = [
.profile,
.body,
Expand Down Expand Up @@ -201,6 +215,10 @@ public enum VitalResource: Equatable, Hashable, Codable {

.nutrition(.water),
.nutrition(.caffeine),

.electrocardiogram,
.heartRateAlert,
.afibBurden,
]

public var logDescription: String {
Expand All @@ -225,6 +243,12 @@ public enum VitalResource: Equatable, Hashable, Codable {
return individual.logDescription
case let .nutrition(nutrition):
return nutrition.logDescription
case .electrocardiogram:
return "electrocardiogram"
case .heartRateAlert:
return "heartRateAlert"
case .afibBurden:
return "afibBurden"
}
}
}
Expand Down Expand Up @@ -265,4 +289,6 @@ public struct BackfillType: RawRepresentable, Codable, Equatable, Hashable {
public static let electrocardiogram = BackfillType(rawValue: "electrocardiogram")
public static let temperature = BackfillType(rawValue: "temperature")
public static let menstrualCycle = BackfillType(rawValue: "menstrual_cycle")
public static let heartRateAlert = BackfillType(rawValue: "heart_rate_alert")
public static let afibBurden = BackfillType(rawValue: "afib_burden")
}
15 changes: 15 additions & 0 deletions Sources/VitalHealthKit/HealthKit/Abstractions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,15 @@ extension VitalHealthKitStore {
HKQuantityType.quantityType(forIdentifier: .dietarySelenium)!:
return [.meal]

case
HKCategoryType.categoryType(forIdentifier: .irregularHeartRhythmEvent)!,
HKCategoryType.categoryType(forIdentifier: .lowHeartRateEvent)!,
HKCategoryType.categoryType(forIdentifier: .highHeartRateEvent)!:
return [.heartRateAlert]

case HKElectrocardiogramType.electrocardiogramType():
return [.electrocardiogram]

default:
if #available(iOS 15.0, *) {
switch type {
Expand All @@ -208,6 +217,12 @@ extension VitalHealthKitStore {
HKCategoryType.categoryType(forIdentifier: .irregularMenstrualCycles)!,
HKCategoryType.categoryType(forIdentifier: .infrequentMenstrualCycles)!:
return [.menstrualCycle]


case
HKQuantityType.quantityType(forIdentifier: .atrialFibrillationBurden)!:
return [.afibBurden]

default:
break
}
Expand Down
24 changes: 24 additions & 0 deletions Sources/VitalHealthKit/HealthKit/HealthKitReads.swift
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,30 @@ func read(

return (.timeSeries(.respiratoryRate(payload.samples)), payload.anchors)


case .electrocardiogram:
// VIT-7905: To be implemented
return (nil, [])

case .heartRateAlert:
// VIT-7905: To be implemented
return (nil, [])

case .afibBurden:
if #available(iOS 16.0, *) {
let payload = try await handleTimeSeries(
.atrialFibrillationBurden,
healthKitStore: healthKitStore,
vitalStorage: vitalStorage,
startDate: instruction.query.lowerBound,
endDate: instruction.query.upperBound
)

return (.timeSeries(.afibBurden(payload.samples)), payload.anchors)
} else {
return (nil, [])
}

case .individual(.exerciseTime), .individual(.bodyFat), .individual(.weight):
throw VitalHealthKitClientError.invalidRemappedResource
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,7 @@ struct QuantityUnit {

if #available(iOS 16.0, *) {
mapping[.appleSleepingWristTemperature] = .degreeCelsius
mapping[.atrialFibrillationBurden] = .percentage
}

mapping[.bodyMass] = .kg
Expand Down Expand Up @@ -289,6 +290,7 @@ struct QuantityUnit {

if #available(iOS 16.0, *) {
mapping[.appleSleepingWristTemperature] = .degreeCelsius()
mapping[.atrialFibrillationBurden] = .percent()
}

mapping[.bodyMass] = .gramUnit(with: .kilo)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,25 @@ func toHealthKitTypes(resource: VitalResource) -> HealthKitObjectTypeRequirement
],
supplementary: []
)

case .electrocardiogram:
return single(HKElectrocardiogramType.electrocardiogramType())
case .afibBurden:
if #available(iOS 16, *) {
return single(HKQuantityType.quantityType(forIdentifier: .atrialFibrillationBurden)!)
} else {
return HealthKitObjectTypeRequirements(required: [], optional: [], supplementary: [])
}
case .heartRateAlert:
return HealthKitObjectTypeRequirements(
required: [],
optional: [
HKCategoryType.categoryType(forIdentifier: .irregularHeartRhythmEvent)!,
HKCategoryType.categoryType(forIdentifier: .highHeartRateEvent)!,
HKCategoryType.categoryType(forIdentifier: .lowHeartRateEvent)!,
],
supplementary: []
)
}
}

Expand Down Expand Up @@ -306,6 +325,14 @@ func observedSampleTypes() -> [[HKSampleType]] {
])
}

var afibBurdenTypes = [HKSampleType]()

if #available(iOS 16.0, *) {
afibBurdenTypes = [
HKQuantityType.quantityType(forIdentifier: .atrialFibrillationBurden)!
]
}

return [
/// Profile
[
Expand Down Expand Up @@ -441,6 +468,21 @@ func observedSampleTypes() -> [[HKSampleType]] {
[
HKSampleType.quantityType(forIdentifier: .respiratoryRate)!
],

/// AFib Burden
afibBurdenTypes,

/// Electrocardiogram
[
HKElectrocardiogramType.electrocardiogramType()
],

/// Heart rate alerts
[
HKCategoryType.categoryType(forIdentifier: .irregularHeartRhythmEvent)!,
HKCategoryType.categoryType(forIdentifier: .highHeartRateEvent)!,
HKCategoryType.categoryType(forIdentifier: .lowHeartRateEvent)!,
]
]
}

Expand Down

0 comments on commit 8db1321

Please sign in to comment.