StatsDataFetcher.swift 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560
  1. // LoopFollow
  2. // StatsDataFetcher.swift
  3. import Foundation
  4. class StatsDataFetcher {
  5. weak var mainViewController: MainViewController?
  6. weak var dataService: StatsDataService?
  7. init(mainViewController: MainViewController?) {
  8. self.mainViewController = mainViewController
  9. }
  10. func fetchBGData(days: Int, completion: @escaping () -> Void) {
  11. guard let mainVC = mainViewController, IsNightscoutEnabled() else {
  12. completion()
  13. return
  14. }
  15. var parameters: [String: String] = [:]
  16. let utcISODateFormatter = ISO8601DateFormatter()
  17. let startDate = dataService?.startDate ?? dateTimeUtils.displayCalendar().date(byAdding: .day, value: -1 * days, to: Date())!
  18. parameters["count"] = "\(days * 2 * 24 * 60 / 5)"
  19. parameters["find[dateString][$gte]"] = utcISODateFormatter.string(from: startDate)
  20. parameters["find[type][$ne]"] = "cal"
  21. NightscoutUtils.executeRequest(eventType: .sgv, parameters: parameters) { (result: Result<[ShareGlucoseData], Error>) in
  22. switch result {
  23. case let .success(entriesResponse):
  24. var nsData = entriesResponse
  25. DispatchQueue.main.async {
  26. // Transform NS data
  27. for i in 0 ..< nsData.count {
  28. nsData[i].date /= 1000
  29. nsData[i].date.round(FloatingPointRoundingRule.toNearestOrEven)
  30. }
  31. var nsData2: [ShareGlucoseData] = []
  32. var lastAddedTime = Double.infinity
  33. var lastAddedSGV: Int?
  34. let minInterval: Double = 30
  35. for reading in nsData {
  36. if (lastAddedSGV == nil || lastAddedSGV != reading.sgv) || (lastAddedTime - reading.date >= minInterval) {
  37. nsData2.append(reading)
  38. lastAddedTime = reading.date
  39. lastAddedSGV = reading.sgv
  40. }
  41. }
  42. let cutoffTime = self.dataService?.startDate.timeIntervalSince1970 ?? (Date().timeIntervalSince1970 - (Double(days) * 24 * 60 * 60))
  43. mainVC.statsBGData.removeAll { $0.date < cutoffTime }
  44. let existingDates = Set(mainVC.statsBGData.map { Int($0.date) })
  45. for reading in nsData2 {
  46. if !existingDates.contains(Int(reading.date)), reading.date >= cutoffTime {
  47. mainVC.statsBGData.append(reading)
  48. }
  49. }
  50. mainVC.statsBGData.sort { $0.date < $1.date }
  51. completion()
  52. }
  53. case let .failure(error):
  54. LogManager.shared.log(category: .nightscout, message: "Failed to fetch stats BG data: \(error)", limitIdentifier: "Failed to fetch stats BG data")
  55. DispatchQueue.main.async {
  56. completion()
  57. }
  58. }
  59. }
  60. }
  61. func fetchTreatmentsData(days: Int, completion: @escaping () -> Void) {
  62. guard let mainVC = mainViewController, IsNightscoutEnabled(), Storage.shared.downloadTreatments.value else {
  63. completion()
  64. return
  65. }
  66. ensureBasalProfileLoaded(mainVC: mainVC) {
  67. self.fetchAndMergeTreatments(days: days, mainVC: mainVC, completion: completion)
  68. }
  69. }
  70. private func ensureBasalProfileLoaded(mainVC: MainViewController, completion: @escaping () -> Void) {
  71. if !mainVC.basalProfile.isEmpty {
  72. completion()
  73. return
  74. }
  75. NightscoutUtils.executeRequest(eventType: .profile, parameters: [:]) { (result: Result<NSProfile, Error>) in
  76. switch result {
  77. case let .success(profileData):
  78. let profileStore = profileData.store["default"] ??
  79. profileData.store["Default"] ??
  80. profileData.store[profileData.defaultProfile]
  81. DispatchQueue.main.async {
  82. if let profileStore {
  83. mainVC.basalProfile = profileStore.basal.map {
  84. MainViewController.basalProfileStruct(
  85. value: $0.value,
  86. time: $0.time,
  87. timeAsSeconds: $0.timeAsSeconds
  88. )
  89. }
  90. }
  91. completion()
  92. }
  93. case let .failure(error):
  94. LogManager.shared.log(
  95. category: .nightscout,
  96. message: "Failed to fetch profile data for stats basal calculations: \(error.localizedDescription)"
  97. )
  98. DispatchQueue.main.async {
  99. completion()
  100. }
  101. }
  102. }
  103. }
  104. private func fetchAndMergeTreatments(days: Int, mainVC: MainViewController, completion: @escaping () -> Void) {
  105. let utcISODateFormatter = ISO8601DateFormatter()
  106. utcISODateFormatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
  107. utcISODateFormatter.timeZone = TimeZone(abbreviation: "UTC")
  108. let startDate = dataService?.startDate ?? dateTimeUtils.displayCalendar().date(byAdding: .day, value: -1 * days, to: Date())!
  109. let endDate = dataService?.endDate ?? Date()
  110. let startTimeString = utcISODateFormatter.string(from: startDate)
  111. let currentTimeString = utcISODateFormatter.string(from: endDate)
  112. let estimatedCount = max(days * 100, 5000)
  113. let parameters: [String: String] = [
  114. "find[created_at][$gte]": startTimeString,
  115. "find[created_at][$lte]": currentTimeString,
  116. "count": "\(estimatedCount)",
  117. ]
  118. NightscoutUtils.executeDynamicRequest(eventType: .treatments, parameters: parameters) { (result: Result<Any, Error>) in
  119. switch result {
  120. case let .success(data):
  121. if let entries = data as? [[String: AnyObject]] {
  122. DispatchQueue.main.async {
  123. self.fetchAndMergeBolusData(entries: entries, days: days, mainVC: mainVC)
  124. self.fetchAndMergeSMBData(entries: entries, days: days, mainVC: mainVC)
  125. self.fetchAndMergeCarbData(entries: entries, days: days, mainVC: mainVC)
  126. self.fetchAndMergeBasalData(entries: entries, days: days, mainVC: mainVC)
  127. completion()
  128. }
  129. } else {
  130. DispatchQueue.main.async {
  131. completion()
  132. }
  133. }
  134. case let .failure(error):
  135. LogManager.shared.log(category: .nightscout, message: "Failed to fetch stats treatments data: \(error.localizedDescription)")
  136. DispatchQueue.main.async {
  137. completion()
  138. }
  139. }
  140. }
  141. }
  142. private func fetchAndMergeBolusData(entries: [[String: AnyObject]], days: Int, mainVC: MainViewController) {
  143. let cutoffTime = dataService?.startDate.timeIntervalSince1970 ?? (Date().timeIntervalSince1970 - (Double(days) * 24 * 60 * 60))
  144. var bolusEntries: [[String: AnyObject]] = []
  145. for entry in entries {
  146. guard let eventType = entry["eventType"] as? String else { continue }
  147. if eventType == "Correction Bolus" || eventType == "Bolus" || eventType == "External Insulin" {
  148. if let automatic = entry["automatic"] as? Bool, automatic {
  149. continue
  150. }
  151. bolusEntries.append(entry)
  152. } else if eventType == "Meal Bolus" {
  153. bolusEntries.append(entry)
  154. }
  155. }
  156. mainVC.statsBolusData.removeAll { $0.date < cutoffTime }
  157. let existingDates = Set(mainVC.statsBolusData.map { Int($0.date) })
  158. var lastFoundIndex = 0
  159. for currentEntry in bolusEntries.reversed() {
  160. var bolusDate: String
  161. if currentEntry["timestamp"] != nil {
  162. bolusDate = currentEntry["timestamp"] as! String
  163. } else if currentEntry["created_at"] != nil {
  164. bolusDate = currentEntry["created_at"] as! String
  165. } else {
  166. continue
  167. }
  168. guard let parsedDate = NightscoutUtils.parseDate(bolusDate),
  169. let bolus = currentEntry["insulin"] as? Double else { continue }
  170. let dateTimeStamp = parsedDate.timeIntervalSince1970
  171. if dateTimeStamp < cutoffTime { continue }
  172. // Avoid duplicates (use Int to handle floating point precision)
  173. if existingDates.contains(Int(dateTimeStamp)) { continue }
  174. let sgv = mainVC.findNearestBGbyTime(needle: dateTimeStamp, haystack: mainVC.statsBGData, startingIndex: lastFoundIndex)
  175. lastFoundIndex = sgv.foundIndex
  176. let dot = MainViewController.bolusGraphStruct(value: bolus, date: Double(dateTimeStamp), sgv: Int(sgv.sgv + 20))
  177. mainVC.statsBolusData.append(dot)
  178. }
  179. mainVC.statsBolusData.sort { $0.date < $1.date }
  180. }
  181. private func fetchAndMergeSMBData(entries: [[String: AnyObject]], days: Int, mainVC: MainViewController) {
  182. let cutoffTime = dataService?.startDate.timeIntervalSince1970 ?? (Date().timeIntervalSince1970 - (Double(days) * 24 * 60 * 60))
  183. var smbEntries: [[String: AnyObject]] = []
  184. for entry in entries {
  185. guard let eventType = entry["eventType"] as? String else { continue }
  186. if eventType == "SMB" {
  187. smbEntries.append(entry)
  188. } else if eventType == "Correction Bolus" || eventType == "Bolus" || eventType == "External Insulin" {
  189. if let automatic = entry["automatic"] as? Bool, automatic {
  190. smbEntries.append(entry)
  191. }
  192. }
  193. }
  194. mainVC.statsSMBData.removeAll { $0.date < cutoffTime }
  195. let existingDates = Set(mainVC.statsSMBData.map { Int($0.date) })
  196. var lastFoundIndex = 0
  197. for currentEntry in smbEntries.reversed() {
  198. var bolusDate: String
  199. if currentEntry["timestamp"] != nil {
  200. bolusDate = currentEntry["timestamp"] as! String
  201. } else if currentEntry["created_at"] != nil {
  202. bolusDate = currentEntry["created_at"] as! String
  203. } else {
  204. continue
  205. }
  206. guard let parsedDate = NightscoutUtils.parseDate(bolusDate),
  207. let bolus = currentEntry["insulin"] as? Double else { continue }
  208. let dateTimeStamp = parsedDate.timeIntervalSince1970
  209. if dateTimeStamp < cutoffTime { continue }
  210. if existingDates.contains(Int(dateTimeStamp)) { continue }
  211. let sgv = mainVC.findNearestBGbyTime(needle: dateTimeStamp, haystack: mainVC.statsBGData, startingIndex: lastFoundIndex)
  212. lastFoundIndex = sgv.foundIndex
  213. let dot = MainViewController.bolusGraphStruct(value: bolus, date: Double(dateTimeStamp), sgv: Int(sgv.sgv + 20))
  214. mainVC.statsSMBData.append(dot)
  215. }
  216. mainVC.statsSMBData.sort { $0.date < $1.date }
  217. }
  218. private func fetchAndMergeCarbData(entries: [[String: AnyObject]], days: Int, mainVC: MainViewController) {
  219. let cutoffTime = dataService?.startDate.timeIntervalSince1970 ?? (Date().timeIntervalSince1970 - (Double(days) * 24 * 60 * 60))
  220. let now = dataService?.endDate.timeIntervalSince1970 ?? Date().timeIntervalSince1970
  221. var carbEntries: [[String: AnyObject]] = []
  222. for entry in entries {
  223. guard let eventType = entry["eventType"] as? String else { continue }
  224. if eventType == "Carb Correction" || eventType == "Meal Bolus" {
  225. carbEntries.append(entry)
  226. }
  227. }
  228. mainVC.statsCarbData.removeAll { $0.date < cutoffTime || $0.date > now }
  229. let existingDates = Set(mainVC.statsCarbData.map { Int($0.date) })
  230. var lastFoundIndex = 0
  231. var lastFoundBolus = 0
  232. for currentEntry in carbEntries.reversed() {
  233. var carbDate: String
  234. if currentEntry["timestamp"] != nil {
  235. carbDate = currentEntry["timestamp"] as! String
  236. } else if currentEntry["created_at"] != nil {
  237. carbDate = currentEntry["created_at"] as! String
  238. } else {
  239. continue
  240. }
  241. let absorptionTime = currentEntry["absorptionTime"] as? Int ?? 0
  242. guard let parsedDate = NightscoutUtils.parseDate(carbDate),
  243. let carbs = currentEntry["carbs"] as? Double else { continue }
  244. let dateTimeStamp = parsedDate.timeIntervalSince1970
  245. if dateTimeStamp < cutoffTime || dateTimeStamp > now { continue }
  246. if existingDates.contains(Int(dateTimeStamp)) { continue }
  247. let sgv = mainVC.findNearestBGbyTime(needle: dateTimeStamp, haystack: mainVC.statsBGData, startingIndex: lastFoundIndex)
  248. lastFoundIndex = sgv.foundIndex
  249. var offset = -50
  250. if sgv.sgv < Double(mainVC.calculateMaxBgGraphValue() - 100) {
  251. let bolusTime = mainVC.findNearestBolusbyTime(timeWithin: 300, needle: dateTimeStamp, haystack: mainVC.statsBolusData, startingIndex: lastFoundBolus)
  252. lastFoundBolus = bolusTime.foundIndex
  253. offset = bolusTime.offset ? 70 : 20
  254. }
  255. let dot = MainViewController.carbGraphStruct(value: Double(carbs), date: Double(dateTimeStamp), sgv: Int(sgv.sgv + Double(offset)), absorptionTime: absorptionTime)
  256. mainVC.statsCarbData.append(dot)
  257. }
  258. mainVC.statsCarbData.sort { $0.date < $1.date }
  259. }
  260. private func fetchAndMergeBasalData(entries: [[String: AnyObject]], days: Int, mainVC: MainViewController) {
  261. let cutoffTime = dataService?.startDate.timeIntervalSince1970 ?? (Date().timeIntervalSince1970 - (Double(days) * 24 * 60 * 60))
  262. let now = dataService?.endDate.timeIntervalSince1970 ?? Date().timeIntervalSince1970
  263. var basalEntries: [[String: AnyObject]] = []
  264. for entry in entries {
  265. guard let eventType = entry["eventType"] as? String else { continue }
  266. if eventType == "Temp Basal" {
  267. basalEntries.append(entry)
  268. }
  269. }
  270. mainVC.statsBasalData.removeAll { $0.date < cutoffTime }
  271. // Clear and rebuild temp basal entries for stats calculation
  272. dataService?.tempBasalEntries.removeAll { $0.startTime < cutoffTime }
  273. var existingTempBasalTimes = Set(dataService?.tempBasalEntries.map { Int($0.startTime) } ?? [])
  274. var existingDates = Set(mainVC.statsBasalData.map { Int($0.date) })
  275. var tempArray = basalEntries
  276. tempArray.reverse()
  277. for i in 0 ..< tempArray.count {
  278. guard let currentEntry = tempArray[i] as [String: AnyObject]? else { continue }
  279. let dateString = currentEntry["timestamp"] as? String ?? currentEntry["created_at"] as? String
  280. guard let rawDateStr = dateString,
  281. let dateParsed = NightscoutUtils.parseDate(rawDateStr)
  282. else {
  283. continue
  284. }
  285. let dateTimeStamp = dateParsed.timeIntervalSince1970
  286. if dateTimeStamp < cutoffTime { continue }
  287. guard let basalRate = currentEntry["absolute"] as? Double else {
  288. continue
  289. }
  290. var duration = currentEntry["duration"] as? Double ?? 0.0
  291. // Store raw temp basal entry for stats calculation
  292. // Adjust duration if it overlaps with next temp basal
  293. var effectiveDuration = duration
  294. if i < tempArray.count - 1 {
  295. let nextEntry = tempArray[i + 1] as [String: AnyObject]?
  296. let nextDateStr = nextEntry?["timestamp"] as? String ?? nextEntry?["created_at"] as? String
  297. if let rawNext = nextDateStr,
  298. let nextDateParsed = NightscoutUtils.parseDate(rawNext)
  299. {
  300. let nextDateTimeStamp = nextDateParsed.timeIntervalSince1970
  301. let tempBasalEnd = dateTimeStamp + (duration * 60)
  302. if nextDateTimeStamp < tempBasalEnd {
  303. // Adjust duration to end when next temp basal starts
  304. effectiveDuration = (nextDateTimeStamp - dateTimeStamp) / 60.0
  305. }
  306. }
  307. }
  308. if !existingTempBasalTimes.contains(Int(dateTimeStamp)) {
  309. let tempBasalEntry = StatsDataService.TempBasalEntry(
  310. rate: basalRate,
  311. startTime: dateTimeStamp,
  312. durationMinutes: effectiveDuration > 0 ? effectiveDuration : 30.0
  313. )
  314. dataService?.tempBasalEntries.append(tempBasalEntry)
  315. existingTempBasalTimes.insert(Int(dateTimeStamp))
  316. }
  317. if i > 0 {
  318. let priorEntry = tempArray[i - 1] as [String: AnyObject]?
  319. let priorDateStr = priorEntry?["timestamp"] as? String ?? priorEntry?["created_at"] as? String
  320. if let rawPrior = priorDateStr,
  321. let priorDateParsed = NightscoutUtils.parseDate(rawPrior)
  322. {
  323. let priorDateTimeStamp = priorDateParsed.timeIntervalSince1970
  324. let priorDuration = priorEntry?["duration"] as? Double ?? 0.0
  325. let priorEndTime = priorDateTimeStamp + (priorDuration * 60)
  326. if (dateTimeStamp - priorEndTime) > 15 {
  327. let gapEntries = createScheduledBasalEntriesForGap(
  328. from: priorEndTime,
  329. to: dateTimeStamp,
  330. profile: mainVC.basalProfile,
  331. existingDates: existingDates
  332. )
  333. for entry in gapEntries {
  334. if !existingDates.contains(Int(entry.date)) {
  335. mainVC.statsBasalData.append(entry)
  336. existingDates.insert(Int(entry.date))
  337. }
  338. }
  339. }
  340. }
  341. }
  342. let startDot = MainViewController.basalGraphStruct(basalRate: basalRate, date: dateTimeStamp)
  343. if !existingDates.contains(Int(startDot.date)) {
  344. mainVC.statsBasalData.append(startDot)
  345. existingDates.insert(Int(startDot.date))
  346. }
  347. var endTime = dateTimeStamp + (duration * 60)
  348. if i == tempArray.count - 1, duration == 0.0 {
  349. endTime = dateTimeStamp + (30 * 60)
  350. }
  351. if i < tempArray.count - 1 {
  352. let nextEntry = tempArray[i + 1] as [String: AnyObject]?
  353. let nextDateStr = nextEntry?["timestamp"] as? String ?? nextEntry?["created_at"] as? String
  354. if let rawNext = nextDateStr,
  355. let nextDateParsed = NightscoutUtils.parseDate(rawNext)
  356. {
  357. let nextDateTimeStamp = nextDateParsed.timeIntervalSince1970
  358. if nextDateTimeStamp < endTime {
  359. endTime = nextDateTimeStamp
  360. }
  361. }
  362. }
  363. let endDot = MainViewController.basalGraphStruct(basalRate: basalRate, date: endTime)
  364. if !existingDates.contains(Int(endDot.date)) {
  365. mainVC.statsBasalData.append(endDot)
  366. existingDates.insert(Int(endDot.date))
  367. }
  368. }
  369. if !tempArray.isEmpty {
  370. let firstEntry = tempArray.first as [String: AnyObject]?
  371. let firstDateStr = firstEntry?["timestamp"] as? String ?? firstEntry?["created_at"] as? String
  372. if let rawFirst = firstDateStr,
  373. let firstDateParsed = NightscoutUtils.parseDate(rawFirst)
  374. {
  375. let firstTempBasalStart = firstDateParsed.timeIntervalSince1970
  376. if firstTempBasalStart > cutoffTime {
  377. let gapEntries = createScheduledBasalEntriesForGap(
  378. from: cutoffTime,
  379. to: firstTempBasalStart,
  380. profile: mainVC.basalProfile,
  381. existingDates: existingDates
  382. )
  383. for entry in gapEntries {
  384. if !existingDates.contains(Int(entry.date)) {
  385. mainVC.statsBasalData.append(entry)
  386. existingDates.insert(Int(entry.date))
  387. }
  388. }
  389. }
  390. }
  391. } else if !mainVC.basalProfile.isEmpty {
  392. let gapEntries = createScheduledBasalEntriesForGap(
  393. from: cutoffTime,
  394. to: now,
  395. profile: mainVC.basalProfile,
  396. existingDates: existingDates
  397. )
  398. for entry in gapEntries {
  399. if !existingDates.contains(Int(entry.date)) {
  400. mainVC.statsBasalData.append(entry)
  401. existingDates.insert(Int(entry.date))
  402. }
  403. }
  404. }
  405. if !tempArray.isEmpty {
  406. let lastEntry = tempArray.last as [String: AnyObject]?
  407. let lastDateStr = lastEntry?["timestamp"] as? String ?? lastEntry?["created_at"] as? String
  408. let lastDuration = lastEntry?["duration"] as? Double ?? 30.0
  409. if let rawLast = lastDateStr,
  410. let lastDateParsed = NightscoutUtils.parseDate(rawLast)
  411. {
  412. let lastTempBasalEnd = lastDateParsed.timeIntervalSince1970 + (lastDuration * 60)
  413. if lastTempBasalEnd < now {
  414. let gapEntries = createScheduledBasalEntriesForGap(
  415. from: lastTempBasalEnd,
  416. to: now,
  417. profile: mainVC.basalProfile,
  418. existingDates: existingDates
  419. )
  420. for entry in gapEntries {
  421. if !existingDates.contains(Int(entry.date)) {
  422. mainVC.statsBasalData.append(entry)
  423. existingDates.insert(Int(entry.date))
  424. }
  425. }
  426. }
  427. }
  428. }
  429. mainVC.statsBasalData.sort { $0.date < $1.date }
  430. }
  431. private func createScheduledBasalEntriesForGap(
  432. from startTime: TimeInterval,
  433. to endTime: TimeInterval,
  434. profile: [MainViewController.basalProfileStruct],
  435. existingDates: Set<Int>
  436. ) -> [MainViewController.basalGraphStruct] {
  437. guard !profile.isEmpty, endTime > startTime else { return [] }
  438. var entries: [MainViewController.basalGraphStruct] = []
  439. let sortedProfile = profile.sorted { $0.timeAsSeconds < $1.timeAsSeconds }
  440. let calendar = dateTimeUtils.displayCalendar()
  441. var currentTime = startTime
  442. while currentTime < endTime {
  443. let currentDate = Date(timeIntervalSince1970: currentTime)
  444. let dayStart = calendar.startOfDay(for: currentDate).timeIntervalSince1970
  445. let nextDayStart = dayStart + 24 * 60 * 60
  446. for i in 0 ..< sortedProfile.count {
  447. let segmentRate = sortedProfile[i].value
  448. let segmentStartInDay = dayStart + sortedProfile[i].timeAsSeconds
  449. let segmentEndInDay: TimeInterval
  450. if i < sortedProfile.count - 1 {
  451. segmentEndInDay = dayStart + sortedProfile[i + 1].timeAsSeconds
  452. } else {
  453. segmentEndInDay = nextDayStart
  454. }
  455. let overlapStart = max(currentTime, segmentStartInDay)
  456. let overlapEnd = min(endTime, segmentEndInDay)
  457. if overlapEnd > overlapStart, !existingDates.contains(Int(overlapStart)) {
  458. let startEntry = MainViewController.basalGraphStruct(basalRate: segmentRate, date: overlapStart)
  459. entries.append(startEntry)
  460. if overlapEnd < endTime, !existingDates.contains(Int(overlapEnd)) {
  461. let endEntry = MainViewController.basalGraphStruct(basalRate: segmentRate, date: overlapEnd)
  462. entries.append(endEntry)
  463. }
  464. }
  465. }
  466. currentTime = nextDayStart
  467. }
  468. return entries
  469. }
  470. }