TempBasalFunctions.swift 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. import Foundation
  2. enum TempBasalFunctionError: LocalizedError, Equatable {
  3. case invalidBasalRateOnProfile
  4. var errorDescription: String? {
  5. switch self {
  6. case .invalidBasalRateOnProfile:
  7. return "The currentBasal, maxBasal, or maxDailyBasal wasn't set on Profile"
  8. }
  9. }
  10. }
  11. enum TempBasalFunctions {
  12. /// Rounds basal rates to match the basal increment for the pump as the basal rate increases
  13. static func roundBasal(profile: Profile, basalRate: Decimal) -> Decimal {
  14. // FIXME: Should we just call the pumpManager here?
  15. let lowestRateScale: Decimal
  16. if let model = profile.model, model.hasSuffix("54") || model.hasSuffix("23") {
  17. lowestRateScale = 40
  18. } else {
  19. lowestRateScale = 20
  20. }
  21. let roundedBasal: Decimal
  22. if basalRate < 1 {
  23. roundedBasal = (basalRate * lowestRateScale).jsRounded() / lowestRateScale
  24. } else if basalRate < 10 {
  25. roundedBasal = (basalRate * 20).jsRounded() / 20
  26. } else {
  27. roundedBasal = (basalRate * 10).jsRounded() / 10
  28. }
  29. return roundedBasal
  30. }
  31. /// defines the max safe basal rate given a profile
  32. static func getMaxSafeBasalRate(profile: Profile) throws -> Decimal {
  33. // use default values if either of these are NaN
  34. let maxDailySafetyMultiplier = profile.maxDailySafetyMultiplier.isNaN ? 3 : profile.maxDailySafetyMultiplier
  35. let currentBasalSafetyMultiplier = profile.currentBasalSafetyMultiplier.isNaN ? 4 : profile.currentBasalSafetyMultiplier
  36. guard let currentBasal = profile.currentBasal, let maxDailyBasal = profile.maxDailyBasal,
  37. let maxBasal = profile.maxBasal
  38. else {
  39. throw TempBasalFunctionError.invalidBasalRateOnProfile
  40. }
  41. return min(
  42. maxBasal,
  43. maxDailySafetyMultiplier * maxDailyBasal,
  44. currentBasalSafetyMultiplier * currentBasal
  45. )
  46. }
  47. static func setTempBasal(
  48. rate: Decimal,
  49. duration: Decimal,
  50. profile: Profile,
  51. determination: Determination,
  52. currentTemp: TempBasal
  53. ) throws -> Determination {
  54. var determination = determination
  55. let maxSafeBasal = try getMaxSafeBasalRate(profile: profile)
  56. var rate = rate
  57. if rate < 0 {
  58. rate = 0
  59. } else if rate > maxSafeBasal {
  60. rate = maxSafeBasal
  61. }
  62. let suggestedRate = roundBasal(profile: profile, basalRate: rate)
  63. if Decimal(currentTemp.duration) > (duration - 10),
  64. currentTemp.duration <= 120,
  65. suggestedRate <= currentTemp.rate * 1.2,
  66. suggestedRate >= currentTemp.rate * 0.8,
  67. duration > 0
  68. {
  69. determination
  70. .reason += " \(currentTemp.duration)m left and \(currentTemp.rate) ~ req \(suggestedRate)U/hr: no temp required"
  71. return determination
  72. }
  73. if suggestedRate == profile.currentBasal {
  74. if profile.skipNeutralTemps {
  75. if currentTemp.duration > 0 {
  76. determination
  77. .reason = determination.reason +
  78. ". Suggested rate is same as profile rate, a temp basal is active, canceling current temp"
  79. determination.duration = 0
  80. determination.rate = 0
  81. return determination
  82. } else {
  83. determination
  84. .reason = determination.reason +
  85. ". Suggested rate is same as profile rate, no temp basal is active, doing nothing"
  86. return determination
  87. }
  88. } else {
  89. determination.reason = determination.reason + ". Setting neutral temp basal of \(profile.currentBasal ?? 0)U/hr"
  90. determination.duration = duration
  91. determination.rate = suggestedRate
  92. return determination
  93. }
  94. } else {
  95. determination.duration = duration
  96. determination.rate = suggestedRate
  97. return determination
  98. }
  99. }
  100. }