LoopMathTests.swift 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443
  1. //
  2. // LoopMathTests.swift
  3. // LoopKit
  4. //
  5. // Created by Nathan Racklyeft on 3/2/16.
  6. // Copyright © 2016 Nathan Racklyeft. All rights reserved.
  7. //
  8. import XCTest
  9. import HealthKit
  10. @testable import LoopKit
  11. typealias RecentGlucoseValue = PredictedGlucoseValue
  12. class LoopMathTests: XCTestCase {
  13. func loadGlucoseEffectFixture(_ resourceName: String, formatter: ISO8601DateFormatter) -> [GlucoseEffect] {
  14. let fixture: [JSONDictionary] = loadFixture(resourceName)
  15. return fixture.map {
  16. return GlucoseEffect(startDate: formatter.date(from: $0["date"] as! String)!, quantity: HKQuantity(unit: HKUnit(from: $0["unit"] as! String), doubleValue:$0["amount"] as! Double))
  17. }
  18. }
  19. func loadGlucoseEffectFixture(_ resourceName: String, formatter: DateFormatter) -> [GlucoseEffect] {
  20. let fixture: [JSONDictionary] = loadFixture(resourceName)
  21. return fixture.map {
  22. return GlucoseEffect(startDate: formatter.date(from: $0["date"] as! String)!, quantity: HKQuantity(unit: HKUnit(from: $0["unit"] as! String), doubleValue: $0["value"] as! Double))
  23. }
  24. }
  25. private func printFixture(_ glucoseEffect: [GlucoseEffect]) {
  26. let unit = HKUnit.milligramsPerDeciliter
  27. print("\n\n")
  28. print(String(data: try! JSONSerialization.data(
  29. withJSONObject: glucoseEffect.map({ (value) -> [String: Any] in
  30. return [
  31. "date": String(describing: value.startDate),
  32. "value": value.quantity.doubleValue(for: unit),
  33. "unit": unit.unitString
  34. ]
  35. }),
  36. options: .prettyPrinted), encoding: .utf8)!)
  37. print("\n\n")
  38. }
  39. func loadSampleValueFixture(_ resourceName: String) -> [(startDate: Date, quantity: HKQuantity)] {
  40. let fixture: [JSONDictionary] = loadFixture(resourceName)
  41. let dateFormatter = ISO8601DateFormatter()
  42. return fixture.map {
  43. (dateFormatter.date(from: $0["startDate"] as! String)!, HKQuantity(unit: HKUnit(from: $0["unit"] as! String), doubleValue: $0["value"] as! Double))
  44. }
  45. }
  46. func loadGlucoseHistoryFixture(_ resourceName: String) -> RecentGlucoseValue {
  47. let fixture: [JSONDictionary] = loadFixture(resourceName)
  48. let dateFormatter = ISO8601DateFormatter.localTimeDate()
  49. return fixture.map {
  50. return RecentGlucoseValue(startDate: dateFormatter.date(from: $0["display_time"] as! String)!, quantity: HKQuantity(unit: HKUnit.milligramsPerDeciliter, doubleValue:$0["glucose"] as! Double))
  51. }.first!
  52. }
  53. func loadGlucoseValueFixture(_ resourceName: String) -> [PredictedGlucoseValue] {
  54. let fixture: [JSONDictionary] = loadFixture(resourceName)
  55. let dateFormatter = ISO8601DateFormatter.localTimeDate()
  56. return fixture.map {
  57. return PredictedGlucoseValue(startDate: dateFormatter.date(from: $0["date"] as! String)!, quantity: HKQuantity(unit: HKUnit(from: $0["unit"] as! String), doubleValue:$0["amount"] as! Double))
  58. }
  59. }
  60. lazy var carbEffect: [GlucoseEffect] = {
  61. return self.loadGlucoseEffectFixture("glucose_from_effects_carb_effect_input", formatter: ISO8601DateFormatter.localTimeDate())
  62. }()
  63. lazy var insulinEffect: [GlucoseEffect] = {
  64. return self.loadGlucoseEffectFixture("glucose_from_effects_insulin_effect_input", formatter: ISO8601DateFormatter.localTimeDate())
  65. }()
  66. func testPredictGlucoseNoMomentum() {
  67. let glucose = loadGlucoseHistoryFixture("glucose_from_effects_glucose_input")
  68. let expected = loadGlucoseValueFixture("glucose_from_effects_no_momentum_output")
  69. let calculated = LoopMath.predictGlucose(startingAt: glucose, effects: carbEffect, insulinEffect)
  70. XCTAssertEqual(expected.count, calculated.count)
  71. for (expected, calculated) in zip(expected, calculated) {
  72. XCTAssertEqual(expected.startDate, calculated.startDate)
  73. XCTAssertEqual(expected.quantity.doubleValue(for: HKUnit.milligramsPerDeciliter), calculated.quantity.doubleValue(for: HKUnit.milligramsPerDeciliter), accuracy: Double(Float.ulpOfOne))
  74. }
  75. }
  76. func testPredictGlucoseFlatMomentum() {
  77. let glucose = loadGlucoseHistoryFixture("glucose_from_effects_momentum_flat_glucose_input")
  78. let momentum = loadGlucoseEffectFixture("glucose_from_effects_momentum_flat_input", formatter: ISO8601DateFormatter.localTimeDate())
  79. let expected = loadGlucoseValueFixture("glucose_from_effects_momentum_flat_output")
  80. let calculated = LoopMath.predictGlucose(startingAt: glucose, momentum: momentum, effects: carbEffect, insulinEffect)
  81. XCTAssertEqual(expected.count, calculated.count)
  82. for (expected, calculated) in zip(expected, calculated) {
  83. XCTAssertEqual(expected.startDate, calculated.startDate)
  84. XCTAssertEqual(expected.quantity.doubleValue(for: HKUnit.milligramsPerDeciliter), calculated.quantity.doubleValue(for: HKUnit.milligramsPerDeciliter), accuracy: Double(Float.ulpOfOne))
  85. }
  86. }
  87. func testPredictGlucoseUpMomentum() {
  88. let glucose = loadGlucoseHistoryFixture("glucose_from_effects_glucose_input")
  89. let momentum = loadGlucoseEffectFixture("glucose_from_effects_momentum_up_input", formatter: ISO8601DateFormatter.localTimeDate())
  90. let expected = loadGlucoseValueFixture("glucose_from_effects_momentum_up_output")
  91. let calculated = LoopMath.predictGlucose(startingAt: glucose, momentum: momentum, effects: carbEffect, insulinEffect)
  92. XCTAssertEqual(expected.count, calculated.count)
  93. for (expected, calculated) in zip(expected, calculated) {
  94. XCTAssertEqual(expected.startDate, calculated.startDate)
  95. XCTAssertEqual(expected.quantity.doubleValue(for: HKUnit.milligramsPerDeciliter), calculated.quantity.doubleValue(for: HKUnit.milligramsPerDeciliter), accuracy: Double(Float.ulpOfOne))
  96. }
  97. }
  98. func testPredictGlucoseDownMomentum() {
  99. let glucose = loadGlucoseHistoryFixture("glucose_from_effects_glucose_input")
  100. let momentum = loadGlucoseEffectFixture("glucose_from_effects_momentum_down_input", formatter: ISO8601DateFormatter.localTimeDate())
  101. let expected = loadGlucoseValueFixture("glucose_from_effects_momentum_down_output")
  102. let calculated = LoopMath.predictGlucose(startingAt: glucose, momentum: momentum, effects: carbEffect, insulinEffect)
  103. XCTAssertEqual(expected.count, calculated.count)
  104. for (expected, calculated) in zip(expected, calculated) {
  105. XCTAssertEqual(expected.startDate, calculated.startDate)
  106. XCTAssertEqual(expected.quantity.doubleValue(for: HKUnit.milligramsPerDeciliter), calculated.quantity.doubleValue(for: HKUnit.milligramsPerDeciliter), accuracy: Double(Float.ulpOfOne))
  107. }
  108. }
  109. func testPredictGlucoseBlendMomentum() {
  110. let glucose = loadGlucoseHistoryFixture("glucose_from_effects_momentum_blend_glucose_input")
  111. let momentum = loadGlucoseEffectFixture("glucose_from_effects_momentum_blend_momentum_input", formatter: ISO8601DateFormatter.localTimeDate())
  112. let insulinEffect = loadGlucoseEffectFixture("glucose_from_effects_momentum_blend_insulin_effect_input", formatter: ISO8601DateFormatter.localTimeDate())
  113. let expected = loadGlucoseValueFixture("glucose_from_effects_momentum_blend_output")
  114. let calculated = LoopMath.predictGlucose(startingAt: glucose, momentum: momentum, effects: insulinEffect)
  115. XCTAssertEqual(expected.count, calculated.count)
  116. for (expected, calculated) in zip(expected, calculated) {
  117. XCTAssertEqual(expected.startDate, calculated.startDate)
  118. XCTAssertEqual(expected.quantity.doubleValue(for: HKUnit.milligramsPerDeciliter), calculated.quantity.doubleValue(for: HKUnit.milligramsPerDeciliter), accuracy: Double(Float.ulpOfOne))
  119. }
  120. }
  121. func testPredictGlucoseStartingEffectsNonZero() {
  122. let glucose = loadSampleValueFixture("glucose_from_effects_non_zero_glucose_input").first!
  123. let insulinEffect = loadSampleValueFixture("glucose_from_effects_non_zero_insulin_input").map {
  124. GlucoseEffect(startDate: $0.startDate, quantity: $0.quantity)
  125. }
  126. let carbEffect = loadSampleValueFixture("glucose_from_effects_non_zero_carb_input").map {
  127. GlucoseEffect(startDate: $0.startDate, quantity: $0.quantity)
  128. }
  129. let expected = loadSampleValueFixture("glucose_from_effects_non_zero_output").map {
  130. GlucoseEffect(startDate: $0.startDate, quantity: $0.quantity)
  131. }
  132. let calculated = LoopMath.predictGlucose(startingAt: RecentGlucoseValue(startDate: glucose.startDate, quantity: glucose.quantity),
  133. effects: insulinEffect, carbEffect
  134. )
  135. XCTAssertEqual(expected.count, calculated.count)
  136. for (expected, calculated) in zip(expected, calculated) {
  137. XCTAssertEqual(expected.startDate, calculated.startDate)
  138. XCTAssertEqual(expected.quantity.doubleValue(for: HKUnit.milligramsPerDeciliter), calculated.quantity.doubleValue(for: HKUnit.milligramsPerDeciliter), accuracy: Double(Float.ulpOfOne))
  139. }
  140. }
  141. func testDecayEffect() {
  142. let calendar = Calendar(identifier: Calendar.Identifier.gregorian)
  143. let glucoseDate = calendar.date(from: DateComponents(year: 2016, month: 2, day: 1, hour: 10, minute: 13, second: 20))!
  144. let type = HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.bloodGlucose)!
  145. let unit = HKUnit.milligramsPerDeciliter
  146. let glucose = HKQuantitySample(type: type, quantity: HKQuantity(unit: unit, doubleValue: 100), start: glucoseDate, end: glucoseDate)
  147. var startingEffect = HKQuantity(unit: unit.unitDivided(by: HKUnit.minute()), doubleValue: 2)
  148. var effects = glucose.decayEffect(atRate: startingEffect, for: .minutes(30))
  149. XCTAssertEqual([100, 110, 118, 124, 128, 130, 130], effects.map { $0.quantity.doubleValue(for: unit) })
  150. let startDate = effects.first!.startDate
  151. XCTAssertEqual([0, 5, 10, 15, 20, 25, 30], effects.map { $0.startDate.timeIntervalSince(startDate).minutes })
  152. startingEffect = HKQuantity(unit: unit.unitDivided(by: HKUnit.minute()), doubleValue: -0.5)
  153. effects = glucose.decayEffect(atRate: startingEffect, for: .minutes(30))
  154. XCTAssertEqual([100, 97.5, 95.5, 94, 93, 92.5, 92.5], effects.map { $0.quantity.doubleValue(for: unit) })
  155. }
  156. func testDecayEffectWithEvenGlucose() {
  157. let calendar = Calendar(identifier: Calendar.Identifier.gregorian)
  158. let glucoseDate = calendar.date(from: DateComponents(year: 2016, month: 2, day: 1, hour: 10, minute: 15, second: 0))!
  159. let type = HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.bloodGlucose)!
  160. let unit = HKUnit.milligramsPerDeciliter
  161. let glucose = HKQuantitySample(type: type, quantity: HKQuantity(unit: unit, doubleValue: 100), start: glucoseDate, end: glucoseDate)
  162. var startingEffect = HKQuantity(unit: unit.unitDivided(by: HKUnit.minute()), doubleValue: 2)
  163. var effects = glucose.decayEffect(atRate: startingEffect, for: .minutes(30))
  164. XCTAssertEqual([100, 110, 118, 124, 128, 130], effects.map { $0.quantity.doubleValue(for: unit) })
  165. let startDate = effects.first!.startDate
  166. XCTAssertEqual([0, 5, 10, 15, 20, 25], effects.map { $0.startDate.timeIntervalSince(startDate).minutes })
  167. startingEffect = HKQuantity(unit: unit.unitDivided(by: HKUnit.minute()), doubleValue: -0.5)
  168. effects = glucose.decayEffect(atRate: startingEffect, for: .minutes(30))
  169. XCTAssertEqual([100, 97.5, 95.5, 94, 93, 92.5], effects.map { $0.quantity.doubleValue(for: unit) })
  170. }
  171. func testSubtractingCarbEffectFromICEWithGaps() {
  172. let perMinute = HKUnit.milligramsPerDeciliter.unitDivided(by: .minute())
  173. let mgdl = HKUnit.milligramsPerDeciliter
  174. let formatter = DateFormatter.descriptionFormatter
  175. let f = { (input) in
  176. return formatter.date(from: input)!
  177. }
  178. let insulinCounteractionEffects = [
  179. GlucoseEffectVelocity(startDate: f("2018-08-16 01:03:43 +0000"), endDate: f("2018-08-16 01:08:43 +0000"), quantity: HKQuantity(unit: perMinute, doubleValue: -0.0063630385383878375)),
  180. GlucoseEffectVelocity(startDate: f("2018-08-16 01:08:43 +0000"), endDate: f("2018-08-16 01:13:44 +0000"), quantity: HKQuantity(unit: perMinute, doubleValue: 0.3798225216554212)),
  181. GlucoseEffectVelocity(startDate: f("2018-08-16 01:13:44 +0000"), endDate: f("2018-08-16 01:18:44 +0000"), quantity: HKQuantity(unit: perMinute, doubleValue: 0.5726970790754702)),
  182. GlucoseEffectVelocity(startDate: f("2018-08-16 01:18:44 +0000"), endDate: f("2018-08-16 01:23:44 +0000"), quantity: HKQuantity(unit: perMinute, doubleValue: 0.7936837738660198)),
  183. GlucoseEffectVelocity(startDate: f("2018-08-16 01:23:44 +0000"), endDate: f("2018-08-16 01:28:43 +0000"), quantity: HKQuantity(unit: perMinute, doubleValue: 1.2143200509835315)),
  184. GlucoseEffectVelocity(startDate: f("2018-08-16 01:28:43 +0000"), endDate: f("2018-08-16 02:03:43 +0000"), quantity: HKQuantity(unit: perMinute, doubleValue: 1.214239367352738)),
  185. GlucoseEffectVelocity(startDate: f("2018-08-16 02:03:43 +0000"), endDate: f("2018-08-16 03:13:44 +0000"), quantity: HKQuantity(unit: perMinute, doubleValue: 0.64964443000756)),
  186. GlucoseEffectVelocity(startDate: f("2018-08-16 03:13:44 +0000"), endDate: f("2018-08-16 03:18:44 +0000"), quantity: HKQuantity(unit: perMinute, doubleValue: 0.40933503856266396)),
  187. GlucoseEffectVelocity(startDate: f("2018-08-16 03:18:44 +0000"), endDate: f("2018-08-16 03:23:43 +0000"), quantity: HKQuantity(unit: perMinute, doubleValue: 0.5994115966821696)),
  188. GlucoseEffectVelocity(startDate: f("2018-08-16 03:23:43 +0000"), endDate: f("2018-08-16 03:28:44 +0000"), quantity: HKQuantity(unit: perMinute, doubleValue: 0.640336708627245)),
  189. GlucoseEffectVelocity(startDate: f("2018-08-16 03:28:44 +0000"), endDate: f("2018-08-16 03:33:43 +0000"), quantity: HKQuantity(unit: perMinute, doubleValue: 1.5109774027636143)),
  190. GlucoseEffectVelocity(startDate: f("2018-08-16 03:33:43 +0000"), endDate: f("2018-08-16 03:38:43 +0000"), quantity: HKQuantity(unit: perMinute, doubleValue: -1.6358198764701966)),
  191. GlucoseEffectVelocity(startDate: f("2018-08-16 03:38:43 +0000"), endDate: f("2018-08-16 03:43:44 +0000"), quantity: HKQuantity(unit: perMinute, doubleValue: 0.4187533857556986)),
  192. GlucoseEffectVelocity(startDate: f("2018-08-16 03:43:44 +0000"), endDate: f("2018-08-16 03:48:43 +0000"), quantity: HKQuantity(unit: perMinute, doubleValue: -1.136005257968153)),
  193. GlucoseEffectVelocity(startDate: f("2018-08-16 03:48:43 +0000"), endDate: f("2018-08-16 03:53:44 +0000"), quantity: HKQuantity(unit: perMinute, doubleValue: -0.29635521996668046)),
  194. GlucoseEffectVelocity(startDate: f("2018-08-16 03:53:44 +0000"), endDate: f("2018-08-16 03:58:43 +0000"), quantity: HKQuantity(unit: perMinute, doubleValue: -1.070285990192832)),
  195. GlucoseEffectVelocity(startDate: f("2018-08-16 03:58:43 +0000"), endDate: f("2018-08-16 04:03:43 +0000"), quantity: HKQuantity(unit: perMinute, doubleValue: -0.25025410282677996)),
  196. GlucoseEffectVelocity(startDate: f("2018-08-16 04:03:43 +0000"), endDate: f("2018-08-16 04:08:43 +0000"), quantity: HKQuantity(unit: perMinute, doubleValue: 0.5598990284715561)),
  197. GlucoseEffectVelocity(startDate: f("2018-08-16 04:08:43 +0000"), endDate: f("2018-08-16 04:13:44 +0000"), quantity: HKQuantity(unit: perMinute, doubleValue: 1.9616378142801167)),
  198. GlucoseEffectVelocity(startDate: f("2018-08-16 04:13:44 +0000"), endDate: f("2018-08-16 04:18:43 +0000"), quantity: HKQuantity(unit: perMinute, doubleValue: 1.7593854114682483)),
  199. GlucoseEffectVelocity(startDate: f("2018-08-16 04:18:43 +0000"), endDate: f("2018-08-16 04:23:44 +0000"), quantity: HKQuantity(unit: perMinute, doubleValue: 0.5467358931981837)),
  200. GlucoseEffectVelocity(startDate: f("2018-08-16 04:23:44 +0000"), endDate: f("2018-08-16 04:28:44 +0000"), quantity: HKQuantity(unit: perMinute, doubleValue: 0.9335047268082058)),
  201. GlucoseEffectVelocity(startDate: f("2018-08-16 04:28:44 +0000"), endDate: f("2018-08-16 04:33:43 +0000"), quantity: HKQuantity(unit: perMinute, doubleValue: -0.2823274349191665)),
  202. ]
  203. let carbEffects = [
  204. GlucoseEffect(startDate: f("2018-08-16 01:00:00 +0000"), quantity: HKQuantity(unit: mgdl, doubleValue: 69.38642455065255)),
  205. GlucoseEffect(startDate: f("2018-08-16 01:05:00 +0000"), quantity: HKQuantity(unit: mgdl, doubleValue: 69.38642455065255)),
  206. GlucoseEffect(startDate: f("2018-08-16 01:10:00 +0000"), quantity: HKQuantity(unit: mgdl, doubleValue: 72.18741125500856)),
  207. GlucoseEffect(startDate: f("2018-08-16 01:15:00 +0000"), quantity: HKQuantity(unit: mgdl, doubleValue: 82.00120184863087)),
  208. GlucoseEffect(startDate: f("2018-08-16 01:20:00 +0000"), quantity: HKQuantity(unit: mgdl, doubleValue: 90.79285286260895)),
  209. GlucoseEffect(startDate: f("2018-08-16 01:25:00 +0000"), quantity: HKQuantity(unit: mgdl, doubleValue: 93.5316456300059)),
  210. GlucoseEffect(startDate: f("2018-08-16 01:30:00 +0000"), quantity: HKQuantity(unit: mgdl, doubleValue: 98.19664696604428)),
  211. GlucoseEffect(startDate: f("2018-08-16 01:35:00 +0000"), quantity: HKQuantity(unit: mgdl, doubleValue: 98.5800323225078)),
  212. GlucoseEffect(startDate: f("2018-08-16 01:40:00 +0000"), quantity: HKQuantity(unit: mgdl, doubleValue: 100.09237800152016)),
  213. GlucoseEffect(startDate: f("2018-08-16 01:45:00 +0000"), quantity: HKQuantity(unit: mgdl, doubleValue: 101.60472368053252)),
  214. GlucoseEffect(startDate: f("2018-08-16 01:50:00 +0000"), quantity: HKQuantity(unit: mgdl, doubleValue: 103.11706935954487)),
  215. GlucoseEffect(startDate: f("2018-08-16 01:55:00 +0000"), quantity: HKQuantity(unit: mgdl, doubleValue: 104.6294150385572)),
  216. GlucoseEffect(startDate: f("2018-08-16 02:00:00 +0000"), quantity: HKQuantity(unit: mgdl, doubleValue: 129.38642455065255)),
  217. GlucoseEffect(startDate: f("2018-08-16 02:05:00 +0000"), quantity: HKQuantity(unit: mgdl, doubleValue: 129.38642455065255)),
  218. GlucoseEffect(startDate: f("2018-08-16 02:10:00 +0000"), quantity: HKQuantity(unit: mgdl, doubleValue: 132.18741125500856)),
  219. GlucoseEffect(startDate: f("2018-08-16 02:15:00 +0000"), quantity: HKQuantity(unit: mgdl, doubleValue: 142.00120184863087)),
  220. GlucoseEffect(startDate: f("2018-08-16 02:20:00 +0000"), quantity: HKQuantity(unit: mgdl, doubleValue: 150.79285286260895)),
  221. GlucoseEffect(startDate: f("2018-08-16 02:25:00 +0000"), quantity: HKQuantity(unit: mgdl, doubleValue: 153.5316456300059)),
  222. GlucoseEffect(startDate: f("2018-08-16 02:30:00 +0000"), quantity: HKQuantity(unit: mgdl, doubleValue: 158.19664696604428)),
  223. GlucoseEffect(startDate: f("2018-08-16 02:35:00 +0000"), quantity: HKQuantity(unit: mgdl, doubleValue: 158.5800323225078)),
  224. GlucoseEffect(startDate: f("2018-08-16 02:40:00 +0000"), quantity: HKQuantity(unit: mgdl, doubleValue: 160.09237800152016)),
  225. GlucoseEffect(startDate: f("2018-08-16 02:45:00 +0000"), quantity: HKQuantity(unit: mgdl, doubleValue: 161.60472368053252)),
  226. GlucoseEffect(startDate: f("2018-08-16 02:50:00 +0000"), quantity: HKQuantity(unit: mgdl, doubleValue: 163.11706935954487)),
  227. GlucoseEffect(startDate: f("2018-08-16 02:55:00 +0000"), quantity: HKQuantity(unit: mgdl, doubleValue: 164.6294150385572)),
  228. GlucoseEffect(startDate: f("2018-08-16 03:00:00 +0000"), quantity: HKQuantity(unit: mgdl, doubleValue: 166.14176071756955)),
  229. GlucoseEffect(startDate: f("2018-08-16 03:05:00 +0000"), quantity: HKQuantity(unit: mgdl, doubleValue: 167.65410639658188)),
  230. GlucoseEffect(startDate: f("2018-08-16 03:10:00 +0000"), quantity: HKQuantity(unit: mgdl, doubleValue: 169.16645207559424)),
  231. GlucoseEffect(startDate: f("2018-08-16 03:15:00 +0000"), quantity: HKQuantity(unit: mgdl, doubleValue: 170.6787977546066)),
  232. GlucoseEffect(startDate: f("2018-08-16 03:20:00 +0000"), quantity: HKQuantity(unit: mgdl, doubleValue: 172.19114343361895)),
  233. GlucoseEffect(startDate: f("2018-08-16 03:25:00 +0000"), quantity: HKQuantity(unit: mgdl, doubleValue: 173.70348911263127)),
  234. GlucoseEffect(startDate: f("2018-08-16 03:30:00 +0000"), quantity: HKQuantity(unit: mgdl, doubleValue: 175.21583479164363)),
  235. GlucoseEffect(startDate: f("2018-08-16 03:35:00 +0000"), quantity: HKQuantity(unit: mgdl, doubleValue: 176.72818047065596)),
  236. GlucoseEffect(startDate: f("2018-08-16 03:40:00 +0000"), quantity: HKQuantity(unit: mgdl, doubleValue: 178.2405261496683)),
  237. GlucoseEffect(startDate: f("2018-08-16 03:45:00 +0000"), quantity: HKQuantity(unit: mgdl, doubleValue: 179.75287182868067)),
  238. GlucoseEffect(startDate: f("2018-08-16 03:50:00 +0000"), quantity: HKQuantity(unit: mgdl, doubleValue: 181.26521750769302)),
  239. GlucoseEffect(startDate: f("2018-08-16 03:55:00 +0000"), quantity: HKQuantity(unit: mgdl, doubleValue: 182.77756318670535)),
  240. GlucoseEffect(startDate: f("2018-08-16 04:00:00 +0000"), quantity: HKQuantity(unit: mgdl, doubleValue: 189.38642455065255)),
  241. GlucoseEffect(startDate: f("2018-08-16 04:05:00 +0000"), quantity: HKQuantity(unit: mgdl, doubleValue: 189.38642455065255)),
  242. GlucoseEffect(startDate: f("2018-08-16 04:10:00 +0000"), quantity: HKQuantity(unit: mgdl, doubleValue: 192.18741125500856)),
  243. GlucoseEffect(startDate: f("2018-08-16 04:15:00 +0000"), quantity: HKQuantity(unit: mgdl, doubleValue: 202.00120184863087)),
  244. GlucoseEffect(startDate: f("2018-08-16 04:20:00 +0000"), quantity: HKQuantity(unit: mgdl, doubleValue: 210.79285286260895)),
  245. GlucoseEffect(startDate: f("2018-08-16 04:25:00 +0000"), quantity: HKQuantity(unit: mgdl, doubleValue: 213.5316456300059)),
  246. GlucoseEffect(startDate: f("2018-08-16 04:30:00 +0000"), quantity: HKQuantity(unit: mgdl, doubleValue: 218.19664696604428)),
  247. GlucoseEffect(startDate: f("2018-08-16 04:35:00 +0000"), quantity: HKQuantity(unit: mgdl, doubleValue: 218.5800323225078)),
  248. GlucoseEffect(startDate: f("2018-08-16 04:40:00 +0000"), quantity: HKQuantity(unit: mgdl, doubleValue: 220.09237800152016)),
  249. GlucoseEffect(startDate: f("2018-08-16 04:45:00 +0000"), quantity: HKQuantity(unit: mgdl, doubleValue: 221.60472368053252)),
  250. GlucoseEffect(startDate: f("2018-08-16 04:50:00 +0000"), quantity: HKQuantity(unit: mgdl, doubleValue: 223.11706935954487)),
  251. GlucoseEffect(startDate: f("2018-08-16 04:55:00 +0000"), quantity: HKQuantity(unit: mgdl, doubleValue: 224.6294150385572)),
  252. GlucoseEffect(startDate: f("2018-08-16 05:00:00 +0000"), quantity: HKQuantity(unit: mgdl, doubleValue: 226.14176071756955)),
  253. GlucoseEffect(startDate: f("2018-08-16 05:05:00 +0000"), quantity: HKQuantity(unit: mgdl, doubleValue: 227.65410639658188)),
  254. GlucoseEffect(startDate: f("2018-08-16 05:10:00 +0000"), quantity: HKQuantity(unit: mgdl, doubleValue: 229.16645207559424)),
  255. GlucoseEffect(startDate: f("2018-08-16 05:15:00 +0000"), quantity: HKQuantity(unit: mgdl, doubleValue: 230.6787977546066)),
  256. GlucoseEffect(startDate: f("2018-08-16 05:20:00 +0000"), quantity: HKQuantity(unit: mgdl, doubleValue: 232.19114343361895)),
  257. GlucoseEffect(startDate: f("2018-08-16 05:25:00 +0000"), quantity: HKQuantity(unit: mgdl, doubleValue: 233.70348911263127)),
  258. GlucoseEffect(startDate: f("2018-08-16 05:30:00 +0000"), quantity: HKQuantity(unit: mgdl, doubleValue: 235.21583479164363)),
  259. GlucoseEffect(startDate: f("2018-08-16 05:35:00 +0000"), quantity: HKQuantity(unit: mgdl, doubleValue: 236.72818047065596)),
  260. GlucoseEffect(startDate: f("2018-08-16 05:40:00 +0000"), quantity: HKQuantity(unit: mgdl, doubleValue: 238.2405261496683)),
  261. GlucoseEffect(startDate: f("2018-08-16 05:45:00 +0000"), quantity: HKQuantity(unit: mgdl, doubleValue: 239.75287182868067)),
  262. GlucoseEffect(startDate: f("2018-08-16 05:50:00 +0000"), quantity: HKQuantity(unit: mgdl, doubleValue: 241.26521750769302)),
  263. GlucoseEffect(startDate: f("2018-08-16 05:55:00 +0000"), quantity: HKQuantity(unit: mgdl, doubleValue: 242.77756318670535)),
  264. GlucoseEffect(startDate: f("2018-08-16 06:00:00 +0000"), quantity: HKQuantity(unit: mgdl, doubleValue: 244.28990886571768)),
  265. GlucoseEffect(startDate: f("2018-08-16 06:05:00 +0000"), quantity: HKQuantity(unit: mgdl, doubleValue: 245.80225454473003)),
  266. GlucoseEffect(startDate: f("2018-08-16 06:10:00 +0000"), quantity: HKQuantity(unit: mgdl, doubleValue: 247.3146002237424)),
  267. GlucoseEffect(startDate: f("2018-08-16 06:15:00 +0000"), quantity: HKQuantity(unit: mgdl, doubleValue: 248.82694590275474)),
  268. GlucoseEffect(startDate: f("2018-08-16 06:20:00 +0000"), quantity: HKQuantity(unit: mgdl, doubleValue: 250.33929158176707)),
  269. GlucoseEffect(startDate: f("2018-08-16 06:25:00 +0000"), quantity: HKQuantity(unit: mgdl, doubleValue: 251.85163726077943)),
  270. GlucoseEffect(startDate: f("2018-08-16 06:30:00 +0000"), quantity: HKQuantity(unit: mgdl, doubleValue: 253.16666666666669)),
  271. GlucoseEffect(startDate: f("2018-08-16 06:35:00 +0000"), quantity: HKQuantity(unit: mgdl, doubleValue: 253.16666666666669)),
  272. GlucoseEffect(startDate: f("2018-08-16 06:40:00 +0000"), quantity: HKQuantity(unit: mgdl, doubleValue: 253.16666666666669)),
  273. ]
  274. let calculated = insulinCounteractionEffects.subtracting(carbEffects, withUniformInterval: .minutes(5))
  275. let expected = loadGlucoseEffectFixture("ice_minus_carb_effect_with_gaps_output", formatter: DateFormatter.descriptionFormatter)
  276. XCTAssertEqual(expected, calculated)
  277. }
  278. func testSubtractingFlatCarbEffectFromICE() {
  279. let perMinute = HKUnit.milligramsPerDeciliter.unitDivided(by: .minute())
  280. let mgdl = HKUnit.milligramsPerDeciliter
  281. let formatter = DateFormatter.descriptionFormatter
  282. let f = { (input) in
  283. return formatter.date(from: input)!
  284. }
  285. let insulinCounteractionEffects = [
  286. GlucoseEffectVelocity(startDate: f("2018-08-26 00:23:27 +0000"), endDate: f("2018-08-26 00:28:28 +0000"), quantity: HKQuantity(unit: perMinute, doubleValue: 0.3711911901542791)),
  287. GlucoseEffectVelocity(startDate: f("2018-08-26 00:28:28 +0000"), endDate: f("2018-08-26 00:33:27 +0000"), quantity: HKQuantity(unit: perMinute, doubleValue: -0.4382943196158106)),
  288. GlucoseEffectVelocity(startDate: f("2018-08-26 00:33:27 +0000"), endDate: f("2018-08-26 00:38:27 +0000"), quantity: HKQuantity(unit: perMinute, doubleValue: -0.24797050925219474)),
  289. GlucoseEffectVelocity(startDate: f("2018-08-26 00:38:27 +0000"), endDate: f("2018-08-26 00:43:28 +0000"), quantity: HKQuantity(unit: perMinute, doubleValue: -0.05800368381887202)),
  290. GlucoseEffectVelocity(startDate: f("2018-08-26 00:43:28 +0000"), endDate: f("2018-08-26 00:48:28 +0000"), quantity: HKQuantity(unit: perMinute, doubleValue: 0.7303422936657988)),
  291. GlucoseEffectVelocity(startDate: f("2018-08-26 00:48:28 +0000"), endDate: f("2018-08-26 00:53:28 +0000"), quantity: HKQuantity(unit: perMinute, doubleValue: 0.7166291304729353)),
  292. GlucoseEffectVelocity(startDate: f("2018-08-26 00:53:28 +0000"), endDate: f("2018-08-26 00:58:28 +0000"), quantity: HKQuantity(unit: perMinute, doubleValue: 0.2962279320324577)),
  293. GlucoseEffectVelocity(startDate: f("2018-08-26 00:58:28 +0000"), endDate: f("2018-08-26 01:03:27 +0000"), quantity: HKQuantity(unit: perMinute, doubleValue: 0.2730196016663657)),
  294. GlucoseEffectVelocity(startDate: f("2018-08-26 01:03:27 +0000"), endDate: f("2018-08-26 01:08:28 +0000"), quantity: HKQuantity(unit: perMinute, doubleValue: 0.44605609358758713)),
  295. GlucoseEffectVelocity(startDate: f("2018-08-26 01:08:28 +0000"), endDate: f("2018-08-26 01:13:28 +0000"), quantity: HKQuantity(unit: perMinute, doubleValue: 0.42131971635266463)),
  296. GlucoseEffectVelocity(startDate: f("2018-08-26 01:13:28 +0000"), endDate: f("2018-08-26 01:18:27 +0000"), quantity: HKQuantity(unit: perMinute, doubleValue: 0.9948293689475411)),
  297. GlucoseEffectVelocity(startDate: f("2018-08-26 01:18:27 +0000"), endDate: f("2018-08-26 01:23:28 +0000"), quantity: HKQuantity(unit: perMinute, doubleValue: 0.9652238210644638)),
  298. ]
  299. let carbEffects = [
  300. GlucoseEffect(startDate: f("2018-08-26 00:45:00 +0000"), quantity: HKQuantity(unit: mgdl, doubleValue: 385.8235294117647))
  301. ]
  302. let calculated = insulinCounteractionEffects.subtracting(carbEffects, withUniformInterval: .minutes(5))
  303. let expected = loadGlucoseEffectFixture("ice_minus_flat_carb_effect_output", formatter: DateFormatter.descriptionFormatter)
  304. XCTAssertEqual(expected, calculated)
  305. }
  306. func testCombinedSumsWithGaps() {
  307. let input = loadGlucoseEffectFixture("ice_minus_carb_effect_with_gaps_output", formatter: DateFormatter.descriptionFormatter)
  308. let unit = HKUnit.milligramsPerDeciliter
  309. let formatter = DateFormatter.descriptionFormatter
  310. let f = { (input) in
  311. return formatter.date(from: input)!
  312. }
  313. let expected = [
  314. GlucoseChange(startDate: f("2018-08-16 01:13:44 +0000"), endDate: f("2018-08-16 01:13:44 +0000"), quantity: HKQuantity(unit: unit, doubleValue: -7.914677985345208)),
  315. GlucoseChange(startDate: f("2018-08-16 01:13:44 +0000"), endDate: f("2018-08-16 01:18:44 +0000"), quantity: HKQuantity(unit: unit, doubleValue: -13.84284360394594)),
  316. GlucoseChange(startDate: f("2018-08-16 01:13:44 +0000"), endDate: f("2018-08-16 01:23:44 +0000"), quantity: HKQuantity(unit: unit, doubleValue: -12.613217502012784)),
  317. GlucoseChange(startDate: f("2018-08-16 01:13:44 +0000"), endDate: f("2018-08-16 01:28:43 +0000"), quantity: HKQuantity(unit: unit, doubleValue: -11.206618583133514)),
  318. GlucoseChange(startDate: f("2018-08-16 02:03:43 +0000"), endDate: f("2018-08-16 02:03:43 +0000"), quantity: HKQuantity(unit: unit, doubleValue: 6.071196836763691)),
  319. GlucoseChange(startDate: f("2018-08-16 03:13:44 +0000"), endDate: f("2018-08-16 03:13:44 +0000"), quantity: HKQuantity(unit: unit, doubleValue: 1.735876471025445)),
  320. GlucoseChange(startDate: f("2018-08-16 03:13:44 +0000"), endDate: f("2018-08-16 03:18:44 +0000"), quantity: HKQuantity(unit: unit, doubleValue: 2.2702059848264096)),
  321. GlucoseChange(startDate: f("2018-08-16 03:13:44 +0000"), endDate: f("2018-08-16 03:23:43 +0000"), quantity: HKQuantity(unit: unit, doubleValue: 3.754918289224931)),
  322. GlucoseChange(startDate: f("2018-08-16 03:13:44 +0000"), endDate: f("2018-08-16 03:28:44 +0000"), quantity: HKQuantity(unit: unit, doubleValue: 5.444256153348801)),
  323. GlucoseChange(startDate: f("2018-08-16 03:13:44 +0000"), endDate: f("2018-08-16 03:33:43 +0000"), quantity: HKQuantity(unit: unit, doubleValue: 11.486797488154545)),
  324. GlucoseChange(startDate: f("2018-08-16 03:13:44 +0000"), endDate: f("2018-08-16 03:38:43 +0000"), quantity: HKQuantity(unit: unit, doubleValue: 1.7953524267912058)),
  325. GlucoseChange(startDate: f("2018-08-16 03:13:44 +0000"), endDate: f("2018-08-16 03:43:44 +0000"), quantity: HKQuantity(unit: unit, doubleValue: 2.376773676557343)),
  326. GlucoseChange(startDate: f("2018-08-16 03:18:44 +0000"), endDate: f("2018-08-16 03:48:43 +0000"), quantity: HKQuantity(unit: unit, doubleValue: -6.551474763321222)),
  327. GlucoseChange(startDate: f("2018-08-16 03:28:44 +0000"), endDate: f("2018-08-16 03:53:44 +0000"), quantity: HKQuantity(unit: unit, doubleValue: -11.56463836036644)),
  328. GlucoseChange(startDate: f("2018-08-16 03:28:44 +0000"), endDate: f("2018-08-16 03:58:43 +0000"), quantity: HKQuantity(unit: unit, doubleValue: -23.524929675277797)),
  329. GlucoseChange(startDate: f("2018-08-16 03:33:43 +0000"), endDate: f("2018-08-16 04:03:43 +0000"), quantity: HKQuantity(unit: unit, doubleValue: -26.46553805353557)),
  330. GlucoseChange(startDate: f("2018-08-16 03:38:43 +0000"), endDate: f("2018-08-16 04:08:43 +0000"), quantity: HKQuantity(unit: unit, doubleValue: -32.50957095033954)),
  331. GlucoseChange(startDate: f("2018-08-16 03:43:44 +0000"), endDate: f("2018-08-16 04:13:44 +0000"), quantity: HKQuantity(unit: unit, doubleValue: -22.823727411197925)),
  332. GlucoseChange(startDate: f("2018-08-16 03:48:43 +0000"), endDate: f("2018-08-16 04:18:43 +0000"), quantity: HKQuantity(unit: unit, doubleValue: -23.399872617600906)),
  333. GlucoseChange(startDate: f("2018-08-16 03:53:44 +0000"), endDate: f("2018-08-16 04:23:44 +0000"), quantity: HKQuantity(unit: unit, doubleValue: -16.21261395015381)),
  334. GlucoseChange(startDate: f("2018-08-16 04:03:43 +0000"), endDate: f("2018-08-16 04:28:44 +0000"), quantity: HKQuantity(unit: unit, doubleValue: -1.2556785583940788)),
  335. GlucoseChange(startDate: f("2018-08-16 04:03:43 +0000"), endDate: f("2018-08-16 04:33:43 +0000"), quantity: HKQuantity(unit: unit, doubleValue: -3.0507010894534323))
  336. ]
  337. let calculated = input.combinedSums(of: .minutes(30))
  338. XCTAssertEqual(expected, calculated)
  339. }
  340. func testNetEffect() {
  341. let formatter = DateFormatter.descriptionFormatter
  342. let f = { (input) in
  343. return formatter.date(from: input)!
  344. }
  345. let unit = HKUnit.milligramsPerDeciliter
  346. let input = [
  347. GlucoseEffect(startDate: f("2018-08-16 01:00:00 +0000"), quantity: HKQuantity(unit: unit, doubleValue: 25)),
  348. GlucoseEffect(startDate: f("2018-08-16 01:05:00 +0000"), quantity: HKQuantity(unit: unit, doubleValue: 26)),
  349. GlucoseEffect(startDate: f("2018-08-16 01:10:00 +0000"), quantity: HKQuantity(unit: unit, doubleValue: 27))
  350. ]
  351. let calculated = input.netEffect()
  352. let expected = GlucoseChange(
  353. startDate: f("2018-08-16 01:00:00 +0000"),
  354. endDate: f("2018-08-16 01:10:00 +0000"),
  355. quantity: HKQuantity(unit: unit, doubleValue: 2))
  356. XCTAssertEqual(expected, calculated)
  357. }
  358. }