Pārlūkot izejas kodu

Merge pull request #265 from mountrcg/maxAbsorptionTime

Introduce configurable MaxMealAbsorptionTime setting
Deniz Cengiz 1 gadu atpakaļ
vecāks
revīzija
9e02c7b2c0

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 1 - 1
Trio/Resources/javascript/bundle/meal.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 1 - 1
Trio/Resources/javascript/bundle/profile.js


+ 1 - 0
Trio/Resources/json/defaults/preferences.json

@@ -14,6 +14,7 @@
   "exercise_mode" : false,
   "exercise_mode" : false,
   "half_basal_exercise_target" : 160,
   "half_basal_exercise_target" : 160,
   "maxCOB" : 120,
   "maxCOB" : 120,
+  "maxMealAbsorptionTime" : 6,
   "wide_bg_target_range" : false,
   "wide_bg_target_range" : false,
   "skip_neutral_temps" : false,
   "skip_neutral_temps" : false,
   "unsuspend_if_no_temp" : false,
   "unsuspend_if_no_temp" : false,

+ 1 - 0
Trio/Sources/Models/DecimalPickerSettings.swift

@@ -77,6 +77,7 @@ struct DecimalPickerSettings {
         type: PickerSetting.PickerSettingType.glucose
         type: PickerSetting.PickerSettingType.glucose
     )
     )
     var maxCOB = PickerSetting(value: 120, step: 5, min: 0, max: 300, type: PickerSetting.PickerSettingType.gram)
     var maxCOB = PickerSetting(value: 120, step: 5, min: 0, max: 300, type: PickerSetting.PickerSettingType.gram)
+    var maxMealAbsorptionTime = PickerSetting(value: 6, step: 1, min: 4, max: 10, type: PickerSetting.PickerSettingType.hour)
     var min5mCarbimpact = PickerSetting(value: 8, step: 1, min: 1, max: 20, type: PickerSetting.PickerSettingType.glucose)
     var min5mCarbimpact = PickerSetting(value: 8, step: 1, min: 1, max: 20, type: PickerSetting.PickerSettingType.glucose)
     var remainingCarbsFraction = PickerSetting(
     var remainingCarbsFraction = PickerSetting(
         value: 1.0,
         value: 1.0,

+ 6 - 0
Trio/Sources/Models/Preferences.swift

@@ -16,6 +16,7 @@ struct Preferences: JSON, Equatable {
     var exerciseMode: Bool = false
     var exerciseMode: Bool = false
     var halfBasalExerciseTarget: Decimal = 160
     var halfBasalExerciseTarget: Decimal = 160
     var maxCOB: Decimal = 120
     var maxCOB: Decimal = 120
+    var maxMealAbsorptionTime: Decimal = 6
     var wideBGTargetRange: Bool = false
     var wideBGTargetRange: Bool = false
     var skipNeutralTemps: Bool = false
     var skipNeutralTemps: Bool = false
     var unsuspendIfNoTemp: Bool = false
     var unsuspendIfNoTemp: Bool = false
@@ -72,6 +73,7 @@ extension Preferences {
         case exerciseMode = "exercise_mode"
         case exerciseMode = "exercise_mode"
         case halfBasalExerciseTarget = "half_basal_exercise_target"
         case halfBasalExerciseTarget = "half_basal_exercise_target"
         case maxCOB
         case maxCOB
+        case maxMealAbsorptionTime
         case wideBGTargetRange = "wide_bg_target_range"
         case wideBGTargetRange = "wide_bg_target_range"
         case skipNeutralTemps = "skip_neutral_temps"
         case skipNeutralTemps = "skip_neutral_temps"
         case unsuspendIfNoTemp = "unsuspend_if_no_temp"
         case unsuspendIfNoTemp = "unsuspend_if_no_temp"
@@ -184,6 +186,10 @@ extension Preferences: Decodable {
             preferences.maxCOB = maxCOB
             preferences.maxCOB = maxCOB
         }
         }
 
 
+        if let maxMealAbsorptionTime = try? container.decode(Decimal.self, forKey: .maxMealAbsorptionTime) {
+            preferences.maxMealAbsorptionTime = maxMealAbsorptionTime
+        }
+
         if let wideBGTargetRange = try? container.decode(Bool.self, forKey: .wideBGTargetRange) {
         if let wideBGTargetRange = try? container.decode(Bool.self, forKey: .wideBGTargetRange) {
             preferences.wideBGTargetRange = wideBGTargetRange
             preferences.wideBGTargetRange = wideBGTargetRange
         }
         }

+ 4 - 0
Trio/Sources/Modules/MealSettings/MealSettingsStateModel.swift

@@ -11,6 +11,7 @@ extension MealSettings {
         @Published var timeCap: Decimal = 8
         @Published var timeCap: Decimal = 8
         @Published var minuteInterval: Decimal = 30
         @Published var minuteInterval: Decimal = 30
         @Published var delay: Decimal = 60
         @Published var delay: Decimal = 60
+        @Published var maxMealAbsorptionTime: Decimal = 6
 
 
         override func subscribe() {
         override func subscribe() {
             units = settingsManager.settings.units
             units = settingsManager.settings.units
@@ -19,12 +20,15 @@ extension MealSettings {
             subscribeSetting(\.maxCarbs, on: $maxCarbs) { maxCarbs = $0 }
             subscribeSetting(\.maxCarbs, on: $maxCarbs) { maxCarbs = $0 }
             subscribeSetting(\.maxFat, on: $maxFat) { maxFat = $0 }
             subscribeSetting(\.maxFat, on: $maxFat) { maxFat = $0 }
             subscribeSetting(\.maxProtein, on: $maxProtein) { maxProtein = $0 }
             subscribeSetting(\.maxProtein, on: $maxProtein) { maxProtein = $0 }
+
             subscribeSetting(\.timeCap, on: $timeCap.map(Int.init), initial: {
             subscribeSetting(\.timeCap, on: $timeCap.map(Int.init), initial: {
                 timeCap = Decimal($0)
                 timeCap = Decimal($0)
             }, map: {
             }, map: {
                 $0
                 $0
             })
             })
 
 
+            subscribePreferencesSetting(\.maxMealAbsorptionTime, on: $maxMealAbsorptionTime) { maxMealAbsorptionTime = $0 }
+
             subscribeSetting(\.minuteInterval, on: $minuteInterval.map(Int.init), initial: {
             subscribeSetting(\.minuteInterval, on: $minuteInterval.map(Int.init), initial: {
                 minuteInterval = Decimal($0)
                 minuteInterval = Decimal($0)
             }, map: {
             }, map: {

+ 31 - 1
Trio/Sources/Modules/MealSettings/View/MealSettingsRootView.swift

@@ -162,7 +162,7 @@ extension MealSettings {
                                             AnyView(
                                             AnyView(
                                                 VStack(alignment: .leading, spacing: 5) {
                                                 VStack(alignment: .leading, spacing: 5) {
                                                     Text("Max Carbs:").bold()
                                                     Text("Max Carbs:").bold()
-                                                    Text("Enter the largest carbohydrate value allowed per meal entry")
+                                                    Text("Enter the largest carb value allowed per meal entry")
                                                     Text("Max Fat:").bold()
                                                     Text("Max Fat:").bold()
                                                     Text("Enter the largest fat value allowed per meal entry")
                                                     Text("Enter the largest fat value allowed per meal entry")
                                                     Text("Max Protein:").bold()
                                                     Text("Max Protein:").bold()
@@ -183,6 +183,36 @@ extension MealSettings {
                 ).listRowBackground(Color.chart)
                 ).listRowBackground(Color.chart)
 
 
                 SettingInputSection(
                 SettingInputSection(
+                    decimalValue: $state.maxMealAbsorptionTime,
+                    booleanValue: $booleanPlaceholder,
+                    shouldDisplayHint: $shouldDisplayHint,
+                    selectedVerboseHint: Binding(
+                        get: { selectedVerboseHint },
+                        set: {
+                            selectedVerboseHint = $0.map { AnyView($0) }
+                            hintLabel = "Maximum Meal Absorption Time"
+                        }
+                    ),
+                    units: state.units,
+                    type: .decimal("maxMealAbsorptionTime"),
+                    label: "Max Meal Absorption Time",
+                    miniHint: "The maximum duration for tracking carb entries in estimating Carbs on Board (COB)",
+                    verboseHint:
+                    VStack(alignment: .leading, spacing: 10) {
+                        Text("Default: 6 hours").bold()
+                        Text(
+                            "Carb entries will be fully decayed by the number of hours specified as Max Meal Absorption Time. Meals that are high in fat and/or protein can have long lasting effects on BG levels. To allow such late meal effects to be considered by the carb decay model, a longer Max Meal Absorption Time than the default 6 hours can be set."
+                        )
+                        Text(
+                            "If carb entries decay too slowly, it is possible to set a lower than default setting. But this should typically be adressed by tuning ISF and CR settings instead, which in combination determines the rate of carb decay."
+                        )
+                        Text(
+                            "Min 4 hours, max 10 hours."
+                        )
+                    }
+                )
+
+                SettingInputSection(
                     decimalValue: $decimalPlaceholder,
                     decimalValue: $decimalPlaceholder,
                     booleanValue: $state.useFPUconversion,
                     booleanValue: $state.useFPUconversion,
                     shouldDisplayHint: $shouldDisplayHint,
                     shouldDisplayHint: $shouldDisplayHint,

+ 1 - 0
Trio/Sources/Modules/Settings/SettingItems.swift

@@ -171,6 +171,7 @@ enum SettingItems {
             view: .mealSettings,
             view: .mealSettings,
             searchContents: [
             searchContents: [
                 "Max Carbs",
                 "Max Carbs",
+                "Max Meal Absorption Time",
                 "Max Fat",
                 "Max Fat",
                 "Max Protein",
                 "Max Protein",
                 "Display and Allow Fat and Protein Entries",
                 "Display and Allow Fat and Protein Entries",

+ 2 - 0
Trio/Sources/Views/SettingInputSection.swift

@@ -107,6 +107,8 @@ struct SettingInputSection<VerboseHint: View>: View {
             return pickerSettingsProvider.settings.hours
             return pickerSettingsProvider.settings.hours
         case "maxCarbs":
         case "maxCarbs":
             return pickerSettingsProvider.settings.maxCarbs
             return pickerSettingsProvider.settings.maxCarbs
+        case "maxMealAbsorptionTime":
+            return pickerSettingsProvider.settings.maxMealAbsorptionTime
         case "maxFat":
         case "maxFat":
             return pickerSettingsProvider.settings.maxFat
             return pickerSettingsProvider.settings.maxFat
         case "maxProtein":
         case "maxProtein":

+ 7 - 1
oref0_source_version.txt

@@ -1,6 +1,12 @@
-oref0 branch: tdd-pumpdataCheck - git version: 46deecb
+oref0 branch: maxAbsorptionTime - git version: a542ed3
 
 
 Last commits:
 Last commits:
+a542ed3 use guarded maxAbsorptionTime
+4c77757 Revert "reduce dynISF logging"
+1567c76 use variable name maxMealAbsorptionTime
+61b4f85 reduce dynISF logging
+0fe81c1 introduce maxAbsorptionTime to orefmaxAbsorptionTime
+ade267d Merge pull request #37 from mountrcg/tdd-pumpdataCheck
 46deecb remove dynisf check on pumpdata calc
 46deecb remove dynisf check on pumpdata calc
 2f258b2 Merge pull request #36 from mountrcg/fixTDDcheck
 2f258b2 Merge pull request #36 from mountrcg/fixTDDcheck
 4998a09 fix condition  to use weightedAverage as TDD
 4998a09 fix condition  to use weightedAverage as TDD

+ 3 - 2
trio-oref/lib/determine-basal/cob.js

@@ -12,7 +12,8 @@ function detectCarbAbsorption(inputs) {
     });
     });
     var iob_inputs = inputs.iob_inputs;
     var iob_inputs = inputs.iob_inputs;
     var basalprofile = inputs.basalprofile;
     var basalprofile = inputs.basalprofile;
-    /* TODO why does declaring profile break tests-command-behavior.tests.sh? */ profile = inputs.iob_inputs.profile;
+    /* TODO why does declaring profile break tests-command-behavior.tests.sh? */
+    profile = inputs.iob_inputs.profile;
     var mealTime = new Date(inputs.mealTime);
     var mealTime = new Date(inputs.mealTime);
     var ciTime = new Date(inputs.ciTime);
     var ciTime = new Date(inputs.ciTime);
 
 
@@ -50,7 +51,7 @@ function detectCarbAbsorption(inputs) {
         }
         }
         // only consider BGs for 6h after a meal for calculating COB
         // only consider BGs for 6h after a meal for calculating COB
         var hoursAfterMeal = (bgTime-mealTime)/(60*60*1000);
         var hoursAfterMeal = (bgTime-mealTime)/(60*60*1000);
-        if (hoursAfterMeal > 6 || foundPreMealBG) {
+        if (hoursAfterMeal > profile.maxMealAbsorptionTime || foundPreMealBG) {
             continue;
             continue;
         } else if (hoursAfterMeal < 0) {
         } else if (hoursAfterMeal < 0) {
 //console.error("Found pre-meal BG:",glucose_data[i].glucose, bgTime, Math.round(hoursAfterMeal*100)/100);
 //console.error("Found pre-meal BG:",glucose_data[i].glucose, bgTime, Math.round(hoursAfterMeal*100)/100);

+ 21 - 6
trio-oref/lib/meal/total.js

@@ -1,6 +1,12 @@
 var tz = require('moment-timezone');
 var tz = require('moment-timezone');
 var calcMealCOB = require('../determine-basal/cob');
 var calcMealCOB = require('../determine-basal/cob');
 
 
+function round(value, digits) {
+    if (! digits) { digits = 0; }
+    var scale = Math.pow(10, digits);
+    return Math.round(value * scale) / scale;
+}
+
 function recentCarbs(opts, time) {
 function recentCarbs(opts, time) {
     var treatments = opts.treatments;
     var treatments = opts.treatments;
     var profile_data = opts.profile;
     var profile_data = opts.profile;
@@ -41,10 +47,18 @@ function recentCarbs(opts, time) {
     var nsCarbsToRemove = 0;
     var nsCarbsToRemove = 0;
     var bwCarbsToRemove = 0;
     var bwCarbsToRemove = 0;
     var journalCarbsToRemove = 0;
     var journalCarbsToRemove = 0;
+    var maxMealAbsorptionTime = 6;
+
+    if (typeof(profile_data.maxMealAbsorptionTime) === 'number' && ! isNaN(profile_data.maxMealAbsorptionTime)) {
+        maxMealAbsorptionTime = profile_data.maxMealAbsorptionTime;
+    } else {
+        console.error("Bad profile.maxMealAbsorptionTime:",profile_data.maxMealAbsorptionTime);
+    }
+
     treatments.forEach(function(treatment) {
     treatments.forEach(function(treatment) {
         var now = time.getTime();
         var now = time.getTime();
-        // consider carbs from up to 6 hours ago in calculating COB
-        var carbWindow = now - 6 * 60*60*1000;
+        // consider carbs from up to the meal preference maxMealAbsorptionTime hours ago in calculating COB
+        var carbWindow = now - maxMealAbsorptionTime * 60*60*1000;
         var treatmentDate = new Date(tz(treatment.timestamp));
         var treatmentDate = new Date(tz(treatment.timestamp));
         var treatmentTime = treatmentDate.getTime();
         var treatmentTime = treatmentDate.getTime();
         if (treatmentTime > carbWindow && treatmentTime <= now) {
         if (treatmentTime > carbWindow && treatmentTime <= now) {
@@ -98,15 +112,16 @@ function recentCarbs(opts, time) {
     // calculate the current deviation and steepest deviation downslope over the last hour
     // calculate the current deviation and steepest deviation downslope over the last hour
     COB_inputs.ciTime = time.getTime();
     COB_inputs.ciTime = time.getTime();
     // set mealTime to 6h ago for Deviation calculations
     // set mealTime to 6h ago for Deviation calculations
-    COB_inputs.mealTime = time.getTime() - 6 * 60 * 60 * 1000;
+    COB_inputs.mealTime = time.getTime() - maxMealAbsorptionTime * 60 * 60 * 1000;
     var c = calcMealCOB(COB_inputs);
     var c = calcMealCOB(COB_inputs);
     //console.error(c.currentDeviation, c.slopeFromMaxDeviation);
     //console.error(c.currentDeviation, c.slopeFromMaxDeviation);
 
 
     // set a hard upper limit on COB to mitigate impact of erroneous or malicious carb entry
     // set a hard upper limit on COB to mitigate impact of erroneous or malicious carb entry
-    if (typeof(profile.maxCOB) === 'number' && ! isNaN(profile.maxCOB)) {
-        mealCOB = Math.min( profile.maxCOB, mealCOB );
+    if (typeof(profile_data.maxCOB) === 'number' && ! isNaN(profile_data.maxCOB)) {
+        mealCOB = Math.min( profile_data.maxCOB, mealCOB );
+        console.error("mealCOB: " + round(mealCOB,1) + " with maxCOB " + profile_data.maxCOB + "g and maxMealAbsorptionTime " + maxMealAbsorptionTime + "hrs.");
     } else {
     } else {
-        console.error("Bad profile.maxCOB:",profile.maxCOB);
+        console.error("Bad profile.maxCOB:",profile_data.maxCOB);
     }
     }
 
 
     // if currentDeviation is null or maxDeviation is 0, set mealCOB to 0 for zombie-carb safety
     // if currentDeviation is null or maxDeviation is 0, set mealCOB to 0 for zombie-carb safety

+ 4 - 1
trio-oref/lib/profile/index.js

@@ -25,6 +25,7 @@ function defaults ( ) {
     // (If someone enters more carbs or stacks more; OpenAPS will just truncate dosing based on 120.
     // (If someone enters more carbs or stacks more; OpenAPS will just truncate dosing based on 120.
     // Essentially, this just limits AMA/SMB as a safety cap against excessive COB entry)
     // Essentially, this just limits AMA/SMB as a safety cap against excessive COB entry)
     , maxCOB: 120
     , maxCOB: 120
+    , maxMealAbsorptionTime: 6 // Handling of long lasting effects of "heavy meals" containing large cqantities of fat and protein might be improved by letting the system consider meal effects for longer than the default six hours.
     , skip_neutral_temps: false // if true, don't set neutral temps
     , skip_neutral_temps: false // if true, don't set neutral temps
     , unsuspend_if_no_temp: false // if true, pump will un-suspend after a zero temp finishes
     , unsuspend_if_no_temp: false // if true, pump will un-suspend after a zero temp finishes
     , min_5m_carbimpact: 8 // mg/dL per 5m (8 mg/dL/5m corresponds to 24g/hr at a CSF of 4 mg/dL/g (x/5*60/4))
     , min_5m_carbimpact: 8 // mg/dL per 5m (8 mg/dL/5m corresponds to 24g/hr at a CSF of 4 mg/dL/g (x/5*60/4))
@@ -74,7 +75,7 @@ function defaults ( ) {
     , useNewFormula: false
     , useNewFormula: false
     , enableDynamicCR: false
     , enableDynamicCR: false
     , sigmoid: false
     , sigmoid: false
-    , weightPercentage: 0.65 
+    , weightPercentage: 0.65
     , tddAdjBasal: false // Enable adjustment of basal based on the ratio of 24 h : 10 day average TDD
     , tddAdjBasal: false // Enable adjustment of basal based on the ratio of 24 h : 10 day average TDD
     , threshold_setting: 60 // Use a configurable threshold setting
     , threshold_setting: 60 // Use a configurable threshold setting
   }
   }
@@ -111,6 +112,8 @@ function displayedDefaults () {
     profile.threshold_setting = allDefaults.threshold_setting;
     profile.threshold_setting = allDefaults.threshold_setting;
     profile.enableSMB_high_bg = allDefaults.enableSMB_high_bg;
     profile.enableSMB_high_bg = allDefaults.enableSMB_high_bg;
     profile.enableSMB_high_bg_target = allDefaults.enableSMB_high_bg_target;
     profile.enableSMB_high_bg_target = allDefaults.enableSMB_high_bg_target;
+    profile.maxCOB = allDefaults.maxCOB;
+    profile.maxMealAbsorptionTime = allDefaults.maxMealAbsorptionTime;
 
 
     console.error(profile);
     console.error(profile);
     return profile
     return profile