Procházet zdrojové kódy

Merge branch 'dev' into unit_tests_ci

Deniz Cengiz před 11 měsíci
rodič
revize
5249cc40ce

+ 1 - 2
.github/CODEOWNERS

@@ -1,2 +1 @@
-*    @dnzxy @bjornoleh @MikePlante1 @aug0211 @AndreasStokholm @Sjoerd-Bo3 @t1dude
-*.js @dnzxy @bjornoleh @MikePlante1 @aug0211 @AndreasStokholm @Sjoerd-Bo3 @t1dude @jeremystorring
+*    @dnzxy @bjornoleh @MikePlante1 @AndreasStokholm @Sjoerd-Bo3 @t1dude @marv-out

+ 2 - 2
Config.xcconfig

@@ -19,11 +19,11 @@ TRIO_APP_GROUP_ID = group.org.nightscout.$(DEVELOPMENT_TEAM).trio.trio-app-group
 
 // The developers set the version numbers, please leave them alone
 APP_VERSION = 0.5.0
-APP_DEV_VERSION = 0.5.0.33
+APP_DEV_VERSION = 0.5.0.38
 APP_BUILD_NUMBER = 1
 COPYRIGHT_NOTICE =
 
 // Optional overrides - these can be used to insert your TEAMID into the DEVELOPER_TEAM field
 #include? "../../ConfigOverride.xcconfig"
 #include? "../ConfigOverride.xcconfig"
-#include? "ConfigOverride.xcconfig"
+#include? "ConfigOverride.xcconfig"

+ 6 - 3
Model/Helper/GlucoseStored+helper.swift

@@ -20,12 +20,15 @@ extension GlucoseStored {
         return request
     }
 
-    static func glucoseIsFlat(_ glucose: [GlucoseStored]) -> Bool {
-        guard glucose.count >= 6 else { return false }
+    static func glucoseIsHIGH(_ glucose: [GlucoseStored]) -> Bool {
+        guard glucose.count >= 4 else { return false }
 
         let firstValue = glucose.first?.glucose
 
-        return glucose.allSatisfy { $0.glucose == firstValue }
+        /// 400 mg/dL covers all Dexcom CGMs as well as European Libre 2 and most readings from xDrip4iOS.
+        /// U.S. / Canadian Libres can emit up to 500 mg/dL until it reads "HI"
+        /// Our condition considers both these values, 400 and 500, as possible "flat" readings when paired CGM reads HIGH.
+        return glucose.allSatisfy { $0.glucose == firstValue && ($0.glucose == 400 || $0.glucose == 500) }
     }
 
     // Preview

+ 4 - 4
Trio.xcodeproj/project.pbxproj

@@ -28,7 +28,7 @@
 		190EBCC829FF13AA00BA767D /* UserInterfaceSettingsStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 190EBCC729FF13AA00BA767D /* UserInterfaceSettingsStateModel.swift */; };
 		190EBCCB29FF13CB00BA767D /* UserInterfaceSettingsRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 190EBCCA29FF13CB00BA767D /* UserInterfaceSettingsRootView.swift */; };
 		191F62682AD6B05A004D7911 /* NightscoutSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 191F62672AD6B05A004D7911 /* NightscoutSettings.swift */; };
-		1935364028496F7D001E0B16 /* Oref2_variables.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1935363F28496F7D001E0B16 /* Oref2_variables.swift */; };
+		1935364028496F7D001E0B16 /* TrioCustomOrefVariables.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1935363F28496F7D001E0B16 /* TrioCustomOrefVariables.swift */; };
 		193F6CDD2A512C8F001240FD /* Loops.swift in Sources */ = {isa = PBXBuildFile; fileRef = 193F6CDC2A512C8F001240FD /* Loops.swift */; };
 		195D80B42AF6973A00D25097 /* DynamicSettingsRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 195D80B32AF6973A00D25097 /* DynamicSettingsRootView.swift */; };
 		195D80B72AF697B800D25097 /* DynamicSettingsDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 195D80B62AF697B800D25097 /* DynamicSettingsDataFlow.swift */; };
@@ -844,7 +844,7 @@
 		190EBCC729FF13AA00BA767D /* UserInterfaceSettingsStateModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserInterfaceSettingsStateModel.swift; sourceTree = "<group>"; };
 		190EBCCA29FF13CB00BA767D /* UserInterfaceSettingsRootView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserInterfaceSettingsRootView.swift; sourceTree = "<group>"; };
 		191F62672AD6B05A004D7911 /* NightscoutSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NightscoutSettings.swift; sourceTree = "<group>"; };
-		1935363F28496F7D001E0B16 /* Oref2_variables.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Oref2_variables.swift; sourceTree = "<group>"; };
+		1935363F28496F7D001E0B16 /* TrioCustomOrefVariables.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrioCustomOrefVariables.swift; sourceTree = "<group>"; };
 		193F6CDC2A512C8F001240FD /* Loops.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Loops.swift; sourceTree = "<group>"; };
 		195D80B32AF6973A00D25097 /* DynamicSettingsRootView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DynamicSettingsRootView.swift; sourceTree = "<group>"; };
 		195D80B62AF697B800D25097 /* DynamicSettingsDataFlow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DynamicSettingsDataFlow.swift; sourceTree = "<group>"; };
@@ -2368,7 +2368,7 @@
 				3871F39B25ED892B0013ECB5 /* TempTarget.swift */,
 				3811DE8E25C9D80400A708ED /* User.swift */,
 				E0D4F80427513ECF00BDF1FE /* HealthKitSample.swift */,
-				1935363F28496F7D001E0B16 /* Oref2_variables.swift */,
+				1935363F28496F7D001E0B16 /* TrioCustomOrefVariables.swift */,
 				CE82E02628E869DF00473A9C /* AlertEntry.swift */,
 				19B0EF2028F6D66200069496 /* Statistics.swift */,
 				19012CDB291D2CB900FB8210 /* LoopStats.swift */,
@@ -4350,7 +4350,7 @@
 				110AEDEB2C51A0AE00615CC9 /* ShortcutsConfigView.swift in Sources */,
 				38DF179027733EAD00B3528F /* SnowScene.swift in Sources */,
 				DD1DB7CC2BECCA1F0048B367 /* BuildDetails.swift in Sources */,
-				1935364028496F7D001E0B16 /* Oref2_variables.swift in Sources */,
+				1935364028496F7D001E0B16 /* TrioCustomOrefVariables.swift in Sources */,
 				CE2FAD3A297D93F0001A872C /* BloodGlucoseExtensions.swift in Sources */,
 				38E4453A274E411700EC9A94 /* Disk+[UIImage].swift in Sources */,
 				DD73FA0F2D74F58E00D19D1E /* BackgroundTask+Helper.swift in Sources */,

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 1 - 1
Trio/Resources/javascript/bundle/determine-basal.js


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 1 - 1
Trio/Resources/javascript/bundle/glucose-get-last.js


+ 6 - 6
Trio/Resources/javascript/prepare/determine-basal.js

@@ -1,12 +1,12 @@
 //для enact/smb-suggested.json параметры: monitor/iob.json monitor/temp_basal.json monitor/glucose.json settings/profile.json settings/autosens.json --meal monitor/meal.json --microbolus --reservoir monitor/reservoir.json
 
-function generate(iob, currenttemp, glucose, profile, autosens = null, meal = null, microbolusAllowed = false, reservoir = null, clock = new Date(), pump_history, preferences, basalProfile, oref2_variables) {
+function generate(iob, currenttemp, glucose, profile, autosens = null, meal = null, microbolusAllowed = false, reservoir = null, clock = new Date(), pump_history, preferences, basalProfile, trio_custom_oref_variables) {
 
     var clock = new Date();
     
     var middleware_was_used = "";
     try {
-        var middlewareReason = middleware(iob, currenttemp, glucose, profile, autosens, meal, reservoir, clock, pump_history, preferences, basalProfile, oref2_variables);
+        var middlewareReason = middleware(iob, currenttemp, glucose, profile, autosens, meal, reservoir, clock, pump_history, preferences, basalProfile, trio_custom_oref_variables);
         middleware_was_used = (middlewareReason || "Nothing changed");
         console.log("Middleware reason: " + middleware_was_used);
     } catch (error) {
@@ -40,10 +40,10 @@ function generate(iob, currenttemp, glucose, profile, autosens = null, meal = nu
         basalprofile = basalProfile;
     }
     
-    var oref2_variables_ = {};
-    if (oref2_variables) {
-        oref2_variables_ = oref2_variables;
+    var trio_custom_oref_variables_temp = {};
+    if (trio_custom_oref_variables) {
+        trio_custom_oref_variables_temp = trio_custom_oref_variables;
     }
     
-    return freeaps_determineBasal(glucose_status, currenttemp, iob, profile, autosens_data, meal_data, freeaps_basalSetTemp, microbolusAllowed, reservoir_data, clock, pumphistory, preferences, basalprofile, oref2_variables_, middleware_was_used);
+    return freeaps_determineBasal(glucose_status, currenttemp, iob, profile, autosens_data, meal_data, freeaps_basalSetTemp, microbolusAllowed, reservoir_data, clock, pumphistory, preferences, basalprofile, trio_custom_oref_variables_temp, middleware_was_used);
 }

+ 0 - 6
Trio/Sources/APS/APSManager.swift

@@ -440,12 +440,6 @@ final class BaseAPSManager: APSManager, Injectable {
                 return false
             }
 
-            guard !GlucoseStored.glucoseIsFlat(glucose) else {
-                debug(.apsManager, "Glucose data is too flat")
-                self.processError(APSError.glucoseError(message: String(localized: "Glucose data is too flat")))
-                return false
-            }
-
             return true
         }
 

+ 1 - 1
Trio/Sources/APS/OpenAPS/Constants.swift

@@ -53,7 +53,7 @@ extension OpenAPS {
         static let iob = "monitor/iob.json"
         static let cgmState = "monitor/cgm-state.json"
         static let podAge = "monitor/pod-age.json"
-        static let oref2_variables = "monitor/oref2_variables.json"
+        static let trio_custom_oref_variables = "monitor/trio_custom_oref_variables.json"
         static let alertHistory = "monitor/alerthistory.json"
         static let statistics = "monitor/statistics.json"
     }

+ 45 - 19
Trio/Sources/APS/OpenAPS/OpenAPS.swift

@@ -98,15 +98,15 @@ final class OpenAPS {
     }
 
     // fetch glucose to pass it to the meal function and to determine basal
-    private func fetchAndProcessGlucose() async throws -> String {
+    private func fetchAndProcessGlucose(fetchLimit: Int?) async throws -> String {
         let results = try await CoreDataStack.shared.fetchEntitiesAsync(
             ofType: GlucoseStored.self,
             onContext: context,
             predicate: NSPredicate.predicateForOneDayAgoInMinutes,
             key: "date",
             ascending: false,
-            fetchLimit: 72,
-            batchSize: 24
+            fetchLimit: fetchLimit,
+            batchSize: 48
         )
 
         return try await context.perform {
@@ -292,8 +292,8 @@ final class OpenAPS {
         // Perform asynchronous calls in parallel
         async let pumpHistoryObjectIDs = fetchPumpHistoryObjectIDs() ?? []
         async let carbs = fetchAndProcessCarbs(additionalCarbs: simulatedCarbsAmount ?? 0, carbsDate: simulatedCarbsDate)
-        async let glucose = fetchAndProcessGlucose()
-        async let oref2 = oref2()
+        async let glucose = fetchAndProcessGlucose(fetchLimit: 72)
+        async let prepareTrioCustomOrefVariables = prepareTrioCustomOrefVariables()
         async let profileAsync = loadFileFromStorageAsync(name: Settings.profile)
         async let basalAsync = loadFileFromStorageAsync(name: Settings.basalProfile)
         async let autosenseAsync = loadFileFromStorageAsync(name: Settings.autosense)
@@ -306,7 +306,7 @@ final class OpenAPS {
             pumpHistoryJSON,
             carbsAsJSON,
             glucoseAsJSON,
-            oref2_variables,
+            trioCustomOrefVariables,
             profile,
             basalProfile,
             autosens,
@@ -316,7 +316,7 @@ final class OpenAPS {
             try parsePumpHistory(await pumpHistoryObjectIDs, simulatedBolusAmount: simulatedBolusAmount),
             try carbs,
             try glucose,
-            try oref2,
+            try prepareTrioCustomOrefVariables,
             profileAsync,
             basalAsync,
             autosenseAsync,
@@ -368,7 +368,7 @@ final class OpenAPS {
             pumpHistory: pumpHistoryJSON,
             preferences: preferences,
             basalProfile: basalProfile,
-            oref2_variables: oref2_variables
+            trioCustomOrefVariables: trioCustomOrefVariables
         )
 
         debug(.openAPS, "\(simulation ? "[SIMULATION]" : "") OREF DETERMINATION: \(orefDetermination)")
@@ -385,11 +385,15 @@ final class OpenAPS {
 
             return determination
         } else {
+            debug(
+                .openAPS,
+                "\(DebuggingIdentifiers.failed) No determination data. orefDetermination: \(orefDetermination), Determination(from: orefDetermination): \(String(describing: Determination(from: orefDetermination))), deliverAt: \(String(describing: Determination(from: orefDetermination)?.deliverAt))"
+            )
             throw APSError.apsError(message: "No determination data.")
         }
     }
 
-    func oref2() async throws -> RawJSON {
+    func prepareTrioCustomOrefVariables() async throws -> RawJSON {
         try await context.perform {
             // Retrieve user preferences
             let userPreferences = self.storage.retrieve(OpenAPS.Settings.preferences, as: Preferences.self)
@@ -425,8 +429,10 @@ final class OpenAPS {
             let averageTDDLastTenDays = totalTDD / Decimal(totalDaysCount)
             let weightedTDD = weightPercentage * averageTDDLastTwoHours + (1 - weightPercentage) * averageTDDLastTenDays
 
-            // Prepare Oref2 variables
-            let oref2Data = Oref2_variables(
+            let glucose = try self.fetchGlucose()
+
+            // Prepare Trio's custom oref variables
+            let trioCustomOrefVariablesData = TrioCustomOrefVariables(
                 average_total_data: currentTDD > 0 ? averageTDDLastTenDays : 0,
                 weightedAverage: currentTDD > 0 ? weightedTDD : 1,
                 currentTDD: currentTDD,
@@ -446,12 +452,13 @@ final class OpenAPS {
                 start: (activeOverrides.first?.start ?? 0) as Decimal,
                 end: (activeOverrides.first?.end ?? 0) as Decimal,
                 smbMinutes: activeOverrides.first?.smbMinutes?.decimalValue ?? maxSMBBasalMinutes,
-                uamMinutes: activeOverrides.first?.uamMinutes?.decimalValue ?? maxUAMBasalMinutes
+                uamMinutes: activeOverrides.first?.uamMinutes?.decimalValue ?? maxUAMBasalMinutes,
+                shouldProtectDueToHIGH: GlucoseStored.glucoseIsHIGH(glucose)
             )
 
-            // Save and return the Oref2 variables
-            self.storage.save(oref2Data, as: OpenAPS.Monitor.oref2_variables)
-            return self.loadFileFromStorage(name: Monitor.oref2_variables)
+            // Save and return contents of Trio's custom oref variables
+            self.storage.save(trioCustomOrefVariablesData, as: OpenAPS.Monitor.trio_custom_oref_variables)
+            return self.loadFileFromStorage(name: Monitor.trio_custom_oref_variables)
         }
     }
 
@@ -461,7 +468,7 @@ final class OpenAPS {
         // Perform asynchronous calls in parallel
         async let pumpHistoryObjectIDs = fetchPumpHistoryObjectIDs() ?? []
         async let carbs = fetchAndProcessCarbs()
-        async let glucose = fetchAndProcessGlucose()
+        async let glucose = fetchAndProcessGlucose(fetchLimit: nil)
         async let getProfile = loadFileFromStorageAsync(name: Settings.profile)
         async let getBasalProfile = loadFileFromStorageAsync(name: Settings.basalProfile)
         async let getTempTargets = loadFileFromStorageAsync(name: Settings.tempTargets)
@@ -671,7 +678,7 @@ final class OpenAPS {
         pumpHistory: JSON,
         preferences: JSON,
         basalProfile: JSON,
-        oref2_variables: JSON
+        trioCustomOrefVariables: JSON
     ) async throws -> RawJSON {
         try await withCheckedThrowingContinuation { continuation in
             jsWorker.inCommonContext { worker in
@@ -700,7 +707,7 @@ final class OpenAPS {
                     pumpHistory,
                     preferences,
                     basalProfile,
-                    oref2_variables
+                    trioCustomOrefVariables
                 ])
 
                 continuation.resume(returning: result)
@@ -830,7 +837,7 @@ final class OpenAPS {
     }
 }
 
-// Non-Async fetch methods for oref2
+// Non-Async fetch methods for trio_custom_oref_variables
 extension OpenAPS {
     func fetchActiveTempTargets() throws -> [TempTargetStored] {
         try CoreDataStack.shared.fetchEntities(
@@ -864,4 +871,23 @@ extension OpenAPS {
             propertiesToFetch: ["date", "total"]
         ) as? [[String: Any]] ?? []
     }
+
+    func fetchGlucose() throws -> [GlucoseStored] {
+        let results = try CoreDataStack.shared.fetchEntities(
+            ofType: GlucoseStored.self,
+            onContext: context,
+            predicate: NSPredicate.predicateFor30MinAgo,
+            key: "date",
+            ascending: false,
+            fetchLimit: 4
+        )
+
+        return try context.perform {
+            guard let glucoseResults = results as? [GlucoseStored] else {
+                throw CoreDataError.fetchError(function: #function, file: #file)
+            }
+
+            return glucoseResults
+        }
+    }
 }

+ 1 - 1
Trio/Sources/APS/Storage/TDDStorage.swift

@@ -626,7 +626,7 @@ final class BaseTDDStorage: TDDStorage, Injectable {
 
             // Get weight percentage from preferences (default 0.65 if not set)
             let userPreferences = self.storage.retrieve(OpenAPS.Settings.preferences, as: Preferences.self)
-            let weightPercentage = userPreferences?.weightPercentage ?? Decimal(0.65) // why is this 1 as default in oref2??
+            let weightPercentage = userPreferences?.weightPercentage ?? Decimal(0.65) // why is this 1 as default in trio-oref??
 
             // Calculate weighted average using the formula:
             // weightedTDD = (weightPercentage × recent_average) + ((1 - weightPercentage) × historical_average)

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 4136 - 6883
Trio/Sources/Localizations/Main/Localizable.xcstrings


+ 7 - 3
Trio/Sources/Models/Oref2_variables.swift

@@ -1,6 +1,6 @@
 import Foundation
 
-struct Oref2_variables: JSON, Equatable {
+struct TrioCustomOrefVariables: JSON, Equatable {
     var average_total_data: Decimal
     var currentTDD: Decimal
     var weightedAverage: Decimal
@@ -21,6 +21,7 @@ struct Oref2_variables: JSON, Equatable {
     var end: Decimal
     var smbMinutes: Decimal
     var uamMinutes: Decimal
+    var shouldProtectDueToHIGH: Bool
 
     init(
         average_total_data: Decimal,
@@ -42,7 +43,8 @@ struct Oref2_variables: JSON, Equatable {
         start: Decimal,
         end: Decimal,
         smbMinutes: Decimal,
-        uamMinutes: Decimal
+        uamMinutes: Decimal,
+        shouldProtectDueToHIGH: Bool
     ) {
         self.average_total_data = average_total_data
         self.weightedAverage = weightedAverage
@@ -64,10 +66,11 @@ struct Oref2_variables: JSON, Equatable {
         self.end = end
         self.smbMinutes = smbMinutes
         self.uamMinutes = uamMinutes
+        self.shouldProtectDueToHIGH = shouldProtectDueToHIGH
     }
 }
 
-extension Oref2_variables {
+extension TrioCustomOrefVariables {
     private enum CodingKeys: String, CodingKey {
         case average_total_data
         case weightedAverage
@@ -89,5 +92,6 @@ extension Oref2_variables {
         case end
         case smbMinutes
         case uamMinutes
+        case shouldProtectDueToHIGH
     }
 }

+ 1 - 1
Trio/Sources/Shortcuts/Carbs/AddCarbPresetIntent.swift

@@ -15,7 +15,7 @@ import Swinject
         description: "Quantity of carbs in g",
         controlStyle: .field,
         inclusiveRange: (lowerBound: 0, upperBound: 200),
-        requestValueDialog: IntentDialog(stringLiteral: String(localized: "How many grams of carbs did you eat?")),
+        requestValueDialog: IntentDialog(stringLiteral: String(localized: "How many grams of carbs did you eat?"))
     ) var carbQuantity: Double?
 
     @Parameter(

+ 8 - 1
oref0_source_version.txt

@@ -1,6 +1,13 @@
-oref0 branch: tcd-fixes - git version: af8f79c
+oref0 branch: dev - git version: c0b46d3
 
 Last commits:
+c0b46d3 Merge pull request #47 from nightscout/fix-400-guard
+1591b14 Remove leftover != 400 condition
+4204b12 Remove autoISF adjustments from glucose-get-last
+2596f3f Refactor 400 glucose guard: - Remove old 400 guard - Replace with check that only disables SMBs and keeps TBR at neutral == current basal rate, if shouldProtectDueToHIGH is not null/undefined and true - Remove all other 400 guards
+2ca5e20 Change folder name to Trio from iAPS
+6ad27e9 Merge pull request #46 from nightscout/tcd
+d98e4fc Merge pull request #45 from nightscout/tcd-fixes
 af8f79c Fix typo; remove enableDynamicCR setting from profile/index.js
 ffb9374 Always add minPredBG to rT object
 814b629 Remove dynamicCR

+ 54 - 59
trio-oref/lib/determine-basal/determine-basal.js

@@ -44,15 +44,15 @@ function convert_bg(value, profile)
         return Math.round(value);
     }
 }
-function enable_smb(profile, microBolusAllowed, meal_data, bg, target_bg, high_bg, oref_variables, time) {
-    if (oref_variables.smbIsScheduledOff){
+function enable_smb(profile, microBolusAllowed, meal_data, bg, target_bg, high_bg, trio_custom_variables, time) {
+    if (trio_custom_variables.smbIsScheduledOff){
         /* Below logic is related to profile overrides which can disable SMBs or disable them for a scheduled window.
          * SMBs will be disabled from [start, end), such that if an SMB is scheduled to be disabled from 10 AM to 2 PM,
          * an SMB will not be allowed from 10:00:00 until 1:59:59.
          */
         let currentHour = new Date(time.getHours());
-        let startTime = oref_variables.start;
-        let endTime = oref_variables.end;
+        let startTime = trio_custom_variables.start;
+        let endTime = trio_custom_variables.end;
 
         if (startTime < endTime && (currentHour >= startTime && currentHour < endTime)) {
             console.error("SMB disabled: current time is in SMB disabled scheduled")
@@ -79,8 +79,8 @@ function enable_smb(profile, microBolusAllowed, meal_data, bg, target_bg, high_b
         console.error("SMB disabled due to Bolus Wizard activity in the last 6 hours.");
         return false;
     // Disable if invalid CGM reading (HIGH)
-    } else if (bg == 400) {
-            console.error("Invalid CGM (HIGH). SMBs disabled.");
+    } else if (!!trio_custom_variables.shouldProtectDueToHIGH) {
+        console.error("Invalid CGM (HIGH). SMBs disabled.");
         return false;
     }
 
@@ -142,22 +142,22 @@ function enable_smb(profile, microBolusAllowed, meal_data, bg, target_bg, high_b
 }
 
 
-var determine_basal = function determine_basal(glucose_status, currenttemp, iob_data, profile, autosens_data, meal_data, tempBasalFunctions, microBolusAllowed, reservoir_data, currentTime, pumphistory, preferences, basalprofile, oref2_variables, middleWare) {
+var determine_basal = function determine_basal(glucose_status, currenttemp, iob_data, profile, autosens_data, meal_data, tempBasalFunctions, microBolusAllowed, reservoir_data, currentTime, pumphistory, preferences, basalprofile, trio_custom_variables, middleWare) {
 
     var profileTarget = profile.min_bg;
-    var overrideTarget = oref2_variables.overrideTarget;
-    if (overrideTarget != 0 && overrideTarget != 6 && oref2_variables.useOverride && !profile.temptargetSet) {
+    var overrideTarget = trio_custom_variables.overrideTarget;
+    if (overrideTarget != 0 && overrideTarget != 6 && trio_custom_variables.useOverride && !profile.temptargetSet) {
         profileTarget = overrideTarget;
     }
-    const smbIsOff = oref2_variables.smbIsOff;
-    const advancedSettings = oref2_variables.advancedSettings;
-    const isfAndCr = oref2_variables.isfAndCr;
-    const isf = oref2_variables.isf;
-    const cr_ = oref2_variables.cr;
-    const smbMinutes = oref2_variables.smbMinutes;
-    const uamMinutes = oref2_variables.uamMinutes;
+    const smbIsOff = trio_custom_variables.smbIsOff;
+    const advancedSettings = trio_custom_variables.advancedSettings;
+    const isfAndCr = trio_custom_variables.isfAndCr;
+    const isf = trio_custom_variables.isf;
+    const cr_ = trio_custom_variables.cr;
+    const smbMinutes = trio_custom_variables.smbMinutes;
+    const uamMinutes = trio_custom_variables.uamMinutes;
     // tdd past 24 hour
-    let tdd = oref2_variables.currentTDD;
+    let tdd = trio_custom_variables.currentTDD;
     var logOutPut = "";
     var tddReason = "";
 
@@ -174,12 +174,12 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_
 
 
 
-    const weightedAverage = oref2_variables.weightedAverage;
+    const weightedAverage = trio_custom_variables.weightedAverage;
     var overrideFactor = 1;
     var sensitivity = profile.sens;
     var carbRatio = profile.carb_ratio;
-    if (oref2_variables.useOverride) {
-        overrideFactor = oref2_variables.overridePercentage / 100;
+    if (trio_custom_variables.useOverride) {
+        overrideFactor = trio_custom_variables.overridePercentage / 100;
         if (isfAndCr) {
             sensitivity /= overrideFactor;
             carbRatio /= overrideFactor;
@@ -189,7 +189,7 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_
         }
     }
     const weightPercentage = profile.weightPercentage;
-    const average_total_data = oref2_variables.average_total_data;
+    const average_total_data = trio_custom_variables.average_total_data;
 
     // In case the autosens.min/max limits are reversed:
     const minLimitChris = Math.min(profile.autosens_min, profile.autosens_max);
@@ -390,11 +390,11 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_
     var basal = profile_current_basal;
 
     // Print Current Override factor, if any
-    if (oref2_variables.useOverride) {
-        if (oref2_variables.duration == 0) {
+    if (trio_custom_variables.useOverride) {
+        if (trio_custom_variables.duration == 0) {
             console.log("Profile Override is active. Override " + round(overrideFactor * 100, 0) + "%. Override Duration: " + "Enabled indefinitely");
         } else
-            console.log("Profile Override is active. Override " + round(overrideFactor * 100, 0) + "%. Override Expires in: " + oref2_variables.duration + " min.");
+            console.log("Profile Override is active. Override " + round(overrideFactor * 100, 0) + "%. Override Expires in: " + trio_custom_variables.duration + " min.");
     }
 
     var bgTime = new Date(glucose_status.date);
@@ -425,21 +425,18 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_
     if (bg <= 10 || bg === 38 || noise >= 3) {  //Dexcom is in ??? mode or calibrating, or xDrip reports high noise
         rT.reason = "CGM is calibrating, in ??? state, or noise is high";
     }
-    var tooflat=false;
-    if (bg > 60 && glucose_status.delta == 0 && glucose_status.short_avgdelta > -1 && glucose_status.short_avgdelta < 1 && glucose_status.long_avgdelta > -1 && glucose_status.long_avgdelta < 1 && bg != 400) {
+    if (bg > 60 && glucose_status.delta == 0 && glucose_status.short_avgdelta > -1 && glucose_status.short_avgdelta < 1 && glucose_status.long_avgdelta > -1 && glucose_status.long_avgdelta < 1) {
         if (glucose_status.device == "fakecgm") {
             console.error("CGM data is unchanged (" + convert_bg(bg,profile) + "+" + convert_bg(glucose_status.delta,profile)+ ") for 5m w/ " + convert_bg(glucose_status.short_avgdelta,profile) + " mg/dL ~15m change & " + convert_bg(glucose_status.long_avgdelta,2) + " mg/dL ~45m change");
             console.error("Simulator mode detected (" + glucose_status.device + "): continuing anyway");
-        } else if (bg != 400) {
-            tooflat=true;
-        }
+        } 
     }
 
     if (minAgo > 12 || minAgo < -5) { // Dexcom data is too old, or way in the future
         rT.reason = "If current system time " + systemTime + " is correct, then BG data is too old. The last BG data was read "+minAgo+"m ago at "+bgTime;
 
         // if BG is too old/noisy, or is completely unchanging, cancel any high temps and shorten any long zero temps
-    } else if ( glucose_status.short_avgdelta === 0 && glucose_status.long_avgdelta === 0 && bg != 400 ) {
+    } else if ( glucose_status.short_avgdelta === 0 && glucose_status.long_avgdelta === 0 ) {
         if ( glucose_status.last_cal && glucose_status.last_cal < 3 ) {
             rT.reason = "CGM was just calibrated";
         } else {
@@ -447,30 +444,28 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_
         }
     }
 
-    if (bg != 400) {
-        if (bg <= 10 || bg === 38 || noise >= 3 || minAgo > 12 || minAgo < -5 || ( glucose_status.short_avgdelta === 0 && glucose_status.long_avgdelta === 0 ) ) {
-            if (currenttemp.rate >= basal) { // high temp is running
-                rT.reason += ". Canceling high temp basal of " + currenttemp.rate;
-                rT.deliverAt = deliverAt;
-                rT.temp = 'absolute';
-                rT.duration = 0;
-                rT.rate = 0;
-                return rT;
-                // don't use setTempBasal(), as it has logic that allows <120% high temps to continue running
-                //return tempBasalFunctions.setTempBasal(basal, 30, profile, rT, currenttemp);
-            } else if ( currenttemp.rate === 0 && currenttemp.duration > 30 ) { //shorten long zero temps to 30m
-                rT.reason += ". Shortening " + currenttemp.duration + "m long zero temp to 30m. ";
-                rT.deliverAt = deliverAt;
-                rT.temp = 'absolute';
-                rT.duration = 30;
-                rT.rate = 0;
-                return rT;
-                // don't use setTempBasal(), as it has logic that allows long zero temps to continue running
-                //return tempBasalFunctions.setTempBasal(0, 30, profile, rT, currenttemp);
-            } else { //do nothing.
-                rT.reason += ". Temp " + currenttemp.rate + " <= current basal " + basal + "U/hr; doing nothing. ";
-                return rT;
-            }
+    if (bg <= 10 || bg === 38 || noise >= 3 || minAgo > 12 || minAgo < -5 || ( glucose_status.short_avgdelta === 0 && glucose_status.long_avgdelta === 0 ) ) {
+        if (currenttemp.rate >= basal) { // high temp is running
+            rT.reason += ". Canceling high temp basal of " + currenttemp.rate;
+            rT.deliverAt = deliverAt;
+            rT.temp = 'absolute';
+            rT.duration = 0;
+            rT.rate = 0;
+            return rT;
+            // don't use setTempBasal(), as it has logic that allows <120% high temps to continue running
+            //return tempBasalFunctions.setTempBasal(basal, 30, profile, rT, currenttemp);
+        } else if ( currenttemp.rate === 0 && currenttemp.duration > 30 ) { //shorten long zero temps to 30m
+            rT.reason += ". Shortening " + currenttemp.duration + "m long zero temp to 30m. ";
+            rT.deliverAt = deliverAt;
+            rT.temp = 'absolute';
+            rT.duration = 30;
+            rT.rate = 0;
+            return rT;
+            // don't use setTempBasal(), as it has logic that allows long zero temps to continue running
+            //return tempBasalFunctions.setTempBasal(0, 30, profile, rT, currenttemp);
+        } else { //do nothing.
+            rT.reason += ". Temp " + currenttemp.rate + " <= current basal " + basal + "U/hr; doing nothing. ";
+            return rT;
         }
     }
 
@@ -782,7 +777,7 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_
             bg,
             target_bg,
             high_bg,
-            oref2_variables,
+            trio_custom_variables,
             systemTime
         );
     }
@@ -1508,11 +1503,11 @@ var maxDelta_bg_threshold;
                 uamMinutesSetting = profile.maxUAMSMBBasalMinutes;
             }
 
-            if (oref2_variables.useOverride && advancedSettings && smbMinutes !== smbMinutesSetting) {
+            if (trio_custom_variables.useOverride && advancedSettings && smbMinutes !== smbMinutesSetting) {
                 console.error("SMB Max Minutes - setting overriden from " + smbMinutesSetting + " to " + smbMinutes);
                 smbMinutesSetting = smbMinutes;
             }
-            if (oref2_variables.useOverride && advancedSettings && uamMinutes !== uamMinutesSetting) {
+            if (trio_custom_variables.useOverride && advancedSettings && uamMinutes !== uamMinutesSetting) {
                 console.error("UAM Max Minutes - setting overriden from " + uamMinutesSetting + " to " + uamMinutes);
                 uamMinutesSetting = uamMinutes;
             }
@@ -1621,8 +1616,8 @@ var maxDelta_bg_threshold;
 
         var maxSafeBasal = tempBasalFunctions.getMaxSafeBasal(profile);
 
-
-        if (bg == 400) {
+        // set neutral TBR at current basal rate because glucose is considered as requiring dosing Protect due to HIGH (400 mg/dL)
+        if (!!trio_custom_variables.shouldProtectDueToHIGH) {
             return tempBasalFunctions.setTempBasal(profile.current_basal, 30, profile, rT, currenttemp);
         }
 

+ 9 - 247
trio-oref/lib/glucose-get-last.js

@@ -1,17 +1,10 @@
 function getDateFromEntry(entry) {
-return entry.date || Date.parse(entry.display_time) || Date.parse(entry.dateString);
-}
-
-function round(value, digits)
-{
-    if (! digits) { digits = 0; }
-    var scale = Math.pow(10, digits);
-    return Math.round(value * scale) / scale;
+  return entry.date || Date.parse(entry.display_time) || Date.parse(entry.dateString);
 }
 
 var getLastGlucose = function (data) {
     data = data.filter(function(obj) {
-    return obj.glucose || obj.sgv;
+      return obj.glucose || obj.sgv;
     }).map(function prepGlucose (obj) {
         //Support the NS sgv field to avoid having to convert in a custom way
         obj.glucose = obj.glucose || obj.sgv;
@@ -51,12 +44,12 @@ var getLastGlucose = function (data) {
                 //console.error(then.glucose, minutesago, avgdelta);
             //}
             // use the average of all data points in the last 2.5m for all further "now" calculations
-            if (-2 < minutesago && minutesago < 2.5) {
+            if (-2 < minutesago && minutesago <= 2.5) {
                 now.glucose = ( now.glucose + then.glucose ) / 2;
                 now_date = ( now_date + then_date ) / 2;
                 //console.error(then.glucose, now.glucose);
             // short_deltas are calculated from everything ~5-15 minutes ago
-            } else if (2.5 < minutesago && minutesago < 17.5) {
+            } else if (2.5 < minutesago && minutesago <= 17.5) {
                 //console.error(minutesago, avgdelta);
                 short_deltas.push(avgdelta);
                 // last_deltas are calculated from everything ~5 minutes ago
@@ -73,26 +66,6 @@ var getLastGlucose = function (data) {
     var last_delta = 0;
     var short_avgdelta = 0;
     var long_avgdelta = 0;
-
-    // start autoISF by https://github.com/ga-zelle/autoISF , relevant variables and functions
-    // mod 7: append 2 variables for 5% range
-    var autoISF_duration = 0;
-    var autoISF_average = 0;
-    // mod 8: append 3 variables for deltas based on regression analysis
-    var slope05 = 0;
-    var slope15 = 0;
-    var slope40 = 0;
-    // mod 14f: append results from best fitting parabola
-    var dura_p = 0;
-    var delta_pl = 0;
-    var delta_pn = 0;
-    var r_squ = 0;
-    var bg_acceleration = 0;
-    var a_0 = 0;
-    var a_1 = 0;
-    var a_2 = 0;
-    var pp_debug = "autoISF Mod14-Debug: ";
-
     if (last_deltas.length > 0) {
         last_delta = last_deltas.reduce(function(a, b) { return a + b; }) / last_deltas.length;
     }
@@ -102,228 +75,17 @@ var getLastGlucose = function (data) {
     if (long_deltas.length > 0) {
         long_avgdelta = long_deltas.reduce(function(a, b) { return a + b; }) / long_deltas.length;
     }
-    var bw = 0.05;
-    var sumBG = now.glucose;
-    var oldavg = now.glucose;
-    var minutesdur = 0;
-    for (var i = 1; i < data.length; i++) {
-        var then = data[i];
-        var then_date = getDateFromEntry(then);
-    //  mod 7c: stop the series if there was a CGM gap greater than 13 minutes, i.e. 2 regular readings
-            if (Math.round((now_date - then_date) / (1000 * 60)) - minutesdur > 13) {
-            break;
-            }
-            if (then.glucose > oldavg*(1-bw) && then.glucose < oldavg*(1+bw)) {
-            sumBG += then.glucose;
-            oldavg = sumBG / (i+1);
-            minutesdur = Math.round((now_date - then_date) / (1000 * 60));
-            } else {
-            break;
-        }
-    }
-            autoISF_average = oldavg;
-            autoISF_duration = minutesdur;
 
-            // mod 8: calculate 3 variables for deltas based on linear regression
-            // initially just test the handling of arguments
-            var slope05 = 1.05;
-            var slope15 = 1.15;
-            var slope40 = 1.40;
-
-            // mod 8a: now do the real maths based on
-            // http://www.carl-engler-schule.de/culm/culm/culm2/th_messdaten/mdv2/auszug_ausgleichsgerade.pdf
-            var sumBG  = 0;         // y
-            var sumt   = 0;         // x
-            var sumBG2 = 0;         // y^2
-            var sumt2  = 0;         // x^2
-            var sumxy  = 0;         // x*y
-            //double a;
-            var b;                   // y = a + b * x
-            var level = 7.5;
-            var minutesL;
-            // here, longer deltas include all values from 0 up the related limit
-            for (var i = 0; i < data.length; i++) {
-                var then = data[i];
-                var then_date = getDateFromEntry(then);
-                minutesL = (now_date - then_date) / (1000 * 60);
-                // watch out: the scan goes backwards in time, so delta has wrong sign
-                if(i * sumt2 == sumt * sumt) {
-                    b = 0.0;
-                }
-                else {
-                    b = (i * sumxy - sumt * sumBG) / (i * sumt2 - sumt * sumt);
-                }
-                if (minutesL > level && level == 7.5) {
-                    slope05 = -b * 5;
-                    level = 17.5;
-                }
-                if (minutesL > level && level == 17.5) {
-                    slope15 = -b * 5;
-                    level = 42.5;
-                }
-                if (minutesL > level && level == 42.5) {
-                    slope40 = -b * 5;
-                    break;
-                }
-
-                sumt   += minutesL;
-                sumt2  += minutesL * minutesL;
-                sumBG  += then.glucose;
-                sumBG2 += then.glucose * then.glucose;
-                sumxy  += then.glucose * minutesL;
-            }
-
-            // mod 14f: calculate best parabola and determine delta by extending it 5 minutes into the future
-            // nach https://www.codeproject.com/Articles/63170/Least-Squares-Regression-for-Quadratic-Curve-Fitti
-            //
-            //  y = a2*x^2 + a1*x + a0      or
-            //  y = a*x^2  + b*x  + c       respectively
-
-            // initially just test the handling of arguments
-            var dura_p  = 0;
-            var delta_pl = 0;
-            var delta_pn = 0;
-            var bg_acceleration = 0;
-            var r_squ   = 0;
-            var best_a = 0;
-            var best_b = 0;
-            var best_c = 0;
-            var a_0 = 0;
-            var a_1 = 0;
-            var a_2 = 0;
-
-            if (data.length <= 3) {                      // last 3 points make a trivial parabola
-                dura_p  = 0;
-                delta_pl = 0;
-                delta_pn = 0;
-                bg_acceleration = 0;
-                r_squ   = 0;
-                a_0 = 0;
-                a_1 = 0;
-                a_2 = 0;
-            } else {
-                //double corrMin = 0.90;                  // go backwards until the correlation coefficient goes below
-                var sy    = 0;                        // y
-                var sx    = 0;                        // x
-                var sx2   = 0;                        // x^2
-                var sx3   = 0;                        // x^3
-                var sx4   = 0;                        // x^4
-                var sxy   = 0;                        // x*y
-                var sx2y  = 0;                        // x^2*y
-                var corrMax = 0;
-                var iframe = data[0];
-                var time_0 = getDateFromEntry(iframe);
-                var ti_last = 0;
-                //# for best numerical accurarcy time and bg must be of same order of magnitude
-                var scaleTime = 300;                  //# in 5m; values are  0, 1, 2, 3, 4, ...
-                var scaleBg   =  50;                  //# TIR range is now 1.4 - 3.6
-
-                for (var i = 0; i < data.length; i++) {
-                    var then = data[i];
-                    var then_date = getDateFromEntry(then);
-                    // skip records older than 47.5 minutes
-                    var ti = (then_date - time_0) / 1000 / scaleTime;
-                    if (-ti *scaleTime > 47 * 60) {                        // skip records older than 47.5 minutes
-                        break;
-                    } else if (ti < ti_last - 7.5 * 60 / scaleTime) {       // stop scan if a CGM gap > 7.5 minutes is detected
-                        if ( i<3) {                             // history too short for fit
-                            dura_p =  -ti_last / 60;
-                            delta_pl = 0;
-                            delta_pn = 0;
-                            bg_acceleration= 0;
-                            r_squ = 0;
-                            a_0 = 0;
-                            a_1 = 0;
-                            a_2 = 0;
-                        }
-                        break;
-                    }
-                    ti_last = ti;
-                    var bg = then.glucose/scaleBg;
-                    sx += ti;
-                    sx2 += Math.pow(ti, 2);
-                    sx3 += Math.pow(ti, 3);
-                    sx4 += Math.pow(ti, 4);
-                    sy  += bg;
-                    sxy += ti * bg;
-                    sx2y += Math.pow(ti, 2) * bg;
-                    var n = i + 1;
-                    var D  = 0;
-                    var Da = 0;
-                    var Db = 0;
-                    var Dc = 0;
-                    if (n > 3) {
-                        D  = sx4 * (sx2 * n - sx * sx) - sx3 * (sx3 * n - sx * sx2) + sx2 * (sx3 * sx - sx2 * sx2);
-                        Da = sx2y* (sx2 * n - sx * sx) - sxy * (sx3 * n - sx * sx2) + sy  * (sx3 * sx - sx2 * sx2);
-                        Db = sx4 * (sxy * n - sy * sx) - sx3 * (sx2y* n - sy * sx2) + sx2 * (sx2y* sx - sxy * sx2);
-                        Dc = sx4 * (sx2 *sy - sx *sxy) - sx3 * (sx3 *sy - sx *sx2y) + sx2 * (sx3 *sxy - sx2 * sx2y);
-                    }
-                    if (D != 0) {
-                        var a = Da / D;
-                        b = Db / D;              // b initialised in linear fit !
-                        var c = Dc / D;
-                        var y_mean = sy / n;
-                        var s_squares = 0;
-                        var s_residual_squares = 0;
-                        for (var j = 0; j <= i; j++) {
-                            var before = data[j];
-                            var before_date = getDateFromEntry(before);
-                            s_squares += Math.pow(before.glucose / scaleBg - y_mean, 2);
-                            var delta_t = (before_date - time_0) / 1000 / scaleTime;
-                            var bg_j = a * Math.pow(delta_t, 2) + b * delta_t + c;
-                            s_residual_squares += Math.pow(before.glucose / scaleBg - bg_j, 2);
-                        }
-                        var r_squ = 0.64;
-                        if (s_squares != 0) {
-                            r_squ = 1 - s_residual_squares / s_squares;
-                        }
-                        if (n > 3) {
-                            if (r_squ >= corrMax) {
-                                corrMax = r_squ;
-                                // double delta_t = (then_date - time_0) / 1000;
-                                dura_p = -ti * scaleTime / 60;            // remember we are going backwards in time
-                                var delta5Min = 5 * 60 / scaleTime;
-                                delta_pl =-scaleBg * (a * Math.pow(- delta5Min, 2) - b * delta5Min);     // 5 minute slope from last fitted bg starting from last bg, i.e. t=0
-                                delta_pn = scaleBg * (a * Math.pow( delta5Min, 2) + b * delta5Min);     // 5 minute slope to next fitted bg starting from last bg, i.e. t=0
-                                bg_acceleration = 2 * a * scaleBg;             // 2nd derivative of parabola per (5min)^2
-                                a_0 = c * scaleBg;
-                                a_1 = b * scaleBg;
-                                a_2 = a * scaleBg;
-                                //r_squ = corrMax;
-                                best_a = a * scaleBg;
-                                best_b = b * scaleBg;
-                                best_c = c * scaleBg;
-                            }
-                        }
-                    }
-                }
-                pp_debug += "coeffs a/b/c=(" + round(best_a,2) + " / " + round(best_b,2) + " / " + round(best_c,2) + "); bg date=" + time_0 + "; ";
-                pp_debug += "Parabola Fits a0/a1/a2=(" + round(a_0,2) + " / " + round(a_1,2) + " / " + round(a_2,2) + "); ";
-            }
-           pp_debug += "Slopes 05/15/40=(" + round(slope05,2) + " / " + round(slope15,2) + " / " + round(slope40,2) + "); "
     return {
-        delta: Math.round( last_delta * 10000 ) / 10000
-        , glucose: Math.round( now.glucose * 10000 ) / 10000
+        delta: Math.round( last_delta * 100 ) / 100
+        , glucose: Math.round( now.glucose * 100 ) / 100
         , noise: Math.round(now.noise)
-        , short_avgdelta: Math.round( short_avgdelta * 10000 ) / 10000
-        , long_avgdelta: Math.round( long_avgdelta * 10000 ) / 10000
-        // autoISF values to return to determineBasal.js
-        , autoISF_average: Math.round( autoISF_average * 10000) / 10000
-        , autoISF_duration: Math.round(autoISF_duration * 10000) / 10000
-        , dura_p: Math.round( dura_p * 10000) / 10000
-        , delta_pl: Math.round( delta_pl * 10000) / 10000
-        , delta_pn: Math.round( delta_pn * 10000) / 10000
-        , bg_acceleration: bg_acceleration
-        , r_squ: Math.round( corrMax * 10000) / 10000
-        , parabola_fit_a0: Math.round( a_0 * 10000) / 10000
-        , parabola_fit_a1: Math.round( a_1 * 10000) / 10000
-        , parabola_fit_a2: Math.round( a_2 * 10000) / 10000
-        , pp_debug
-        // end autoISF values
+        , short_avgdelta: Math.round( short_avgdelta * 100 ) / 100
+        , long_avgdelta: Math.round( long_avgdelta * 100 ) / 100
         , date: now_date
         , last_cal: last_cal
         , device: now.device
     };
 };
 
-module.exports = getLastGlucose;
+module.exports = getLastGlucose;

+ 1 - 1
trio-oref/oref_source_file_info.txt

@@ -1,2 +1,2 @@
 These source files are copied from https://github.com/nightscout/trio-oref, and are for information purposes only.
-The algorithm is run based on minimised files in FreeAPS/Resources/javascript/bundle.
+The algorithm is run based on minimised files in Trio/Resources/javascript/bundle.