Targets.swift 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101
  1. import Foundation
  2. struct Targets {
  3. /// The Javascript implementation was hard to port. First, it mutates
  4. /// the inputs in a way that is visible in the Profile. Second, there
  5. /// is a line of code where it sets the high value to low but only if
  6. /// it's not a temp target. I'm going to port it as is for now, but this
  7. /// is worth revisiting after we're done with the port.
  8. ///
  9. // TODO: See if we can get rid of the logic that mutates inputs in Javascript
  10. static func lookup(
  11. targets: BGTargets,
  12. tempTargets: [TempTarget],
  13. profile: Profile,
  14. now: Date
  15. ) throws -> (ComputedBGTargets, Int) {
  16. // Find current target
  17. var bgComputedTargets = targets.targets
  18. .map { ComputedBGTargetEntry(low: $0.low, high: $0.high, start: $0.start, offset: $0.offset) }
  19. guard !bgComputedTargets.isEmpty else {
  20. throw ProfileError.invalidBgTargets
  21. }
  22. var targetIdx = bgComputedTargets.count - 1
  23. for (idx, (curr, next)) in zip(bgComputedTargets, bgComputedTargets.dropFirst()).enumerated() {
  24. if try now.isMinutesFromMidnightWithinRange(lowerBound: curr.offset, upperBound: next.offset) {
  25. targetIdx = idx
  26. break
  27. }
  28. }
  29. // Apply profile target if specified
  30. if let targetBg = profile.targetBg {
  31. bgComputedTargets[targetIdx].low = targetBg
  32. }
  33. bgComputedTargets[targetIdx].high = bgComputedTargets[targetIdx].low
  34. // Handle temp targets
  35. let sortedTempTargets = tempTargets.sorted { $0.createdAt > $1.createdAt }
  36. for target in sortedTempTargets {
  37. let start = target.createdAt
  38. let expires = start.addingTimeInterval(Double(target.duration) * 60)
  39. if now >= start, target.duration == 0 {
  40. // Cancel temp targets
  41. break
  42. } else if let targetBottom = target.targetBottom,
  43. let targetTop = target.targetTop
  44. {
  45. if now >= start, now < expires {
  46. bgComputedTargets[targetIdx].high = targetTop
  47. bgComputedTargets[targetIdx].low = targetBottom
  48. bgComputedTargets[targetIdx].temptargetSet = true
  49. break
  50. }
  51. } else {
  52. print("eventualBG target range invalid: \(target.targetBottom ?? -1)-\(target.targetTop ?? -1)")
  53. break
  54. }
  55. }
  56. return (
  57. ComputedBGTargets(units: targets.units, userPreferredUnits: targets.userPreferredUnits, targets: bgComputedTargets),
  58. targetIdx
  59. )
  60. }
  61. static func boundTargetRange(_ entry: ComputedBGTargetEntry) -> ComputedBGTargetEntry {
  62. var target = entry
  63. // Convert from mmol/L to mg/dL if needed
  64. if target.high < 20 { target.high *= 18 }
  65. if target.low < 20 { target.low *= 18 }
  66. // hard-code lower bounds for min_bg and max_bg in case pump is set too low, or units are wrong
  67. var maxBg = max(80, target.high)
  68. var minBg = max(80, target.low)
  69. // hard-code upper bound for min_bg in case pump is set too high
  70. minBg = min(200, minBg)
  71. maxBg = min(200, maxBg)
  72. target.minBg = minBg
  73. target.maxBg = maxBg
  74. return target
  75. }
  76. static func bgTargetsLookup(
  77. targets: BGTargets,
  78. tempTargets: [TempTarget],
  79. profile: Profile,
  80. now: Date = Date()
  81. ) throws -> (ComputedBGTargets, ComputedBGTargetEntry) {
  82. var (computedBgTargets, targetIdx) = try lookup(targets: targets, tempTargets: tempTargets, profile: profile, now: now)
  83. let currentTarget = boundTargetRange(computedBgTargets.targets[targetIdx])
  84. computedBgTargets.targets[targetIdx] = currentTarget
  85. return (computedBgTargets, currentTarget)
  86. }
  87. }