InsulinMathTests.swift 114 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220
  1. //
  2. // InsulinMathTests.swift
  3. // InsulinMathTests
  4. //
  5. // Created by Nathan Racklyeft on 1/27/16.
  6. // Copyright © 2016 Nathan Racklyeft. All rights reserved.
  7. //
  8. import XCTest
  9. import HealthKit
  10. @testable import LoopKit
  11. struct NewReservoirValue: ReservoirValue {
  12. let startDate: Date
  13. let unitVolume: Double
  14. }
  15. extension DoseUnit {
  16. var unit: HKUnit {
  17. switch self {
  18. case .units:
  19. return .internationalUnit()
  20. case .unitsPerHour:
  21. return HKUnit(from: "IU/hr")
  22. }
  23. }
  24. }
  25. class InsulinMathTests: XCTestCase {
  26. var fixtureDateformatter: DateFormatter!
  27. private let fixtureTimeZone = TimeZone(secondsFromGMT: -0 * 60 * 60)!
  28. private let model = WalshInsulinModel(actionDuration: TimeInterval(hours: 4))
  29. private let insulinType: InsulinType = .novolog
  30. private let exponentialModel = ExponentialInsulinModel(actionDuration: TimeInterval(minutes: 360), peakActivityTime: TimeInterval(minutes: 75))
  31. let insulinModelSettings = InsulinModelSettings(model: ExponentialInsulinModelPreset.rapidActingAdult)!
  32. let walshModelSettings = InsulinModelSettings(model: WalshInsulinModel(actionDuration: TimeInterval(hours: 4)))!
  33. private func fixtureDate(_ input: String) -> Date {
  34. return fixtureDateformatter.date(from: input)!
  35. }
  36. override func setUp() {
  37. fixtureDateformatter = DateFormatter.descriptionFormatter
  38. fixtureDateformatter.timeZone = fixtureTimeZone
  39. }
  40. private func printInsulinValues(_ insulinValues: [InsulinValue]) {
  41. print("\n\n")
  42. print(String(data: try! JSONSerialization.data(
  43. withJSONObject: insulinValues.map({ (value) -> [String: Any] in
  44. return [
  45. "date": ISO8601DateFormatter.localTimeDate(timeZone: fixtureTimeZone).string(from: value.startDate),
  46. "value": value.value,
  47. "unit": "U"
  48. ]
  49. }),
  50. options: .prettyPrinted), encoding: .utf8)!)
  51. print("\n\n")
  52. }
  53. private func printDoses(_ doses: [DoseEntry]) {
  54. print("\n\n")
  55. print(String(data: try! JSONSerialization.data(
  56. withJSONObject: doses.map({ (value) -> [String: Any] in
  57. var obj: [String: Any] = [
  58. "type": value.type.pumpEventType!.rawValue,
  59. "start_at": ISO8601DateFormatter.localTimeDate(timeZone: fixtureTimeZone).string(from: value.startDate),
  60. "end_at": ISO8601DateFormatter.localTimeDate(timeZone: fixtureTimeZone).string(from: value.endDate),
  61. "amount": value.value,
  62. "unit": value.unit.rawValue
  63. ]
  64. if let syncIdentifier = value.syncIdentifier {
  65. obj["raw"] = syncIdentifier
  66. }
  67. if let scheduledBasalRate = value.scheduledBasalRate {
  68. obj["scheduled"] = scheduledBasalRate.doubleValue(for: HKUnit(from: "IU/hr"))
  69. }
  70. return obj
  71. }),
  72. options: .prettyPrinted), encoding: .utf8)!)
  73. print("\n\n")
  74. }
  75. func loadReservoirFixture(_ resourceName: String) -> [NewReservoirValue] {
  76. let fixture: [JSONDictionary] = loadFixture(resourceName)
  77. let dateFormatter = ISO8601DateFormatter.localTimeDate(timeZone: fixtureTimeZone)
  78. return fixture.map {
  79. return NewReservoirValue(startDate: dateFormatter.date(from: $0["date"] as! String)!, unitVolume: $0["amount"] as! Double)
  80. }
  81. }
  82. func loadDoseFixture(_ resourceName: String, insulinType: InsulinType? = .novolog) -> [DoseEntry] {
  83. let fixture: [JSONDictionary] = loadFixture(resourceName)
  84. let dateFormatter = ISO8601DateFormatter.localTimeDate(timeZone: fixtureTimeZone)
  85. return fixture.compactMap {
  86. guard let unit = DoseUnit(rawValue: $0["unit"] as! String),
  87. let pumpType = PumpEventType(rawValue: $0["type"] as! String),
  88. let type = DoseType(pumpEventType: pumpType)
  89. else {
  90. return nil
  91. }
  92. var dose = DoseEntry(
  93. type: type,
  94. startDate: dateFormatter.date(from: $0["start_at"] as! String)!,
  95. endDate: dateFormatter.date(from: $0["end_at"] as! String)!,
  96. value: $0["amount"] as! Double,
  97. unit: unit,
  98. deliveredUnits: $0["delivered"] as? Double,
  99. description: $0["description"] as? String,
  100. syncIdentifier: $0["raw"] as? String,
  101. insulinType: insulinType
  102. )
  103. if let scheduled = $0["scheduled"] as? Double {
  104. dose.scheduledBasalRate = HKQuantity(unit: unit.unit, doubleValue: scheduled)
  105. }
  106. return dose
  107. }
  108. }
  109. func loadInsulinValueFixture(_ resourceName: String) -> [InsulinValue] {
  110. let fixture: [JSONDictionary] = loadFixture(resourceName)
  111. let dateFormatter = ISO8601DateFormatter.localTimeDate(timeZone: fixtureTimeZone)
  112. return fixture.map {
  113. return InsulinValue(startDate: dateFormatter.date(from: $0["date"] as! String)!, value: $0["value"] as! Double)
  114. }
  115. }
  116. func loadGlucoseEffectFixture(_ resourceName: String) -> [GlucoseEffect] {
  117. let fixture: [JSONDictionary] = loadFixture(resourceName)
  118. let dateFormatter = ISO8601DateFormatter.localTimeDate(timeZone: fixtureTimeZone)
  119. return fixture.map {
  120. return GlucoseEffect(startDate: dateFormatter.date(from: $0["date"] as! String)!, quantity: HKQuantity(unit: HKUnit(from: $0["unit"] as! String), doubleValue:$0["amount"] as! Double))
  121. }
  122. }
  123. func loadBasalRateScheduleFixture(_ resourceName: String) -> BasalRateSchedule {
  124. let fixture: [JSONDictionary] = loadFixture(resourceName)
  125. let items = fixture.map {
  126. return RepeatingScheduleValue(startTime: TimeInterval(minutes: $0["minutes"] as! Double), value: $0["rate"] as! Double)
  127. }
  128. return BasalRateSchedule(dailyItems: items, timeZone: fixtureTimeZone)!
  129. }
  130. var insulinSensitivitySchedule: InsulinSensitivitySchedule {
  131. return InsulinSensitivitySchedule(unit: HKUnit.milligramsPerDeciliter, dailyItems: [RepeatingScheduleValue(startTime: 0.0, value: 40.0)], timeZone: fixtureTimeZone)!
  132. }
  133. func testDoseEntriesFromReservoirValues() {
  134. let input = loadReservoirFixture("reservoir_history_with_rewind_and_prime_input")
  135. let output = loadDoseFixture("reservoir_history_with_rewind_and_prime_output").reversed()
  136. let doses = input.doseEntries
  137. XCTAssertEqual(output.count, doses.count)
  138. for (expected, calculated) in zip(output, doses) {
  139. XCTAssertEqual(expected.startDate, calculated.startDate)
  140. XCTAssertEqual(expected.endDate, calculated.endDate)
  141. XCTAssertEqual(expected.value, calculated.value, accuracy: Double(Float.ulpOfOne))
  142. XCTAssertEqual(expected.unit, calculated.unit)
  143. }
  144. }
  145. func testContinuousReservoirValues() {
  146. var input = loadReservoirFixture("reservoir_history_with_rewind_and_prime_input")
  147. let within = TimeInterval(minutes: 30)
  148. let dateFormatter = ISO8601DateFormatter.localTimeDate(timeZone: fixtureTimeZone)
  149. XCTAssertTrue(input.isContinuous(from: dateFormatter.date(from: "2016-01-30T16:40:00")!, to: dateFormatter.date(from: "2016-01-30T20:40:00")!, within: within))
  150. // We don't assert whether it's "stale".
  151. XCTAssertTrue(input.isContinuous(from: dateFormatter.date(from: "2016-01-30T16:40:00")!, to: dateFormatter.date(from: "2016-01-30T22:40:00")!, within: within))
  152. XCTAssertTrue(input.isContinuous(from: dateFormatter.date(from: "2016-01-30T16:40:00")!, to: Date(), within: within))
  153. // The values must extend the startDate boundary
  154. XCTAssertFalse(input.isContinuous(from: dateFormatter.date(from: "2016-01-30T15:00:00")!, to: dateFormatter.date(from: "2016-01-30T20:40:00")!, within: within))
  155. // (the boundary condition is GTE)
  156. XCTAssertTrue(input.isContinuous(from: dateFormatter.date(from: "2016-01-30T16:00:42")!, to: dateFormatter.date(from: "2016-01-30T20:40:00")!, within: within))
  157. // Rises in reservoir volume taint the entire range
  158. XCTAssertFalse(input.isContinuous(from: dateFormatter.date(from: "2016-01-30T15:55:00")!, to: dateFormatter.date(from: "2016-01-30T20:40:00")!, within: within))
  159. // Any values of 0 taint the entire range
  160. input.append(NewReservoirValue(startDate: dateFormatter.date(from: "2016-01-30T20:37:00")!, unitVolume: 0))
  161. XCTAssertFalse(input.isContinuous(from: dateFormatter.date(from: "2016-01-30T16:40:00")!, to: dateFormatter.date(from: "2016-01-30T20:40:00")!, within: within))
  162. // As long as the 0 is within the date interval bounds
  163. XCTAssertTrue(input.isContinuous(from: dateFormatter.date(from: "2016-01-30T16:40:00")!, to: dateFormatter.date(from: "2016-01-30T19:40:00")!, within: within))
  164. }
  165. func testNonContinuousReservoirValues() {
  166. let input = loadReservoirFixture("reservoir_history_with_continuity_holes")
  167. let dateFormatter = ISO8601DateFormatter.localTimeDate(timeZone: fixtureTimeZone)
  168. XCTAssertTrue(input.isContinuous(from: dateFormatter.date(from: "2016-01-30T18:30:00")!, to: dateFormatter.date(from: "2016-01-30T20:40:00")!, within: .minutes(30)))
  169. XCTAssertFalse(input.isContinuous(from: dateFormatter.date(from: "2016-01-30T17:30:00")!, to: dateFormatter.date(from: "2016-01-30T20:40:00")!, within: .minutes(30)))
  170. }
  171. func testIOBFromSuspend() {
  172. let input = loadDoseFixture("suspend_dose")
  173. let reconciledOutput = loadDoseFixture("suspend_dose_reconciled")
  174. let normalizedOutput = loadDoseFixture("suspend_dose_reconciled_normalized")
  175. let iobOutput = loadInsulinValueFixture("suspend_dose_reconciled_normalized_iob")
  176. let basals = loadBasalRateScheduleFixture("basal")
  177. let reconciled = input.reconciled()
  178. XCTAssertEqual(reconciledOutput.count, reconciled.count)
  179. for (expected, calculated) in zip(reconciledOutput, reconciled) {
  180. XCTAssertEqual(expected.startDate, calculated.startDate)
  181. XCTAssertEqual(expected.endDate, calculated.endDate)
  182. XCTAssertEqual(expected.value, calculated.value)
  183. XCTAssertEqual(expected.unit, calculated.unit)
  184. }
  185. let normalized = reconciled.annotated(with: basals)
  186. XCTAssertEqual(normalizedOutput.count, normalized.count)
  187. for (expected, calculated) in zip(normalizedOutput, normalized) {
  188. XCTAssertEqual(expected.startDate, calculated.startDate)
  189. XCTAssertEqual(expected.endDate, calculated.endDate)
  190. XCTAssertEqual(expected.value, calculated.netBasalUnitsPerHour, accuracy: Double(Float.ulpOfOne))
  191. }
  192. let iob = normalized.insulinOnBoard(insulinModelSettings: walshModelSettings)
  193. XCTAssertEqual(iobOutput.count, iob.count)
  194. for (expected, calculated) in zip(iobOutput, iob) {
  195. XCTAssertEqual(expected.startDate, calculated.startDate)
  196. XCTAssertEqual(expected.value, calculated.value, accuracy: Double(Float.ulpOfOne))
  197. }
  198. }
  199. func testIOBFromDoses() {
  200. let input = loadDoseFixture("normalized_doses", insulinType: .novolog)
  201. let output = loadInsulinValueFixture("iob_from_doses_output")
  202. measure {
  203. _ = input.insulinOnBoard(insulinModelSettings: walshModelSettings)
  204. }
  205. let iob = input.insulinOnBoard(insulinModelSettings: walshModelSettings)
  206. XCTAssertEqual(output.count, iob.count)
  207. for (expected, calculated) in zip(output, iob) {
  208. XCTAssertEqual(expected.startDate, calculated.startDate)
  209. XCTAssertEqual(expected.value, calculated.value, accuracy: 0.5)
  210. }
  211. }
  212. func testIOBFromNoDoses() {
  213. let input: [DoseEntry] = []
  214. let iob = input.insulinOnBoard(insulinModelSettings: insulinModelSettings)
  215. XCTAssertEqual(0, iob.count)
  216. }
  217. func testInsulinOnBoardLimitsForExponentialModel() {
  218. let insulinModel = ExponentialInsulinModel(actionDuration: TimeInterval(minutes: 360), peakActivityTime: TimeInterval(minutes: 75), delay: TimeInterval(minutes: 0))
  219. let childModel = ExponentialInsulinModel(actionDuration: TimeInterval(minutes: 360), peakActivityTime: TimeInterval(minutes: 65), delay: TimeInterval(minutes: 0))
  220. XCTAssertEqual(1, insulinModel.percentEffectRemaining(at: .minutes(-1)), accuracy: 0.001)
  221. XCTAssertEqual(1, insulinModel.percentEffectRemaining(at: .minutes(0)), accuracy: 0.001)
  222. XCTAssertEqual(0, insulinModel.percentEffectRemaining(at: .minutes(360)), accuracy: 0.001)
  223. XCTAssertEqual(0, insulinModel.percentEffectRemaining(at: .minutes(361)), accuracy: 0.001)
  224. // Test random point
  225. XCTAssertEqual(0.5110493617156, insulinModel.percentEffectRemaining(at: .minutes(108)), accuracy: 0.001)
  226. // Test for child curve
  227. XCTAssertEqual(0.6002510111374046, childModel.percentEffectRemaining(at: .minutes(82)), accuracy: 0.001)
  228. }
  229. func testIOBFromDosesExponential() {
  230. let input = loadDoseFixture("normalized_doses", insulinType: .novolog)
  231. let output = loadInsulinValueFixture("iob_from_doses_exponential_output")
  232. measure {
  233. _ = input.insulinOnBoard(insulinModelSettings: insulinModelSettings)
  234. }
  235. let iob = input.insulinOnBoard(insulinModelSettings: insulinModelSettings)
  236. XCTAssertEqual(output.count, iob.count)
  237. for (expected, calculated) in zip(output, iob) {
  238. XCTAssertEqual(expected.startDate, calculated.startDate)
  239. XCTAssertEqual(expected.value, calculated.value, accuracy: 0.5)
  240. }
  241. }
  242. func testIOBFromBolusExponential() {
  243. let input = loadDoseFixture("bolus_dose", insulinType: .novolog)
  244. let output = loadInsulinValueFixture("iob_from_bolus_exponential_output")
  245. let iob = input.insulinOnBoard(insulinModelSettings: insulinModelSettings)
  246. XCTAssertEqual(output.count, iob.count)
  247. for (expected, calculated) in zip(output, iob) {
  248. XCTAssertEqual(expected.startDate, calculated.startDate)
  249. XCTAssertEqual(expected.value, calculated.value, accuracy: Double(Float.ulpOfOne))
  250. }
  251. }
  252. func testIOBFromBolus() {
  253. for hours in [2, 3, 4, 5, 5.2, 6, 7] as [Double] {
  254. let actionDuration = TimeInterval(hours: hours)
  255. let insulinModelSettings = InsulinModelSettings(model: WalshInsulinModel(actionDuration: actionDuration))!
  256. let input = loadDoseFixture("bolus_dose", insulinType: .novolog)
  257. let output = loadInsulinValueFixture("iob_from_bolus_\(Int(actionDuration.minutes))min_output")
  258. let iob = input.insulinOnBoard(insulinModelSettings: insulinModelSettings)
  259. XCTAssertEqual(output.count, iob.count)
  260. for (expected, calculated) in zip(output, iob) {
  261. XCTAssertEqual(expected.startDate, calculated.startDate)
  262. XCTAssertEqual(expected.value, calculated.value, accuracy: Double(Float.ulpOfOne))
  263. }
  264. }
  265. }
  266. func testIOBFromDosesWithDifferentInsulinCurves() {
  267. let formatter = DateFormatter.descriptionFormatter
  268. let f = { (input) in
  269. return formatter.date(from: input)!
  270. }
  271. let output = loadInsulinValueFixture("iob_from_multiple_curves_output")
  272. let doses = [
  273. DoseEntry(type: .basal, startDate: f("2018-05-15 14:42:36 +0000"), endDate: f("2018-05-16 14:42:36 +0000"), value: 0.84999999999999998, unit: .unitsPerHour, syncIdentifier: "7b02646a070f120e2200", scheduledBasalRate: nil),
  274. DoseEntry(type: .bolus, startDate: f("2018-05-15 14:44:46 +0000"), endDate: f("2018-05-15 14:44:46 +0000"), value: 0.9, unit: .units, syncIdentifier: "01004a004a006d006e22354312", scheduledBasalRate: nil),
  275. DoseEntry(type: .tempBasal, startDate: f("2018-05-15 14:42:36 +0000"), endDate: f("2018-05-15 14:42:36 +0000"), value: 0.0, unit: .unitsPerHour, syncIdentifier: "1600646a074f12", scheduledBasalRate: nil),
  276. DoseEntry(type: .tempBasal, startDate: f("2018-05-15 14:32:51 +0000"), endDate: f("2018-05-15 15:02:51 +0000"), value: 1.8999999999999999, unit: .unitsPerHour, syncIdentifier: "16017360074f12", scheduledBasalRate: nil),
  277. DoseEntry(type: .bolus, startDate: f("2018-05-15 14:52:51 +0000"), endDate: f("2018-05-15 15:52:51 +0000"), value: 0.9, unit: .units, syncIdentifier: "01004a004a006d006e22354312", scheduledBasalRate: nil),
  278. ]
  279. let iobWithoutModel = doses.insulinOnBoard(insulinModelSettings: insulinModelSettings)
  280. let dosesWithModel = [
  281. DoseEntry(type: .basal, startDate: f("2018-05-15 14:42:36 +0000"), endDate: f("2018-05-16 14:42:36 +0000"), value: 0.84999999999999998, unit: .unitsPerHour, syncIdentifier: "7b02646a070f120e2200", scheduledBasalRate: nil),
  282. DoseEntry(type: .bolus, startDate: f("2018-05-15 14:44:46 +0000"), endDate: f("2018-05-15 14:44:46 +0000"), value: 0.9, unit: .units, syncIdentifier: "01004a004a006d006e22354312", scheduledBasalRate: nil, insulinType: .fiasp),
  283. DoseEntry(type: .tempBasal, startDate: f("2018-05-15 14:42:36 +0000"), endDate: f("2018-05-15 14:42:36 +0000"), value: 0.0, unit: .unitsPerHour, syncIdentifier: "1600646a074f12", scheduledBasalRate: nil),
  284. DoseEntry(type: .tempBasal, startDate: f("2018-05-15 14:32:51 +0000"), endDate: f("2018-05-15 15:02:51 +0000"), value: 1.8999999999999999, unit: .unitsPerHour, syncIdentifier: "16017360074f12", scheduledBasalRate: nil),
  285. DoseEntry(type: .bolus, startDate: f("2018-05-15 14:52:51 +0000"), endDate: f("2018-05-15 15:52:51 +0000"), value: 0.9, unit: .units, syncIdentifier: "01004a004a006d006e22354312", scheduledBasalRate: nil, insulinType: .novolog),
  286. ]
  287. let insulinModelSettings = InsulinModelSettings(model: ExponentialInsulinModelPreset.rapidActingChild)!
  288. let iobWithModel = dosesWithModel.insulinOnBoard(insulinModelSettings: insulinModelSettings)
  289. XCTAssertEqual(iobWithoutModel.count, iobWithModel.count)
  290. for (expected, calculated) in zip(output, iobWithModel) {
  291. XCTAssertEqual(expected.startDate, calculated.startDate)
  292. XCTAssertEqual(expected.value, calculated.value, accuracy: Double(Float.ulpOfOne))
  293. }
  294. }
  295. func testIOBFromReservoirDoses() {
  296. let input = loadDoseFixture("normalized_reservoir_history_output")
  297. let output = loadInsulinValueFixture("iob_from_reservoir_output")
  298. let insulinModel = WalshInsulinModel(actionDuration: TimeInterval(hours: 4))
  299. let insulinModelSettings = InsulinModelSettings(model: insulinModel)!
  300. measure {
  301. _ = input.insulinOnBoard(insulinModelSettings: insulinModelSettings)
  302. }
  303. let iob = input.insulinOnBoard(insulinModelSettings: insulinModelSettings)
  304. XCTAssertEqual(output.count, iob.count)
  305. for (expected, calculated) in zip(output, iob) {
  306. XCTAssertEqual(expected.startDate, calculated.startDate)
  307. XCTAssertEqual(expected.value, calculated.value, accuracy: 0.4)
  308. }
  309. }
  310. func testNormalizeReservoirDoses() {
  311. let input = loadDoseFixture("reservoir_history_with_rewind_and_prime_output")
  312. let output = loadDoseFixture("normalized_reservoir_history_output")
  313. let basals = loadBasalRateScheduleFixture("basal")
  314. measure {
  315. _ = input.annotated(with: basals)
  316. }
  317. let doses = input.annotated(with: basals)
  318. XCTAssertEqual(output.count, doses.count)
  319. // Total delivery on split doses should add up to delivery from original doses
  320. XCTAssertEqual(
  321. input.map {$0.unitsInDeliverableIncrements}.reduce(0,+),
  322. doses.map {$0.unitsInDeliverableIncrements}.reduce(0,+),
  323. accuracy: Double(Float.ulpOfOne))
  324. for (expected, calculated) in zip(output, doses) {
  325. XCTAssertEqual(expected.startDate, calculated.startDate)
  326. XCTAssertEqual(expected.endDate, calculated.endDate)
  327. XCTAssertEqual(expected.value, calculated.unitsPerHour, accuracy: Double(Float.ulpOfOne))
  328. XCTAssertEqual(expected.scheduledBasalRate, calculated.scheduledBasalRate)
  329. }
  330. }
  331. func testNormalizeEdgeCaseDoses() {
  332. let input = loadDoseFixture("normalize_edge_case_doses_input")
  333. let output = loadDoseFixture("normalize_edge_case_doses_output")
  334. let basals = loadBasalRateScheduleFixture("basal")
  335. measure {
  336. _ = input.annotated(with: basals)
  337. }
  338. let doses = input.annotated(with: basals)
  339. XCTAssertEqual(output.count, doses.count)
  340. for (expected, calculated) in zip(output, doses) {
  341. XCTAssertEqual(expected.startDate, calculated.startDate)
  342. XCTAssertEqual(expected.endDate, calculated.endDate)
  343. XCTAssertEqual(expected.value, calculated.unit == .units ? calculated.netBasalUnits : calculated.netBasalUnitsPerHour)
  344. XCTAssertEqual(expected.unit, calculated.unit)
  345. }
  346. }
  347. func testReconcileTempBasals() {
  348. // Fixture contains numerous overlapping temp basals, as well as a Suspend event interleaved with a temp basal
  349. let input = loadDoseFixture("reconcile_history_input")
  350. let output = loadDoseFixture("reconcile_history_output").sorted { $0.startDate < $1.startDate }
  351. let doses = input.reconciled().sorted { $0.startDate < $1.startDate }
  352. XCTAssertEqual(output.count, doses.count)
  353. for (expected, calculated) in zip(output, doses) {
  354. XCTAssertEqual(expected.startDate, calculated.startDate)
  355. XCTAssertEqual(expected.endDate, calculated.endDate)
  356. XCTAssertEqual(expected.value, calculated.value)
  357. XCTAssertEqual(expected.unit, calculated.unit)
  358. XCTAssertEqual(expected.syncIdentifier, calculated.syncIdentifier)
  359. }
  360. }
  361. func testReconcileResumeBeforeRewind() {
  362. let input = loadDoseFixture("reconcile_resume_before_rewind_input")
  363. let output = loadDoseFixture("reconcile_resume_before_rewind_output")
  364. let doses = input.reconciled()
  365. XCTAssertEqual(output.count, doses.count)
  366. for (expected, calculated) in zip(output, doses) {
  367. XCTAssertEqual(expected.startDate, calculated.startDate)
  368. XCTAssertEqual(expected.endDate, calculated.endDate)
  369. XCTAssertEqual(expected.value, calculated.value)
  370. XCTAssertEqual(expected.unit, calculated.unit)
  371. XCTAssertEqual(expected.syncIdentifier, calculated.syncIdentifier)
  372. }
  373. }
  374. func testGlucoseEffectFromBolus() {
  375. let input = loadDoseFixture("bolus_dose")
  376. let output = loadGlucoseEffectFixture("effect_from_bolus_output")
  377. let insulinSensitivitySchedule = self.insulinSensitivitySchedule
  378. measure {
  379. _ = input.glucoseEffects(insulinModelSettings: walshModelSettings, insulinSensitivity: insulinSensitivitySchedule)
  380. }
  381. let effects = input.glucoseEffects(insulinModelSettings: walshModelSettings, insulinSensitivity: insulinSensitivitySchedule)
  382. XCTAssertEqual(Float(output.count), Float(effects.count), accuracy: 1.0)
  383. for (expected, calculated) in zip(output, effects) {
  384. XCTAssertEqual(expected.startDate, calculated.startDate)
  385. XCTAssertEqual(expected.quantity.doubleValue(for: .milligramsPerDeciliter), calculated.quantity.doubleValue(for: .milligramsPerDeciliter), accuracy: 1.0)
  386. }
  387. }
  388. func testGlucoseEffectFromShortTempBasal() {
  389. let input = loadDoseFixture("short_basal_dose")
  390. let output = loadGlucoseEffectFixture("effect_from_bolus_output")
  391. let insulinSensitivitySchedule = self.insulinSensitivitySchedule
  392. measure {
  393. _ = input.glucoseEffects(insulinModelSettings: walshModelSettings, insulinSensitivity: insulinSensitivitySchedule)
  394. }
  395. let effects = input.glucoseEffects(insulinModelSettings: walshModelSettings, insulinSensitivity: insulinSensitivitySchedule)
  396. XCTAssertEqual(output.count, effects.count)
  397. for (expected, calculated) in zip(output, effects) {
  398. XCTAssertEqual(expected.startDate, calculated.startDate)
  399. XCTAssertEqual(expected.quantity.doubleValue(for: .milligramsPerDeciliter), calculated.quantity.doubleValue(for: .milligramsPerDeciliter), accuracy: Double(Float.ulpOfOne))
  400. }
  401. }
  402. func testGlucoseEffectFromTempBasal() {
  403. let input = loadDoseFixture("basal_dose")
  404. let output = loadGlucoseEffectFixture("effect_from_basal_output")
  405. let insulinSensitivitySchedule = self.insulinSensitivitySchedule
  406. measure {
  407. _ = input.glucoseEffects(insulinModelSettings: walshModelSettings, insulinSensitivity: insulinSensitivitySchedule)
  408. }
  409. let effects = input.glucoseEffects(insulinModelSettings: walshModelSettings, insulinSensitivity: insulinSensitivitySchedule)
  410. XCTAssertEqual(output.count, effects.count)
  411. for (expected, calculated) in zip(output, effects) {
  412. XCTAssertEqual(expected.startDate, calculated.startDate)
  413. XCTAssertEqual(expected.quantity.doubleValue(for: HKUnit.milligramsPerDeciliter), calculated.quantity.doubleValue(for: HKUnit.milligramsPerDeciliter), accuracy: 1.0, String(describing: expected.startDate))
  414. }
  415. }
  416. func testGlucoseEffectFromTempBasalExponential() {
  417. let input = loadDoseFixture("basal_dose_with_delivered", insulinType: .novolog)
  418. let output = loadGlucoseEffectFixture("effect_from_basal_output_exponential")
  419. let effects = input.glucoseEffects(insulinModelSettings: insulinModelSettings, insulinSensitivity: insulinSensitivitySchedule)
  420. XCTAssertEqual(output.count, effects.count)
  421. for (expected, calculated) in zip(output, effects) {
  422. XCTAssertEqual(expected.startDate, calculated.startDate)
  423. XCTAssertEqual(expected.quantity.doubleValue(for: HKUnit.milligramsPerDeciliter), calculated.quantity.doubleValue(for: HKUnit.milligramsPerDeciliter), accuracy: 1.0, String(describing: expected.startDate))
  424. print(expected.quantity.doubleValue(for: HKUnit.milligramsPerDeciliter), calculated.quantity.doubleValue(for: HKUnit.milligramsPerDeciliter))
  425. }
  426. }
  427. func testGlucoseEffectFromHistory() {
  428. let input = loadDoseFixture("normalized_doses")
  429. let output = loadGlucoseEffectFixture("effect_from_history_output")
  430. let insulinSensitivitySchedule = self.insulinSensitivitySchedule
  431. measure {
  432. _ = input.glucoseEffects(insulinModelSettings: walshModelSettings, insulinSensitivity: insulinSensitivitySchedule)
  433. }
  434. let effects = input.glucoseEffects(insulinModelSettings: walshModelSettings, insulinSensitivity: insulinSensitivitySchedule)
  435. XCTAssertEqual(output.count, effects.count)
  436. for (expected, calculated) in zip(output, effects) {
  437. XCTAssertEqual(expected.startDate, calculated.startDate)
  438. XCTAssertEqual(expected.quantity.doubleValue(for: HKUnit.milligramsPerDeciliter), calculated.quantity.doubleValue(for: HKUnit.milligramsPerDeciliter), accuracy: 3.0)
  439. }
  440. }
  441. func testGlucoseEffectFromNoDoses() {
  442. let input: [DoseEntry] = []
  443. let insulinSensitivitySchedule = self.insulinSensitivitySchedule
  444. let effects = input.glucoseEffects(insulinModelSettings: insulinModelSettings, insulinSensitivity: insulinSensitivitySchedule)
  445. XCTAssertEqual(0, effects.count)
  446. }
  447. func testTotalDelivery() {
  448. let input = loadDoseFixture("normalize_edge_case_doses_input")
  449. let output = input.totalDelivery
  450. XCTAssertEqual(18.8, output, accuracy: 0.01)
  451. }
  452. func testTrimContinuingDoses() {
  453. let dateFormatter = ISO8601DateFormatter.localTimeDate(timeZone: fixtureTimeZone)
  454. let input = loadDoseFixture("normalized_doses").reversed()
  455. // Last temp ends at 2015-10-15T22:29:50
  456. let endDate = dateFormatter.date(from: "2015-10-15T22:25:50")!
  457. let trimmed = input.map { $0.trimmed(to: endDate) }
  458. print(input, "\n\n\n")
  459. print(trimmed)
  460. XCTAssertEqual(endDate, trimmed.last!.endDate)
  461. XCTAssertEqual(input.count, trimmed.count)
  462. }
  463. func testDosesOverlayBasalProfile() {
  464. let dateFormatter = ISO8601DateFormatter.localTimeDate(timeZone: fixtureTimeZone)
  465. let input = loadDoseFixture("reconcile_history_output").sorted { $0.startDate < $1.startDate }
  466. let output = loadDoseFixture("doses_overlay_basal_profile_output")
  467. let basals = loadBasalRateScheduleFixture("basal")
  468. let doses = input.annotated(with: basals).overlayBasalSchedule(
  469. basals,
  470. // A start date before the first entry should generate a basal
  471. startingAt: dateFormatter.date(from: "2016-02-15T14:01:04")!,
  472. endingAt: Date(),
  473. insertingBasalEntries: true
  474. )
  475. XCTAssertEqual(output.count, doses.count)
  476. XCTAssertEqual(doses.first?.startDate, dateFormatter.date(from: "2016-02-15T14:01:04")!)
  477. for (expected, calculated) in zip(output, doses) {
  478. XCTAssertEqual(expected.startDate, calculated.startDate)
  479. XCTAssertEqual(expected.endDate, calculated.endDate)
  480. XCTAssertEqual(expected.value, calculated.value)
  481. XCTAssertEqual(expected.unit, calculated.unit)
  482. if let syncID = expected.syncIdentifier {
  483. XCTAssertEqual(syncID, calculated.syncIdentifier!)
  484. }
  485. }
  486. // Test trimming end
  487. let dosesTrimmedEnd = input[0..<input.count - 11].annotated(with: basals).overlayBasalSchedule(
  488. basals,
  489. startingAt: dateFormatter.date(from: "2016-02-15T14:01:04")!,
  490. // An end date before some input entries should omit them
  491. endingAt: dateFormatter.date(from: "2016-02-15T19:45:00")!,
  492. insertingBasalEntries: true
  493. )
  494. XCTAssertEqual(output.count - 14, dosesTrimmedEnd.count)
  495. // The BasalProfileStart event shouldn't be generated
  496. XCTAssertEqual(dosesTrimmedEnd.last!.endDate, dateFormatter.date(from: "2016-02-15T19:36:11")!)
  497. // Test a start date equal to the first entry, the expected case
  498. let dosesMatchingStart = input.overlayBasalSchedule(
  499. basals,
  500. startingAt: dateFormatter.date(from: "2016-02-15T15:06:05")!,
  501. endingAt: Date(),
  502. insertingBasalEntries: true
  503. )
  504. // The inserted entries aren't included
  505. XCTAssertEqual(output.count - 2, dosesMatchingStart.count)
  506. XCTAssertEqual(dosesMatchingStart.first!.startDate, dateFormatter.date(from: "2016-02-15T14:58:02")!)
  507. }
  508. func testReconcilingBasalProfileStartBeforeResume() {
  509. let formatter = DateFormatter.descriptionFormatter
  510. let f = { (input) in
  511. return formatter.date(from: input)!
  512. }
  513. // getRecentPumpEventValues
  514. let doses = [
  515. DoseEntry(type: .tempBasal, startDate: f("2018-04-04 05:14:15 +0000"), endDate: f("2018-04-04 05:44:15 +0000"), value: 1.9, unit: .unitsPerHour, syncIdentifier: "16014f0e164312", scheduledBasalRate: nil),
  516. DoseEntry(type: .resume, startDate: f("2018-04-04 05:11:02 +0000"), endDate: f("2018-04-04 05:11:02 +0000"), value: 0.0, unit: .units, syncIdentifier: "1f20420b160312", scheduledBasalRate: nil),
  517. DoseEntry(type: .basal, startDate: f("2018-04-04 05:11:01 +0000"), endDate: f("2018-04-05 05:11:01 +0000"), value: 1.2, unit: .unitsPerHour, syncIdentifier: "7b05410b1603122a3000", scheduledBasalRate: nil),
  518. DoseEntry(type: .suspend, startDate: f("2018-04-04 04:40:06 +0000"), endDate: f("2018-04-04 04:40:06 +0000"), value: 0.0, unit: .units, syncIdentifier: "1e014628150312", scheduledBasalRate: nil),
  519. DoseEntry(type: .tempBasal, startDate: f("2018-04-04 04:39:15 +0000"), endDate: f("2018-04-04 05:09:15 +0000"), value: 4.5, unit: .unitsPerHour, syncIdentifier: "16014f27154312", scheduledBasalRate: nil),
  520. DoseEntry(type: .bolus, startDate: f("2018-04-04 04:34:46 +0000"), endDate: f("2018-04-04 04:34:46 +0000"), value: 1.85, unit: .units, syncIdentifier: "01004a004a006d006e22354312", scheduledBasalRate: nil),
  521. DoseEntry(type: .tempBasal, startDate: f("2018-04-04 04:34:15 +0000"), endDate: f("2018-04-04 05:04:15 +0000"), value: 1.85, unit: .unitsPerHour, syncIdentifier: "16014f22154312", scheduledBasalRate: nil)
  522. ]
  523. let reconciled = [
  524. DoseEntry(type: .bolus, startDate: f("2018-04-04 04:34:46 +0000"), endDate: f("2018-04-04 04:34:46 +0000"), value: 1.85, unit: .units, deliveredUnits: 1.85, syncIdentifier: "01004a004a006d006e22354312", scheduledBasalRate: nil),
  525. DoseEntry(type: .tempBasal, startDate: f("2018-04-04 04:34:15 +0000"), endDate: f("2018-04-04 04:39:15 +0000"), value: 1.85, unit: .unitsPerHour, deliveredUnits: 0.15, syncIdentifier: "16014f22154312", scheduledBasalRate: nil),
  526. DoseEntry(type: .tempBasal, startDate: f("2018-04-04 04:39:15 +0000"), endDate: f("2018-04-04 04:40:06 +0000"), value: 4.5, unit: .unitsPerHour, deliveredUnits: 0.05, syncIdentifier: "16014f27154312", scheduledBasalRate: nil),
  527. DoseEntry(type: .suspend, startDate: f("2018-04-04 04:40:06 +0000"), endDate: f("2018-04-04 05:11:02 +0000"), value: 0.0, unit: .units, deliveredUnits: 0.0, syncIdentifier: "1e014628150312", scheduledBasalRate: nil),
  528. DoseEntry(type: .basal, startDate: f("2018-04-04 05:11:02 +0000"), endDate: f("2018-04-04 05:14:15 +0000"), value: 1.2, unit: .unitsPerHour, deliveredUnits: 0.06433333333333334, syncIdentifier: "1f20420b160312", scheduledBasalRate: nil),
  529. DoseEntry(type: .tempBasal, startDate: f("2018-04-04 05:14:15 +0000"), endDate: f("2018-04-04 05:44:15 +0000"), value: 1.9, unit: .unitsPerHour, deliveredUnits: 0.95, syncIdentifier: "16014f0e164312", scheduledBasalRate: nil),
  530. ]
  531. XCTAssertEqual(reconciled, doses.reversed().reconciled())
  532. }
  533. func testReconcileMultipleResumes() {
  534. let formatter = DateFormatter.descriptionFormatter
  535. let f = { (input) in
  536. return formatter.date(from: input)!
  537. }
  538. let doses = [
  539. DoseEntry(type: .basal, startDate: f("2018-05-15 14:42:36 +0000"), endDate: f("2018-05-16 14:42:36 +0000"), value: 0.84999999999999998, unit: .unitsPerHour, syncIdentifier: "7b02646a070f120e2200", scheduledBasalRate: nil),
  540. DoseEntry(type: .tempBasal, startDate: f("2018-05-15 14:42:36 +0000"), endDate: f("2018-05-15 14:42:36 +0000"), value: 0.0, unit: .unitsPerHour, syncIdentifier: "1600646a074f12", scheduledBasalRate: nil),
  541. DoseEntry(type: .tempBasal, startDate: f("2018-05-15 14:32:51 +0000"), endDate: f("2018-05-15 15:02:51 +0000"), value: 1.8999999999999999, unit: .unitsPerHour, syncIdentifier: "16017360074f12", scheduledBasalRate: nil),
  542. DoseEntry(type: .tempBasal, startDate: f("2018-05-15 14:32:49 +0000"), endDate: f("2018-05-15 15:02:49 +0000"), value: 1.8999999999999999, unit: .unitsPerHour, syncIdentifier: "16017160074f12", scheduledBasalRate: nil),
  543. DoseEntry(type: .basal, startDate: f("2018-05-15 14:25:42 +0000"), endDate: f("2018-05-16 14:25:42 +0000"), value: 0.84999999999999998, unit: .unitsPerHour, syncIdentifier: "7b026a59070f120e2200", scheduledBasalRate: nil),
  544. DoseEntry(type: .resume, startDate: f("2018-05-15 14:24:04 +0000"), endDate: f("2018-05-15 14:24:04 +0000"), value: 0, unit: .units, syncIdentifier: "prime2", scheduledBasalRate: nil),
  545. DoseEntry(type: .resume, startDate: f("2018-05-15 14:22:28 +0000"), endDate: f("2018-05-15 14:22:28 +0000"), value: 0, unit: .units, syncIdentifier: "prime1", scheduledBasalRate: nil),
  546. DoseEntry(type: .suspend, startDate: f("2018-05-15 14:21:33 +0000"), endDate: f("2018-05-15 14:21:33 +0000"), value: 0.0, unit: .units, syncIdentifier: "21006155070f12", scheduledBasalRate: nil),
  547. DoseEntry(type: .tempBasal, startDate: f("2018-05-15 14:10:29 +0000"), endDate: f("2018-05-15 14:10:29 +0000"), value: 0.0, unit: .unitsPerHour, syncIdentifier: "16005d4a074f12", scheduledBasalRate: nil),
  548. DoseEntry(type: .basal, startDate: f("2018-05-15 14:10:29 +0000"), endDate: f("2018-05-16 14:10:29 +0000"), value: 0.84999999999999998, unit: .unitsPerHour, syncIdentifier: "7b025d4a070f120e2200", scheduledBasalRate: nil),
  549. DoseEntry(type: .tempBasal, startDate: f("2018-05-15 14:05:29 +0000"), endDate: f("2018-05-15 14:35:29 +0000"), value: 2.9249999999999998, unit: .unitsPerHour, syncIdentifier: "16015d45074f12", scheduledBasalRate: nil),
  550. ]
  551. let reconciled = [
  552. DoseEntry(type: .tempBasal, startDate: f("2018-05-15 14:05:29 +0000"), endDate: f("2018-05-15 14:10:29 +0000"), value: 2.9249999999999998, unit: .unitsPerHour, deliveredUnits: 0.25, description: nil, syncIdentifier: "16015d45074f12", scheduledBasalRate: nil),
  553. DoseEntry(type: .tempBasal, startDate: f("2018-05-15 14:10:29 +0000"), endDate: f("2018-05-15 14:10:29 +0000"), value: 0.0, unit: .unitsPerHour, deliveredUnits: 0.0, description: nil, syncIdentifier: "16005d4a074f12", scheduledBasalRate: nil),
  554. DoseEntry(type: .suspend, startDate: f("2018-05-15 14:21:33 +0000"), endDate: f("2018-05-15 14:22:28 +0000"), value: 0.0, unit: .units, deliveredUnits: 0.0, description: nil, syncIdentifier: "21006155070f12", scheduledBasalRate: nil),
  555. DoseEntry(type: .basal, startDate: f("2018-05-15 14:25:42 +0000"), endDate: f("2018-05-15 14:32:49 +0000"), value: 0.84999999999999998, unit: .unitsPerHour, deliveredUnits: 0.10081944444444443,description: nil, syncIdentifier: "7b026a59070f120e2200", scheduledBasalRate: nil),
  556. DoseEntry(type: .tempBasal, startDate: f("2018-05-15 14:32:49 +0000"), endDate: f("2018-05-15 14:32:51 +0000"), value: 1.8999999999999999, unit: .unitsPerHour, deliveredUnits: 0.0, description: nil, syncIdentifier: "16017160074f12", scheduledBasalRate: nil),
  557. DoseEntry(type: .tempBasal, startDate: f("2018-05-15 14:32:51 +0000"), endDate: f("2018-05-15 14:42:36 +0000"), value: 1.8999999999999999, unit: .unitsPerHour, deliveredUnits: 0.3, description: nil, syncIdentifier: "16017360074f12", scheduledBasalRate: nil),
  558. DoseEntry(type: .basal, startDate: f("2018-05-15 14:42:36 +0000"), endDate: f("2018-05-16 14:42:36 +0000"), value: 0.84999999999999998, unit: .unitsPerHour, deliveredUnits: 20.4, description: nil, syncIdentifier: "7b02646a070f120e2200", scheduledBasalRate: nil)
  559. ]
  560. XCTAssertEqual(reconciled, doses.reversed().reconciled())
  561. }
  562. func testSuspendAndBasalProfileStartInteraction() {
  563. let formatter = DateFormatter.descriptionFormatter
  564. let f = { (input) in
  565. return formatter.date(from: input)!
  566. }
  567. let doses = [
  568. DoseEntry(type: .tempBasal, startDate: f("2018-07-11 05:02:15 +0000"), endDate: f("2018-07-11 05:32:15 +0000"), value: 0.0, unit: .unitsPerHour, scheduledBasalRate: nil),
  569. DoseEntry(type: .basal, startDate: f("2018-07-11 05:01:14 +0000"), endDate: f("2018-07-12 05:01:14 +0000"), value: 1.2, unit: .unitsPerHour),
  570. DoseEntry(type: .resume, startDate: f("2018-07-11 05:01:14 +0000"), endDate: f("2018-07-11 05:01:14 +0000"), value: 0.0, unit: .units),
  571. DoseEntry(type: .suspend, startDate: f("2018-07-11 04:31:55 +0000"), endDate: f("2018-07-11 04:31:55 +0000"), value: 0.0, unit: .units),
  572. DoseEntry(type: .basal, startDate: f("2018-07-11 04:12:15 +0000"), endDate: f("2018-07-12 04:12:15 +0000"), value: 1.2, unit: .unitsPerHour),
  573. DoseEntry(type: .tempBasal, startDate: f("2018-07-11 04:12:15 +0000"), endDate: f("2018-07-11 04:12:15 +0000"), value: 0.0, unit: .unitsPerHour),
  574. DoseEntry(type: .tempBasal, startDate: f("2018-07-11 04:07:15 +0000"), endDate: f("2018-07-11 04:37:15 +0000"), value: 0.675, unit: .unitsPerHour),
  575. DoseEntry(type: .basal, startDate: f("2018-07-11 04:00:00 +0000"), endDate: f("2018-07-12 04:00:00 +0000"), value: 1.2, unit: .unitsPerHour),
  576. ]
  577. let reconciled = [
  578. DoseEntry(type: .basal, startDate: f("2018-07-11 04:00:00 +0000"), endDate: f("2018-07-11 04:07:15 +0000"), value: 1.2, unit: .unitsPerHour, deliveredUnits: 0.145),
  579. DoseEntry(type: .tempBasal, startDate: f("2018-07-11 04:07:15 +0000"), endDate: f("2018-07-11 04:12:15 +0000"), value: 0.675, unit: .unitsPerHour, deliveredUnits: 0.05),
  580. DoseEntry(type: .basal, startDate: f("2018-07-11 04:12:15 +0000"), endDate: f("2018-07-11 04:31:55 +0000"), value: 1.2, unit: .unitsPerHour, deliveredUnits: 0.3933333333333333),
  581. DoseEntry(type: .suspend, startDate: f("2018-07-11 04:31:55 +0000"), endDate: f("2018-07-11 05:01:14 +0000"), value: 0.0, unit: .units, deliveredUnits: 0.0),
  582. DoseEntry(type: .basal, startDate: f("2018-07-11 05:01:14 +0000"), endDate: f("2018-07-11 05:02:15 +0000"), value: 1.2, unit: .unitsPerHour, deliveredUnits: 0.02033333333333333),
  583. DoseEntry(type: .tempBasal, startDate: f("2018-07-11 05:02:15 +0000"), endDate: f("2018-07-11 05:32:15 +0000"), value: 0.0, unit: .unitsPerHour, deliveredUnits: 0.0)
  584. ]
  585. XCTAssertEqual(reconciled, doses.reversed().reconciled())
  586. }
  587. func testOverlayBasalScheduleWithSuspend() {
  588. let formatter = DateFormatter.descriptionFormatter
  589. let f = { (input) in
  590. return formatter.date(from: input)!
  591. }
  592. let reconciled = [
  593. DoseEntry(type: .tempBasal, startDate: f("2018-07-11 04:07:15 +0000"), endDate: f("2018-07-11 04:12:15 +0000"), value: 0.67500000000000004, unit: .unitsPerHour),
  594. DoseEntry(type: .suspend, startDate: f("2018-07-11 04:31:55 +0000"), endDate: f("2018-07-11 05:01:14 +0000"), value: 0.0, unit: .units),
  595. DoseEntry(type: .tempBasal, startDate: f("2018-07-11 05:02:15 +0000"), endDate: f("2018-07-11 05:32:15 +0000"), value: 0.0, unit: .unitsPerHour)
  596. ]
  597. let reconciledWithBasal = [
  598. DoseEntry(type: .basal, startDate: f("2018-07-11 04:00:00 +0000"), endDate: f("2018-07-11 04:07:15 +0000"), value: 1.2, unit: .unitsPerHour, syncIdentifier: "BasalRateSchedule 2018-07-11T04:00:00Z 2018-07-11T04:07:15Z"),
  599. DoseEntry(type: .tempBasal, startDate: f("2018-07-11 04:07:15 +0000"), endDate: f("2018-07-11 04:12:15 +0000"), value: 0.67500000000000004, unit: .unitsPerHour),
  600. DoseEntry(type: .basal, startDate: f("2018-07-11 04:12:15 +0000"), endDate: f("2018-07-11 04:31:55 +0000"), value: 1.2, unit: .unitsPerHour, syncIdentifier: "BasalRateSchedule 2018-07-11T04:12:15Z 2018-07-11T04:31:55Z"),
  601. DoseEntry(type: .suspend, startDate: f("2018-07-11 04:31:55 +0000"), endDate: f("2018-07-11 05:01:14 +0000"), value: 0.0, unit: .units),
  602. DoseEntry(type: .basal, startDate: f("2018-07-11 05:01:14 +0000"), endDate: f("2018-07-11 05:02:15 +0000"), value: 1.2, unit: .unitsPerHour, syncIdentifier: "BasalRateSchedule 2018-07-11T05:01:14Z 2018-07-11T05:02:15Z"),
  603. DoseEntry(type: .tempBasal, startDate: f("2018-07-11 05:02:15 +0000"), endDate: f("2018-07-11 05:32:15 +0000"), value: 0.0, unit: .unitsPerHour)
  604. ]
  605. let basalSchedule = BasalRateSchedule(dailyItems: [RepeatingScheduleValue(startTime: 0, value: 1.2)])
  606. XCTAssertEqual(reconciledWithBasal, reconciled.overlayBasalSchedule(basalSchedule!, startingAt: f("2018-07-11 04:00:00 +0000"), endingAt: f("2018-07-11 05:32:15 +0000"), insertingBasalEntries: true))
  607. }
  608. func testAppendedUnionOfPumpEvents() {
  609. let formatter = DateFormatter.descriptionFormatter
  610. let f = { (input) in
  611. return formatter.date(from: input)!
  612. }
  613. let unit = DoseEntry.unitsPerHour
  614. let normalizedDoseEntries = [
  615. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 03:34:29 +0000"), endDate: f("2018-07-15 03:54:29 +0000"), value: 0.0, unit: .unitsPerHour, syncIdentifier: "16015de2144e12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1.2)),
  616. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 03:54:29 +0000"), endDate: f("2018-07-15 04:14:31 +0000"), value: 0.0, unit: .unitsPerHour, syncIdentifier: "16015df6144e12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1.2)),
  617. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 04:14:31 +0000"), endDate: f("2018-07-15 04:29:28 +0000"), value: 0.0, unit: .unitsPerHour, syncIdentifier: "16015fce154e12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1.2)),
  618. DoseEntry(type: .basal, startDate: f("2018-07-15 04:29:28 +0000"), endDate: f("2018-07-15 04:44:28 +0000"), value: 1.2, unit: .unitsPerHour, syncIdentifier: "7b055cdd150e122a3000", scheduledBasalRate: nil),
  619. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 04:44:28 +0000"), endDate: f("2018-07-15 04:49:29 +0000"), value: 3.6499999999999999, unit: .unitsPerHour, syncIdentifier: "16015cec154e12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1.2)),
  620. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 04:49:29 +0000"), endDate: f("2018-07-15 04:54:28 +0000"), value: 3.8500000000000001, unit: .unitsPerHour, syncIdentifier: "16015df1154e12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1.2)),
  621. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 04:54:28 +0000"), endDate: f("2018-07-15 04:59:28 +0000"), value: 3.5750000000000002, unit: .unitsPerHour, syncIdentifier: "16015cf6154e12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1.2)),
  622. DoseEntry(type: .bolus, startDate: f("2018-07-15 05:00:01 +0000"), endDate: f("2018-07-15 05:00:01 +0000"), value: 3.0, unit: .units, syncIdentifier: "0100780078004c0041c0364e12", scheduledBasalRate: nil),
  623. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 04:59:28 +0000"), endDate: f("2018-07-15 05:04:29 +0000"), value: 3.1000000000000001, unit: .unitsPerHour, syncIdentifier: "16015cfb154e12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1.2)),
  624. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 05:04:29 +0000"), endDate: f("2018-07-15 05:24:29 +0000"), value: 0.0, unit: .unitsPerHour, syncIdentifier: "16015dc4164e12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1.2)),
  625. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 05:24:29 +0000"), endDate: f("2018-07-15 05:44:29 +0000"), value: 0.0, unit: .unitsPerHour, syncIdentifier: "16015dd8164e12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1.2)),
  626. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 05:44:29 +0000"), endDate: f("2018-07-15 05:59:29 +0000"), value: 0.0, unit: .unitsPerHour, syncIdentifier: "16015dec164e12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1.2)),
  627. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 05:59:29 +0000"), endDate: f("2018-07-15 06:04:29 +0000"), value: 0.625, unit: .unitsPerHour, syncIdentifier: "16015dfb164e12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1.2)),
  628. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 06:04:29 +0000"), endDate: f("2018-07-15 06:09:29 +0000"), value: 0.17499999999999999, unit: .unitsPerHour, syncIdentifier: "16015dc4174e12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1.2)),
  629. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 06:09:29 +0000"), endDate: f("2018-07-15 06:14:29 +0000"), value: 1.95, unit: .unitsPerHour, syncIdentifier: "16015dc9174e12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1.2)),
  630. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 06:14:29 +0000"), endDate: f("2018-07-15 06:19:29 +0000"), value: 0.59999999999999998, unit: .unitsPerHour, syncIdentifier: "16015dce174e12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1.2)),
  631. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 06:19:29 +0000"), endDate: f("2018-07-15 06:24:29 +0000"), value: 1.8999999999999999, unit: .unitsPerHour, syncIdentifier: "16015dd3174e12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1.2)),
  632. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 06:24:29 +0000"), endDate: f("2018-07-15 06:29:29 +0000"), value: 3.9750000000000001, unit: .unitsPerHour, syncIdentifier: "16015dd8174e12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1.2)),
  633. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 06:29:29 +0000"), endDate: f("2018-07-15 06:34:28 +0000"), value: 4.0499999999999998, unit: .unitsPerHour, syncIdentifier: "16015ddd174e12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1.2)),
  634. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 06:34:28 +0000"), endDate: f("2018-07-15 06:39:28 +0000"), value: 3.0499999999999998, unit: .unitsPerHour, syncIdentifier: "16015ce2174e12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1.2)),
  635. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 06:39:28 +0000"), endDate: f("2018-07-15 06:44:29 +0000"), value: 3.625, unit: .unitsPerHour, syncIdentifier: "16015ce7174e12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1.2)),
  636. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 06:44:29 +0000"), endDate: f("2018-07-15 06:49:31 +0000"), value: 2.7999999999999998, unit: .unitsPerHour, syncIdentifier: "16015dec174e12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1.2)),
  637. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 06:49:31 +0000"), endDate: f("2018-07-15 06:54:30 +0000"), value: 1.9750000000000001, unit: .unitsPerHour, syncIdentifier: "16015ff1174e12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1.2)),
  638. DoseEntry(type: .basal, startDate: f("2018-07-15 06:54:30 +0000"), endDate: f("2018-07-15 07:00:00 +0000"), value: 1.2, unit: .unitsPerHour, syncIdentifier: "7b055ef6170e122a3000", scheduledBasalRate: nil),
  639. DoseEntry(type: .basal, startDate: f("2018-07-15 07:00:00 +0000"), endDate: f("2018-07-15 07:09:28 +0000"), value: 1.2, unit: .unitsPerHour, syncIdentifier: "7b0040c0000f12003000", scheduledBasalRate: nil),
  640. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 07:09:28 +0000"), endDate: f("2018-07-15 07:14:28 +0000"), value: 0.45000000000000001, unit: .unitsPerHour, syncIdentifier: "16015cc9004f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1.2)),
  641. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 07:14:28 +0000"), endDate: f("2018-07-15 07:19:29 +0000"), value: 0.5, unit: .unitsPerHour, syncIdentifier: "16015cce004f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1.2)),
  642. DoseEntry(type: .basal, startDate: f("2018-07-15 07:19:29 +0000"), endDate: f("2018-07-15 07:24:29 +0000"), value: 1.2, unit: .unitsPerHour, syncIdentifier: "7b005dd3000f12003000", scheduledBasalRate: nil),
  643. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 07:24:29 +0000"), endDate: f("2018-07-15 07:29:28 +0000"), value: 2.1499999999999999, unit: .unitsPerHour, syncIdentifier: "16015dd8004f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1.2)),
  644. DoseEntry(type: .basal, startDate: f("2018-07-15 07:29:29 +0000"), endDate: f("2018-07-15 07:34:29 +0000"), value: 1.2, unit: .unitsPerHour, syncIdentifier: "7b005ddd000f12003000", scheduledBasalRate: nil),
  645. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 07:34:29 +0000"), endDate: f("2018-07-15 07:39:29 +0000"), value: 1.825, unit: .unitsPerHour, syncIdentifier: "16015de2004f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1.2)),
  646. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 07:39:29 +0000"), endDate: f("2018-07-15 07:44:28 +0000"), value: 2.5249999999999999, unit: .unitsPerHour, syncIdentifier: "16015de7004f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1.2)),
  647. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 07:44:28 +0000"), endDate: f("2018-07-15 07:49:28 +0000"), value: 2.5499999999999998, unit: .unitsPerHour, syncIdentifier: "16015cec004f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1.2)),
  648. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 07:49:28 +0000"), endDate: f("2018-07-15 07:54:28 +0000"), value: 2.6000000000000001, unit: .unitsPerHour, syncIdentifier: "16015cf1004f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1.2)),
  649. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 07:54:28 +0000"), endDate: f("2018-07-15 07:59:31 +0000"), value: 2.625, unit: .unitsPerHour, syncIdentifier: "16015cf6004f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1.2)),
  650. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 07:59:31 +0000"), endDate: f("2018-07-15 08:04:30 +0000"), value: 2.2250000000000001, unit: .unitsPerHour, syncIdentifier: "16015ffb004f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1.2)),
  651. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 08:04:30 +0000"), endDate: f("2018-07-15 08:09:28 +0000"), value: 2.3500000000000001, unit: .unitsPerHour, syncIdentifier: "16015ec4014f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1.2)),
  652. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 08:09:28 +0000"), endDate: f("2018-07-15 08:14:28 +0000"), value: 2.3250000000000002, unit: .unitsPerHour, syncIdentifier: "16015cc9014f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1.2)),
  653. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 08:14:28 +0000"), endDate: f("2018-07-15 08:19:28 +0000"), value: 1.925, unit: .unitsPerHour, syncIdentifier: "16015cce014f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1.2)),
  654. DoseEntry(type: .basal, startDate: f("2018-07-15 08:19:29 +0000"), endDate: f("2018-07-15 08:24:29 +0000"), value: 1.2, unit: .unitsPerHour, syncIdentifier: "7b005dd3010f12003000", scheduledBasalRate: nil),
  655. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 08:24:29 +0000"), endDate: f("2018-07-15 08:29:29 +0000"), value: 1.8500000000000001, unit: .unitsPerHour, syncIdentifier: "16015dd8014f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1.2)),
  656. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 08:29:29 +0000"), endDate: f("2018-07-15 08:34:15 +0000"), value: 2.2250000000000001, unit: .unitsPerHour, syncIdentifier: "16015ddd014f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1.2)),
  657. DoseEntry(type: .basal, startDate: f("2018-07-15 08:34:15 +0000"), endDate: f("2018-07-15 08:49:14 +0000"), value: 1.2, unit: .unitsPerHour, syncIdentifier: "7b004fe2010f12003000", scheduledBasalRate: nil),
  658. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 08:49:14 +0000"), endDate: f("2018-07-15 08:54:14 +0000"), value: 2.5, unit: .unitsPerHour, syncIdentifier: "16014ef1014f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1.2)),
  659. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 08:54:14 +0000"), endDate: f("2018-07-15 08:59:15 +0000"), value: 3.4500000000000002, unit: .unitsPerHour, syncIdentifier: "16014ef6014f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1.2)),
  660. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 08:59:15 +0000"), endDate: f("2018-07-15 09:04:14 +0000"), value: 3.5750000000000002, unit: .unitsPerHour, syncIdentifier: "16014ffb014f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1.2)),
  661. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 09:04:14 +0000"), endDate: f("2018-07-15 09:09:15 +0000"), value: 2.875, unit: .unitsPerHour, syncIdentifier: "16014ec4024f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1.2)),
  662. DoseEntry(type: .basal, startDate: f("2018-07-15 09:09:15 +0000"), endDate: f("2018-07-15 10:00:00 +0000"), value: 1.2, unit: .unitsPerHour, syncIdentifier: "7b004fc9020f12003000", scheduledBasalRate: nil),
  663. DoseEntry(type: .basal, startDate: f("2018-07-15 10:00:00 +0000"), endDate: f("2018-07-15 11:09:15 +0000"), value: 1.0, unit: .unitsPerHour, syncIdentifier: "7b0140c0030f12062800", scheduledBasalRate: nil),
  664. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 11:09:15 +0000"), endDate: f("2018-07-15 11:14:14 +0000"), value: 0.0, unit: .unitsPerHour, syncIdentifier: "16014fc9044f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1)),
  665. DoseEntry(type: .basal, startDate: f("2018-07-15 11:14:15 +0000"), endDate: f("2018-07-15 11:39:14 +0000"), value: 1.0, unit: .unitsPerHour, syncIdentifier: "7b014fce040f12062800", scheduledBasalRate: nil),
  666. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 11:39:14 +0000"), endDate: f("2018-07-15 11:44:14 +0000"), value: 2.4750000000000001, unit: .unitsPerHour, syncIdentifier: "16014ee7044f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1)),
  667. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 11:44:14 +0000"), endDate: f("2018-07-15 11:49:15 +0000"), value: 2.3999999999999999, unit: .unitsPerHour, syncIdentifier: "16014eec044f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1)),
  668. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 11:49:15 +0000"), endDate: f("2018-07-15 11:54:14 +0000"), value: 2.3250000000000002, unit: .unitsPerHour, syncIdentifier: "16014ff1044f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1)),
  669. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 11:54:14 +0000"), endDate: f("2018-07-15 11:59:15 +0000"), value: 2.0499999999999998, unit: .unitsPerHour, syncIdentifier: "16014ef6044f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1)),
  670. DoseEntry(type: .basal, startDate: f("2018-07-15 11:59:15 +0000"), endDate: f("2018-07-15 14:00:00 +0000"), value: 1.0, unit: .unitsPerHour, syncIdentifier: "7b014ffb040f12062800", scheduledBasalRate: nil),
  671. DoseEntry(type: .basal, startDate: f("2018-07-15 14:00:00 +0000"), endDate: f("2018-07-15 14:09:48 +0000"), value: 0.90000000000000002, unit: .unitsPerHour, syncIdentifier: "7b0240c0070f120e2400", scheduledBasalRate: nil),
  672. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 14:09:48 +0000"), endDate: f("2018-07-15 14:14:15 +0000"), value: 2.0499999999999998, unit: .unitsPerHour, syncIdentifier: "160170c9074f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 0.9)),
  673. DoseEntry(type: .basal, startDate: f("2018-07-15 14:14:15 +0000"), endDate: f("2018-07-15 15:29:15 +0000"), value: 0.90000000000000002, unit: .unitsPerHour, syncIdentifier: "7b024fce070f120e2400", scheduledBasalRate: nil),
  674. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 15:29:15 +0000"), endDate: f("2018-07-15 15:34:14 +0000"), value: 1.8999999999999999, unit: .unitsPerHour, syncIdentifier: "16014fdd084f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 0.9)),
  675. DoseEntry(type: .basal, startDate: f("2018-07-15 15:34:15 +0000"), endDate: f("2018-07-15 15:39:14 +0000"), value: 0.90000000000000002, unit: .unitsPerHour, syncIdentifier: "7b024fe2080f120e2400", scheduledBasalRate: nil),
  676. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 15:39:14 +0000"), endDate: f("2018-07-15 15:44:14 +0000"), value: 1.95, unit: .unitsPerHour, syncIdentifier: "16014ee7084f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 0.9)),
  677. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 15:44:14 +0000"), endDate: f("2018-07-15 15:49:14 +0000"), value: 2.1499999999999999, unit: .unitsPerHour, syncIdentifier: "16014eec084f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 0.9)),
  678. DoseEntry(type: .bolus, startDate: f("2018-07-15 15:49:33 +0000"), endDate: f("2018-07-15 15:49:33 +0000"), value: 2.4500000000000002, unit: .units, syncIdentifier: "010062006200000061f1284f12", scheduledBasalRate: nil),
  679. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 15:49:14 +0000"), endDate: f("2018-07-15 15:54:16 +0000"), value: 1.95, unit: .unitsPerHour, syncIdentifier: "16014ef1084f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 0.9)),
  680. DoseEntry(type: .basal, startDate: f("2018-07-15 15:54:16 +0000"), endDate: f("2018-07-15 16:14:15 +0000"), value: 0.90000000000000002, unit: .unitsPerHour, syncIdentifier: "7b0250f6080f120e2400", scheduledBasalRate: nil),
  681. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 16:14:15 +0000"), endDate: f("2018-07-15 16:34:15 +0000"), value: 0.0, unit: .unitsPerHour, syncIdentifier: "16014fce094f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 0.9)),
  682. DoseEntry(type: .basal, startDate: f("2018-07-15 16:34:15 +0000"), endDate: f("2018-07-15 16:44:14 +0000"), value: 0.90000000000000002, unit: .unitsPerHour, syncIdentifier: "7b024fe2090f120e2400", scheduledBasalRate: nil),
  683. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 16:44:14 +0000"), endDate: f("2018-07-15 17:14:14 +0000"), value: 4.6500000000000004, unit: .unitsPerHour, syncIdentifier: "16014eec094f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 0.9)),
  684. DoseEntry(type: .bolus, startDate: f("2018-07-15 17:55:12 +0000"), endDate: f("2018-07-15 17:55:12 +0000"), value: 2.5499999999999998, unit: .units, syncIdentifier: "01006600660029004cf72a4f12", scheduledBasalRate: nil),
  685. DoseEntry(type: .basal, startDate: f("2018-07-15 17:14:15 +0000"), endDate: f("2018-07-15 18:30:00 +0000"), value: 0.90000000000000002, unit: .unitsPerHour, syncIdentifier: "7b024fce0a0f120e2400", scheduledBasalRate: nil),
  686. DoseEntry(type: .basal, startDate: f("2018-07-15 18:30:00 +0000"), endDate: f("2018-07-15 18:59:15 +0000"), value: 0.80000000000000004, unit: .unitsPerHour, syncIdentifier: "7b0340de0b0f12172000", scheduledBasalRate: nil),
  687. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 18:59:15 +0000"), endDate: f("2018-07-15 19:04:15 +0000"), value: 4.6500000000000004, unit: .unitsPerHour, syncIdentifier: "16014ffb0b4f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 0.8)),
  688. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 19:04:15 +0000"), endDate: f("2018-07-15 19:09:14 +0000"), value: 3.9750000000000001, unit: .unitsPerHour, syncIdentifier: "16014fc40c4f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 0.8)),
  689. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 19:09:14 +0000"), endDate: f("2018-07-15 19:19:15 +0000"), value: 4.6500000000000004, unit: .unitsPerHour, syncIdentifier: "16014ec90c4f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 0.8)),
  690. DoseEntry(type: .basal, startDate: f("2018-07-15 19:19:15 +0000"), endDate: f("2018-07-15 19:24:15 +0000"), value: 0.80000000000000004, unit: .unitsPerHour, syncIdentifier: "7b034fd30c0f12172000", scheduledBasalRate: nil),
  691. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 19:24:15 +0000"), endDate: f("2018-07-15 19:29:14 +0000"), value: 2.7749999999999999, unit: .unitsPerHour, syncIdentifier: "16014fd80c4f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 0.8)),
  692. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 19:29:14 +0000"), endDate: f("2018-07-15 19:34:14 +0000"), value: 4.6500000000000004, unit: .unitsPerHour, syncIdentifier: "16014edd0c4f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 0.8)),
  693. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 19:34:14 +0000"), endDate: f("2018-07-15 19:39:14 +0000"), value: 4.625, unit: .unitsPerHour, syncIdentifier: "16014ee20c4f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 0.8)),
  694. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 19:39:14 +0000"), endDate: f("2018-07-15 19:44:15 +0000"), value: 2.6000000000000001, unit: .unitsPerHour, syncIdentifier: "16014ee70c4f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 0.8)),
  695. DoseEntry(type: .basal, startDate: f("2018-07-15 19:44:15 +0000"), endDate: f("2018-07-15 20:04:15 +0000"), value: 0.80000000000000004, unit: .unitsPerHour, syncIdentifier: "7b034fec0c0f12172000", scheduledBasalRate: nil),
  696. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 20:04:15 +0000"), endDate: f("2018-07-15 20:23:00 +0000"), value: 0.0, unit: .unitsPerHour, syncIdentifier: "16014fc40d4f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 0.8)),
  697. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 20:23:00 +0000"), endDate: f("2018-07-15 20:29:15 +0000"), value: 0.0, unit: .unitsPerHour, syncIdentifier: "160140d70d4f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 0.8)),
  698. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 20:29:15 +0000"), endDate: f("2018-07-15 20:49:14 +0000"), value: 0.0, unit: .unitsPerHour, syncIdentifier: "16014fdd0d4f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 0.8)),
  699. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 20:49:14 +0000"), endDate: f("2018-07-15 21:09:14 +0000"), value: 0.0, unit: .unitsPerHour, syncIdentifier: "16014ef10d4f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 0.8)),
  700. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 21:09:14 +0000"), endDate: f("2018-07-15 21:29:30 +0000"), value: 0.0, unit: .unitsPerHour, syncIdentifier: "16014ec90e4f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 0.8)),
  701. DoseEntry(type: .basal, startDate: f("2018-07-15 21:29:31 +0000"), endDate: f("2018-07-15 21:30:00 +0000"), value: 0.80000000000000004, unit: .unitsPerHour, syncIdentifier: "7b035fdd0e0f12172000", scheduledBasalRate: nil),
  702. DoseEntry(type: .basal, startDate: f("2018-07-15 21:30:00 +0000"), endDate: f("2018-07-15 21:49:29 +0000"), value: 0.90000000000000002, unit: .unitsPerHour, syncIdentifier: "7b0440de0e0f121d2400", scheduledBasalRate: nil),
  703. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 21:49:29 +0000"), endDate: f("2018-07-15 21:54:32 +0000"), value: 2.75, unit: .unitsPerHour, syncIdentifier: "16015df10e4f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 0.9)),
  704. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 21:54:32 +0000"), endDate: f("2018-07-15 21:59:29 +0000"), value: 3.0499999999999998, unit: .unitsPerHour, syncIdentifier: "160160f60e4f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 0.9)),
  705. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 21:59:29 +0000"), endDate: f("2018-07-15 22:04:31 +0000"), value: 3.2250000000000001, unit: .unitsPerHour, syncIdentifier: "16015dfb0e4f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 0.9)),
  706. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 22:04:31 +0000"), endDate: f("2018-07-15 22:09:31 +0000"), value: 4.5999999999999996, unit: .unitsPerHour, syncIdentifier: "16015fc40f4f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 0.9)),
  707. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 22:09:31 +0000"), endDate: f("2018-07-15 22:14:32 +0000"), value: 4.3250000000000002, unit: .unitsPerHour, syncIdentifier: "16015fc90f4f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 0.9)),
  708. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 22:14:32 +0000"), endDate: f("2018-07-15 22:19:30 +0000"), value: 3.875, unit: .unitsPerHour, syncIdentifier: "160160ce0f4f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 0.9)),
  709. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 22:19:30 +0000"), endDate: f("2018-07-15 22:24:29 +0000"), value: 3.5249999999999999, unit: .unitsPerHour, syncIdentifier: "16015ed30f4f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 0.9)),
  710. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 22:24:29 +0000"), endDate: f("2018-07-15 22:29:46 +0000"), value: 3.2000000000000002, unit: .unitsPerHour, syncIdentifier: "16015dd80f4f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 0.9)),
  711. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 22:29:46 +0000"), endDate: f("2018-07-15 22:34:45 +0000"), value: 2.1499999999999999, unit: .unitsPerHour, syncIdentifier: "16016edd0f4f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 0.9)),
  712. DoseEntry(type: .basal, startDate: f("2018-07-15 22:34:45 +0000"), endDate: f("2018-07-15 22:39:29 +0000"), value: 0.90000000000000002, unit: .unitsPerHour, syncIdentifier: "7b046de20f0f121d2400", scheduledBasalRate: nil),
  713. DoseEntry(type: .bolus, startDate: f("2018-07-15 22:54:39 +0000"), endDate: f("2018-07-15 22:54:39 +0000"), value: 2.8500000000000001, unit: .units, syncIdentifier: "010072007200000067f62f4f12", scheduledBasalRate: nil),
  714. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 22:39:29 +0000"), endDate: f("2018-07-15 23:01:42 +0000"), value: 0.40000000000000002, unit: .unitsPerHour, syncIdentifier: "16015de70f4f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 0.9)),
  715. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 23:01:42 +0000"), endDate: f("2018-07-15 23:24:29 +0000"), value: 0.0, unit: .unitsPerHour, syncIdentifier: "16016ac1104f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 0.9)),
  716. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 23:24:29 +0000"), endDate: f("2018-07-15 23:29:44 +0000"), value: 0.0, unit: .unitsPerHour, syncIdentifier: "16015dd8104f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 0.9)),
  717. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 23:29:44 +0000"), endDate: f("2018-07-15 23:34:28 +0000"), value: 1.55, unit: .unitsPerHour, syncIdentifier: "16016cdd104f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 0.9)),
  718. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 23:34:28 +0000"), endDate: f("2018-07-15 23:39:29 +0000"), value: 1.625, unit: .unitsPerHour, syncIdentifier: "16015ce2104f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 0.9)),
  719. DoseEntry(type: .bolus, startDate: f("2018-07-15 23:43:57 +0000"), endDate: f("2018-07-15 23:43:57 +0000"), value: 1.5, unit: .units, syncIdentifier: "01003c003c00620079eb304f12", scheduledBasalRate: nil),
  720. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 23:39:29 +0000"), endDate: f("2018-07-15 23:49:29 +0000"), value: 1.55, unit: .unitsPerHour, syncIdentifier: "16015de7104f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 0.9)),
  721. DoseEntry(type: .bolus, startDate: f("2018-07-16 00:02:37 +0000"), endDate: f("2018-07-16 00:02:37 +0000"), value: 2.6000000000000001, unit: .units, syncIdentifier: "010068006800910065c2314f12", scheduledBasalRate: nil),
  722. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 23:49:29 +0000"), endDate: f("2018-07-16 00:04:42 +0000"), value: 0.0, unit: .unitsPerHour, syncIdentifier: "16015df1104f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 0.9)),
  723. DoseEntry(type: .tempBasal, startDate: f("2018-07-16 00:04:42 +0000"), endDate: f("2018-07-16 00:09:29 +0000"), value: 0.025000000000000001, unit: .unitsPerHour, syncIdentifier: "16016ac4114f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 0.9)),
  724. DoseEntry(type: .bolus, startDate: f("2018-07-16 00:20:20 +0000"), endDate: f("2018-07-16 00:20:20 +0000"), value: 1.1499999999999999, unit: .units, syncIdentifier: "01002e002e00e70054d4314f12", scheduledBasalRate: nil),
  725. DoseEntry(type: .basal, startDate: f("2018-07-16 00:09:29 +0000"), endDate: f("2018-07-16 00:24:32 +0000"), value: 0.90000000000000002, unit: .unitsPerHour, syncIdentifier: "7b045dc9110f121d2400", scheduledBasalRate: nil),
  726. DoseEntry(type: .tempBasal, startDate: f("2018-07-16 00:24:32 +0000"), endDate: f("2018-07-16 00:44:28 +0000"), value: 0.0, unit: .unitsPerHour, syncIdentifier: "160160d8114f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 0.9)),
  727. DoseEntry(type: .tempBasal, startDate: f("2018-07-16 00:44:28 +0000"), endDate: f("2018-07-16 01:04:29 +0000"), value: 0.0, unit: .unitsPerHour, syncIdentifier: "16015cec114f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 0.9)),
  728. DoseEntry(type: .tempBasal, startDate: f("2018-07-16 01:04:29 +0000"), endDate: f("2018-07-16 01:27:16 +0000"), value: 0.0, unit: .unitsPerHour, syncIdentifier: "16015dc4124f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 0.9)),
  729. DoseEntry(type: .tempBasal, startDate: f("2018-07-16 01:27:16 +0000"), endDate: f("2018-07-16 01:49:29 +0000"), value: 0.0, unit: .unitsPerHour, syncIdentifier: "160150db124f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 0.9)),
  730. DoseEntry(type: .bolus, startDate: f("2018-07-16 01:58:53 +0000"), endDate: f("2018-07-16 01:58:53 +0000"), value: 3.6499999999999999, unit: .units, syncIdentifier: "010092009200730075fa324f12", scheduledBasalRate: nil),
  731. DoseEntry(type: .tempBasal, startDate: f("2018-07-16 01:49:29 +0000"), endDate: f("2018-07-16 02:04:30 +0000"), value: 0.0, unit: .unitsPerHour, syncIdentifier: "16015df1124f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 0.9)),
  732. DoseEntry(type: .tempBasal, startDate: f("2018-07-16 02:04:30 +0000"), endDate: f("2018-07-16 02:33:36 +0000"), value: 1.7250000000000001, unit: .unitsPerHour, syncIdentifier: "16015ec4134f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 0.9)),
  733. DoseEntry(type: .suspend, startDate: f("2018-07-16 02:33:36 +0000"), endDate: f("2018-07-16 02:33:36 +0000"), value: 0.0, unit: .unitsPerHour, syncIdentifier: "1e0164e1130f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 0.9)),
  734. ]
  735. let cachedDoseEntries = [
  736. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 03:34:29 +0000"), endDate: f("2018-07-15 03:54:29 +0000"), value: 0.0, unit: .units, syncIdentifier: "16015de2144e12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1.2)),
  737. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 03:54:29 +0000"), endDate: f("2018-07-15 04:14:31 +0000"), value: 0.0, unit: .units, syncIdentifier: "16015df6144e12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1.2)),
  738. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 04:14:31 +0000"), endDate: f("2018-07-15 04:29:28 +0000"), value: 0.0, unit: .units, syncIdentifier: "16015fce154e12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1.2)),
  739. DoseEntry(type: .basal, startDate: f("2018-07-15 04:29:28 +0000"), endDate: f("2018-07-15 04:44:28 +0000"), value: 0.29999999999999999, unit: .units, syncIdentifier: "7b055cdd150e122a3000", scheduledBasalRate: nil),
  740. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 04:44:28 +0000"), endDate: f("2018-07-15 04:49:29 +0000"), value: 0.29999999999999999, unit: .units, syncIdentifier: "16015cec154e12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1.2)),
  741. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 04:49:29 +0000"), endDate: f("2018-07-15 04:54:28 +0000"), value: 0.29999999999999999, unit: .units, syncIdentifier: "16015df1154e12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1.2)),
  742. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 04:54:28 +0000"), endDate: f("2018-07-15 04:59:28 +0000"), value: 0.29999999999999999, unit: .units, syncIdentifier: "16015cf6154e12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1.2)),
  743. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 04:59:28 +0000"), endDate: f("2018-07-15 05:04:29 +0000"), value: 0.25, unit: .units, syncIdentifier: "16015cfb154e12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1.2)),
  744. DoseEntry(type: .bolus, startDate: f("2018-07-15 05:00:01 +0000"), endDate: f("2018-07-15 05:00:01 +0000"), value: 3.0, unit: .units, syncIdentifier: "0100780078004c0041c0364e12", scheduledBasalRate: nil),
  745. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 05:04:29 +0000"), endDate: f("2018-07-15 05:24:29 +0000"), value: 0.0, unit: .units, syncIdentifier: "16015dc4164e12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1.2)),
  746. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 05:24:29 +0000"), endDate: f("2018-07-15 05:44:29 +0000"), value: 0.0, unit: .units, syncIdentifier: "16015dd8164e12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1.2)),
  747. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 05:44:29 +0000"), endDate: f("2018-07-15 05:59:29 +0000"), value: 0.0, unit: .units, syncIdentifier: "16015dec164e12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1.2)),
  748. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 05:59:29 +0000"), endDate: f("2018-07-15 06:04:29 +0000"), value: 0.050000000000000003, unit: .units, syncIdentifier: "16015dfb164e12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1.2)),
  749. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 06:04:29 +0000"), endDate: f("2018-07-15 06:09:29 +0000"), value: 0.0, unit: .units, syncIdentifier: "16015dc4174e12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1.2)),
  750. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 06:09:29 +0000"), endDate: f("2018-07-15 06:14:29 +0000"), value: 0.14999999999999999, unit: .units, syncIdentifier: "16015dc9174e12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1.2)),
  751. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 06:14:29 +0000"), endDate: f("2018-07-15 06:19:29 +0000"), value: 0.050000000000000003, unit: .units, syncIdentifier: "16015dce174e12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1.2)),
  752. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 06:19:29 +0000"), endDate: f("2018-07-15 06:24:29 +0000"), value: 0.14999999999999999, unit: .units, syncIdentifier: "16015dd3174e12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1.2)),
  753. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 06:24:29 +0000"), endDate: f("2018-07-15 06:29:29 +0000"), value: 0.34999999999999998, unit: .units, syncIdentifier: "16015dd8174e12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1.2)),
  754. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 06:29:29 +0000"), endDate: f("2018-07-15 06:34:28 +0000"), value: 0.34999999999999998, unit: .units, syncIdentifier: "16015ddd174e12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1.2)),
  755. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 06:34:28 +0000"), endDate: f("2018-07-15 06:39:28 +0000"), value: 0.25, unit: .units, syncIdentifier: "16015ce2174e12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1.2)),
  756. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 06:39:28 +0000"), endDate: f("2018-07-15 06:44:29 +0000"), value: 0.29999999999999999, unit: .units, syncIdentifier: "16015ce7174e12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1.2)),
  757. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 06:44:29 +0000"), endDate: f("2018-07-15 06:49:31 +0000"), value: 0.25, unit: .units, syncIdentifier: "16015dec174e12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1.2)),
  758. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 06:49:31 +0000"), endDate: f("2018-07-15 06:54:30 +0000"), value: 0.14999999999999999, unit: .units, syncIdentifier: "16015ff1174e12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1.2)),
  759. DoseEntry(type: .basal, startDate: f("2018-07-15 06:54:30 +0000"), endDate: f("2018-07-15 07:00:00 +0000"), value: 0.10000000000000001, unit: .units, syncIdentifier: "7b055ef6170e122a3000", scheduledBasalRate: nil),
  760. DoseEntry(type: .basal, startDate: f("2018-07-15 07:00:00 +0000"), endDate: f("2018-07-15 07:09:28 +0000"), value: 0.20000000000000001, unit: .units, syncIdentifier: "7b0040c0000f12003000", scheduledBasalRate: nil),
  761. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 07:09:28 +0000"), endDate: f("2018-07-15 07:14:28 +0000"), value: 0.050000000000000003, unit: .units, syncIdentifier: "16015cc9004f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1.2)),
  762. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 07:14:28 +0000"), endDate: f("2018-07-15 07:19:29 +0000"), value: 0.050000000000000003, unit: .units, syncIdentifier: "16015cce004f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1.2)),
  763. DoseEntry(type: .basal, startDate: f("2018-07-15 07:19:29 +0000"), endDate: f("2018-07-15 07:24:29 +0000"), value: 0.10000000000000001, unit: .units, syncIdentifier: "7b005dd3000f12003000", scheduledBasalRate: nil),
  764. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 07:24:29 +0000"), endDate: f("2018-07-15 07:29:28 +0000"), value: 0.20000000000000001, unit: .units, syncIdentifier: "16015dd8004f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1.2)),
  765. DoseEntry(type: .basal, startDate: f("2018-07-15 07:29:29 +0000"), endDate: f("2018-07-15 07:34:29 +0000"), value: 0.10000000000000001, unit: .units, syncIdentifier: "7b005ddd000f12003000", scheduledBasalRate: nil),
  766. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 07:34:29 +0000"), endDate: f("2018-07-15 07:39:29 +0000"), value: 0.14999999999999999, unit: .units, syncIdentifier: "16015de2004f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1.2)),
  767. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 07:39:29 +0000"), endDate: f("2018-07-15 07:44:28 +0000"), value: 0.20000000000000001, unit: .units, syncIdentifier: "16015de7004f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1.2)),
  768. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 07:44:28 +0000"), endDate: f("2018-07-15 07:49:28 +0000"), value: 0.20000000000000001, unit: .units, syncIdentifier: "16015cec004f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1.2)),
  769. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 07:49:28 +0000"), endDate: f("2018-07-15 07:54:28 +0000"), value: 0.20000000000000001, unit: .units, syncIdentifier: "16015cf1004f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1.2)),
  770. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 07:54:28 +0000"), endDate: f("2018-07-15 07:59:31 +0000"), value: 0.20000000000000001, unit: .units, syncIdentifier: "16015cf6004f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1.2)),
  771. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 07:59:31 +0000"), endDate: f("2018-07-15 08:04:30 +0000"), value: 0.20000000000000001, unit: .units, syncIdentifier: "16015ffb004f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1.2)),
  772. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 08:04:30 +0000"), endDate: f("2018-07-15 08:09:28 +0000"), value: 0.20000000000000001, unit: .units, syncIdentifier: "16015ec4014f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1.2)),
  773. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 08:09:28 +0000"), endDate: f("2018-07-15 08:14:28 +0000"), value: 0.20000000000000001, unit: .units, syncIdentifier: "16015cc9014f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1.2)),
  774. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 08:14:28 +0000"), endDate: f("2018-07-15 08:19:28 +0000"), value: 0.14999999999999999, unit: .units, syncIdentifier: "16015cce014f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1.2)),
  775. DoseEntry(type: .basal, startDate: f("2018-07-15 08:19:29 +0000"), endDate: f("2018-07-15 08:24:29 +0000"), value: 0.10000000000000001, unit: .units, syncIdentifier: "7b005dd3010f12003000", scheduledBasalRate: nil),
  776. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 08:24:29 +0000"), endDate: f("2018-07-15 08:29:29 +0000"), value: 0.14999999999999999, unit: .units, syncIdentifier: "16015dd8014f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1.2)),
  777. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 08:29:29 +0000"), endDate: f("2018-07-15 08:34:15 +0000"), value: 0.20000000000000001, unit: .units, syncIdentifier: "16015ddd014f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1.2)),
  778. DoseEntry(type: .basal, startDate: f("2018-07-15 08:34:15 +0000"), endDate: f("2018-07-15 08:49:14 +0000"), value: 0.29999999999999999, unit: .units, syncIdentifier: "7b004fe2010f12003000", scheduledBasalRate: nil),
  779. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 08:49:14 +0000"), endDate: f("2018-07-15 08:54:14 +0000"), value: 0.20000000000000001, unit: .units, syncIdentifier: "16014ef1014f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1.2)),
  780. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 08:54:14 +0000"), endDate: f("2018-07-15 08:59:15 +0000"), value: 0.29999999999999999, unit: .units, syncIdentifier: "16014ef6014f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1.2)),
  781. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 08:59:15 +0000"), endDate: f("2018-07-15 09:04:14 +0000"), value: 0.29999999999999999, unit: .units, syncIdentifier: "16014ffb014f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1.2)),
  782. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 09:04:14 +0000"), endDate: f("2018-07-15 09:09:15 +0000"), value: 0.25, unit: .units, syncIdentifier: "16014ec4024f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1.2)),
  783. DoseEntry(type: .basal, startDate: f("2018-07-15 09:09:15 +0000"), endDate: f("2018-07-15 10:00:00 +0000"), value: 1.0, unit: .units, syncIdentifier: "7b004fc9020f12003000", scheduledBasalRate: nil),
  784. DoseEntry(type: .basal, startDate: f("2018-07-15 10:00:00 +0000"), endDate: f("2018-07-15 11:09:15 +0000"), value: 1.1499999999999999, unit: .units, syncIdentifier: "7b0140c0030f12062800", scheduledBasalRate: nil),
  785. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 11:09:15 +0000"), endDate: f("2018-07-15 11:14:14 +0000"), value: 0.0, unit: .units, syncIdentifier: "16014fc9044f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1)),
  786. DoseEntry(type: .basal, startDate: f("2018-07-15 11:14:15 +0000"), endDate: f("2018-07-15 11:39:14 +0000"), value: 0.40000000000000002, unit: .units, syncIdentifier: "7b014fce040f12062800", scheduledBasalRate: nil),
  787. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 11:39:14 +0000"), endDate: f("2018-07-15 11:44:14 +0000"), value: 0.20000000000000001, unit: .units, syncIdentifier: "16014ee7044f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1)),
  788. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 11:44:14 +0000"), endDate: f("2018-07-15 11:49:15 +0000"), value: 0.20000000000000001, unit: .units, syncIdentifier: "16014eec044f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1)),
  789. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 11:49:15 +0000"), endDate: f("2018-07-15 11:54:14 +0000"), value: 0.20000000000000001, unit: .units, syncIdentifier: "16014ff1044f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1)),
  790. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 11:54:14 +0000"), endDate: f("2018-07-15 11:59:15 +0000"), value: 0.14999999999999999, unit: .units, syncIdentifier: "16014ef6044f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1)),
  791. DoseEntry(type: .basal, startDate: f("2018-07-15 11:59:15 +0000"), endDate: f("2018-07-15 14:00:00 +0000"), value: 2.0, unit: .units, syncIdentifier: "7b014ffb040f12062800", scheduledBasalRate: nil),
  792. DoseEntry(type: .basal, startDate: f("2018-07-15 14:00:00 +0000"), endDate: f("2018-07-15 14:09:48 +0000"), value: 0.14999999999999999, unit: .units, syncIdentifier: "7b0240c0070f120e2400", scheduledBasalRate: nil),
  793. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 14:09:48 +0000"), endDate: f("2018-07-15 14:14:15 +0000"), value: 0.14999999999999999, unit: .units, syncIdentifier: "160170c9074f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 0.9)),
  794. DoseEntry(type: .basal, startDate: f("2018-07-15 14:14:15 +0000"), endDate: f("2018-07-15 15:29:15 +0000"), value: 1.1499999999999999, unit: .units, syncIdentifier: "7b024fce070f120e2400", scheduledBasalRate: nil),
  795. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 15:29:15 +0000"), endDate: f("2018-07-15 15:34:14 +0000"), value: 0.14999999999999999, unit: .units, syncIdentifier: "16014fdd084f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 0.9)),
  796. DoseEntry(type: .basal, startDate: f("2018-07-15 15:34:15 +0000"), endDate: f("2018-07-15 15:39:14 +0000"), value: 0.050000000000000003, unit: .units, syncIdentifier: "7b024fe2080f120e2400", scheduledBasalRate: nil),
  797. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 15:39:14 +0000"), endDate: f("2018-07-15 15:44:14 +0000"), value: 0.14999999999999999, unit: .units, syncIdentifier: "16014ee7084f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 0.9)),
  798. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 15:44:14 +0000"), endDate: f("2018-07-15 15:49:14 +0000"), value: 0.20000000000000001, unit: .units, syncIdentifier: "16014eec084f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 0.9)),
  799. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 15:49:14 +0000"), endDate: f("2018-07-15 15:54:16 +0000"), value: 0.14999999999999999, unit: .units, syncIdentifier: "16014ef1084f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 0.9)),
  800. DoseEntry(type: .bolus, startDate: f("2018-07-15 15:49:33 +0000"), endDate: f("2018-07-15 15:49:33 +0000"), value: 2.4500000000000002, unit: .units, syncIdentifier: "010062006200000061f1284f12", scheduledBasalRate: nil),
  801. DoseEntry(type: .basal, startDate: f("2018-07-15 15:54:16 +0000"), endDate: f("2018-07-15 16:14:15 +0000"), value: 0.29999999999999999, unit: .units, syncIdentifier: "7b0250f6080f120e2400", scheduledBasalRate: nil),
  802. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 16:14:15 +0000"), endDate: f("2018-07-15 16:34:15 +0000"), value: 0.0, unit: .units, syncIdentifier: "16014fce094f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 0.9)),
  803. DoseEntry(type: .basal, startDate: f("2018-07-15 16:34:15 +0000"), endDate: f("2018-07-15 16:44:14 +0000"), value: 0.14999999999999999, unit: .units, syncIdentifier: "7b024fe2090f120e2400", scheduledBasalRate: nil),
  804. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 16:44:14 +0000"), endDate: f("2018-07-15 17:14:14 +0000"), value: 2.3500000000000001, unit: .units, syncIdentifier: "16014eec094f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 0.9)),
  805. DoseEntry(type: .basal, startDate: f("2018-07-15 17:14:15 +0000"), endDate: f("2018-07-15 18:30:00 +0000"), value: 1.1499999999999999, unit: .units, syncIdentifier: "7b024fce0a0f120e2400", scheduledBasalRate: nil),
  806. DoseEntry(type: .bolus, startDate: f("2018-07-15 17:55:12 +0000"), endDate: f("2018-07-15 17:55:12 +0000"), value: 2.5499999999999998, unit: .units, syncIdentifier: "01006600660029004cf72a4f12", scheduledBasalRate: nil),
  807. DoseEntry(type: .basal, startDate: f("2018-07-15 18:30:00 +0000"), endDate: f("2018-07-15 18:59:15 +0000"), value: 0.40000000000000002, unit: .units, syncIdentifier: "7b0340de0b0f12172000", scheduledBasalRate: nil),
  808. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 18:59:15 +0000"), endDate: f("2018-07-15 19:04:15 +0000"), value: 0.40000000000000002, unit: .units, syncIdentifier: "16014ffb0b4f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 0.8)),
  809. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 19:04:15 +0000"), endDate: f("2018-07-15 19:09:14 +0000"), value: 0.34999999999999998, unit: .units, syncIdentifier: "16014fc40c4f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 0.8)),
  810. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 19:09:14 +0000"), endDate: f("2018-07-15 19:19:15 +0000"), value: 0.80000000000000004, unit: .units, syncIdentifier: "16014ec90c4f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 0.8)),
  811. DoseEntry(type: .basal, startDate: f("2018-07-15 19:19:15 +0000"), endDate: f("2018-07-15 19:24:15 +0000"), value: 0.050000000000000003, unit: .units, syncIdentifier: "7b034fd30c0f12172000", scheduledBasalRate: nil),
  812. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 19:24:15 +0000"), endDate: f("2018-07-15 19:29:14 +0000"), value: 0.25, unit: .units, syncIdentifier: "16014fd80c4f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 0.8)),
  813. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 19:29:14 +0000"), endDate: f("2018-07-15 19:34:14 +0000"), value: 0.40000000000000002, unit: .units, syncIdentifier: "16014edd0c4f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 0.8)),
  814. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 19:34:14 +0000"), endDate: f("2018-07-15 19:39:14 +0000"), value: 0.40000000000000002, unit: .units, syncIdentifier: "16014ee20c4f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 0.8)),
  815. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 19:39:14 +0000"), endDate: f("2018-07-15 19:44:15 +0000"), value: 0.20000000000000001, unit: .units, syncIdentifier: "16014ee70c4f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 0.8)),
  816. DoseEntry(type: .basal, startDate: f("2018-07-15 19:44:15 +0000"), endDate: f("2018-07-15 20:04:15 +0000"), value: 0.25, unit: .units, syncIdentifier: "7b034fec0c0f12172000", scheduledBasalRate: nil),
  817. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 20:04:15 +0000"), endDate: f("2018-07-15 20:23:00 +0000"), value: 0.0, unit: .units, syncIdentifier: "16014fc40d4f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 0.8)),
  818. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 20:23:00 +0000"), endDate: f("2018-07-15 20:29:15 +0000"), value: 0.0, unit: .units, syncIdentifier: "160140d70d4f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 0.8)),
  819. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 20:29:15 +0000"), endDate: f("2018-07-15 20:49:14 +0000"), value: 0.0, unit: .units, syncIdentifier: "16014fdd0d4f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 0.8)),
  820. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 20:49:14 +0000"), endDate: f("2018-07-15 21:09:14 +0000"), value: 0.0, unit: .units, syncIdentifier: "16014ef10d4f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 0.8)),
  821. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 21:09:14 +0000"), endDate: f("2018-07-15 21:29:30 +0000"), value: 0.0, unit: .units, syncIdentifier: "16014ec90e4f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 0.8)),
  822. DoseEntry(type: .basal, startDate: f("2018-07-15 21:29:31 +0000"), endDate: f("2018-07-15 21:30:00 +0000"), value: 0.0, unit: .units, syncIdentifier: "7b035fdd0e0f12172000", scheduledBasalRate: nil),
  823. DoseEntry(type: .basal, startDate: f("2018-07-15 21:30:00 +0000"), endDate: f("2018-07-15 21:49:29 +0000"), value: 0.29999999999999999, unit: .units, syncIdentifier: "7b0440de0e0f121d2400", scheduledBasalRate: nil),
  824. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 21:49:29 +0000"), endDate: f("2018-07-15 21:54:32 +0000"), value: 0.25, unit: .units, syncIdentifier: "16015df10e4f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 0.9)),
  825. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 21:54:32 +0000"), endDate: f("2018-07-15 21:59:29 +0000"), value: 0.25, unit: .units, syncIdentifier: "160160f60e4f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 0.9)),
  826. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 21:59:29 +0000"), endDate: f("2018-07-15 22:04:31 +0000"), value: 0.25, unit: .units, syncIdentifier: "16015dfb0e4f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 0.9)),
  827. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 22:04:31 +0000"), endDate: f("2018-07-15 22:09:31 +0000"), value: 0.40000000000000002, unit: .units, syncIdentifier: "16015fc40f4f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 0.9)),
  828. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 22:09:31 +0000"), endDate: f("2018-07-15 22:14:32 +0000"), value: 0.34999999999999998, unit: .units, syncIdentifier: "16015fc90f4f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 0.9)),
  829. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 22:14:32 +0000"), endDate: f("2018-07-15 22:19:30 +0000"), value: 0.29999999999999999, unit: .units, syncIdentifier: "160160ce0f4f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 0.9)),
  830. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 22:19:30 +0000"), endDate: f("2018-07-15 22:24:29 +0000"), value: 0.29999999999999999, unit: .units, syncIdentifier: "16015ed30f4f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 0.9)),
  831. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 22:24:29 +0000"), endDate: f("2018-07-15 22:29:46 +0000"), value: 0.29999999999999999, unit: .units, syncIdentifier: "16015dd80f4f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 0.9)),
  832. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 22:29:46 +0000"), endDate: f("2018-07-15 22:34:45 +0000"), value: 0.20000000000000001, unit: .units, syncIdentifier: "16016edd0f4f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 0.9)),
  833. DoseEntry(type: .basal, startDate: f("2018-07-15 22:34:45 +0000"), endDate: f("2018-07-15 22:39:29 +0000"), value: 0.050000000000000003, unit: .units, syncIdentifier: "7b046de20f0f121d2400", scheduledBasalRate: nil),
  834. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 22:39:29 +0000"), endDate: f("2018-07-15 23:01:42 +0000"), value: 0.14999999999999999, unit: .units, syncIdentifier: "16015de70f4f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 0.9)),
  835. DoseEntry(type: .bolus, startDate: f("2018-07-15 22:54:39 +0000"), endDate: f("2018-07-15 22:54:39 +0000"), value: 2.8500000000000001, unit: .units, syncIdentifier: "010072007200000067f62f4f12", scheduledBasalRate: nil),
  836. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 23:01:42 +0000"), endDate: f("2018-07-15 23:24:29 +0000"), value: 0.0, unit: .units, syncIdentifier: "16016ac1104f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 0.9)),
  837. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 23:24:29 +0000"), endDate: f("2018-07-15 23:29:44 +0000"), value: 0.0, unit: .units, syncIdentifier: "16015dd8104f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 0.9)),
  838. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 23:29:44 +0000"), endDate: f("2018-07-15 23:34:28 +0000"), value: 0.10000000000000001, unit: .units, syncIdentifier: "16016cdd104f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 0.9)),
  839. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 23:34:28 +0000"), endDate: f("2018-07-15 23:39:29 +0000"), value: 0.14999999999999999, unit: .units, syncIdentifier: "16015ce2104f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 0.9)),
  840. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 23:39:29 +0000"), endDate: f("2018-07-15 23:49:29 +0000"), value: 0.25, unit: .units, syncIdentifier: "16015de7104f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 0.9)),
  841. DoseEntry(type: .bolus, startDate: f("2018-07-15 23:43:57 +0000"), endDate: f("2018-07-15 23:43:57 +0000"), value: 1.5, unit: .units, syncIdentifier: "01003c003c00620079eb304f12", scheduledBasalRate: nil),
  842. DoseEntry(type: .tempBasal, startDate: f("2018-07-15 23:49:29 +0000"), endDate: f("2018-07-16 00:04:42 +0000"), value: 0.0, unit: .units, syncIdentifier: "16015df1104f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 0.9)),
  843. DoseEntry(type: .bolus, startDate: f("2018-07-16 00:02:37 +0000"), endDate: f("2018-07-16 00:02:37 +0000"), value: 2.6000000000000001, unit: .units, syncIdentifier: "010068006800910065c2314f12", scheduledBasalRate: nil),
  844. DoseEntry(type: .tempBasal, startDate: f("2018-07-16 00:04:42 +0000"), endDate: f("2018-07-16 00:09:29 +0000"), value: 0.0, unit: .units, syncIdentifier: "16016ac4114f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 0.9)),
  845. DoseEntry(type: .basal, startDate: f("2018-07-16 00:09:29 +0000"), endDate: f("2018-07-16 00:24:32 +0000"), value: 0.25, unit: .units, syncIdentifier: "7b045dc9110f121d2400", scheduledBasalRate: nil),
  846. DoseEntry(type: .bolus, startDate: f("2018-07-16 00:20:20 +0000"), endDate: f("2018-07-16 00:20:20 +0000"), value: 1.1499999999999999, unit: .units, syncIdentifier: "01002e002e00e70054d4314f12", scheduledBasalRate: nil),
  847. DoseEntry(type: .tempBasal, startDate: f("2018-07-16 00:24:32 +0000"), endDate: f("2018-07-16 00:44:28 +0000"), value: 0.0, unit: .units, syncIdentifier: "160160d8114f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 0.9)),
  848. DoseEntry(type: .tempBasal, startDate: f("2018-07-16 00:44:28 +0000"), endDate: f("2018-07-16 01:04:29 +0000"), value: 0.0, unit: .units, syncIdentifier: "16015cec114f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 0.9)),
  849. DoseEntry(type: .tempBasal, startDate: f("2018-07-16 01:04:29 +0000"), endDate: f("2018-07-16 01:27:16 +0000"), value: 0.0, unit: .units, syncIdentifier: "16015dc4124f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 0.9)),
  850. DoseEntry(type: .tempBasal, startDate: f("2018-07-16 01:27:16 +0000"), endDate: f("2018-07-16 01:49:29 +0000"), value: 0.0, unit: .units, syncIdentifier: "160150db124f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 0.9)),
  851. DoseEntry(type: .tempBasal, startDate: f("2018-07-16 01:49:29 +0000"), endDate: f("2018-07-16 02:04:30 +0000"), value: 0.0, unit: .units, syncIdentifier: "16015df1124f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 0.9)),
  852. DoseEntry(type: .bolus, startDate: f("2018-07-16 01:58:53 +0000"), endDate: f("2018-07-16 01:58:53 +0000"), value: 3.6499999999999999, unit: .units, syncIdentifier: "010092009200730075fa324f12", scheduledBasalRate: nil),
  853. ]
  854. XCTAssertEqual(f("2018-07-16 02:04:30 +0000"), cachedDoseEntries.lastBasalEndDate!)
  855. let appended = cachedDoseEntries.appendedUnion(with: normalizedDoseEntries)
  856. XCTAssertEqual(appended.count, normalizedDoseEntries.count)
  857. XCTAssertEqual(
  858. appended,
  859. cachedDoseEntries.appendedUnion(with: normalizedDoseEntries.filterDateRange(cachedDoseEntries.lastBasalEndDate, nil)),
  860. "Filtering has the same outcome"
  861. )
  862. let date = f("2018-07-16 03:40:00 +0000")
  863. XCTAssertEqual(
  864. normalizedDoseEntries.insulinOnBoard(insulinModelSettings: insulinModelSettings, from: date, to: date).first!.value,
  865. appended.insulinOnBoard(insulinModelSettings: insulinModelSettings, from: date, to: date).first!.value,
  866. accuracy: 1.0/40
  867. )
  868. let emptyCacheAppended = ([DoseEntry]()).appendedUnion(with: normalizedDoseEntries)
  869. XCTAssertEqual(
  870. normalizedDoseEntries.insulinOnBoard(insulinModelSettings: insulinModelSettings, from: date, to: date).first!.value,
  871. emptyCacheAppended.insulinOnBoard(insulinModelSettings: insulinModelSettings, from: date, to: date).first!.value,
  872. accuracy: 1.0/40,
  873. "Empty cache doesn't affect outcome"
  874. )
  875. let fullCache = cachedDoseEntries.appendedUnion(with: [])
  876. XCTAssertEqual(
  877. cachedDoseEntries.insulinOnBoard(insulinModelSettings: insulinModelSettings, from: date, to: date).first!.value,
  878. fullCache.insulinOnBoard(insulinModelSettings: insulinModelSettings, from: date, to: date).first!.value,
  879. accuracy: 1.0/40,
  880. "Only cache doesn't affect outcome"
  881. )
  882. }
  883. func testAppendedUnionOfReservoirEvents() {
  884. let formatter = DateFormatter.descriptionFormatter
  885. let f = { (input) in
  886. return formatter.date(from: input)!
  887. }
  888. let unit = DoseEntry.unitsPerHour
  889. let normalizedReservoirDoseEntries = [
  890. DoseEntry(type: .tempBasal, startDate: f("2018-07-16 03:59:00 +0000"), endDate: f("2018-07-16 04:04:00 +0000"), value: 2.4000000000000341, unit: .unitsPerHour, scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1.2)),
  891. DoseEntry(type: .tempBasal, startDate: f("2018-07-16 04:04:00 +0000"), endDate: f("2018-07-16 04:09:00 +0000"), value: 2.3999999999998636, unit: .unitsPerHour, scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1.2)),
  892. DoseEntry(type: .tempBasal, startDate: f("2018-07-16 04:09:00 +0000"), endDate: f("2018-07-16 04:14:00 +0000"), value: 1.2000000000001023, unit: .unitsPerHour, scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1.2)),
  893. DoseEntry(type: .tempBasal, startDate: f("2018-07-16 04:14:00 +0000"), endDate: f("2018-07-16 04:19:00 +0000"), value: 2.4000000000000341, unit: .unitsPerHour, scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1.2)),
  894. DoseEntry(type: .tempBasal, startDate: f("2018-07-16 04:19:00 +0000"), endDate: f("2018-07-16 04:24:00 +0000"), value: 2.3999999999998636, unit: .unitsPerHour, scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1.2)),
  895. DoseEntry(type: .tempBasal, startDate: f("2018-07-16 04:24:00 +0000"), endDate: f("2018-07-16 04:29:00 +0000"), value: 1.2000000000001023, unit: .unitsPerHour, scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1.2)),
  896. DoseEntry(type: .tempBasal, startDate: f("2018-07-16 04:29:00 +0000"), endDate: f("2018-07-16 04:34:00 +0000"), value: 0.0, unit: .unitsPerHour, scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1.2)),
  897. DoseEntry(type: .tempBasal, startDate: f("2018-07-16 04:34:00 +0000"), endDate: f("2018-07-16 04:39:00 +0000"), value: 0.0, unit: .unitsPerHour, scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1.2)),
  898. DoseEntry(type: .tempBasal, startDate: f("2018-07-16 04:39:00 +0000"), endDate: f("2018-07-16 04:44:00 +0000"), value: 0.0, unit: .unitsPerHour, scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1.2)),
  899. DoseEntry(type: .tempBasal, startDate: f("2018-07-16 04:44:00 +0000"), endDate: f("2018-07-16 04:49:00 +0000"), value: 0.0, unit: .unitsPerHour, scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1.2)),
  900. DoseEntry(type: .tempBasal, startDate: f("2018-07-16 04:49:00 +0000"), endDate: f("2018-07-16 04:54:00 +0000"), value: 0.0, unit: .unitsPerHour, scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1.2)),
  901. DoseEntry(type: .tempBasal, startDate: f("2018-07-16 04:54:00 +0000"), endDate: f("2018-07-16 04:59:00 +0000"), value: 0.0, unit: .unitsPerHour, scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1.2)),
  902. DoseEntry(type: .tempBasal, startDate: f("2018-07-16 04:59:00 +0000"), endDate: f("2018-07-16 05:04:00 +0000"), value: 0.0, unit: .unitsPerHour, scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1.2)),
  903. DoseEntry(type: .tempBasal, startDate: f("2018-07-16 05:04:00 +0000"), endDate: f("2018-07-16 05:09:00 +0000"), value: 0.0, unit: .unitsPerHour, scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1.2)),
  904. DoseEntry(type: .tempBasal, startDate: f("2018-07-16 05:09:00 +0000"), endDate: f("2018-07-16 05:14:00 +0000"), value: 0.0, unit: .unitsPerHour, scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1.2)),
  905. DoseEntry(type: .tempBasal, startDate: f("2018-07-16 05:14:00 +0000"), endDate: f("2018-07-16 05:19:00 +0000"), value: 1.1999999999999318, unit: .unitsPerHour, scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1.2)),
  906. DoseEntry(type: .tempBasal, startDate: f("2018-07-16 05:19:00 +0000"), endDate: f("2018-07-16 05:24:00 +0000"), value: 1.2000000000001023, unit: .unitsPerHour, scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1.2)),
  907. DoseEntry(type: .tempBasal, startDate: f("2018-07-16 05:24:00 +0000"), endDate: f("2018-07-16 05:29:00 +0000"), value: 1.1999999999999318, unit: .unitsPerHour, scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1.2)),
  908. ]
  909. let cachedDoseEntries = [
  910. DoseEntry(type: .tempBasal, startDate: f("2018-07-16 03:59:15 +0000"), endDate: f("2018-07-16 04:04:14 +0000"), value: 0.20000000000000001, unit: .units, syncIdentifier: "16014ffb144f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1.2)),
  911. DoseEntry(type: .tempBasal, startDate: f("2018-07-16 04:04:14 +0000"), endDate: f("2018-07-16 04:09:15 +0000"), value: 0.14999999999999999, unit: .units, syncIdentifier: "16014ec4154f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1.2)),
  912. DoseEntry(type: .tempBasal, startDate: f("2018-07-16 04:09:15 +0000"), endDate: f("2018-07-16 04:14:14 +0000"), value: 0.20000000000000001, unit: .units, syncIdentifier: "16014fc9154f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1.2)),
  913. DoseEntry(type: .tempBasal, startDate: f("2018-07-16 04:14:14 +0000"), endDate: f("2018-07-16 04:19:14 +0000"), value: 0.20000000000000001, unit: .units, syncIdentifier: "16014ece154f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1.2)),
  914. DoseEntry(type: .tempBasal, startDate: f("2018-07-16 04:19:14 +0000"), endDate: f("2018-07-16 04:24:15 +0000"), value: 0.14999999999999999, unit: .units, syncIdentifier: "16014ed3154f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1.2)),
  915. DoseEntry(type: .basal, startDate: f("2018-07-16 04:24:15 +0000"), endDate: f("2018-07-16 04:29:14 +0000"), value: 0.10000000000000001, unit: .units, syncIdentifier: "7b054fd8150f122a3000", scheduledBasalRate: nil),
  916. DoseEntry(type: .tempBasal, startDate: f("2018-07-16 04:29:14 +0000"), endDate: f("2018-07-16 04:49:15 +0000"), value: 0.0, unit: .units, syncIdentifier: "16014edd154f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1.2)),
  917. DoseEntry(type: .tempBasal, startDate: f("2018-07-16 04:49:15 +0000"), endDate: f("2018-07-16 05:09:15 +0000"), value: 0.0, unit: .units, syncIdentifier: "16014ff1154f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1.2)),
  918. DoseEntry(type: .tempBasal, startDate: f("2018-07-16 05:09:15 +0000"), endDate: f("2018-07-16 05:14:15 +0000"), value: 0.0, unit: .units, syncIdentifier: "16014fc9164f12", scheduledBasalRate: HKQuantity(unit: unit, doubleValue: 1.2)),
  919. ]
  920. XCTAssertEqual(f("2018-07-16 05:14:15 +0000"), cachedDoseEntries.lastBasalEndDate!)
  921. let appended = cachedDoseEntries + normalizedReservoirDoseEntries.filterDateRange(cachedDoseEntries.lastBasalEndDate!, nil).map({ $0.trimmed(from: cachedDoseEntries.lastBasalEndDate!) })
  922. XCTAssertEqual(appended.count, cachedDoseEntries.count + 3, "The last 4 reservoir doses should be appended")
  923. let date = f("2018-07-16 05:30:00 +0000")
  924. XCTAssertEqual(
  925. normalizedReservoirDoseEntries.insulinOnBoard(insulinModelSettings: insulinModelSettings, from: date, to: date).first!.value,
  926. appended.insulinOnBoard(insulinModelSettings: insulinModelSettings, from: date, to: date).first!.value,
  927. accuracy: 0.1
  928. )
  929. }
  930. func testNetBasalUnits() {
  931. let startDate = fixtureDate("2018-07-16 03:49:00 +0000")
  932. let endDate = startDate.addingTimeInterval(TimeInterval(minutes: 5))
  933. let scheduledRate = 0.15 // Scheduled amount = 0.15 U/hr = 3 pulses per hour, actual expected 522 delivery over 5m = 0 pulses = 0 U
  934. let tempBasalRate = 0.4 // Temp rate = 0.4 U/hr = 8 pulses per hour, actual expected 522 delivery over 5m = 1 pulses = 0.05 U
  935. let netRate = tempBasalRate - scheduledRate
  936. let dose = DoseEntry(type: .tempBasal, startDate: startDate, endDate: endDate, value: tempBasalRate, unit: .unitsPerHour, scheduledBasalRate: HKQuantity(unit: .internationalUnitsPerHour, doubleValue: scheduledRate))
  937. XCTAssertEqual(netRate, dose.netBasalUnitsPerHour, accuracy: .ulpOfOne)
  938. XCTAssertEqual(0.0375, dose.netBasalUnits, accuracy: .ulpOfOne)
  939. }
  940. func testDoseEntryUnitsInDeliverableIncrements() {
  941. let makeDose = { (deliveredUnits: Double?) -> DoseEntry in
  942. let startDate = self.fixtureDate("2018-07-16 03:49:00 +0000")
  943. let endDate = startDate.addingTimeInterval(TimeInterval(minutes: 5))
  944. let tempBasalRate = 1.0
  945. return DoseEntry(
  946. type: .tempBasal,
  947. startDate: startDate,
  948. endDate: endDate,
  949. value: tempBasalRate,
  950. unit: .unitsPerHour,
  951. deliveredUnits: deliveredUnits)
  952. }
  953. XCTAssertEqual(0.1, makeDose(nil).unitsInDeliverableIncrements, accuracy: .ulpOfOne)
  954. XCTAssertEqual(0.05, makeDose(0.05).unitsInDeliverableIncrements, accuracy: .ulpOfOne)
  955. }
  956. func testDoseEntryAnnotateShouldSplitDosesProportionally() {
  957. let startDate = self.fixtureDate("2018-07-16 11:59:00 +0000")
  958. let endDate = startDate.addingTimeInterval(TimeInterval(minutes: 5))
  959. let tempBasalRate = 1.0
  960. let dose = DoseEntry(
  961. type: .tempBasal,
  962. startDate: startDate,
  963. endDate: endDate,
  964. value: tempBasalRate,
  965. unit: .unitsPerHour,
  966. deliveredUnits: 0.1
  967. )
  968. let delivery = dose.unitsInDeliverableIncrements
  969. let basals = loadBasalRateScheduleFixture("basal")
  970. let splitDoses = [dose].annotated(with: basals)
  971. XCTAssertEqual(2, splitDoses.count)
  972. // A 5 minute dose starting one minute before midnight, split at midnight, means split should be 1/5, 4/5
  973. XCTAssertEqual(delivery * 1.0/5.0, splitDoses[0].unitsInDeliverableIncrements, accuracy: .ulpOfOne)
  974. XCTAssertEqual(delivery * 4.0/5.0, splitDoses[1].unitsInDeliverableIncrements, accuracy: .ulpOfOne)
  975. }
  976. func testDoseEntryWithoutDeliveredUnitsShouldSplitDosesProportionally() {
  977. let startDate = self.fixtureDate("2018-07-16 11:59:00 +0000")
  978. let endDate = startDate.addingTimeInterval(TimeInterval(minutes: 5))
  979. let tempBasalRate = 1.0
  980. let dose = DoseEntry(
  981. type: .tempBasal,
  982. startDate: startDate,
  983. endDate: endDate,
  984. value: tempBasalRate,
  985. unit: .unitsPerHour,
  986. deliveredUnits: 0.05
  987. )
  988. let delivery = dose.unitsInDeliverableIncrements
  989. let basals = loadBasalRateScheduleFixture("basal")
  990. let splitDoses = [dose].annotated(with: basals)
  991. XCTAssertEqual(2, splitDoses.count)
  992. // A 5 minute dose starting one minute before midnight, split at midnight, means split should be 1/5, 4/5
  993. XCTAssertEqual(delivery * 1.0/5.0, splitDoses[0].unitsInDeliverableIncrements, accuracy: .ulpOfOne)
  994. XCTAssertEqual(delivery * 4.0/5.0, splitDoses[1].unitsInDeliverableIncrements, accuracy: .ulpOfOne)
  995. }
  996. }