BasalSchedule.swift 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  1. //
  2. // BasalSchedule.swift
  3. // OmniKit
  4. //
  5. // Created by Pete Schwamb on 4/4/18.
  6. // Copyright © 2018 Pete Schwamb. All rights reserved.
  7. //
  8. import Foundation
  9. public struct BasalScheduleEntry: RawRepresentable, Equatable {
  10. public typealias RawValue = [String: Any]
  11. let rate: Double
  12. let startTime: TimeInterval
  13. public init(rate: Double, startTime: TimeInterval) {
  14. self.rate = rate
  15. self.startTime = startTime
  16. }
  17. // MARK: - RawRepresentable
  18. public init?(rawValue: RawValue) {
  19. guard
  20. let rate = rawValue["rate"] as? Double,
  21. let startTime = rawValue["startTime"] as? Double
  22. else {
  23. return nil
  24. }
  25. self.rate = rate
  26. self.startTime = startTime
  27. }
  28. public var rawValue: RawValue {
  29. let rawValue: RawValue = [
  30. "rate": rate,
  31. "startTime": startTime
  32. ]
  33. return rawValue
  34. }
  35. }
  36. // A basal schedule starts at midnight and should contain 24 hours worth of entries
  37. public struct BasalSchedule: RawRepresentable, Equatable {
  38. public typealias RawValue = [String: Any]
  39. let entries: [BasalScheduleEntry]
  40. public func rateAt(offset: TimeInterval) -> Double {
  41. let (_, entry, _) = lookup(offset: offset)
  42. return entry.rate
  43. }
  44. // Returns index, entry, and time remaining
  45. func lookup(offset: TimeInterval) -> (Int, BasalScheduleEntry, TimeInterval) {
  46. guard offset >= 0 && offset < .hours(24) else {
  47. fatalError("Schedule offset out of bounds")
  48. }
  49. var last: TimeInterval = .hours(24)
  50. for (index, entry) in entries.reversed().enumerated() {
  51. if entry.startTime <= offset {
  52. return (entries.count - (index + 1), entry, last - entry.startTime)
  53. }
  54. last = entry.startTime
  55. }
  56. fatalError("Schedule incomplete")
  57. }
  58. public init(entries: [BasalScheduleEntry]) {
  59. self.entries = entries
  60. }
  61. public func durations() -> [(rate: Double, duration: TimeInterval, startTime: TimeInterval)] {
  62. var last: TimeInterval = .hours(24)
  63. let durations = entries.reversed().map { (entry) -> (rate: Double, duration: TimeInterval, startTime: TimeInterval) in
  64. let duration = (rate: entry.rate, duration: last - entry.startTime, startTime: entry.startTime)
  65. last = entry.startTime
  66. return duration
  67. }
  68. return durations.reversed()
  69. }
  70. // MARK: - RawRepresentable
  71. public init?(rawValue: RawValue) {
  72. guard
  73. let entries = rawValue["entries"] as? [BasalScheduleEntry.RawValue]
  74. else {
  75. return nil
  76. }
  77. self.entries = entries.compactMap { BasalScheduleEntry(rawValue: $0) }
  78. }
  79. public var rawValue: RawValue {
  80. let rawValue: RawValue = [
  81. "entries": entries.map { $0.rawValue }
  82. ]
  83. return rawValue
  84. }
  85. }
  86. public extension Sequence where Element == BasalScheduleEntry {
  87. func adjacentEqualRatesMerged() -> [BasalScheduleEntry] {
  88. var output = [BasalScheduleEntry]()
  89. let _ = self.reduce(nil) { (lastRate, entry) -> TimeInterval? in
  90. if entry.rate != lastRate {
  91. output.append(entry)
  92. }
  93. return entry.rate
  94. }
  95. return output
  96. }
  97. }