OpenAPS.swift 46 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205
  1. import Combine
  2. import CoreData
  3. import Foundation
  4. import JavaScriptCore
  5. final class OpenAPS {
  6. private let jsWorker = JavaScriptWorker()
  7. private let processQueue = DispatchQueue(label: "OpenAPS.processQueue", qos: .utility)
  8. private let storage: FileStorage
  9. private let tddStorage: TDDStorage
  10. let context = CoreDataStack.shared.newTaskContext()
  11. let jsonConverter = JSONConverter()
  12. init(storage: FileStorage, tddStorage: TDDStorage) {
  13. self.storage = storage
  14. self.tddStorage = tddStorage
  15. }
  16. static let dateFormatter: ISO8601DateFormatter = {
  17. let formatter = ISO8601DateFormatter()
  18. formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
  19. return formatter
  20. }()
  21. // Helper function to convert a Decimal? to NSDecimalNumber?
  22. func decimalToNSDecimalNumber(_ value: Decimal?) -> NSDecimalNumber? {
  23. guard let value = value else { return nil }
  24. return NSDecimalNumber(decimal: value)
  25. }
  26. // Use the helper function for cleaner code
  27. func processDetermination(_ determination: Determination) async {
  28. await context.perform {
  29. let newOrefDetermination = OrefDetermination(context: self.context)
  30. newOrefDetermination.id = UUID()
  31. newOrefDetermination.insulinSensitivity = self.decimalToNSDecimalNumber(determination.isf)
  32. newOrefDetermination.currentTarget = self.decimalToNSDecimalNumber(determination.current_target)
  33. newOrefDetermination.eventualBG = determination.eventualBG.map(NSDecimalNumber.init)
  34. newOrefDetermination.deliverAt = determination.deliverAt
  35. newOrefDetermination.carbRatio = self.decimalToNSDecimalNumber(determination.carbRatio)
  36. newOrefDetermination.glucose = self.decimalToNSDecimalNumber(determination.bg)
  37. newOrefDetermination.reservoir = self.decimalToNSDecimalNumber(determination.reservoir)
  38. newOrefDetermination.insulinReq = self.decimalToNSDecimalNumber(determination.insulinReq)
  39. newOrefDetermination.temp = determination.temp?.rawValue ?? "absolute"
  40. newOrefDetermination.rate = self.decimalToNSDecimalNumber(determination.rate)
  41. newOrefDetermination.reason = determination.reason
  42. newOrefDetermination.duration = self.decimalToNSDecimalNumber(determination.duration)
  43. newOrefDetermination.iob = self.decimalToNSDecimalNumber(determination.iob)
  44. newOrefDetermination.threshold = self.decimalToNSDecimalNumber(determination.threshold)
  45. newOrefDetermination.minDelta = self.decimalToNSDecimalNumber(determination.minDelta)
  46. newOrefDetermination.sensitivityRatio = self.decimalToNSDecimalNumber(determination.sensitivityRatio)
  47. newOrefDetermination.expectedDelta = self.decimalToNSDecimalNumber(determination.expectedDelta)
  48. newOrefDetermination.cob = Int16(Int(determination.cob ?? 0))
  49. newOrefDetermination.smbToDeliver = determination.units.map { NSDecimalNumber(decimal: $0) }
  50. newOrefDetermination.carbsRequired = Int16(Int(determination.carbsReq ?? 0))
  51. newOrefDetermination.isUploadedToNS = false
  52. if let predictions = determination.predictions {
  53. ["iob": predictions.iob, "zt": predictions.zt, "cob": predictions.cob, "uam": predictions.uam]
  54. .forEach { type, values in
  55. if let values = values {
  56. let forecast = Forecast(context: self.context)
  57. forecast.id = UUID()
  58. forecast.type = type
  59. forecast.date = Date()
  60. forecast.orefDetermination = newOrefDetermination
  61. for (index, value) in values.enumerated() {
  62. let forecastValue = ForecastValue(context: self.context)
  63. forecastValue.index = Int32(index)
  64. forecastValue.value = Int32(value)
  65. forecast.addToForecastValues(forecastValue)
  66. }
  67. newOrefDetermination.addToForecasts(forecast)
  68. }
  69. }
  70. }
  71. }
  72. // First save the current Determination to Core Data
  73. await attemptToSaveContext()
  74. }
  75. func attemptToSaveContext() async {
  76. await context.perform {
  77. do {
  78. guard self.context.hasChanges else { return }
  79. try self.context.save()
  80. } catch {
  81. debugPrint("\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to save Determination to Core Data")
  82. }
  83. }
  84. }
  85. // fetch glucose to pass it to the meal function and to determine basal
  86. private func fetchAndProcessGlucose(fetchLimit: Int?) async throws -> String {
  87. let results = try await CoreDataStack.shared.fetchEntitiesAsync(
  88. ofType: GlucoseStored.self,
  89. onContext: context,
  90. predicate: NSPredicate.predicateForOneDayAgoInMinutes,
  91. key: "date",
  92. ascending: false,
  93. fetchLimit: fetchLimit,
  94. batchSize: 48
  95. )
  96. return try await context.perform {
  97. guard let glucoseResults = results as? [GlucoseStored] else {
  98. throw CoreDataError.fetchError(function: #function, file: #file)
  99. }
  100. // convert to JSON
  101. return self.jsonConverter.convertToJSON(glucoseResults)
  102. }
  103. }
  104. private func fetchAndProcessCarbs(additionalCarbs: Decimal? = nil, carbsDate: Date? = nil) async throws -> String {
  105. let results = try await CoreDataStack.shared.fetchEntitiesAsync(
  106. ofType: CarbEntryStored.self,
  107. onContext: context,
  108. predicate: NSPredicate.predicateForOneDayAgo,
  109. key: "date",
  110. ascending: false
  111. )
  112. let json = try await context.perform {
  113. guard let carbResults = results as? [CarbEntryStored] else {
  114. throw CoreDataError.fetchError(function: #function, file: #file)
  115. }
  116. var jsonArray = self.jsonConverter.convertToJSON(carbResults)
  117. if let additionalCarbs = additionalCarbs {
  118. let formattedDate = carbsDate.map { ISO8601DateFormatter().string(from: $0) } ?? ISO8601DateFormatter()
  119. .string(from: Date())
  120. let additionalEntry = [
  121. "carbs": Double(additionalCarbs),
  122. "actualDate": formattedDate,
  123. "id": UUID().uuidString,
  124. "note": NSNull(),
  125. "protein": 0,
  126. "created_at": formattedDate,
  127. "isFPU": false,
  128. "fat": 0,
  129. "enteredBy": "Trio"
  130. ] as [String: Any]
  131. // Assuming jsonArray is a String, convert it to a list of dictionaries first
  132. if let jsonData = jsonArray.data(using: .utf8) {
  133. var jsonList = try? JSONSerialization.jsonObject(with: jsonData, options: []) as? [[String: Any]]
  134. jsonList?.append(additionalEntry)
  135. // Convert back to JSON string
  136. if let updatedJsonData = try? JSONSerialization
  137. .data(withJSONObject: jsonList ?? [], options: .prettyPrinted)
  138. {
  139. jsonArray = String(data: updatedJsonData, encoding: .utf8) ?? jsonArray
  140. }
  141. }
  142. }
  143. return jsonArray
  144. }
  145. return json
  146. }
  147. private func fetchPumpHistoryObjectIDs() async throws -> [NSManagedObjectID]? {
  148. let results = try await CoreDataStack.shared.fetchEntitiesAsync(
  149. ofType: PumpEventStored.self,
  150. onContext: context,
  151. predicate: NSPredicate.pumpHistoryLast1440Minutes,
  152. key: "timestamp",
  153. ascending: false,
  154. batchSize: 50
  155. )
  156. return try await context.perform {
  157. guard let pumpEventResults = results as? [PumpEventStored] else {
  158. throw CoreDataError.fetchError(function: #function, file: #file)
  159. }
  160. return pumpEventResults.map(\.objectID)
  161. }
  162. }
  163. private func parsePumpHistory(
  164. _ pumpHistoryObjectIDs: [NSManagedObjectID],
  165. simulatedBolusAmount: Decimal? = nil
  166. ) async -> String {
  167. // Return an empty JSON object if the list of object IDs is empty
  168. guard !pumpHistoryObjectIDs.isEmpty else { return "{}" }
  169. // Execute all operations on the background context
  170. return await context.perform {
  171. // Load and map pump events to DTOs
  172. var dtos = self.loadAndMapPumpEvents(pumpHistoryObjectIDs)
  173. // Optionally add the IOB as a DTO
  174. if let simulatedBolusAmount = simulatedBolusAmount {
  175. let simulatedBolusDTO = self.createSimulatedBolusDTO(simulatedBolusAmount: simulatedBolusAmount)
  176. dtos.insert(simulatedBolusDTO, at: 0)
  177. }
  178. // Convert the DTOs to JSON
  179. return self.jsonConverter.convertToJSON(dtos)
  180. }
  181. }
  182. private func loadAndMapPumpEvents(_ pumpHistoryObjectIDs: [NSManagedObjectID]) -> [PumpEventDTO] {
  183. OpenAPS.loadAndMapPumpEvents(pumpHistoryObjectIDs, from: context)
  184. }
  185. /// Fetches and parses pump events, expose this as static and not private for testing
  186. static func loadAndMapPumpEvents(
  187. _ pumpHistoryObjectIDs: [NSManagedObjectID],
  188. from context: NSManagedObjectContext
  189. ) -> [PumpEventDTO] {
  190. // Load the pump events from the object IDs
  191. let pumpHistory: [PumpEventStored] = pumpHistoryObjectIDs
  192. .compactMap { context.object(with: $0) as? PumpEventStored }
  193. // Create the DTOs
  194. let dtos: [PumpEventDTO] = pumpHistory.flatMap { event -> [PumpEventDTO] in
  195. var eventDTOs: [PumpEventDTO] = []
  196. if let bolusDTO = event.toBolusDTOEnum() {
  197. eventDTOs.append(bolusDTO)
  198. }
  199. if let tempBasalDurationDTO = event.toTempBasalDurationDTOEnum() {
  200. eventDTOs.append(tempBasalDurationDTO)
  201. }
  202. if let tempBasalDTO = event.toTempBasalDTOEnum() {
  203. eventDTOs.append(tempBasalDTO)
  204. }
  205. if let pumpSuspendDTO = event.toPumpSuspendDTO() {
  206. eventDTOs.append(pumpSuspendDTO)
  207. }
  208. if let pumpResumeDTO = event.toPumpResumeDTO() {
  209. eventDTOs.append(pumpResumeDTO)
  210. }
  211. if let rewindDTO = event.toRewindDTO() {
  212. eventDTOs.append(rewindDTO)
  213. }
  214. if let primeDTO = event.toPrimeDTO() {
  215. eventDTOs.append(primeDTO)
  216. }
  217. return eventDTOs
  218. }
  219. return dtos
  220. }
  221. private func createSimulatedBolusDTO(simulatedBolusAmount: Decimal) -> PumpEventDTO {
  222. let oneSecondAgo = Calendar.current
  223. .date(
  224. byAdding: .second,
  225. value: -1,
  226. to: Date()
  227. )! // adding -1s to the current Date ensures that oref actually uses the mock entry to calculate iob and not guard it away
  228. let dateFormatted = PumpEventStored.dateFormatter.string(from: oneSecondAgo)
  229. let bolusDTO = BolusDTO(
  230. id: UUID().uuidString,
  231. timestamp: dateFormatted,
  232. amount: Double(simulatedBolusAmount),
  233. isExternal: false,
  234. isSMB: true,
  235. duration: 0,
  236. _type: "Bolus"
  237. )
  238. return .bolus(bolusDTO)
  239. }
  240. func determineBasal(
  241. currentTemp: TempBasal,
  242. clock: Date,
  243. useSwiftOref: Bool,
  244. simulatedCarbsAmount: Decimal? = nil,
  245. simulatedBolusAmount: Decimal? = nil,
  246. simulatedCarbsDate: Date? = nil,
  247. simulation: Bool = false
  248. ) async throws -> Determination? {
  249. debug(.openAPS, "Start determineBasal")
  250. // temp_basal
  251. let tempBasal = currentTemp.rawJSON
  252. // Perform asynchronous calls in parallel
  253. async let pumpHistoryObjectIDs = fetchPumpHistoryObjectIDs() ?? []
  254. async let carbs = fetchAndProcessCarbs(additionalCarbs: simulatedCarbsAmount ?? 0, carbsDate: simulatedCarbsDate)
  255. async let glucose = fetchAndProcessGlucose(fetchLimit: 72)
  256. async let prepareTrioCustomOrefVariables = prepareTrioCustomOrefVariables()
  257. async let profileAsync = loadFileFromStorageAsync(name: Settings.profile)
  258. async let basalAsync = loadFileFromStorageAsync(name: Settings.basalProfile)
  259. async let autosenseAsync = loadFileFromStorageAsync(name: Settings.autosense)
  260. async let reservoirAsync = loadFileFromStorageAsync(name: Monitor.reservoir)
  261. async let preferencesAsync = storage.retrieveAsync(OpenAPS.Settings.preferences, as: Preferences.self) ?? Preferences()
  262. async let hasSufficientTddForDynamic = tddStorage.hasSufficientTDD()
  263. // Await the results of asynchronous tasks
  264. let (
  265. pumpHistoryJSON,
  266. carbsAsJSON,
  267. glucoseAsJSON,
  268. trioCustomOrefVariables,
  269. profile,
  270. basalProfile,
  271. autosens,
  272. reservoir,
  273. hasSufficientTdd
  274. ) = await (
  275. try parsePumpHistory(await pumpHistoryObjectIDs, simulatedBolusAmount: simulatedBolusAmount),
  276. try carbs,
  277. try glucose,
  278. try prepareTrioCustomOrefVariables,
  279. profileAsync,
  280. basalAsync,
  281. autosenseAsync,
  282. reservoirAsync,
  283. try hasSufficientTddForDynamic
  284. )
  285. // Meal calculation
  286. let meal = try await self.meal(
  287. pumphistory: pumpHistoryJSON,
  288. profile: profile,
  289. basalProfile: basalProfile,
  290. clock: clock,
  291. carbs: carbsAsJSON,
  292. glucose: glucoseAsJSON,
  293. useSwiftOref: useSwiftOref
  294. )
  295. // IOB calculation
  296. let iob = try await self.iob(
  297. pumphistory: pumpHistoryJSON,
  298. profile: profile,
  299. clock: clock,
  300. autosens: autosens.isEmpty ? .null : autosens,
  301. useSwiftOref: useSwiftOref
  302. )
  303. // TODO: refactor this to core data
  304. if !simulation {
  305. storage.save(iob, as: Monitor.iob)
  306. }
  307. var preferences = await preferencesAsync
  308. if !hasSufficientTdd, preferences.useNewFormula || (preferences.useNewFormula && preferences.sigmoid) {
  309. debug(.openAPS, "Insufficient TDD for dynamic formula; disabling for determine basal run.")
  310. preferences.useNewFormula = false
  311. preferences.sigmoid = false
  312. }
  313. // Determine basal
  314. let orefDetermination = try await determineBasal(
  315. glucose: glucoseAsJSON,
  316. currentTemp: tempBasal,
  317. iob: iob,
  318. profile: profile,
  319. autosens: autosens.isEmpty ? .null : autosens,
  320. meal: meal,
  321. microBolusAllowed: true,
  322. reservoir: reservoir,
  323. pumpHistory: pumpHistoryJSON,
  324. preferences: preferences,
  325. basalProfile: basalProfile,
  326. trioCustomOrefVariables: trioCustomOrefVariables,
  327. useSwiftOref: useSwiftOref
  328. )
  329. debug(.openAPS, "\(simulation ? "[SIMULATION]" : "") OREF DETERMINATION: \(orefDetermination)")
  330. if var determination = Determination(from: orefDetermination), let deliverAt = determination.deliverAt {
  331. // set both timestamp and deliverAt to the SAME date; this will be updated for timestamp once it is enacted
  332. // AAPS does it the same way! we'll follow their example!
  333. determination.timestamp = deliverAt
  334. if !simulation {
  335. // save to core data asynchronously
  336. await processDetermination(determination)
  337. }
  338. return determination
  339. } else {
  340. debug(
  341. .openAPS,
  342. "\(DebuggingIdentifiers.failed) No determination data. orefDetermination: \(orefDetermination), Determination(from: orefDetermination): \(String(describing: Determination(from: orefDetermination))), deliverAt: \(String(describing: Determination(from: orefDetermination)?.deliverAt))"
  343. )
  344. throw APSError.apsError(message: "No determination data.")
  345. }
  346. }
  347. func prepareTrioCustomOrefVariables() async throws -> RawJSON {
  348. try await context.perform {
  349. // Retrieve user preferences
  350. let userPreferences = self.storage.retrieve(OpenAPS.Settings.preferences, as: Preferences.self)
  351. let weightPercentage = userPreferences?.weightPercentage ?? 1.0
  352. let maxSMBBasalMinutes = userPreferences?.maxSMBBasalMinutes ?? 30
  353. let maxUAMBasalMinutes = userPreferences?.maxUAMSMBBasalMinutes ?? 30
  354. // Fetch historical events for Total Daily Dose (TDD) calculation
  355. let tenDaysAgo = Date().addingTimeInterval(-10.days.timeInterval)
  356. let twoHoursAgo = Date().addingTimeInterval(-2.hours.timeInterval)
  357. let historicalTDDData = try self.fetchHistoricalTDDData(from: tenDaysAgo)
  358. // Fetch the last active Override
  359. let activeOverrides = try self.fetchActiveOverrides()
  360. let isOverrideActive = activeOverrides.first?.enabled ?? false
  361. let overridePercentage = Decimal(activeOverrides.first?.percentage ?? 100)
  362. let isOverrideIndefinite = activeOverrides.first?.indefinite ?? true
  363. let disableSMBs = activeOverrides.first?.smbIsOff ?? false
  364. let overrideTargetBG = activeOverrides.first?.target?.decimalValue ?? 0
  365. // Calculate averages for Total Daily Dose (TDD)
  366. let totalTDD = historicalTDDData.compactMap { ($0["total"] as? NSDecimalNumber)?.decimalValue }.reduce(0, +)
  367. let totalDaysCount = max(historicalTDDData.count, 1)
  368. // Fetch recent TDD data for the past two hours
  369. let recentTDDData = historicalTDDData.filter { ($0["date"] as? Date ?? Date()) >= twoHoursAgo }
  370. let recentDataCount = max(recentTDDData.count, 1)
  371. let recentTotalTDD = recentTDDData.compactMap { ($0["total"] as? NSDecimalNumber)?.decimalValue }
  372. .reduce(0, +)
  373. let currentTDD = historicalTDDData.last?["total"] as? Decimal ?? 0
  374. let averageTDDLastTwoHours = recentTotalTDD / Decimal(recentDataCount)
  375. let averageTDDLastTenDays = totalTDD / Decimal(totalDaysCount)
  376. let weightedTDD = weightPercentage * averageTDDLastTwoHours + (1 - weightPercentage) * averageTDDLastTenDays
  377. let glucose = try self.fetchGlucose()
  378. // Prepare Trio's custom oref variables
  379. let trioCustomOrefVariablesData = TrioCustomOrefVariables(
  380. average_total_data: currentTDD > 0 ? averageTDDLastTenDays : 0,
  381. weightedAverage: currentTDD > 0 ? weightedTDD : 1,
  382. currentTDD: currentTDD,
  383. past2hoursAverage: currentTDD > 0 ? averageTDDLastTwoHours : 0,
  384. date: Date(),
  385. overridePercentage: overridePercentage,
  386. useOverride: isOverrideActive,
  387. duration: activeOverrides.first?.duration?.decimalValue ?? 0,
  388. unlimited: isOverrideIndefinite,
  389. overrideTarget: overrideTargetBG,
  390. smbIsOff: disableSMBs,
  391. advancedSettings: activeOverrides.first?.advancedSettings ?? false,
  392. isfAndCr: activeOverrides.first?.isfAndCr ?? false,
  393. isf: activeOverrides.first?.isf ?? false,
  394. cr: activeOverrides.first?.cr ?? false,
  395. smbIsScheduledOff: activeOverrides.first?.smbIsScheduledOff ?? false,
  396. start: (activeOverrides.first?.start ?? 0) as Decimal,
  397. end: (activeOverrides.first?.end ?? 0) as Decimal,
  398. smbMinutes: activeOverrides.first?.smbMinutes?.decimalValue ?? maxSMBBasalMinutes,
  399. uamMinutes: activeOverrides.first?.uamMinutes?.decimalValue ?? maxUAMBasalMinutes
  400. )
  401. // Save and return contents of Trio's custom oref variables
  402. self.storage.save(trioCustomOrefVariablesData, as: OpenAPS.Monitor.trio_custom_oref_variables)
  403. return self.loadFileFromStorage(name: Monitor.trio_custom_oref_variables)
  404. }
  405. }
  406. func autosense(useSwiftOref: Bool) async throws -> Autosens? {
  407. debug(.openAPS, "Start autosens")
  408. // Perform asynchronous calls in parallel
  409. async let pumpHistoryObjectIDs = fetchPumpHistoryObjectIDs() ?? []
  410. async let carbs = fetchAndProcessCarbs()
  411. async let glucose = fetchAndProcessGlucose(fetchLimit: nil)
  412. async let getProfile = loadFileFromStorageAsync(name: Settings.profile)
  413. async let getBasalProfile = loadFileFromStorageAsync(name: Settings.basalProfile)
  414. async let getTempTargets = loadFileFromStorageAsync(name: Settings.tempTargets)
  415. // Await the results of asynchronous tasks
  416. let (pumpHistoryJSON, carbsAsJSON, glucoseAsJSON, profile, basalProfile, tempTargets) = await (
  417. try parsePumpHistory(await pumpHistoryObjectIDs),
  418. try carbs,
  419. try glucose,
  420. getProfile,
  421. getBasalProfile,
  422. getTempTargets
  423. )
  424. // Autosense
  425. let autosenseResult = try await autosense(
  426. glucose: glucoseAsJSON,
  427. pumpHistory: pumpHistoryJSON,
  428. basalprofile: basalProfile,
  429. profile: profile,
  430. carbs: carbsAsJSON,
  431. temptargets: tempTargets,
  432. useSwiftOref: useSwiftOref
  433. )
  434. debug(.openAPS, "AUTOSENS: \(autosenseResult)")
  435. if var autosens = Autosens(from: autosenseResult) {
  436. autosens.timestamp = Date()
  437. await storage.saveAsync(autosens, as: Settings.autosense)
  438. return autosens
  439. } else {
  440. return nil
  441. }
  442. }
  443. func createProfiles(useSwiftOref: Bool) async throws {
  444. debug(.openAPS, "Start creating pump profile and user profile")
  445. // Load required settings and profiles asynchronously
  446. async let getPumpSettings = loadFileFromStorageAsync(name: Settings.settings)
  447. async let getBGTargets = loadFileFromStorageAsync(name: Settings.bgTargets)
  448. async let getBasalProfile = loadFileFromStorageAsync(name: Settings.basalProfile)
  449. async let getISF = loadFileFromStorageAsync(name: Settings.insulinSensitivities)
  450. async let getCR = loadFileFromStorageAsync(name: Settings.carbRatios)
  451. async let getTempTargets = loadFileFromStorageAsync(name: Settings.tempTargets)
  452. async let getModel = loadFileFromStorageAsync(name: Settings.model)
  453. async let getTrioSettingDefaults = loadFileFromStorageAsync(name: Trio.settings)
  454. let (pumpSettings, bgTargets, basalProfile, isf, cr, tempTargets, model, trioSettings) = await (
  455. getPumpSettings,
  456. getBGTargets,
  457. getBasalProfile,
  458. getISF,
  459. getCR,
  460. getTempTargets,
  461. getModel,
  462. getTrioSettingDefaults
  463. )
  464. // Retrieve user preferences, or set defaults if not available
  465. let preferences = storage.retrieve(OpenAPS.Settings.preferences, as: Preferences.self) ?? Preferences()
  466. let defaultHalfBasalTarget = preferences.halfBasalExerciseTarget
  467. var adjustedPreferences = preferences
  468. // Check for active Temp Targets and adjust HBT if necessary
  469. try await context.perform {
  470. // Check if a Temp Target is active and check HBT differs from setting and adjust
  471. if let activeTempTarget = try self.fetchActiveTempTargets().first,
  472. activeTempTarget.enabled,
  473. let targetValue = activeTempTarget.target?.decimalValue
  474. {
  475. // Compute effective HBT - handles both custom HBT and standard TT (where HBT might need adjustment)
  476. let effectiveHBT = TempTargetCalculations.computeEffectiveHBT(
  477. tempTargetHalfBasalTarget: activeTempTarget.halfBasalTarget?.decimalValue,
  478. settingHalfBasalTarget: defaultHalfBasalTarget,
  479. target: targetValue,
  480. autosensMax: preferences.autosensMax
  481. )
  482. if let effectiveHBT, effectiveHBT != defaultHalfBasalTarget {
  483. adjustedPreferences.halfBasalExerciseTarget = effectiveHBT
  484. let percentage = Int(TempTargetCalculations.computeAdjustedPercentage(
  485. halfBasalTarget: effectiveHBT,
  486. target: targetValue,
  487. autosensMax: preferences.autosensMax
  488. ))
  489. debug(
  490. .openAPS,
  491. "TempTarget: target=\(targetValue), HBT=\(defaultHalfBasalTarget), effectiveHBT=\(effectiveHBT), percentage=\(percentage)%, adjustmentType=Custom"
  492. )
  493. }
  494. }
  495. // Overwrite the lowTTlowersSens if autosensMax does not support it
  496. if preferences.lowTemptargetLowersSensitivity, preferences.autosensMax <= 1 {
  497. adjustedPreferences.lowTemptargetLowersSensitivity = false
  498. debug(.openAPS, "Setting lowTTlowersSens to false due to insufficient autosensMax: \(preferences.autosensMax)")
  499. }
  500. }
  501. do {
  502. let pumpProfile = try await makeProfile(
  503. preferences: adjustedPreferences,
  504. pumpSettings: pumpSettings,
  505. bgTargets: bgTargets,
  506. basalProfile: basalProfile,
  507. isf: isf,
  508. carbRatio: cr,
  509. tempTargets: tempTargets,
  510. model: model,
  511. autotune: RawJSON.null,
  512. trioSettings: trioSettings,
  513. useSwiftOref: useSwiftOref
  514. )
  515. let profile = try await makeProfile(
  516. preferences: adjustedPreferences,
  517. pumpSettings: pumpSettings,
  518. bgTargets: bgTargets,
  519. basalProfile: basalProfile,
  520. isf: isf,
  521. carbRatio: cr,
  522. tempTargets: tempTargets,
  523. model: model,
  524. autotune: RawJSON.null,
  525. trioSettings: trioSettings,
  526. useSwiftOref: useSwiftOref
  527. )
  528. // Save the profiles
  529. await storage.saveAsync(pumpProfile, as: Settings.pumpProfile)
  530. await storage.saveAsync(profile, as: Settings.profile)
  531. } catch {
  532. debug(
  533. .apsManager,
  534. "\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to create pump profile and normal profile: \(error)"
  535. )
  536. throw error
  537. }
  538. }
  539. private func iob(pumphistory: JSON, profile: JSON, clock: JSON, autosens: JSON, useSwiftOref: Bool) async throws -> RawJSON {
  540. // FIXME: For now we'll just remove duplicate suspends here (ISSUE-399)
  541. var pumphistory = pumphistory
  542. if let pumpHistoryArray = try? JSONBridge.pumpHistory(from: pumphistory) {
  543. pumphistory = pumpHistoryArray.removingDuplicateSuspendResumeEvents().rawJSON
  544. }
  545. let startJavascriptAt = Date()
  546. let jsResult = await iobJavascript(pumphistory: pumphistory, profile: profile, clock: clock, autosens: autosens)
  547. let javascriptDuration = Date().timeIntervalSince(startJavascriptAt)
  548. // Important: we want to make sure that this flag ensures that none
  549. // of the native code runs
  550. guard useSwiftOref else {
  551. return try jsResult.returnOrThrow()
  552. }
  553. let startSwiftAt = Date()
  554. let (swiftResult, iobInputs) = OpenAPSSwift
  555. .iob(pumphistory: pumphistory, profile: profile, clock: clock, autosens: autosens)
  556. let swiftDuration = Date().timeIntervalSince(startSwiftAt)
  557. JSONCompare.logDifferences(
  558. function: .iob,
  559. swift: swiftResult,
  560. swiftDuration: swiftDuration,
  561. javascript: jsResult,
  562. javascriptDuration: javascriptDuration,
  563. iobInputs: iobInputs
  564. )
  565. return try jsResult.returnOrThrow()
  566. }
  567. func iobJavascript(pumphistory: JSON, profile: JSON, clock: JSON, autosens: JSON) async -> OrefFunctionResult {
  568. do {
  569. let result = try await withCheckedThrowingContinuation { continuation in
  570. jsWorker.inCommonContext { worker in
  571. worker.evaluateBatch(scripts: [
  572. Script(name: Prepare.log),
  573. Script(name: Bundle.iob),
  574. Script(name: Prepare.iob)
  575. ])
  576. let result = worker.call(function: Function.generate, with: [
  577. pumphistory,
  578. profile,
  579. clock,
  580. autosens
  581. ])
  582. continuation.resume(returning: result)
  583. }
  584. }
  585. return .success(result)
  586. } catch {
  587. return .failure(error)
  588. }
  589. }
  590. private func meal(
  591. pumphistory: JSON,
  592. profile: JSON,
  593. basalProfile: JSON,
  594. clock: JSON,
  595. carbs: JSON,
  596. glucose: JSON,
  597. useSwiftOref: Bool
  598. ) async throws -> RawJSON {
  599. let startJavascriptAt = Date()
  600. let jsResult = await mealJavascript(
  601. pumphistory: pumphistory,
  602. profile: profile,
  603. basalProfile: basalProfile,
  604. clock: clock,
  605. carbs: carbs,
  606. glucose: glucose
  607. )
  608. let javascriptDuration = Date().timeIntervalSince(startJavascriptAt)
  609. // Important: we want to make sure that this flag ensures that none
  610. // of the native code runs
  611. guard useSwiftOref else {
  612. return try jsResult.returnOrThrow()
  613. }
  614. let startSwiftAt = Date()
  615. let (swiftResult, mealInputs) = OpenAPSSwift
  616. .meal(
  617. pumphistory: pumphistory,
  618. profile: profile,
  619. basalProfile: basalProfile,
  620. clock: clock,
  621. carbs: carbs,
  622. glucose: glucose
  623. )
  624. let swiftDuration = Date().timeIntervalSince(startSwiftAt)
  625. JSONCompare.logDifferences(
  626. function: .meal,
  627. swift: swiftResult,
  628. swiftDuration: swiftDuration,
  629. javascript: jsResult,
  630. javascriptDuration: javascriptDuration,
  631. mealInputs: mealInputs
  632. )
  633. return try jsResult.returnOrThrow()
  634. }
  635. private func mealJavascript(
  636. pumphistory: JSON,
  637. profile: JSON,
  638. basalProfile: JSON,
  639. clock: JSON,
  640. carbs: JSON,
  641. glucose: JSON
  642. ) async -> OrefFunctionResult {
  643. do {
  644. let result = try await withCheckedThrowingContinuation { continuation in
  645. jsWorker.inCommonContext { worker in
  646. worker.evaluateBatch(scripts: [
  647. Script(name: Prepare.log),
  648. Script(name: Bundle.meal),
  649. Script(name: Prepare.meal)
  650. ])
  651. let result = worker.call(function: Function.generate, with: [
  652. pumphistory,
  653. profile,
  654. clock,
  655. glucose,
  656. basalProfile,
  657. carbs
  658. ])
  659. continuation.resume(returning: result)
  660. }
  661. }
  662. return .success(result)
  663. } catch {
  664. return .failure(error)
  665. }
  666. }
  667. private func autosense(
  668. glucose: JSON,
  669. pumpHistory: JSON,
  670. basalprofile: JSON,
  671. profile: JSON,
  672. carbs: JSON,
  673. temptargets: JSON,
  674. useSwiftOref: Bool
  675. ) async throws -> RawJSON {
  676. let startJavascriptAt = Date()
  677. let jsResult = await autosenseJavascript(
  678. glucose: glucose,
  679. pumpHistory: pumpHistory,
  680. basalprofile: basalprofile,
  681. profile: profile,
  682. carbs: carbs,
  683. temptargets: temptargets
  684. )
  685. let javascriptDuration = Date().timeIntervalSince(startJavascriptAt)
  686. // Important: we want to make sure that this flag ensures that none
  687. // of the native code runs
  688. guard useSwiftOref else {
  689. return try jsResult.returnOrThrow()
  690. }
  691. let startSwiftAt = Date()
  692. let (swiftResult, autosensInputs) = OpenAPSSwift
  693. .autosense(
  694. glucose: glucose,
  695. pumpHistory: pumpHistory,
  696. basalProfile: basalprofile,
  697. profile: profile,
  698. carbs: carbs,
  699. tempTargets: temptargets,
  700. clock: Date()
  701. )
  702. let swiftDuration = Date().timeIntervalSince(startSwiftAt)
  703. JSONCompare.logDifferences(
  704. function: .autosens,
  705. swift: swiftResult,
  706. swiftDuration: swiftDuration,
  707. javascript: jsResult,
  708. javascriptDuration: javascriptDuration,
  709. autosensInputs: autosensInputs
  710. )
  711. return try jsResult.returnOrThrow()
  712. }
  713. private func autosenseJavascript(
  714. glucose: JSON,
  715. pumpHistory: JSON,
  716. basalprofile: JSON,
  717. profile: JSON,
  718. carbs: JSON,
  719. temptargets: JSON
  720. ) async -> OrefFunctionResult {
  721. do {
  722. let result = try await withCheckedThrowingContinuation { continuation in
  723. jsWorker.inCommonContext { worker in
  724. worker.evaluateBatch(scripts: [
  725. Script(name: Prepare.log),
  726. Script(name: Bundle.autosens),
  727. Script(name: Prepare.autosens)
  728. ])
  729. let result = worker.call(function: Function.generate, with: [
  730. glucose,
  731. pumpHistory,
  732. basalprofile,
  733. profile,
  734. carbs,
  735. temptargets
  736. ])
  737. continuation.resume(returning: result)
  738. }
  739. }
  740. return .success(result)
  741. } catch {
  742. return .failure(error)
  743. }
  744. }
  745. private func determineBasal(
  746. glucose: JSON,
  747. currentTemp: JSON,
  748. iob: JSON,
  749. profile: JSON,
  750. autosens: JSON,
  751. meal: JSON,
  752. microBolusAllowed: Bool,
  753. reservoir: JSON,
  754. pumpHistory: JSON,
  755. preferences: JSON,
  756. basalProfile: JSON,
  757. trioCustomOrefVariables: JSON,
  758. useSwiftOref: Bool
  759. ) async throws -> RawJSON {
  760. let clock = Date()
  761. let startJavascriptAt = Date()
  762. let jsResult = await determineBasalJavascript(
  763. glucose: glucose,
  764. currentTemp: currentTemp,
  765. iob: iob,
  766. profile: profile,
  767. autosens: autosens,
  768. meal: meal,
  769. microBolusAllowed: microBolusAllowed,
  770. reservoir: reservoir,
  771. pumpHistory: pumpHistory,
  772. preferences: preferences,
  773. basalProfile: basalProfile,
  774. trioCustomOrefVariables: trioCustomOrefVariables,
  775. clock: clock
  776. )
  777. let javascriptDuration = Date().timeIntervalSince(startJavascriptAt)
  778. // Important: we want to make sure that this flag ensures that none
  779. // of the native code runs
  780. guard useSwiftOref else {
  781. return try jsResult.returnOrThrow()
  782. }
  783. let startSwiftAt = Date()
  784. let (swiftResult, determineBasalInputs) = OpenAPSSwift.determineBasal(
  785. glucose: glucose,
  786. currentTemp: currentTemp,
  787. iob: iob,
  788. profile: profile,
  789. autosens: autosens,
  790. meal: meal,
  791. microBolusAllowed: microBolusAllowed,
  792. reservoir: reservoir,
  793. pumpHistory: pumpHistory,
  794. preferences: preferences,
  795. basalProfile: basalProfile,
  796. trioCustomOrefVariables: trioCustomOrefVariables,
  797. clock: clock
  798. )
  799. let swiftDuration = Date().timeIntervalSince(startSwiftAt)
  800. JSONCompare.logDifferences(
  801. function: .determineBasal,
  802. swift: swiftResult,
  803. swiftDuration: swiftDuration,
  804. javascript: jsResult,
  805. javascriptDuration: javascriptDuration,
  806. determineBasalInputs: determineBasalInputs
  807. )
  808. return try jsResult.returnOrThrow()
  809. }
  810. private func determineBasalJavascript(
  811. glucose: JSON,
  812. currentTemp: JSON,
  813. iob: JSON,
  814. profile: JSON,
  815. autosens: JSON,
  816. meal: JSON,
  817. microBolusAllowed: Bool,
  818. reservoir: JSON,
  819. pumpHistory: JSON,
  820. preferences: JSON,
  821. basalProfile: JSON,
  822. trioCustomOrefVariables: JSON,
  823. clock: Date
  824. ) async -> OrefFunctionResult {
  825. do {
  826. let result = try await withCheckedThrowingContinuation { continuation in
  827. jsWorker.inCommonContext { worker in
  828. worker.evaluateBatch(scripts: [
  829. Script(name: Prepare.log),
  830. Script(name: Prepare.determineBasal),
  831. Script(name: Bundle.basalSetTemp),
  832. Script(name: Bundle.getLastGlucose),
  833. Script(name: Bundle.determineBasal)
  834. ])
  835. if let middleware = self.middlewareScript(name: OpenAPS.Middleware.determineBasal) {
  836. worker.evaluate(script: middleware)
  837. }
  838. let result = worker.call(function: Function.generate, with: [
  839. iob,
  840. currentTemp,
  841. glucose,
  842. profile,
  843. autosens,
  844. meal,
  845. microBolusAllowed,
  846. reservoir,
  847. clock,
  848. pumpHistory,
  849. preferences,
  850. basalProfile,
  851. trioCustomOrefVariables
  852. ])
  853. continuation.resume(returning: result)
  854. }
  855. }
  856. return .success(result)
  857. } catch {
  858. return .failure(error)
  859. }
  860. }
  861. private func exportDefaultPreferences() -> RawJSON {
  862. dispatchPrecondition(condition: .onQueue(processQueue))
  863. return jsWorker.inCommonContext { worker in
  864. worker.evaluateBatch(scripts: [
  865. Script(name: Prepare.log),
  866. Script(name: Bundle.profile),
  867. Script(name: Prepare.profile)
  868. ])
  869. return worker.call(function: Function.exportDefaults, with: [])
  870. }
  871. }
  872. // use `internal` protection to expose to unit tests
  873. func makeProfileJavascript(
  874. preferences: JSON,
  875. pumpSettings: JSON,
  876. bgTargets: JSON,
  877. basalProfile: JSON,
  878. isf: JSON,
  879. carbRatio: JSON,
  880. tempTargets: JSON,
  881. model: JSON,
  882. autotune: JSON,
  883. trioSettings: JSON
  884. ) async -> OrefFunctionResult {
  885. do {
  886. let result = try await withCheckedThrowingContinuation { continuation in
  887. jsWorker.inCommonContext { worker in
  888. worker.evaluateBatch(scripts: [
  889. Script(name: Prepare.log),
  890. Script(name: Bundle.profile),
  891. Script(name: Prepare.profile)
  892. ])
  893. let result = worker.call(function: Function.generate, with: [
  894. pumpSettings,
  895. bgTargets,
  896. isf,
  897. basalProfile,
  898. preferences,
  899. carbRatio,
  900. tempTargets,
  901. model,
  902. autotune,
  903. trioSettings
  904. ])
  905. continuation.resume(returning: result)
  906. }
  907. }
  908. return .success(result)
  909. } catch {
  910. return .failure(error)
  911. }
  912. }
  913. private func makeProfile(
  914. preferences: JSON,
  915. pumpSettings: JSON,
  916. bgTargets: JSON,
  917. basalProfile: JSON,
  918. isf: JSON,
  919. carbRatio: JSON,
  920. tempTargets: JSON,
  921. model: JSON,
  922. autotune: JSON,
  923. trioSettings: JSON,
  924. useSwiftOref: Bool
  925. ) async throws -> RawJSON {
  926. let startJavascriptAt = Date()
  927. let jsResult = await makeProfileJavascript(
  928. preferences: preferences,
  929. pumpSettings: pumpSettings,
  930. bgTargets: bgTargets,
  931. basalProfile: basalProfile,
  932. isf: isf,
  933. carbRatio: carbRatio,
  934. tempTargets: tempTargets,
  935. model: model,
  936. autotune: autotune,
  937. trioSettings: trioSettings
  938. )
  939. let javascriptDuration = Date().timeIntervalSince(startJavascriptAt)
  940. // Important: we want to make sure that this flag ensures that none
  941. // of the native code runs
  942. guard useSwiftOref else {
  943. return try jsResult.returnOrThrow()
  944. }
  945. let startSwiftAt = Date()
  946. let swiftResult = OpenAPSSwift.makeProfile(
  947. preferences: preferences,
  948. pumpSettings: pumpSettings,
  949. bgTargets: bgTargets,
  950. basalProfile: basalProfile,
  951. isf: isf,
  952. carbRatio: carbRatio,
  953. tempTargets: tempTargets,
  954. model: model,
  955. trioSettings: trioSettings
  956. )
  957. let swiftDuration = Date().timeIntervalSince(startSwiftAt)
  958. JSONCompare.logDifferences(
  959. function: .makeProfile,
  960. swift: swiftResult,
  961. swiftDuration: swiftDuration,
  962. javascript: jsResult,
  963. javascriptDuration: javascriptDuration
  964. )
  965. return try jsResult.returnOrThrow()
  966. }
  967. private func loadJSON(name: String) -> String {
  968. try! String(contentsOf: Foundation.Bundle.main.url(forResource: "json/\(name)", withExtension: "json")!)
  969. }
  970. private func loadFileFromStorage(name: String) -> RawJSON {
  971. storage.retrieveRaw(name) ?? OpenAPS.defaults(for: name)
  972. }
  973. private func loadFileFromStorageAsync(name: String) async -> RawJSON {
  974. await withCheckedContinuation { continuation in
  975. DispatchQueue.global(qos: .userInitiated).async {
  976. let result = self.storage.retrieveRaw(name) ?? OpenAPS.defaults(for: name)
  977. continuation.resume(returning: result)
  978. }
  979. }
  980. }
  981. private func middlewareScript(name: String) -> Script? {
  982. if let body = storage.retrieveRaw(name) {
  983. return Script(name: name, body: body)
  984. }
  985. if let url = Foundation.Bundle.main.url(forResource: "javascript/\(name)", withExtension: "") {
  986. do {
  987. let body = try String(contentsOf: url)
  988. return Script(name: name, body: body)
  989. } catch {
  990. debug(.openAPS, "Failed to load script \(name): \(error)")
  991. }
  992. }
  993. return nil
  994. }
  995. static func defaults(for file: String) -> RawJSON {
  996. let prefix = file.hasSuffix(".json") ? "json/defaults" : "javascript"
  997. guard let url = Foundation.Bundle.main.url(forResource: "\(prefix)/\(file)", withExtension: "") else {
  998. return ""
  999. }
  1000. return (try? String(contentsOf: url)) ?? ""
  1001. }
  1002. func processAndSave(forecastData: [String: [Int]]) {
  1003. let currentDate = Date()
  1004. context.perform {
  1005. for (type, values) in forecastData {
  1006. self.createForecast(type: type, values: values, date: currentDate, context: self.context)
  1007. }
  1008. do {
  1009. guard self.context.hasChanges else { return }
  1010. try self.context.save()
  1011. } catch {
  1012. print(error.localizedDescription)
  1013. }
  1014. }
  1015. }
  1016. func createForecast(type: String, values: [Int], date: Date, context: NSManagedObjectContext) {
  1017. let forecast = Forecast(context: context)
  1018. forecast.id = UUID()
  1019. forecast.date = date
  1020. forecast.type = type
  1021. for (index, value) in values.enumerated() {
  1022. let forecastValue = ForecastValue(context: context)
  1023. forecastValue.value = Int32(value)
  1024. forecastValue.index = Int32(index)
  1025. forecastValue.forecast = forecast
  1026. }
  1027. }
  1028. }
  1029. // Non-Async fetch methods for trio_custom_oref_variables
  1030. extension OpenAPS {
  1031. func fetchActiveTempTargets() throws -> [TempTargetStored] {
  1032. try CoreDataStack.shared.fetchEntities(
  1033. ofType: TempTargetStored.self,
  1034. onContext: context,
  1035. predicate: NSPredicate.lastActiveTempTarget,
  1036. key: "date",
  1037. ascending: false,
  1038. fetchLimit: 1
  1039. ) as? [TempTargetStored] ?? []
  1040. }
  1041. func fetchActiveOverrides() throws -> [OverrideStored] {
  1042. try CoreDataStack.shared.fetchEntities(
  1043. ofType: OverrideStored.self,
  1044. onContext: context,
  1045. predicate: NSPredicate.lastActiveOverride,
  1046. key: "date",
  1047. ascending: false,
  1048. fetchLimit: 1
  1049. ) as? [OverrideStored] ?? []
  1050. }
  1051. func fetchHistoricalTDDData(from date: Date) throws -> [[String: Any]] {
  1052. try CoreDataStack.shared.fetchEntities(
  1053. ofType: TDDStored.self,
  1054. onContext: context,
  1055. predicate: NSPredicate(format: "date > %@ AND total > 0", date as NSDate),
  1056. key: "date",
  1057. ascending: true,
  1058. propertiesToFetch: ["date", "total"]
  1059. ) as? [[String: Any]] ?? []
  1060. }
  1061. func fetchGlucose() throws -> [GlucoseStored] {
  1062. let results = try CoreDataStack.shared.fetchEntities(
  1063. ofType: GlucoseStored.self,
  1064. onContext: context,
  1065. predicate: NSPredicate.predicateFor30MinAgo,
  1066. key: "date",
  1067. ascending: false,
  1068. fetchLimit: 4
  1069. )
  1070. return try context.perform {
  1071. guard let glucoseResults = results as? [GlucoseStored] else {
  1072. throw CoreDataError.fetchError(function: #function, file: #file)
  1073. }
  1074. return glucoseResults
  1075. }
  1076. }
  1077. }