Skip to content

Commit 14d0492

Browse files
author
Darin Krauss
authored
[LOOP-3842/3843] Capture trend rate and out-of-range condition in glucose sample (#454)
- https://tidepool.atlassian.net/browse/LOOP-3842 - https://tidepool.atlassian.net/browse/LOOP-3843 - https://tidepool.atlassian.net/browse/LOOP-970 - https://tidepool.atlassian.net/browse/LOOP-1257 - Add glucose trend rate and glucose condition
1 parent f619cad commit 14d0492

15 files changed

+98
-29
lines changed

Common/Models/StatusExtensionContext.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ struct GlucoseDisplayableContext: GlucoseDisplayable {
2424
let isStateValid: Bool
2525
let stateDescription: String
2626
let trendType: GlucoseTrend?
27+
let trendRate: HKQuantity?
2728
let isLocal: Bool
2829
let glucoseRangeCategory: GlucoseRangeCategory?
2930
}
@@ -136,6 +137,7 @@ extension GlucoseDisplayableContext: RawRepresentable {
136137
stateDescription = other.stateDescription
137138
isLocal = other.isLocal
138139
trendType = other.trendType
140+
trendRate = other.trendRate
139141
glucoseRangeCategory = other.glucoseRangeCategory
140142
}
141143

@@ -158,6 +160,12 @@ extension GlucoseDisplayableContext: RawRepresentable {
158160
trendType = nil
159161
}
160162

163+
if let trendRateUnit = rawValue["trendRateUnit"] as? String, let trendRateValue = rawValue["trendRateValue"] as? Double {
164+
trendRate = HKQuantity(unit: HKUnit(from: trendRateUnit), doubleValue: trendRateValue)
165+
} else {
166+
trendRate = nil
167+
}
168+
161169
if let glucoseRangeCategoryRawValue = rawValue["glucoseRangeCategory"] as? GlucoseRangeCategory.RawValue {
162170
glucoseRangeCategory = GlucoseRangeCategory(rawValue: glucoseRangeCategoryRawValue)
163171
} else {
@@ -172,6 +180,10 @@ extension GlucoseDisplayableContext: RawRepresentable {
172180
"isLocal": isLocal
173181
]
174182
raw["trendType"] = trendType?.rawValue
183+
if let trendRate = trendRate {
184+
raw["trendRateUnit"] = HKUnit.milligramsPerDeciliterPerMinute.unitString
185+
raw["trendRateValue"] = trendRate.doubleValue(for: HKUnit.milligramsPerDeciliterPerMinute)
186+
}
175187
raw["glucoseRangeCategory"] = glucoseRangeCategory?.rawValue
176188

177189
return raw

Common/Models/WatchContext.swift

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@ final class WatchContext: RawRepresentable {
2121
var displayGlucoseUnit: HKUnit?
2222

2323
var glucose: HKQuantity?
24-
var glucoseTrendRawValue: Int?
24+
var glucoseCondition: GlucoseCondition?
25+
var glucoseTrend: GlucoseTrend?
26+
var glucoseTrendRate: HKQuantity?
2527
var glucoseDate: Date?
2628
var glucoseIsDisplayOnly: Bool?
2729
var glucoseWasUserEntered: Bool?
@@ -68,7 +70,15 @@ final class WatchContext: RawRepresentable {
6870
glucose = HKQuantity(unit: unit, doubleValue: glucoseValue)
6971
}
7072

71-
glucoseTrendRawValue = rawValue["gt"] as? Int
73+
if let rawGlucoseCondition = rawValue["gc"] as? GlucoseCondition.RawValue {
74+
glucoseCondition = GlucoseCondition(rawValue: rawGlucoseCondition)
75+
}
76+
if let rawGlucoseTrend = rawValue["gt"] as? GlucoseTrend.RawValue {
77+
glucoseTrend = GlucoseTrend(rawValue: rawGlucoseTrend)
78+
}
79+
if let glucoseTrendRateUnitString = rawValue["gtru"] as? String, let glucoseTrendRateValue = rawValue["gtrv"] as? Double {
80+
glucoseTrendRate = HKQuantity(unit: HKUnit(from: glucoseTrendRateUnitString), doubleValue: glucoseTrendRateValue)
81+
}
7282
glucoseDate = rawValue["gd"] as? Date
7383
glucoseIsDisplayOnly = rawValue["gdo"] as? Bool
7484
glucoseWasUserEntered = rawValue["gue"] as? Bool
@@ -114,7 +124,13 @@ final class WatchContext: RawRepresentable {
114124
raw["gu"] = displayGlucoseUnit?.unitString
115125
raw["gv"] = glucose?.doubleValue(for: unit)
116126

117-
raw["gt"] = glucoseTrendRawValue
127+
raw["gc"] = glucoseCondition?.rawValue
128+
raw["gt"] = glucoseTrend?.rawValue
129+
if let glucoseTrendRate = glucoseTrendRate {
130+
let unitPerMinute = unit.unitDivided(by: .minute())
131+
raw["gtru"] = unitPerMinute.unitString
132+
raw["gtrv"] = glucoseTrendRate.doubleValue(for: unitPerMinute)
133+
}
118134
raw["gd"] = glucoseDate
119135
raw["gdo"] = glucoseIsDisplayOnly
120136
raw["gue"] = glucoseWasUserEntered
@@ -149,7 +165,9 @@ extension WatchContext {
149165
if let quantity = glucose, let date = glucoseDate, let syncIdentifier = glucoseSyncIdentifier {
150166
return NewGlucoseSample(date: date,
151167
quantity: quantity,
152-
trend: glucoseTrendRawValue.flatMap { GlucoseTrend(rawValue: $0) },
168+
condition: glucoseCondition,
169+
trend: glucoseTrend,
170+
trendRate: glucoseTrendRate,
153171
isDisplayOnly: glucoseIsDisplayOnly ?? false,
154172
wasUserEntered: glucoseWasUserEntered ?? false,
155173
syncIdentifier: syncIdentifier, syncVersion: 0)

Common/Models/WatchHistoricalGlucose.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,9 @@ extension WatchHistoricalGlucose: RawRepresentable {
4141
let syncVersions: [Int?]
4242
let startDates: [Date]
4343
let quantities: [Double]
44+
let conditions: [GlucoseCondition?]
4445
let trends: [GlucoseTrend?]
46+
let trendRates: [Double?]
4547
let isDisplayOnlys: [Bool]
4648
let wasUserEntereds: [Bool]
4749
let devices: [Data?]
@@ -54,7 +56,9 @@ extension WatchHistoricalGlucose: RawRepresentable {
5456
self.syncVersions = samples.map { $0.syncVersion }
5557
self.startDates = samples.map { $0.startDate }
5658
self.quantities = samples.map { $0.quantity.doubleValue(for: .milligramsPerDeciliter) }
59+
self.conditions = samples.map { $0.condition }
5760
self.trends = samples.map { $0.trend }
61+
self.trendRates = samples.map { $0.trendRate.flatMap { $0.doubleValue(for: .milligramsPerDeciliterPerMinute) } }
5862
self.isDisplayOnlys = samples.map { $0.isDisplayOnly }
5963
self.wasUserEntereds = samples.map { $0.wasUserEntered }
6064
self.devices = samples.map { try? WatchHistoricalGlucose.encoder.encode($0.device) }
@@ -69,7 +73,9 @@ extension WatchHistoricalGlucose: RawRepresentable {
6973
syncVersion: syncVersions[$0],
7074
startDate: startDates[$0],
7175
quantity: HKQuantity(unit: .milligramsPerDeciliter, doubleValue: quantities[$0]),
76+
condition: conditions[$0],
7277
trend: trends[$0],
78+
trendRate: trendRates[$0].flatMap { HKQuantity(unit: .milligramsPerDeciliterPerMinute, doubleValue: $0) },
7379
isDisplayOnly: isDisplayOnlys[$0],
7480
wasUserEntered: wasUserEntereds[$0],
7581
device: devices[$0].flatMap { try? HKDevice(from: $0) },

Loop/Extensions/GlucoseStore+SimulatedCoreData.swift

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,11 @@ extension GlucoseStore {
2828
var simulated = [NewGlucoseSample]()
2929

3030
while startDate < endDate {
31-
let prev = simulatedValueBase + simulatedValueAmplitude * sin(value - simulatedValueIncrement)
31+
let previous = simulatedValueBase + simulatedValueAmplitude * sin(value - simulatedValueIncrement)
3232
let new = simulatedValueBase + simulatedValueAmplitude * sin(value)
33+
let trendRateValue = new - previous
3334
let trend: GlucoseTrend? = {
34-
switch new - prev {
35+
switch trendRateValue {
3536
case -0.01...0.01:
3637
return .flat
3738
case -2 ..< -0.01:
@@ -50,7 +51,10 @@ extension GlucoseStore {
5051
return nil
5152
}
5253
}()
53-
simulated.append(NewGlucoseSample.simulated(date: startDate, value: new, trend: trend))
54+
simulated.append(NewGlucoseSample.simulated(date: startDate,
55+
quantity: HKQuantity(unit: .milligramsPerDeciliter, doubleValue: new),
56+
trend: trend,
57+
trendRate: HKQuantity(unit: .milligramsPerDeciliterPerMinute, doubleValue: trendRateValue)))
5458

5559
if simulated.count >= simulatedLimit {
5660
if let error = addSimulatedHistoricalGlucoseObjects(samples: simulated) {
@@ -84,10 +88,12 @@ extension GlucoseStore {
8488
}
8589

8690
fileprivate extension NewGlucoseSample {
87-
static func simulated(date: Date, value: Double, trend: GlucoseTrend?, unit: HKUnit = HKUnit.milligramsPerDeciliter) -> NewGlucoseSample {
91+
static func simulated(date: Date, quantity: HKQuantity, trend: GlucoseTrend?, trendRate: HKQuantity?) -> NewGlucoseSample {
8892
return NewGlucoseSample(date: date,
89-
quantity: HKQuantity(unit: unit, doubleValue: value),
93+
quantity: quantity,
94+
condition: nil,
9095
trend: trend,
96+
trendRate: trendRate,
9197
isDisplayOnly: false,
9298
wasUserEntered: false,
9399
syncIdentifier: UUID().uuidString)

Loop/Managers/ExtensionDataManager.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ final class ExtensionDataManager {
129129
isStateValid: glucoseDisplay.isStateValid,
130130
stateDescription: glucoseDisplay.stateDescription,
131131
trendType: glucoseDisplay.trendType,
132+
trendRate: glucoseDisplay.trendRate,
132133
isLocal: glucoseDisplay.isLocal,
133134
glucoseRangeCategory: glucoseDisplay.glucoseRangeCategory
134135
)

Loop/Managers/WatchDataManager.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,11 @@ final class WatchDataManager: NSObject {
242242
context.loopLastRunDate = manager.lastLoopCompleted
243243
context.recommendedBolusDose = recommendedBolus?.recommendation.amount
244244
context.cob = carbsOnBoard?.quantity.doubleValue(for: HKUnit.gram())
245-
context.glucoseTrendRawValue = self.deviceManager.glucoseDisplay(for: glucose)?.trendType?.rawValue
245+
246+
if let glucoseDisplay = self.deviceManager.glucoseDisplay(for: glucose) {
247+
context.glucoseTrend = glucoseDisplay.trendType
248+
context.glucoseTrendRate = glucoseDisplay.trendRate
249+
}
246250

247251
dosingDecision.carbsOnBoard = carbsOnBoard
248252
dosingDecision.recommendedBolus = recommendedBolus?.recommendation
@@ -253,10 +257,6 @@ final class WatchDataManager: NSObject {
253257

254258
context.isClosedLoop = settings.dosingEnabled
255259

256-
if let trend = self.deviceManager.cgmManager?.glucoseDisplay?.trendType {
257-
context.glucoseTrendRawValue = trend.rawValue
258-
}
259-
260260
if let potentialCarbEntry = potentialCarbEntry {
261261
context.potentialCarbEntry = potentialCarbEntry
262262
if let recommendedBolusDoseConsideringPotentialCarbEntry = try? state.recommendBolus(consideringPotentialCarbEntry: potentialCarbEntry, replacingCarbEntry: nil) {

Loop/Models/GlucoseDisplay.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,21 +7,25 @@
77
//
88

99
import Foundation
10+
import HealthKit
1011
import LoopKit
1112

1213
struct GlucoseDisplay: GlucoseDisplayable {
1314
let isStateValid: Bool
1415
let trendType: GlucoseTrend?
16+
let trendRate: HKQuantity?
1517
let isLocal: Bool
1618
var glucoseRangeCategory: GlucoseRangeCategory?
1719

1820
init(isStateValid: Bool,
1921
trendType: GlucoseTrend?,
22+
trendRate: HKQuantity?,
2023
isLocal: Bool,
2124
glucoseRangeCategory: GlucoseRangeCategory?)
2225
{
2326
self.isStateValid = isStateValid
2427
self.trendType = trendType
28+
self.trendRate = trendRate
2529
self.isLocal = isLocal
2630
self.glucoseRangeCategory = glucoseRangeCategory
2731
}
@@ -32,6 +36,7 @@ struct GlucoseDisplay: GlucoseDisplayable {
3236
}
3337
self.isStateValid = glucoseDisplayable.isStateValid
3438
self.trendType = glucoseDisplayable.trendType
39+
self.trendRate = glucoseDisplayable.trendRate
3540
self.isLocal = glucoseDisplayable.isLocal
3641
self.glucoseRangeCategory = glucoseDisplayable.glucoseRangeCategory
3742
}
@@ -40,12 +45,14 @@ struct GlucoseDisplay: GlucoseDisplayable {
4045
struct ManualGlucoseDisplay: GlucoseDisplayable {
4146
let isStateValid: Bool
4247
let trendType: GlucoseTrend?
48+
let trendRate: HKQuantity?
4349
let isLocal: Bool
4450
let glucoseRangeCategory: GlucoseRangeCategory?
4551

4652
init(glucoseRangeCategory: GlucoseRangeCategory?) {
4753
isStateValid = true
4854
trendType = nil
55+
trendRate = nil
4956
isLocal = true
5057
self.glucoseRangeCategory = glucoseRangeCategory
5158
}

Loop/View Models/BolusEntryViewModel.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -502,7 +502,9 @@ final class BolusEntryViewModel: ObservableObject {
502502
NewGlucoseSample(
503503
date: entryDate,
504504
quantity: quantity,
505-
trend: nil, // All manual glucose entries are assumed to have no trend.
505+
condition: nil, // All manual glucose entries are assumed to have no condition.
506+
trend: nil, // All manual glucose entries are assumed to have no trend.
507+
trendRate: nil, // All manual glucose entries are assumed to have no trend rate.
506508
isDisplayOnly: false,
507509
wasUserEntered: true,
508510
syncIdentifier: uuidProvider()

Loop/View Models/SimpleBolusViewModel.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -355,7 +355,9 @@ class SimpleBolusViewModel: ObservableObject {
355355
if let manualGlucoseQuantity = manualGlucoseQuantity {
356356
let manualGlucoseSample = NewGlucoseSample(date: saveDate,
357357
quantity: manualGlucoseQuantity,
358-
trend: nil, // All manual glucose entries are assumed to have no trend.
358+
condition: nil, // All manual glucose entries are assumed to have no condition.
359+
trend: nil, // All manual glucose entries are assumed to have no trend.
360+
trendRate: nil, // All manual glucose entries are assumed to have no trend rate.
359361
isDisplayOnly: false,
360362
wasUserEntered: true,
361363
syncIdentifier: UUID().uuidString)

LoopCore/HKUnit.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,14 @@ extension HKUnit {
1818
return HKUnit.moleUnit(with: .milli, molarMass: HKUnitMolarMassBloodGlucose).unitDivided(by: .liter())
1919
}()
2020

21+
public static let milligramsPerDeciliterPerMinute: HKUnit = {
22+
return HKUnit.milligramsPerDeciliter.unitDivided(by: .minute())
23+
}()
24+
25+
public static let millimolesPerLiterPerMinute: HKUnit = {
26+
return HKUnit.millimolesPerLiter.unitDivided(by: .minute())
27+
}()
28+
2129
public static let internationalUnitsPerHour: HKUnit = {
2230
return HKUnit.internationalUnit().unitDivided(by: .hour())
2331
}()

0 commit comments

Comments
 (0)