OpenAPSFixed.swift 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293
  1. import Combine
  2. import Foundation
  3. import JavaScriptCore
  4. @testable import Trio
  5. /// This class provides us with an implementation of trio-oref with a number of iob bugs that are fixed.
  6. /// We can use this during testing to confirm that for an input that generated an error that a corrected
  7. /// Javascript implementation would have produced the same results
  8. final class OpenAPSFixed {
  9. func sortPumpHistory(pumpHistory: JSON) throws -> JSON {
  10. let pumpHistorySwift = try JSONBridge.pumpHistory(from: pumpHistory)
  11. return try JSONBridge.to(pumpHistorySwift.sorted(by: { $0.timestamp > $1.timestamp }))
  12. }
  13. private func middlewareScript(name: String) -> Script? {
  14. if let url = Foundation.Bundle.main.url(forResource: "javascript/\(name)", withExtension: "") {
  15. do {
  16. let body = try String(contentsOf: url)
  17. return Script(name: name, body: body)
  18. } catch {
  19. debug(.openAPS, "Failed to load script \(name): \(error)")
  20. }
  21. }
  22. return nil
  23. }
  24. func determineBasalJavascript(
  25. glucose: JSON,
  26. currentTemp: JSON,
  27. iob: JSON,
  28. profile: JSON,
  29. autosens: JSON,
  30. meal: JSON,
  31. microBolusAllowed: Bool,
  32. reservoir: JSON,
  33. pumpHistory: JSON,
  34. preferences: JSON,
  35. basalProfile: JSON,
  36. trioCustomOrefVariables: JSON,
  37. clock: Date
  38. ) async throws -> OrefFunctionResult {
  39. do {
  40. let jsWorker = JavaScriptWorker(poolSize: 1)
  41. let testBundle = Bundle(for: OpenAPSFixed.self)
  42. let result = try await withCheckedThrowingContinuation { continuation in
  43. jsWorker.inCommonContext { worker in
  44. worker.evaluateBatch(scripts: [
  45. Script(name: "prepare/log.js"),
  46. Script.fromTestingBundle(name: "determine-basal-prepare.js", bundle: testBundle),
  47. Script.fromTestingBundle(name: "basal-set-temp.js", bundle: testBundle),
  48. Script.fromTestingBundle(name: "glucose-get-last.js", bundle: testBundle),
  49. Script.fromTestingBundle(name: "determine-basal.js", bundle: testBundle)
  50. ])
  51. if let middleware = self.middlewareScript(name: OpenAPS.Middleware.determineBasal) {
  52. worker.evaluate(script: middleware)
  53. }
  54. let result = worker.call(function: "generate", with: [
  55. iob,
  56. currentTemp,
  57. glucose,
  58. profile,
  59. autosens,
  60. meal,
  61. microBolusAllowed,
  62. reservoir,
  63. clock,
  64. pumpHistory,
  65. preferences,
  66. basalProfile,
  67. trioCustomOrefVariables
  68. ])
  69. continuation.resume(returning: result)
  70. }
  71. }
  72. return .success(result)
  73. } catch {
  74. return .failure(error)
  75. }
  76. }
  77. func iobHistory(pumphistory: JSON, profile: JSON, clock: JSON, autosens: JSON, zeroTempDuration: JSON) async throws -> JSON {
  78. let jsWorker = JavaScriptWorker(poolSize: 1)
  79. let testBundle = Bundle(for: OpenAPSFixed.self)
  80. let pumphistory: JSON = try! sortPumpHistory(pumpHistory: pumphistory)
  81. let result = try await withCheckedThrowingContinuation { continuation in
  82. jsWorker.inCommonContext { worker in
  83. worker.evaluateBatch(scripts: [
  84. Script(name: "prepare/log.js"),
  85. Script.fromTestingBundle(name: "iob-history.js", bundle: testBundle),
  86. Script.fromTestingBundle(name: "iob-history-prepare.js", bundle: testBundle)
  87. ])
  88. let result = worker.call(function: "generate", with: [
  89. pumphistory,
  90. profile,
  91. clock,
  92. autosens,
  93. zeroTempDuration
  94. ])
  95. continuation.resume(returning: result)
  96. }
  97. }
  98. return result
  99. }
  100. func mealJavascript(
  101. pumphistory: JSON,
  102. profile: JSON,
  103. basalProfile: JSON,
  104. clock: JSON,
  105. carbs: JSON,
  106. glucose: JSON
  107. ) async -> OrefFunctionResult {
  108. let jsWorker = JavaScriptWorker(poolSize: 1)
  109. let testBundle = Bundle(for: OpenAPSFixed.self)
  110. do {
  111. let result = try await withCheckedThrowingContinuation { continuation in
  112. jsWorker.inCommonContext { worker in
  113. worker.evaluateBatch(scripts: [
  114. Script(name: "prepare/log.js"),
  115. Script.fromTestingBundle(name: "meal.js", bundle: testBundle),
  116. Script.fromTestingBundle(name: "meal-prepare.js", bundle: testBundle)
  117. ])
  118. let result = worker.call(function: "generate", with: [
  119. pumphistory,
  120. profile,
  121. clock,
  122. glucose,
  123. basalProfile,
  124. carbs
  125. ])
  126. continuation.resume(returning: result)
  127. }
  128. }
  129. return .success(result)
  130. } catch {
  131. return .failure(error)
  132. }
  133. }
  134. static let prepare = "autosens-prepare.js"
  135. static let prepare24 = "autosens-prepare-24.js"
  136. static let prepare8 = "autosens-prepare-8.js"
  137. func autosenseJavascript(
  138. glucose: JSON,
  139. pumpHistory: JSON,
  140. basalprofile: JSON,
  141. profile: JSON,
  142. carbs: JSON,
  143. temptargets: JSON,
  144. clock: JSON,
  145. prepareFile: String
  146. ) async -> OrefFunctionResult {
  147. do {
  148. let result = try await withCheckedThrowingContinuation { continuation in
  149. let jsWorker = JavaScriptWorker(poolSize: 1)
  150. let testBundle = Bundle(for: OpenAPSFixed.self)
  151. jsWorker.inCommonContext { worker in
  152. worker.evaluateBatch(scripts: [
  153. Script(name: "prepare/log.js"),
  154. Script.fromTestingBundle(name: "autosens.js", bundle: testBundle),
  155. Script.fromTestingBundle(name: prepareFile, bundle: testBundle)
  156. ])
  157. let result = worker.call(function: "generate", with: [
  158. glucose,
  159. pumpHistory,
  160. basalprofile,
  161. profile,
  162. carbs,
  163. temptargets,
  164. clock
  165. ])
  166. continuation.resume(returning: result)
  167. }
  168. }
  169. return .success(result)
  170. } catch {
  171. return .failure(error)
  172. }
  173. }
  174. func iobJavascript(pumphistory: JSON, profile: JSON, clock: JSON, autosens: JSON) async -> OrefFunctionResult {
  175. do {
  176. let testBundle = Bundle(for: OpenAPSFixed.self)
  177. let pumphistory: JSON = try! sortPumpHistory(pumpHistory: pumphistory)
  178. let result = try await withCheckedThrowingContinuation { continuation in
  179. let jsWorker = JavaScriptWorker(poolSize: 1)
  180. jsWorker.inCommonContext { worker in
  181. worker.evaluateBatch(scripts: [
  182. Script(name: "prepare/log.js"),
  183. Script.fromTestingBundle(name: "iob.js", bundle: testBundle),
  184. Script(name: "prepare/iob.js")
  185. ])
  186. let result = worker.call(function: "generate", with: [
  187. pumphistory,
  188. profile,
  189. clock,
  190. autosens
  191. ])
  192. continuation.resume(returning: result)
  193. }
  194. }
  195. return .success(result)
  196. } catch {
  197. return .failure(error)
  198. }
  199. }
  200. func makeProfileJavascript(
  201. preferences: JSON,
  202. pumpSettings: JSON,
  203. bgTargets: JSON,
  204. basalProfile: JSON,
  205. isf: JSON,
  206. carbRatio: JSON,
  207. tempTargets: JSON,
  208. model: JSON,
  209. autotune: JSON,
  210. trioSettings: JSON,
  211. clock: JSON
  212. ) async -> OrefFunctionResult {
  213. do {
  214. let testBundle = Bundle(for: OpenAPSFixed.self)
  215. let result = try await withCheckedThrowingContinuation { continuation in
  216. let jsWorker = JavaScriptWorker(poolSize: 1)
  217. jsWorker.inCommonContext { worker in
  218. worker.evaluateBatch(scripts: [
  219. Script(name: "prepare/log.js"),
  220. Script.fromTestingBundle(name: "profile.js", bundle: testBundle),
  221. Script.fromTestingBundle(name: "profile-prepare.js", bundle: testBundle)
  222. ])
  223. let result = worker.call(function: "generate", with: [
  224. pumpSettings,
  225. bgTargets,
  226. isf,
  227. basalProfile,
  228. preferences,
  229. carbRatio,
  230. tempTargets,
  231. model,
  232. autotune,
  233. trioSettings,
  234. clock
  235. ])
  236. continuation.resume(returning: result)
  237. }
  238. }
  239. return .success(result)
  240. } catch {
  241. return .failure(error)
  242. }
  243. }
  244. }
  245. extension Script {
  246. static func fromTestingBundle(name: String, bundle: Bundle) -> Script {
  247. let body: String
  248. if let url = bundle.url(forResource: "\(name)", withExtension: "") {
  249. do {
  250. body = try String(contentsOf: url)
  251. } catch {
  252. print("Error loading script: \(error.localizedDescription)")
  253. body = "Error loading script"
  254. }
  255. } else {
  256. print("Resource not found: javascript/\(name)")
  257. testPrintAllJSFiles(testBundle: bundle)
  258. body = "Resource not found"
  259. }
  260. return Script(name: name, body: body)
  261. }
  262. static func testPrintAllJSFiles(testBundle: Bundle) {
  263. // Get all .js files in the bundle
  264. if let jsURLs = testBundle.urls(forResourcesWithExtension: "js", subdirectory: nil) {
  265. print("JavaScript files in test bundle:")
  266. for jsURL in jsURLs {
  267. print("- \(jsURL.lastPathComponent)")
  268. print(" Full path: \(jsURL.path)")
  269. }
  270. print("Total JS files found: \(jsURLs.count)")
  271. } else {
  272. print("No JavaScript files found in test bundle")
  273. }
  274. }
  275. }