diff --git a/LoopKit/InsulinKit/CachedInsulinDeliveryObject+CoreDataClass.swift b/LoopKit/InsulinKit/CachedInsulinDeliveryObject+CoreDataClass.swift index 1b064519c..4d832c789 100644 --- a/LoopKit/InsulinKit/CachedInsulinDeliveryObject+CoreDataClass.swift +++ b/LoopKit/InsulinKit/CachedInsulinDeliveryObject+CoreDataClass.swift @@ -276,7 +276,8 @@ extension CachedInsulinDeliveryObject { self.endDate = entry.endDate self.syncIdentifier = entry.syncIdentifier self.deliveredUnits = entry.unitsInDeliverableIncrements - self.scheduledBasalRate = entry.scheduledBasalRate + // A `.basal` dose's rate (used via `.unitsPerHour` accessor) is its `value`; preserve it as the rate field so the cache reports the exact rate instead of one derived from a quantized delivered total. + self.scheduledBasalRate = entry.scheduledBasalRate ?? (entry.type == .basal ? LoopQuantity(unit: .internationalUnitsPerHour, doubleValue: entry.unitsPerHour) : nil) self.programmedTempBasalRate = (entry.type == .tempBasal) ? LoopQuantity(unit: .internationalUnitsPerHour, doubleValue: entry.unitsPerHour) : nil self.programmedUnits = (entry.type == .bolus) ? entry.programmedUnits : nil self.reason = (entry.type == .bolus) ? .bolus : .basal @@ -302,7 +303,8 @@ extension CachedInsulinDeliveryObject { self.endDate = entry.endDate self.syncIdentifier = entry.syncIdentifier self.deliveredUnits = entry.unitsInDeliverableIncrements - self.scheduledBasalRate = entry.scheduledBasalRate + // A `.basal` dose's rate is its `value` (used via `.unitsPerHour` accessor); preserve it as the rate field so the cache reports the exact rate instead of one derived from a quantized delivered total. + self.scheduledBasalRate = entry.scheduledBasalRate ?? (entry.type == .basal ? LoopQuantity(unit: .internationalUnitsPerHour, doubleValue: entry.unitsPerHour) : nil) self.programmedTempBasalRate = (entry.type == .tempBasal) ? LoopQuantity(unit: .internationalUnitsPerHour, doubleValue: entry.unitsPerHour) : nil self.programmedUnits = (entry.type == .bolus) ? entry.programmedUnits : nil self.reason = (entry.type == .bolus) ? .bolus : .basal diff --git a/LoopKitHostedTests/InsulinDeliveryStoreTests.swift b/LoopKitHostedTests/InsulinDeliveryStoreTests.swift index 0a268e4b4..1bcfa5298 100644 --- a/LoopKitHostedTests/InsulinDeliveryStoreTests.swift +++ b/LoopKitHostedTests/InsulinDeliveryStoreTests.swift @@ -20,7 +20,7 @@ class InsulinDeliveryStoreTestsBase: PersistenceControllerTestCase { decisionId: nil, deliveredUnits: 0.015, syncIdentifier: "4B14522E-A7B5-4E73-B76B-5043CD7176B0", - scheduledBasalRate: nil) + scheduledBasalRate: LoopQuantity(unit: .internationalUnitsPerHour, doubleValue: 1.8)) internal let entry2 = DoseEntry(type: .tempBasal, startDate: Date(timeIntervalSinceNow: -.minutes(2)), endDate: Date(timeIntervalSinceNow: -.minutes(1.5)), @@ -202,8 +202,8 @@ class InsulinDeliveryStoreTests: InsulinDeliveryStoreTestsBase { XCTAssertEqual(entries[0].type, self.entry1.type) XCTAssertEqual(entries[0].startDate, self.entry1.startDate) XCTAssertEqual(entries[0].endDate, self.entry1.endDate) - XCTAssertEqual(entries[0].value, 0.015) - XCTAssertEqual(entries[0].unit, .units) + XCTAssertEqual(entries[0].value, 1.8) + XCTAssertEqual(entries[0].unit, .unitsPerHour) XCTAssertEqual(entries[0].deliveredUnits, 0.015) XCTAssertEqual(entries[0].description, self.entry1.description) XCTAssertEqual(entries[0].syncIdentifier, self.entry1.syncIdentifier) @@ -282,8 +282,8 @@ class InsulinDeliveryStoreTests: InsulinDeliveryStoreTestsBase { XCTAssertEqual(entries[0].type, self.entry1.type) XCTAssertEqual(entries[0].startDate, self.entry1.startDate) XCTAssertEqual(entries[0].endDate, self.entry1.endDate) - XCTAssertEqual(entries[0].value, 0.015) - XCTAssertEqual(entries[0].unit, .units) + XCTAssertEqual(entries[0].value, 1.8) + XCTAssertEqual(entries[0].unit, .unitsPerHour) XCTAssertEqual(entries[0].deliveredUnits, 0.015) XCTAssertEqual(entries[0].description, self.entry1.description) XCTAssertEqual(entries[0].syncIdentifier, self.entry1.syncIdentifier) @@ -314,8 +314,8 @@ class InsulinDeliveryStoreTests: InsulinDeliveryStoreTestsBase { XCTAssertEqual(entries[0].type, self.entry1.type) XCTAssertEqual(entries[0].startDate, self.entry1.startDate) XCTAssertEqual(entries[0].endDate, self.entry1.endDate) - XCTAssertEqual(entries[0].value, 0.015) - XCTAssertEqual(entries[0].unit, .units) + XCTAssertEqual(entries[0].value, 1.8) + XCTAssertEqual(entries[0].unit, .unitsPerHour) XCTAssertEqual(entries[0].deliveredUnits, 0.015) XCTAssertEqual(entries[0].description, self.entry1.description) XCTAssertEqual(entries[0].syncIdentifier, self.entry1.syncIdentifier) diff --git a/LoopKitTests/Persistence/CachedInsulinDeliveryObjectTests.swift b/LoopKitTests/Persistence/CachedInsulinDeliveryObjectTests.swift index 865ef1828..953786624 100644 --- a/LoopKitTests/Persistence/CachedInsulinDeliveryObjectTests.swift +++ b/LoopKitTests/Persistence/CachedInsulinDeliveryObjectTests.swift @@ -346,6 +346,34 @@ class CachedInsulinDeliveryObjectOperationsTests: PersistenceControllerTestCase } } + func testCreateFromScheduledBasalEntryPreservesRate() { + // A scheduled `.basal` reported without a scheduledBasalRate (e.g. after a PumpEvent + // round-trip, which doesn't persist it) must keep its exact rate. Without the fix the + // cache stores only the quantized delivered total and the read-back rate drifts. + let start = dateFormatter.date(from: "2020-01-02T03:04:05Z")! + let entry = DoseEntry(type: .basal, + startDate: start, + endDate: start.addingTimeInterval(180.2), // 3 min 0.2 s → quantized total would drift + value: 1.0, + unit: .unitsPerHour, + decisionId: nil, + deliveredUnits: nil, + syncIdentifier: "scheduled-basal", + scheduledBasalRate: nil, + isMutable: true) + cacheStore.managedObjectContext.performAndWait { + let object = CachedInsulinDeliveryObject(context: cacheStore.managedObjectContext) + object.create(from: entry, by: "Test Providence Identifier", at: start) + + XCTAssertEqual(object.scheduledBasalRate, LoopQuantity(unit: .internationalUnitsPerHour, doubleValue: 1.0)) + + let dose = object.dose + XCTAssertNotNil(dose) + XCTAssertEqual(dose!.unit, .unitsPerHour) + XCTAssertEqual(dose!.unitsPerHour, 1.0, accuracy: 1e-9) + } + } + func testCreateAndUpdateFromEntry() { let createEntry = DoseEntry(type: .tempBasal, startDate: dateFormatter.date(from: "2020-02-03T04:05:06Z")!,