diff --git a/app/src/main/assets/OpenAPSSMB/determine-basal.js b/app/src/main/assets/OpenAPSSMB/determine-basal.js index cb6dcd71fb..19ebbb4e70 100644 --- a/app/src/main/assets/OpenAPSSMB/determine-basal.js +++ b/app/src/main/assets/OpenAPSSMB/determine-basal.js @@ -26,12 +26,12 @@ function round(value, digits) // we expect BG to rise or fall at the rate of BGI, // adjusted by the rate at which BG would need to rise / -// fall to get eventualBG to target over DIA/2 hours -function calculate_expected_delta(dia, target_bg, eventual_bg, bgi) { - // (hours * mins_per_hour) / 5 = how many 5 minute periods in dia/2 - var dia_in_5min_blocks = (dia/2 * 60) / 5; +// fall to get eventualBG to target over 2 hours +function calculate_expected_delta(target_bg, eventual_bg, bgi) { + // (hours * mins_per_hour) / 5 = how many 5 minute periods in 2h = 24 + var five_min_blocks = (2 * 60) / 5; var target_delta = target_bg - eventual_bg; - var expectedDelta = round(bgi + (target_delta / dia_in_5min_blocks), 1); + var expectedDelta = round(bgi + (target_delta / five_min_blocks), 1); return expectedDelta; } @@ -59,26 +59,37 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_ } var profile_current_basal = round_basal(profile.current_basal, profile); var basal = profile_current_basal; - if (typeof autosens_data !== 'undefined' ) { - basal = profile.current_basal * autosens_data.ratio; - basal = round_basal(basal, profile); - if (basal != profile_current_basal) { - console.error("Autosens adjusting basal from "+profile_current_basal+" to "+basal+"; "); - } else { - console.error("Basal unchanged: "+basal+"; "); - } - } + + var systemTime = new Date(); + var bgTime = new Date(glucose_status.date); + var minAgo = round( (systemTime - bgTime) / 60 / 1000 ,1); var bg = glucose_status.glucose; if (bg < 39) { //Dexcom is in ??? mode or calibrating rT.reason = "CGM is calibrating or in ??? state"; - if (basal <= currenttemp.rate * 1.2) { // high temp is running - rT.reason += "; setting current basal of " + basal + " as temp. "; + } + 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 < 39 || minAgo > 12 || minAgo < -5) { + if (currenttemp.rate >= basal) { // high temp is running + rT.reason += ". Canceling high temp basal of "+currenttemp.rate; rT.deliverAt = deliverAt; rT.temp = 'absolute'; - return tempBasalFunctions.setTempBasal(basal, 30, profile, rT, currenttemp); + rT.duration = 0; + rT.rate = 0; + return rT; + //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; + //return tempBasalFunctions.setTempBasal(0, 30, profile, rT, currenttemp); } else { //do nothing. - rT.reason += ", temp " + currenttemp.rate + " <~ current basal " + basal + "U/hr. "; + rT.reason += ". Temp " + currenttemp.rate + " <= current basal " + basal + "U/hr; doing nothing. "; return rT; } } @@ -102,24 +113,58 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_ return rT; } - // adjust min, max, and target BG for sensitivity, such that 50% increase in ISF raises target from 100 to 120 - if (typeof autosens_data !== 'undefined' && profile.autosens_adjust_targets) { - if (profile.temptargetSet) { - console.error("Temp Target set, not adjusting with autosens; "); - } else { - // with a target of 100, default 0.7-1.2 autosens min/max range would allow a 93-117 target range - min_bg = round((min_bg - 60) / autosens_data.ratio) + 60; - max_bg = round((max_bg - 60) / autosens_data.ratio) + 60; - new_target_bg = round((target_bg - 60) / autosens_data.ratio) + 60; - // don't allow target_bg below 80 - new_target_bg = Math.max(80, new_target_bg); - if (target_bg == new_target_bg) { - console.error("target_bg unchanged: "+new_target_bg+"; "); + var sensitivityRatio; + var high_temptarget_raises_sensitivity = profile.exercise_mode || profile.high_temptarget_raises_sensitivity; + var normalTarget = 100; // evaluate high/low temptarget against 100, not scheduled basal (which might change) + if ( profile.half_basal_exercise_target ) { + var halfBasalTarget = profile.half_basal_exercise_target; + } else { + var halfBasalTarget = 160; // when temptarget is 160 mg/dL, run 50% basal (120 = 75%; 140 = 60%) + // 80 mg/dL with low_temptarget_lowers_sensitivity would give 1.5x basal, but is limited to autosens_max (1.2x by default) + } + if ( high_temptarget_raises_sensitivity && profile.temptargetSet && target_bg > normalTarget + 10 + || profile.low_temptarget_lowers_sensitivity && profile.temptargetSet && target_bg < normalTarget ) { + // w/ target 100, temp target 110 = .89, 120 = 0.8, 140 = 0.67, 160 = .57, and 200 = .44 + // e.g.: Sensitivity ratio set to 0.8 based on temp target of 120; Adjusting basal from 1.65 to 1.35; ISF from 58.9 to 73.6 + //sensitivityRatio = 2/(2+(target_bg-normalTarget)/40); + var c = halfBasalTarget - normalTarget; + sensitivityRatio = c/(c+target_bg-normalTarget); + // limit sensitivityRatio to profile.autosens_max (1.2x by default) + sensitivityRatio = Math.min(sensitivityRatio, profile.autosens_max); + sensitivityRatio = round(sensitivityRatio,2); + console.error("Sensitivity ratio set to "+sensitivityRatio+" based on temp target of "+target_bg+"; "); + } else if (typeof autosens_data !== 'undefined' ) { + sensitivityRatio = autosens_data.ratio; + console.error("Autosens ratio: "+sensitivityRatio+"; "); + } + if (sensitivityRatio) { + basal = profile.current_basal * sensitivityRatio; + basal = round_basal(basal, profile); + if (basal != profile_current_basal) { + console.error("Adjusting basal from "+profile_current_basal+" to "+basal+"; "); } else { - console.error("target_bg from "+target_bg+" to "+new_target_bg+"; "); + console.error("Basal unchanged: "+basal+"; "); + } + } + + // adjust min, max, and target BG for sensitivity, such that 50% increase in ISF raises target from 100 to 120 + if (profile.temptargetSet) { + //console.error("Temp Target set, not adjusting with autosens; "); + } else if (typeof autosens_data !== 'undefined' ) { + if ( profile.sensitivity_raises_target && autosens_data.ratio < 1 || profile.resistance_lowers_target && autosens_data.ratio > 1 ) { + // with a target of 100, default 0.7-1.2 autosens min/max range would allow a 93-117 target range + min_bg = round((min_bg - 60) / autosens_data.ratio) + 60; + max_bg = round((max_bg - 60) / autosens_data.ratio) + 60; + new_target_bg = round((target_bg - 60) / autosens_data.ratio) + 60; + // don't allow target_bg below 80 + new_target_bg = Math.max(80, new_target_bg); + if (target_bg == new_target_bg) { + console.error("target_bg unchanged: "+new_target_bg+"; "); + } else { + console.error("target_bg from "+target_bg+" to "+new_target_bg+"; "); + } + target_bg = new_target_bg; } - target_bg = new_target_bg; - } } if (typeof iob_data === 'undefined' ) { @@ -148,20 +193,56 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_ //var minDelta = Math.min(glucose_status.delta, glucose_status.short_avgdelta, glucose_status.long_avgdelta); var minDelta = Math.min(glucose_status.delta, glucose_status.short_avgdelta); var minAvgDelta = Math.min(glucose_status.short_avgdelta, glucose_status.long_avgdelta); + var maxDelta = Math.max(glucose_status.delta, glucose_status.short_avgdelta, glucose_status.long_avgdelta); var profile_sens = round(profile.sens,1) var sens = profile.sens; if (typeof autosens_data !== 'undefined' ) { - sens = profile.sens / autosens_data.ratio; + sens = profile.sens / sensitivityRatio; sens = round(sens, 1); if (sens != profile_sens) { - console.error("sens from "+profile_sens+" to "+sens); + console.error("ISF from "+profile_sens+" to "+sens); } else { - console.error("sens unchanged: "+sens); + console.error("ISF unchanged: "+sens); } - console.error(" (autosens ratio "+autosens_data.ratio+")"); + //console.error(" (autosens ratio "+sensitivityRatio+")"); + } + console.error("; CR:",profile.carb_ratio); + + // compare currenttemp to iob_data.lastTemp and cancel temp if they don't match + var lastTempAge; + if (typeof iob_data.lastTemp !== 'undefined' ) { + lastTempAge = round(( new Date().getTime() - iob_data.lastTemp.date ) / 60000); // in minutes + } + //console.error("currenttemp:",currenttemp,"lastTemp:",JSON.stringify(iob_data.lastTemp),"lastTempAge:",lastTempAge,"m"); + tempModulus = (lastTempAge + currenttemp.duration) % 30; + console.error("currenttemp:",currenttemp,"lastTempAge:",lastTempAge,"m","tempModulus:",tempModulus,"m"); + rT.temp = 'absolute'; + rT.deliverAt = deliverAt; + if ( microBolusAllowed && currenttemp && iob_data.lastTemp && currenttemp.rate != iob_data.lastTemp.rate ) { + rT.reason = "Warning: currenttemp rate "+currenttemp.rate+" != lastTemp rate "+iob_data.lastTemp.rate+" from pumphistory; setting neutral temp of "+basal+"."; + return tempBasalFunctions.setTempBasal(basal, 30, profile, rT, currenttemp); + } + if ( currenttemp && iob_data.lastTemp && currenttemp.duration > 0 ) { + // TODO: fix this (lastTemp.duration is how long it has run; currenttemp.duration is time left + //if ( currenttemp.duration < iob_data.lastTemp.duration - 2) { + //rT.reason = "Warning: currenttemp duration "+currenttemp.duration+" << lastTemp duration "+round(iob_data.lastTemp.duration,1)+" from pumphistory; setting neutral temp of "+basal+"."; + //return tempBasalFunctions.setTempBasal(basal, 30, profile, rT, currenttemp); + //} + //console.error(lastTempAge, round(iob_data.lastTemp.duration,1), round(lastTempAge - iob_data.lastTemp.duration,1)); + var lastTempEnded = lastTempAge - iob_data.lastTemp.duration + if ( lastTempEnded > 5 ) { + rT.reason = "Warning: currenttemp running but lastTemp from pumphistory ended "+lastTempEnded+"m ago; setting neutral temp of "+basal+"."; + //console.error(currenttemp, round(iob_data.lastTemp,1), round(lastTempAge,1)); + return tempBasalFunctions.setTempBasal(basal, 30, profile, rT, currenttemp); + } + // TODO: figure out a way to do this check that doesn't fail across basal schedule boundaries + //if ( tempModulus < 25 && tempModulus > 5 ) { + //rT.reason = "Warning: currenttemp duration "+currenttemp.duration+" + lastTempAge "+lastTempAge+" isn't a multiple of 30m; setting neutral temp of "+basal+"."; + //console.error(rT.reason); + //return tempBasalFunctions.setTempBasal(basal, 30, profile, rT, currenttemp); + //} } - console.error(""); //calculate BG impact: the amount BG "should" be rising or falling based on insulin activity alone var bgi = round(( -iob_data.activity * sens * 5 ), 2); @@ -170,6 +251,10 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_ // don't overreact to a big negative delta: use minAvgDelta if deviation is negative if (deviation < 0) { deviation = round( (30 / 5) * ( minAvgDelta - bgi ) ); + // and if deviation is still negative, use long_avgdelta + if (deviation < 0) { + deviation = round( (30 / 5) * ( glucose_status.long_avgdelta - bgi ) ); + } } // calculate the naive (bolus calculator math) eventual BG based on net IOB and sensitivity @@ -181,14 +266,14 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_ // and adjust it for the deviation above var eventualBG = naive_eventualBG + deviation; // calculate what portion of that is due to bolussnooze - var bolusContrib = iob_data.bolussnooze * sens; + //var bolusContrib = iob_data.bolussnooze * sens; // and add it back in to get snoozeBG, plus another 50% to avoid low-temping at mealtime - var naive_snoozeBG = round( naive_eventualBG + 1.5 * bolusContrib ); + //var naive_snoozeBG = round( naive_eventualBG + 1.5 * bolusContrib ); // adjust that for deviation like we did eventualBG - var snoozeBG = naive_snoozeBG + deviation; + //var snoozeBG = naive_snoozeBG + deviation; // adjust target BG range if needed to safely bring down high BG faster without causing lows - if ( bg > max_bg && profile.adv_target_adjustments ) { + if ( bg > max_bg && profile.adv_target_adjustments && ! profile.temptargetSet ) { // with target=100, as BG rises from 100 to 160, adjustedTarget drops from 100 to 80 var adjustedMinBG = round(Math.max(80, min_bg - (bg - min_bg)/3 ),0); var adjustedTargetBG =round( Math.max(80, target_bg - (bg - target_bg)/3 ),0); @@ -217,7 +302,7 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_ } } - var expectedDelta = calculate_expected_delta(profile.dia, target_bg, eventualBG, bgi); + var expectedDelta = calculate_expected_delta(target_bg, eventualBG, bgi); if (typeof eventualBG === 'undefined' || isNaN(eventualBG)) { rT.error ='Error: could not calculate eventualBG. '; return rT; @@ -227,58 +312,99 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_ var threshold = min_bg - 0.5*(min_bg-40); //console.error(reservoir_data); - var deliverAt = new Date(); rT = { 'temp': 'absolute' , 'bg': bg , 'tick': tick , 'eventualBG': eventualBG - , 'snoozeBG': snoozeBG + //, 'snoozeBG': snoozeBG , 'insulinReq': 0 - , 'reservoir' : reservoir_data // The expected reservoir volume at which to deliver the microbolus (the reservoir volume from immediately before the last pumphistory run) + , 'reservoir' : reservoir_data // The expected reservoir volume at which to deliver the microbolus (the reservoir volume from right before the last pumphistory run) , 'deliverAt' : deliverAt // The time at which the microbolus should be delivered - , 'minPredBG' : 999 + , 'sensitivityRatio' : sensitivityRatio // autosens ratio (fraction of normal basal) }; - var basaliob = iob_data.basaliob; - //if (iob_data.basaliob) { basaliob = iob_data.basaliob; } - //else { basaliob = iob_data.iob - iob_data.bolussnooze; } - var bolusiob = iob_data.iob - basaliob; - // generate predicted future BGs based on IOB, COB, and current absorption rate var COBpredBGs = []; var aCOBpredBGs = []; var IOBpredBGs = []; var UAMpredBGs = []; + var ZTpredBGs = []; COBpredBGs.push(bg); aCOBpredBGs.push(bg); IOBpredBGs.push(bg); + ZTpredBGs.push(bg); UAMpredBGs.push(bg); // enable SMB whenever we have COB or UAM is enabled - // SMB is diabled by default, unless explicitly enabled in preferences.json + // SMB is disabled by default, unless explicitly enabled in preferences.json var enableSMB=false; // disable SMB when a high temptarget is set - if (profile.temptargetSet && target_bg > 100) { + if (! microBolusAllowed) { + console.error("SMB disabled (!microBolusAllowed)") + } else if (! profile.allowSMB_with_high_temptarget && profile.temptargetSet && target_bg > 100) { + console.error("SMB disabled due to high temptarget of",target_bg); enableSMB=false; - // enable SMB/UAM (if enabled in preferences) for DIA hours after bolus - } else if (profile.enableSMB_with_bolus && bolusiob > 0.1) { - enableSMB=true; // enable SMB/UAM (if enabled in preferences) while we have COB - } else if (profile.enableSMB_with_COB && meal_data.mealCOB) { - enableSMB=true; - // enable SMB/UAM (if enabled in preferences) if a low temptarget is set - } else if (profile.enableSMB_with_temptarget && (profile.temptargetSet && target_bg < 100)) { - enableSMB=true; + } else if (profile.enableSMB_with_COB === true && meal_data.mealCOB) { + if (meal_data.bwCarbs) { + if (profile.A52_risk_enable) { + console.error("Warning: SMB enabled with Bolus Wizard carbs: be sure to easy bolus 30s before using Bolus Wizard") + enableSMB=true; + } else { + console.error("SMB not enabled for Bolus Wizard COB"); + } + } else { + console.error("SMB enabled for COB of",meal_data.mealCOB); + enableSMB=true; + } // enable SMB/UAM (if enabled in preferences) for a full 6 hours after any carb entry // (6 hours is defined in carbWindow in lib/meal/total.js) - } else if (profile.enableSMB_after_carbs && meal_data.carbs) { - enableSMB=true; + } else if (profile.enableSMB_after_carbs === true && meal_data.carbs ) { + if (meal_data.bwCarbs) { + if (profile.A52_risk_enable) { + console.error("Warning: SMB enabled with Bolus Wizard carbs: be sure to easy bolus 30s before using Bolus Wizard") + enableSMB=true; + } else { + console.error("SMB not enabled for Bolus Wizard carbs"); + } + } else { + console.error("SMB enabled for 6h after carb entry"); + enableSMB=true; + } + // enable SMB/UAM (if enabled in preferences) if a low temptarget is set + } else if (profile.enableSMB_with_temptarget === true && (profile.temptargetSet && target_bg < 100)) { + if (meal_data.bwFound) { + if (profile.A52_risk_enable) { + console.error("Warning: SMB enabled within 6h of using Bolus Wizard: be sure to easy bolus 30s before using Bolus Wizard") + enableSMB=true; + } else { + console.error("enableSMB_with_temptarget not supported within 6h of using Bolus Wizard"); + } + } else { + console.error("SMB enabled for temptarget of",convert_bg(target_bg, profile)); + enableSMB=true; + } + // enable SMB/UAM if always-on (unless previously disabled for high temptarget) + } else if (profile.enableSMB_always === true) { + if (meal_data.bwFound) { + if (profile.A52_risk_enable === true) { + console.error("Warning: SMB enabled within 6h of using Bolus Wizard: be sure to easy bolus 30s before using Bolus Wizard") + enableSMB=true; + } else { + console.error("enableSMB_always not supported within 6h of using Bolus Wizard"); + } + } else { + console.error("SMB enabled due to enableSMB_always"); + enableSMB=true; + } + } else { + console.error("SMB disabled (no enableSMB preferences active)"); } - // enable UAM (if enabled in preferences) for DIA hours after bolus, or if SMB is enabled - var enableUAM=(profile.enableUAM && (bolusiob > 0.1 || enableSMB)); + // enable UAM (if enabled in preferences) if SMB is enabled + var enableUAM=(profile.enableUAM && enableSMB); //console.error(meal_data); @@ -288,20 +414,54 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_ // calculate current carb absorption rate, and how long to absorb all carbs // CI = current carb impact on BG in mg/dL/5m ci = round((minDelta - bgi),1); - uci = round((minAvgDelta - bgi),1); + uci = round((minDelta - bgi),1); // ISF (mg/dL/U) / CR (g/U) = CSF (mg/dL/g) - var csf = sens / profile.carb_ratio + if (profile.temptargetSet) { + // if temptargetSet, use unadjusted profile.sens to allow activity mode sensitivityRatio to adjust CR + var csf = profile.sens / profile.carb_ratio; + } else { + // otherwise, use autosens-adjusted sens to counteract autosens meal insulin dosing adjustments + // so that autotuned CR is still in effect even when basals and ISF are being adjusted by autosens + var csf = sens / profile.carb_ratio; + } + var maxCarbAbsorptionRate = 30; // g/h; maximum rate to assume carbs will absorb if no CI observed + // limit Carb Impact to maxCarbAbsorptionRate * csf in mg/dL per 5m + maxCI = round(maxCarbAbsorptionRate*csf*5/60,1) + if (ci > maxCI) { + console.error("Limiting carb impact from",ci,"to",maxCI,"mg/dL/5m (",maxCarbAbsorptionRate,"g/h )"); + ci = maxCI; + } // set meal_carbimpact high enough to absorb all meal carbs over 6 hours // total_impact (mg/dL) = CSF (mg/dL/g) * carbs (g) //console.error(csf * meal_data.carbs); // meal_carbimpact (mg/dL/5m) = CSF (mg/dL/g) * carbs (g) / 6 (h) * (1h/60m) * 5 (m/5m) * 2 (for linear decay) //var meal_carbimpact = round((csf * meal_data.carbs / 6 / 60 * 5 * 2),1) - // calculate the number of carbs absorbed over 4h at current CI + var remainingCATimeMin = 3; // h; before carb absorption starts + // adjust remainingCATime (instead of CR) for autosens + remainingCATimeMin = remainingCATimeMin / sensitivityRatio; + // 20 g/h means that anything <= 60g will get a remainingCATimeMin, 80g will get 4h, and 120g 6h + // when actual absorption ramps up it will take over from remainingCATime + var assumedCarbAbsorptionRate = 20; // g/h; maximum rate to assume carbs will absorb if no CI observed + var remainingCATime; + if (meal_data.carbs) { + // if carbs * assumedCarbAbsorptionRate > remainingCATimeMin, raise it + // so <= 90g is assumed to take 3h, and 120g=4h + remainingCATimeMin = Math.max(remainingCATimeMin, meal_data.mealCOB/assumedCarbAbsorptionRate); + var lastCarbAge = round(( new Date().getTime() - meal_data.lastCarbTime ) / 60000); + //console.error(meal_data.lastCarbTime, lastCarbAge); + + fractionCOBAbsorbed = ( meal_data.carbs - meal_data.mealCOB ) / meal_data.carbs; + remainingCATime = remainingCATimeMin + 1.5 * lastCarbAge/60; + remainingCATime = round(remainingCATime,1); + //console.error(fractionCOBAbsorbed, remainingCATimeAdjustment, remainingCATime) + console.error("Last carbs",lastCarbAge,"minutes ago; remainingCATime:",remainingCATime,"hours;",round(fractionCOBAbsorbed*100)+"% carbs absorbed"); + } + + // calculate the number of carbs absorbed over remainingCATime hours at current CI // CI (mg/dL/5m) * (5m)/5 (m) * 60 (min/hr) * 4 (h) / 2 (linear decay factor) = total carb impact (mg/dL) - var totalCI = Math.max(0, ci / 5 * 60 * 4 / 2); + var totalCI = Math.max(0, ci / 5 * 60 * remainingCATime / 2); // totalCI (mg/dL) / CSF (mg/dL/g) = total carbs absorbed (g) var totalCA = totalCI / csf; - // exclude the last 1/3 of carbs from remainingCarbs, and then cap it at 90 var remainingCarbsCap = 90; // default to 90 var remainingCarbsFraction = 1; if (profile.remainingCarbsCap) { remainingCarbsCap = Math.min(90,profile.remainingCarbsCap); } @@ -309,27 +469,44 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_ var remainingCarbsIgnore = 1 - remainingCarbsFraction; var remainingCarbs = Math.max(0, meal_data.mealCOB - totalCA - meal_data.carbs*remainingCarbsIgnore); remainingCarbs = Math.min(remainingCarbsCap,remainingCarbs); - // assume remainingCarbs will absorb over 4h - // remainingCI (mg/dL/5m) = remainingCarbs (g) * CSF (mg/dL/g) * 5 (m/5m) * 1h/60m / 4 (h) - var remainingCI = remainingCarbs * csf * 5 / 60 / 4; - //console.error(profile.min_5m_carbimpact,ci,totalCI,totalCA,remainingCarbs,remainingCI); + // assume remainingCarbs will absorb in a /\ shaped bilinear curve + // peaking at remainingCATime / 2 and ending at remainingCATime hours + // area of the /\ triangle is the same as a remainingCIpeak-height rectangle out to remainingCATime/2 + // remainingCIpeak (mg/dL/5m) = remainingCarbs (g) * CSF (mg/dL/g) * 5 (m/5m) * 1h/60m / (remainingCATime/2) (h) + var remainingCIpeak = remainingCarbs * csf * 5 / 60 / (remainingCATime/2); + //console.error(profile.min_5m_carbimpact,ci,totalCI,totalCA,remainingCarbs,remainingCI,remainingCATime); //if (meal_data.mealCOB * 3 > meal_data.carbs) { } // calculate peak deviation in last hour, and slope from that to current deviation - var minDeviationSlope = round(meal_data.minDeviationSlope,2); - //console.error(minDeviationSlope); + var slopeFromMaxDeviation = round(meal_data.slopeFromMaxDeviation,2); + // calculate lowest deviation in last hour, and slope from that to current deviation + var slopeFromMinDeviation = round(meal_data.slopeFromMinDeviation,2); + // assume deviations will drop back down at least at 1/3 the rate they ramped up + var slopeFromDeviations = Math.min(slopeFromMaxDeviation,-slopeFromMinDeviation/3); + //console.error(slopeFromMaxDeviation); aci = 10; //5m data points = g * (1U/10g) * (40mg/dL/1U) / (mg/dL/5m) // duration (in 5m data points) = COB (g) * CSF (mg/dL/g) / ci (mg/dL/5m) - cid = Math.max(0, meal_data.mealCOB * csf / ci ); + // limit cid to remainingCATime hours: the reset goes to remainingCI + if (ci == 0) { + // avoid divide by zero + cid = 0; + } else { + cid = Math.min(remainingCATime*60/5/2,Math.max(0, meal_data.mealCOB * csf / ci )); + } acid = Math.max(0, meal_data.mealCOB * csf / aci ); // duration (hours) = duration (5m) * 5 / 60 * 2 (to account for linear decay) - console.error("Carb Impact:",ci,"mg/dL per 5m; CI Duration:",round(cid*5/60*2,1),"hours; remaining 4h+ CI:",round(remainingCI,1),"mg/dL per 5m"); - console.error("Accel. Carb Impact:",aci,"mg/dL per 5m; ACI Duration:",round(acid*5/60*2,1),"hours"); + console.error("Carb Impact:",ci,"mg/dL per 5m; CI Duration:",round(cid*5/60*2,1),"hours; remaining CI (~2h peak):",round(remainingCIpeak,1),"mg/dL per 5m"); + //console.error("Accel. Carb Impact:",aci,"mg/dL per 5m; ACI Duration:",round(acid*5/60*2,1),"hours"); var minIOBPredBG = 999; var minCOBPredBG = 999; var minUAMPredBG = 999; + var minGuardBG = bg; + var minCOBGuardBG = 999; + var minUAMGuardBG = 999; + var minIOBGuardBG = 999; + var minZTGuardBG = 999; var minPredBG; var avgPredBG; var IOBpredBG = eventualBG; @@ -341,51 +518,78 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_ var lastIOBpredBG; var lastCOBpredBG; var lastUAMpredBG; + var lastZTpredBG; var UAMduration = 0; + var remainingCItotal = 0; + var remainingCIs = []; + var predCIs = []; try { iobArray.forEach(function(iobTick) { //console.error(iobTick); predBGI = round(( -iobTick.activity * sens * 5 ), 2); + predZTBGI = round(( -iobTick.iobWithZeroTemp.activity * sens * 5 ), 2); // for IOBpredBGs, predicted deviation impact drops linearly from current deviation down to zero // over 60 minutes (data points every 5m) predDev = ci * ( 1 - Math.min(1,IOBpredBGs.length/(60/5)) ); IOBpredBG = IOBpredBGs[IOBpredBGs.length-1] + predBGI + predDev; - //IOBpredBG = IOBpredBGs[IOBpredBGs.length-1] + predBGI; + // calculate predBGs with long zero temp without deviations + ZTpredBG = ZTpredBGs[ZTpredBGs.length-1] + predZTBGI; // for COBpredBGs, predicted carb impact drops linearly from current carb impact down to zero // eventually accounting for all carbs (if they can be absorbed over DIA) predCI = Math.max(0, Math.max(0,ci) * ( 1 - COBpredBGs.length/Math.max(cid*2,1) ) ); predACI = Math.max(0, Math.max(0,aci) * ( 1 - COBpredBGs.length/Math.max(acid*2,1) ) ); - // if any carbs aren't absorbed after 4 hours, assume they'll absorb at a constant rate for next 4h + // if any carbs aren't absorbed after remainingCATime hours, assume they'll absorb in a /\ shaped + // bilinear curve peaking at remainingCIpeak at remainingCATime/2 hours (remainingCATime/2*12 * 5m) + // and ending at remainingCATime h (remainingCATime*12 * 5m intervals) + var intervals = Math.min( COBpredBGs.length, (remainingCATime*12)-COBpredBGs.length ); + var remainingCI = Math.max(0, intervals / (remainingCATime/2*12) * remainingCIpeak ); + remainingCItotal += predCI+remainingCI; + remainingCIs.push(round(remainingCI,0)); + predCIs.push(round(predCI,0)); + //console.error(round(predCI,1)+"+"+round(remainingCI,1)+" "); COBpredBG = COBpredBGs[COBpredBGs.length-1] + predBGI + Math.min(0,predDev) + predCI + remainingCI; - // stop adding remainingCI after 4h - if (COBpredBGs.length > 4 * 60 / 5) { remainingCI = 0; } aCOBpredBG = aCOBpredBGs[aCOBpredBGs.length-1] + predBGI + Math.min(0,predDev) + predACI; - // for UAMpredBGs, predicted carb impact drops at minDeviationSlope - // calculate predicted CI from UAM based on minDeviationSlope - predUCIslope = Math.max(0, uci + ( UAMpredBGs.length*minDeviationSlope ) ); - // if minDeviationSlope is too flat, predicted deviation impact drops linearly from - // current deviation down to zero over DIA (data points every 5m) - predUCIdia = Math.max(0, uci * ( 1 - UAMpredBGs.length/Math.max(profile.dia*60/5,1) ) ); - //console.error(predUCIslope, predUCIdia); + // for UAMpredBGs, predicted carb impact drops at slopeFromDeviations + // calculate predicted CI from UAM based on slopeFromDeviations + predUCIslope = Math.max(0, uci + ( UAMpredBGs.length*slopeFromDeviations ) ); + // if slopeFromDeviations is too flat, predicted deviation impact drops linearly from + // current deviation down to zero over 3h (data points every 5m) + predUCImax = Math.max(0, uci * ( 1 - UAMpredBGs.length/Math.max(3*60/5,1) ) ); + //console.error(predUCIslope, predUCImax); // predicted CI from UAM is the lesser of CI based on deviationSlope or DIA - predUCI = Math.min(predUCIslope, predUCIdia); + predUCI = Math.min(predUCIslope, predUCImax); if(predUCI>0) { - //console.error(UAMpredBGs.length,minDeviationSlope, predUCI); + //console.error(UAMpredBGs.length,slopeFromDeviations, predUCI); UAMduration=round((UAMpredBGs.length+1)*5/60,1); } UAMpredBG = UAMpredBGs[UAMpredBGs.length-1] + predBGI + Math.min(0, predDev) + predUCI; //console.error(predBGI, predCI, predUCI); - // truncate all BG predictions at 3.5 hours - if ( IOBpredBGs.length < 42) { IOBpredBGs.push(IOBpredBG); } - if ( COBpredBGs.length < 42) { COBpredBGs.push(COBpredBG); } - if ( aCOBpredBGs.length < 42) { aCOBpredBGs.push(aCOBpredBG); } - if ( UAMpredBGs.length < 42) { UAMpredBGs.push(UAMpredBG); } + // truncate all BG predictions at 4 hours + if ( IOBpredBGs.length < 48) { IOBpredBGs.push(IOBpredBG); } + if ( COBpredBGs.length < 48) { COBpredBGs.push(COBpredBG); } + if ( aCOBpredBGs.length < 48) { aCOBpredBGs.push(aCOBpredBG); } + if ( UAMpredBGs.length < 48) { UAMpredBGs.push(UAMpredBG); } + if ( ZTpredBGs.length < 48) { ZTpredBGs.push(ZTpredBG); } + // calculate minGuardBGs without a wait from COB, UAM, IOB predBGs + if ( COBpredBG < minCOBGuardBG ) { minCOBGuardBG = round(COBpredBG); } + if ( UAMpredBG < minUAMGuardBG ) { minUAMGuardBG = round(UAMpredBG); } + if ( IOBpredBG < minIOBGuardBG ) { minIOBGuardBG = round(IOBpredBG); } + if ( ZTpredBG < minZTGuardBG ) { minZTGuardBG = round(ZTpredBG); } + + // set minPredBGs starting when currently-dosed insulin activity will peak + // look ahead 60m (regardless of insulin type) so as to be less aggressive on slower insulins + var insulinPeakTime = 60; + // add 30m to allow for insluin delivery (SMBs or temps) + insulinPeakTime = 90; + var insulinPeak5m = (insulinPeakTime/60)*12; + //console.error(insulinPeakTime, insulinPeak5m, profile.insulinPeakTime, profile.curve); + // wait 90m before setting minIOBPredBG - if ( IOBpredBGs.length > 18 && (IOBpredBG < minIOBPredBG) ) { minIOBPredBG = round(IOBpredBG); } + if ( IOBpredBGs.length > insulinPeak5m && (IOBpredBG < minIOBPredBG) ) { minIOBPredBG = round(IOBpredBG); } if ( IOBpredBG > maxIOBPredBG ) { maxIOBPredBG = IOBpredBG; } - // wait 60m before setting COB and UAM minPredBGs - if ( (cid || remainingCI > 0) && COBpredBGs.length > 12 && (COBpredBG < minCOBPredBG) ) { minCOBPredBG = round(COBpredBG); } - if ( (cid || remainingCI > 0) && COBpredBG > maxIOBPredBG ) { maxCOBPredBG = COBpredBG; } + // wait 85-105m before setting COB and 60m for UAM minPredBGs + if ( (cid || remainingCIpeak > 0) && COBpredBGs.length > insulinPeak5m && (COBpredBG < minCOBPredBG) ) { minCOBPredBG = round(COBpredBG); } + if ( (cid || remainingCIpeak > 0) && COBpredBG > maxIOBPredBG ) { maxCOBPredBG = COBpredBG; } if ( enableUAM && UAMpredBGs.length > 12 && (UAMpredBG < minUAMPredBG) ) { minUAMPredBG = round(UAMpredBG); } if ( enableUAM && UAMpredBG > maxIOBPredBG ) { maxUAMPredBG = UAMpredBG; } }); @@ -394,6 +598,11 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_ } catch (e) { console.error("Problem with iobArray. Optional feature Advanced Meal Assist disabled:",e); } + if (meal_data.mealCOB) { + console.error("predCIs (mg/dL/5m):",predCIs.join(" ")); + console.error("remainingCIs: ",remainingCIs.join(" ")); + } + //,"totalCA:",round(totalCA,2),"remainingCItotal/csf+totalCA:",round(remainingCItotal/csf+totalCA,2)); rT.predBGs = {}; IOBpredBGs.forEach(function(p, i, theArray) { theArray[i] = round(Math.min(401,Math.max(39,p))); @@ -404,6 +613,17 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_ } rT.predBGs.IOB = IOBpredBGs; lastIOBpredBG=round(IOBpredBGs[IOBpredBGs.length-1]); + ZTpredBGs.forEach(function(p, i, theArray) { + theArray[i] = round(Math.min(401,Math.max(39,p))); + }); + for (var i=ZTpredBGs.length-1; i > 6; i--) { + //if (ZTpredBGs[i-1] != ZTpredBGs[i]) { break; } + // stop displaying ZTpredBGs once they're rising and above target + if (ZTpredBGs[i-1] >= ZTpredBGs[i] || ZTpredBGs[i] < target_bg) { break; } + else { ZTpredBGs.pop(); } + } + rT.predBGs.ZT = ZTpredBGs; + lastZTpredBG=round(ZTpredBGs[ZTpredBGs.length-1]); if (meal_data.mealCOB > 0) { aCOBpredBGs.forEach(function(p, i, theArray) { theArray[i] = round(Math.min(401,Math.max(39,p))); @@ -412,9 +632,10 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_ if (aCOBpredBGs[i-1] != aCOBpredBGs[i]) { break; } else { aCOBpredBGs.pop(); } } - rT.predBGs.aCOB = aCOBpredBGs; + // disable for now. may want to add a preference to re-enable + //rT.predBGs.aCOB = aCOBpredBGs; } - if (meal_data.mealCOB > 0 && ( ci > 0 || remainingCI > 0 )) { + if (meal_data.mealCOB > 0 && ( ci > 0 || remainingCIpeak > 0 )) { COBpredBGs.forEach(function(p, i, theArray) { theArray[i] = round(Math.min(401,Math.max(39,p))); }); @@ -426,7 +647,7 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_ lastCOBpredBG=round(COBpredBGs[COBpredBGs.length-1]); eventualBG = Math.max(eventualBG, round(COBpredBGs[COBpredBGs.length-1]) ); } - if (ci > 0 || remainingCI > 0) { + if (ci > 0 || remainingCIpeak > 0) { if (enableUAM) { UAMpredBGs.forEach(function(p, i, theArray) { theArray[i] = round(Math.min(401,Math.max(39,p))); @@ -437,7 +658,9 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_ } rT.predBGs.UAM = UAMpredBGs; lastUAMpredBG=round(UAMpredBGs[UAMpredBGs.length-1]); - eventualBG = Math.max(eventualBG, round(UAMpredBGs[UAMpredBGs.length-1]) ); + if (UAMpredBGs[UAMpredBGs.length-1]) { + eventualBG = Math.max(eventualBG, round(UAMpredBGs[UAMpredBGs.length-1]) ); + } } // set eventualBG and snoozeBG based on COB or UAM predBGs @@ -446,81 +669,117 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_ console.error("UAM Impact:",uci,"mg/dL per 5m; UAM Duration:",UAMduration,"hours"); + minIOBPredBG = Math.max(39,minIOBPredBG); minCOBPredBG = Math.max(39,minCOBPredBG); minUAMPredBG = Math.max(39,minUAMPredBG); minPredBG = round(minIOBPredBG); - // if we have COB and UAM is enabled, average all three - if ( minUAMPredBG < 400 && minCOBPredBG < 400 ) { - avgPredBG = round( (IOBpredBG + UAMpredBG + COBpredBG)/3 ); + var fractionCarbsLeft = meal_data.mealCOB/meal_data.carbs; + // if we have COB and UAM is enabled, average both + if ( minUAMPredBG < 999 && minCOBPredBG < 999 ) { + // weight COBpredBG vs. UAMpredBG based on how many carbs remain as COB + avgPredBG = round( (1-fractionCarbsLeft)*UAMpredBG + fractionCarbsLeft*COBpredBG ); // if UAM is disabled, average IOB and COB - } else if ( minCOBPredBG < 400 ) { + } else if ( minCOBPredBG < 999 ) { avgPredBG = round( (IOBpredBG + COBpredBG)/2 ); - // if carbs are expired, use IOB instead of COB - } else if ( meal_data.carbs && minUAMPredBG < 400 ) { - avgPredBG = round( (2*IOBpredBG + UAMpredBG)/3 ); - // in pure UAM mode, just average IOB and UAM - } else if ( minUAMPredBG < 400 ) { + // if we have UAM but no COB, average IOB and UAM + } else if ( minUAMPredBG < 999 ) { avgPredBG = round( (IOBpredBG + UAMpredBG)/2 ); } else { avgPredBG = round( IOBpredBG ); } + // if avgPredBG is below minZTGuardBG, bring it up to that level + if ( minZTGuardBG > avgPredBG ) { + avgPredBG = minZTGuardBG; + } + // if we have both minCOBGuardBG and minUAMGuardBG, blend according to fractionCarbsLeft + if ( (cid || remainingCIpeak > 0) ) { + if ( enableUAM ) { + minGuardBG = fractionCarbsLeft*minCOBGuardBG + (1-fractionCarbsLeft)*minUAMGuardBG; + } else { + minGuardBG = minCOBGuardBG; + } + } else if ( enableUAM ) { + minGuardBG = minUAMGuardBG; + } else { + minGuardBG = minIOBGuardBG; + } + minGuardBG = round(minGuardBG); + //console.error(minCOBGuardBG, minUAMGuardBG, minIOBGuardBG, minGuardBG); + + var minZTUAMPredBG = minUAMPredBG; + // if minZTGuardBG is below threshold, bring down any super-high minUAMPredBG by averaging + // this helps prevent UAM from giving too much insulin in case absorption falls off suddenly + if ( minZTGuardBG < threshold ) { + minZTUAMPredBG = (minUAMPredBG + minZTGuardBG) / 2; + // if minZTGuardBG is between threshold and target, blend in the averaging + } else if ( minZTGuardBG < target_bg ) { + // target 100, threshold 70, minZTGuardBG 85 gives 50%: (85-70) / (100-70) + var blendPct = (minZTGuardBG-threshold) / (target_bg-threshold); + var blendedMinZTGuardBG = minUAMPredBG*blendPct + minZTGuardBG*(1-blendPct); + minZTUAMPredBG = (minUAMPredBG + blendedMinZTGuardBG) / 2; + //minZTUAMPredBG = minUAMPredBG - target_bg + minZTGuardBG; + // if minUAMPredBG is below minZTGuardBG, bring minUAMPredBG up by averaging + // this allows more insulin if lastUAMPredBG is below target, but minZTGuardBG is still high + } else if ( minZTGuardBG > minUAMPredBG ) { + minZTUAMPredBG = (minUAMPredBG + minZTGuardBG) / 2; + } + minZTUAMPredBG = round(minZTUAMPredBG); + //console.error("minUAMPredBG:",minUAMPredBG,"minZTGuardBG:",minZTGuardBG,"minZTUAMPredBG:",minZTUAMPredBG); // if any carbs have been entered recently if (meal_data.carbs) { // average the minIOBPredBG and minUAMPredBG if available - if ( minUAMPredBG < 400 ) { + /* + if ( minUAMPredBG < 999 ) { avgMinPredBG = round( (minIOBPredBG+minUAMPredBG)/2 ); } else { avgMinPredBG = minIOBPredBG; } + */ // if UAM is disabled, use max of minIOBPredBG, minCOBPredBG - if ( ! enableUAM && minCOBPredBG < 400 ) { + if ( ! enableUAM && minCOBPredBG < 999 ) { minPredBG = round(Math.max(minIOBPredBG, minCOBPredBG)); // if we have COB, use minCOBPredBG, or blendedMinPredBG if it's higher - } else if ( minCOBPredBG < 400 ) { + } else if ( minCOBPredBG < 999 ) { // calculate blendedMinPredBG based on how many carbs remain as COB - fractionCarbsLeft = meal_data.mealCOB/meal_data.carbs; - blendedMinPredBG = fractionCarbsLeft*minCOBPredBG + (1-fractionCarbsLeft)*avgMinPredBG; + //blendedMinPredBG = fractionCarbsLeft*minCOBPredBG + (1-fractionCarbsLeft)*minUAMPredBG; + blendedMinPredBG = fractionCarbsLeft*minCOBPredBG + (1-fractionCarbsLeft)*minZTUAMPredBG; // if blendedMinPredBG > minCOBPredBG, use that instead minPredBG = round(Math.max(minIOBPredBG, minCOBPredBG, blendedMinPredBG)); - // if carbs have been entered, but have expired, use avg of minIOBPredBG and minUAMPredBG + // if carbs have been entered, but have expired, use minUAMPredBG } else { - minPredBG = avgMinPredBG; + //minPredBG = minUAMPredBG; + minPredBG = minZTUAMPredBG; } // in pure UAM mode, use the higher of minIOBPredBG,minUAMPredBG } else if ( enableUAM ) { - minPredBG = round(Math.max(minIOBPredBG,minUAMPredBG)); + //minPredBG = round(Math.max(minIOBPredBG,minUAMPredBG)); + minPredBG = round(Math.max(minIOBPredBG,minZTUAMPredBG)); } // make sure minPredBG isn't higher than avgPredBG minPredBG = Math.min( minPredBG, avgPredBG ); - console.error("minPredBG: "+minPredBG+" minIOBPredBG: "+minIOBPredBG); - if (minCOBPredBG < 400) { + console.error("minPredBG: "+minPredBG+" minIOBPredBG: "+minIOBPredBG+" minZTGuardBG: "+minZTGuardBG); + if (minCOBPredBG < 999) { console.error(" minCOBPredBG: "+minCOBPredBG); } - if (minUAMPredBG < 400) { + if (minUAMPredBG < 999) { console.error(" minUAMPredBG: "+minUAMPredBG); } - console.error(" avgPredBG:",avgPredBG,"COB:",meal_data.mealCOB,"carbs:",meal_data.carbs); + console.error(" avgPredBG:",avgPredBG,"COB:",meal_data.mealCOB,"/",meal_data.carbs); // But if the COB line falls off a cliff, don't trust UAM too much: // use maxCOBPredBG if it's been set and lower than minPredBG if ( maxCOBPredBG > bg ) { minPredBG = Math.min(minPredBG, maxCOBPredBG); } - // set snoozeBG to minPredBG if it's higher - if (minPredBG < 400) { - snoozeBG = round(Math.max(snoozeBG,minPredBG)); - } - rT.snoozeBG = snoozeBG; - //console.error(minPredBG, minIOBPredBG, minUAMPredBG, minCOBPredBG, maxCOBPredBG, snoozeBG); rT.COB=meal_data.mealCOB; rT.IOB=iob_data.iob; - rT.reason="COB: " + meal_data.mealCOB + ", Dev: " + deviation + ", BGI: " + bgi + ", ISF: " + convert_bg(sens, profile) + ", Target: " + convert_bg(target_bg, profile) + ", minPredBG " + convert_bg(minPredBG, profile) + ", IOBpredBG " + convert_bg(lastIOBpredBG, profile); + rT.reason="COB: " + meal_data.mealCOB + ", Dev: " + convert_bg(deviation, profile) + ", BGI: " + convert_bg(bgi, profile) + ", ISF: " + convert_bg(sens, profile) + ", CR: " + round(profile.carb_ratio, 2) + ", Target: " + convert_bg(target_bg, profile) + ", minPredBG " + convert_bg(minPredBG, profile) + ", minGuardBG " + convert_bg(minGuardBG, profile) + ", IOBpredBG " + convert_bg(lastIOBpredBG, profile); if (lastCOBpredBG > 0) { rT.reason += ", COBpredBG " + convert_bg(lastCOBpredBG, profile); } @@ -528,10 +787,18 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_ rT.reason += ", UAMpredBG " + convert_bg(lastUAMpredBG, profile) } rT.reason += "; "; - var bgUndershoot = target_bg - Math.max( naive_eventualBG, eventualBG, lastIOBpredBG ); + //var bgUndershoot = threshold - Math.min(minGuardBG, Math.max( naive_eventualBG, eventualBG )); + // use naive_eventualBG if above 40, but switch to minGuardBG if both eventualBGs hit floor of 39 + //var carbsReqBG = Math.max( naive_eventualBG, eventualBG ); + var carbsReqBG = naive_eventualBG; + if ( carbsReqBG < 40 ) { + carbsReqBG = Math.min( minGuardBG, carbsReqBG ); + } + var bgUndershoot = threshold - carbsReqBG; // calculate how long until COB (or IOB) predBGs drop below min_bg var minutesAboveMinBG = 240; - if (meal_data.mealCOB > 0 && ( ci > 0 || remainingCI > 0 )) { + var minutesAboveThreshold = 240; + if (meal_data.mealCOB > 0 && ( ci > 0 || remainingCIpeak > 0 )) { for (var i=0; i 0.20 * bg ) { + console.error("maxDelta",convert_bg(maxDelta, profile),"> 20% of BG",convert_bg(bg, profile),"- disabling SMB"); + rT.reason += "maxDelta "+convert_bg(maxDelta, profile)+" > 20% of BG "+convert_bg(bg, profile)+": SMB disabled; "; + enableSMB = false; + } + + console.error("BG projected to remain above",convert_bg(min_bg, profile),"for",minutesAboveMinBG,"minutes"); + if ( minutesAboveThreshold < 240 || minutesAboveMinBG < 60 ) { + console.error("BG projected to remain above",convert_bg(threshold,profile),"for",minutesAboveThreshold,"minutes"); + } // include at least minutesAboveMinBG worth of zero temps in calculating carbsReq // always include at least 30m worth of zero temp (carbs to 80, low temp up to target) - var zeroTempDuration = Math.max(30,minutesAboveMinBG); + //var zeroTempDuration = Math.max(30,minutesAboveMinBG); + var zeroTempDuration = minutesAboveThreshold; // BG undershoot, minus effect of zero temps until hitting min_bg, converted to grams, minus COB var zeroTempEffect = profile.current_basal*sens*zeroTempDuration/60; - var carbsReq = (bgUndershoot - zeroTempEffect) / csf - meal_data.mealCOB; + // don't count the last 25% of COB against carbsReq + var COBforCarbsReq = Math.max(0, meal_data.mealCOB - 0.25*meal_data.carbs); + var carbsReq = (bgUndershoot - zeroTempEffect) / csf - COBforCarbsReq; zeroTempEffect = round(zeroTempEffect); carbsReq = round(carbsReq); - console.error("bgUndershoot:",bgUndershoot,"zeroTempDuration:",zeroTempDuration,"zeroTempEffect:",zeroTempEffect,"carbsReq:",carbsReq); - if ( carbsReq > 0 ) { + console.error("naive_eventualBG:",naive_eventualBG,"bgUndershoot:",bgUndershoot,"zeroTempDuration:",zeroTempDuration,"zeroTempEffect:",zeroTempEffect,"carbsReq:",carbsReq); + if ( carbsReq >= profile.carbsReqThreshold && minutesAboveThreshold <= 45 ) { rT.carbsReq = carbsReq; - rT.reason += carbsReq + " add'l carbs req + " + minutesAboveMinBG + "m zero temp; "; + rT.reason += carbsReq + " add'l carbs req w/in " + minutesAboveThreshold + "m; "; } // don't low glucose suspend if IOB is already super negative and BG is rising faster than predicted if (bg < threshold && iob_data.iob < -profile.current_basal*20/60 && minDelta > 0 && minDelta > expectedDelta) { rT.reason += "IOB "+iob_data.iob+" < " + round(-profile.current_basal*20/60,2); - rT.reason += " and minDelta " + minDelta + " > " + "expectedDelta " + expectedDelta + "; "; - } - // low glucose suspend mode: BG is < ~80 - else if (bg < threshold) { - rT.reason += "BG " + convert_bg(bg, profile) + "<" + convert_bg(threshold, profile); - if ((glucose_status.delta <= 0 && minDelta <= 0) || (glucose_status.delta < expectedDelta && minDelta < expectedDelta) || bg < 60 ) { - // BG is still falling / rising slower than predicted - if ( minDelta < expectedDelta ) { - rT.reason += ", minDelta " + minDelta + " < " + "expectedDelta " + expectedDelta + "; "; - } - return tempBasalFunctions.setTempBasal(0, 30, profile, rT, currenttemp); - } - if (glucose_status.delta > minDelta) { - rT.reason += ", delta " + glucose_status.delta + ">0"; - } else { - rT.reason += ", min delta " + minDelta.toFixed(2) + ">0"; - } - if (currenttemp.duration > 15 && (round_basal(basal, profile) === round_basal(currenttemp.rate, profile))) { - rT.reason += ", temp " + currenttemp.rate + " ~ req " + basal + "U/hr. "; - return rT; - } else { - rT.reason += "; setting current basal of " + basal + " as temp. "; - return tempBasalFunctions.setTempBasal(basal, 30, profile, rT, currenttemp); - } + rT.reason += " and minDelta " + convert_bg(minDelta, profile) + " > " + "expectedDelta " + convert_bg(expectedDelta, profile) + "; "; + // predictive low glucose suspend mode: BG is / is projected to be < threshold + } else if ( bg < threshold || minGuardBG < threshold ) { + rT.reason += "minGuardBG " + convert_bg(minGuardBG, profile) + "<" + convert_bg(threshold, profile); + var bgUndershoot = target_bg - minGuardBG; + var worstCaseInsulinReq = bgUndershoot / sens; + var durationReq = round(60*worstCaseInsulinReq / profile.current_basal); + durationReq = round(durationReq/30)*30; + // always set a 30-120m zero temp (oref0-pump-loop will let any longer SMB zero temp run) + durationReq = Math.min(120,Math.max(30,durationReq)); + return tempBasalFunctions.setTempBasal(0, durationReq, profile, rT, currenttemp); } if (eventualBG < min_bg) { // if eventual BG is below target: rT.reason += "Eventual BG " + convert_bg(eventualBG, profile) + " < " + convert_bg(min_bg, profile); // if 5m or 30m avg BG is rising faster than expected delta - if (minDelta > expectedDelta && minDelta > 0) { + if ( minDelta > expectedDelta && minDelta > 0 && !carbsReq ) { // if naive_eventualBG < 40, set a 30m zero temp (oref0-pump-loop will let any longer SMB zero temp run) if (naive_eventualBG < 40) { rT.reason += ", naive_eventualBG < 40. "; return tempBasalFunctions.setTempBasal(0, 30, profile, rT, currenttemp); } if (glucose_status.delta > minDelta) { - rT.reason += ", but Delta " + tick + " > expectedDelta " + expectedDelta; + rT.reason += ", but Delta " + convert_bg(tick, profile) + " > expectedDelta " + convert_bg(expectedDelta, profile); } else { - rT.reason += ", but Min. Delta " + minDelta.toFixed(2) + " > Exp. Delta " + expectedDelta; + rT.reason += ", but Min. Delta " + minDelta.toFixed(2) + " > Exp. Delta " + convert_bg(expectedDelta, profile); } if (currenttemp.duration > 15 && (round_basal(basal, profile) === round_basal(currenttemp.rate, profile))) { rT.reason += ", temp " + currenttemp.rate + " ~ req " + basal + "U/hr. "; @@ -615,111 +900,71 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_ } } - if (eventualBG < min_bg) { - // if we've bolused recently, we can snooze until the bolus IOB decays (at double speed) - if (snoozeBG > min_bg) { // if adding back in the bolus contribution BG would be above min - // If we're not in SMB mode with COB, or lastCOBpredBG > target_bg, bolus snooze - if (! (microBolusAllowed && rT.COB) || lastCOBpredBG > target_bg) { - rT.reason += ", bolus snooze: eventual BG range " + convert_bg(eventualBG, profile) + "-" + convert_bg(snoozeBG, profile); - //console.error(currenttemp, basal ); - if (currenttemp.duration > 15 && (round_basal(basal, profile) === round_basal(currenttemp.rate, profile))) { - rT.reason += ", temp " + currenttemp.rate + " ~ req " + basal + "U/hr. "; - return rT; - } else { - rT.reason += "; setting current basal of " + basal + " as temp. "; - return tempBasalFunctions.setTempBasal(basal, 30, profile, rT, currenttemp); - } + // calculate 30m low-temp required to get projected BG up to target + // use snoozeBG to more gradually ramp in any counteraction of the user's boluses + // multiply by 2 to low-temp faster for increased hypo safety + //var insulinReq = 2 * Math.min(0, (snoozeBG - target_bg) / sens); + var insulinReq = 2 * Math.min(0, (eventualBG - target_bg) / sens); + insulinReq = round( insulinReq , 2); + // calculate naiveInsulinReq based on naive_eventualBG + var naiveInsulinReq = Math.min(0, (naive_eventualBG - target_bg) / sens); + naiveInsulinReq = round( naiveInsulinReq , 2); + if (minDelta < 0 && minDelta > expectedDelta) { + // if we're barely falling, newinsulinReq should be barely negative + //rT.reason += ", Snooze BG " + convert_bg(snoozeBG, profile); + var newinsulinReq = round(( insulinReq * (minDelta / expectedDelta) ), 2); + //console.error("Increasing insulinReq from " + insulinReq + " to " + newinsulinReq); + insulinReq = newinsulinReq; + } + // rate required to deliver insulinReq less insulin over 30m: + var rate = basal + (2 * insulinReq); + rate = round_basal(rate, profile); + // if required temp < existing temp basal + var insulinScheduled = currenttemp.duration * (currenttemp.rate - basal) / 60; + // if current temp would deliver a lot (30% of basal) less than the required insulin, + // by both normal and naive calculations, then raise the rate + var minInsulinReq = Math.min(insulinReq,naiveInsulinReq); + if (insulinScheduled < minInsulinReq - basal*0.3) { + rT.reason += ", "+currenttemp.duration + "m@" + (currenttemp.rate).toFixed(2) + " is a lot less than needed. "; + return tempBasalFunctions.setTempBasal(rate, 30, profile, rT, currenttemp); + } + if (typeof currenttemp.rate !== 'undefined' && (currenttemp.duration > 5 && rate >= currenttemp.rate * 0.8)) { + rT.reason += ", temp " + currenttemp.rate + " ~< req " + rate + "U/hr. "; + return rT; + } else { + // calculate a long enough zero temp to eventually correct back up to target + if ( rate <=0 ) { + var bgUndershoot = target_bg - naive_eventualBG; + var worstCaseInsulinReq = bgUndershoot / sens; + var durationReq = round(60*worstCaseInsulinReq / profile.current_basal); + if (durationReq < 0) { + durationReq = 0; + // don't set a temp longer than 120 minutes + } else { + durationReq = round(durationReq/30)*30; + durationReq = Math.min(120,Math.max(0,durationReq)); + } + //console.error(durationReq); + //rT.reason += "insulinReq " + insulinReq + "; " + if (durationReq > 0) { + rT.reason += ", setting " + durationReq + "m zero temp. "; + return tempBasalFunctions.setTempBasal(rate, durationReq, profile, rT, currenttemp); } } else { - // calculate 30m low-temp required to get projected BG up to target - // use snoozeBG to more gradually ramp in any counteraction of the user's boluses - // multiply by 2 to low-temp faster for increased hypo safety - var insulinReq = 2 * Math.min(0, (snoozeBG - target_bg) / sens); - insulinReq = round( insulinReq , 2); - // calculate naiveInsulinReq based on naive_eventualBG - var naiveInsulinReq = Math.min(0, (naive_eventualBG - target_bg) / sens); - naiveInsulinReq = round( naiveInsulinReq , 2); - if (minDelta < 0 && minDelta > expectedDelta) { - // if we're barely falling, newinsulinReq should be barely negative - rT.reason += ", Snooze BG " + convert_bg(snoozeBG, profile); - var newinsulinReq = round(( insulinReq * (minDelta / expectedDelta) ), 2); - //console.error("Increasing insulinReq from " + insulinReq + " to " + newinsulinReq); - insulinReq = newinsulinReq; - } - // rate required to deliver insulinReq less insulin over 30m: - var rate = basal + (2 * insulinReq); - rate = round_basal(rate, profile); - // if required temp < existing temp basal - var insulinScheduled = currenttemp.duration * (currenttemp.rate - basal) / 60; - // if current temp would deliver a lot (30% of basal) less than the required insulin, - // by both normal and naive calculations, then raise the rate - var minInsulinReq = Math.min(insulinReq,naiveInsulinReq); - if (insulinScheduled < minInsulinReq - basal*0.3) { - rT.reason += ", "+currenttemp.duration + "m@" + (currenttemp.rate).toFixed(2) + " is a lot less than needed. "; - return tempBasalFunctions.setTempBasal(rate, 30, profile, rT, currenttemp); - } - if (typeof currenttemp.rate !== 'undefined' && (currenttemp.duration > 5 && rate >= currenttemp.rate * 0.8)) { - rT.reason += ", temp " + currenttemp.rate + " ~< req " + rate + "U/hr. "; - return rT; - } else { - // calculate a long enough zero temp to eventually correct back up to target - if ( rate < 0 ) { - var bgUndershoot = target_bg - naive_eventualBG; - var worstCaseInsulinReq = bgUndershoot / sens; - var durationReq = round(60*worstCaseInsulinReq / profile.current_basal); - if (durationReq < 0) { - durationReq = 0; - // don't set a temp longer than 120 minutes - } else { - durationReq = round(durationReq/30)*30; - durationReq = Math.min(120,Math.max(0,durationReq)); - } - //console.error(durationReq); - //rT.reason += "insulinReq " + insulinReq + "; " - if (durationReq > 0) { - rT.reason += ", setting " + durationReq + "m zero temp. "; - return tempBasalFunctions.setTempBasal(rate, durationReq, profile, rT, currenttemp); - } - } else { - rT.reason += ", setting " + rate + "U/hr. "; - } - return tempBasalFunctions.setTempBasal(rate, 30, profile, rT, currenttemp); - } + rT.reason += ", setting " + rate + "U/hr. "; } + return tempBasalFunctions.setTempBasal(rate, 30, profile, rT, currenttemp); } } - /* - var minutes_running; - if (typeof currenttemp.duration == 'undefined' || currenttemp.duration == 0) { - minutes_running = 30; - } else if (typeof currenttemp.minutesrunning !== 'undefined'){ - // If the time the current temp is running is not defined, use default request duration of 30 minutes. - minutes_running = currenttemp.minutesrunning; - } else { - minutes_running = 30 - currenttemp.duration; - } - - // if there is a low-temp running, and eventualBG would be below min_bg without it, let it run - if (round_basal(currenttemp.rate, profile) < round_basal(basal, profile) ) { - var lowtempimpact = (currenttemp.rate - basal) * ((30-minutes_running)/60) * sens; - var adjEventualBG = eventualBG + lowtempimpact; - // don't return early if microBolusAllowed etc. - if ( adjEventualBG < min_bg && ! (microBolusAllowed && enableSMB)) { - rT.reason += "letting low temp of " + currenttemp.rate + " run."; - return rT; - } - } - */ - // if eventual BG is above min but BG is falling faster than expected Delta if (minDelta < expectedDelta) { // if in SMB mode, don't cancel SMB zero temp if (! (microBolusAllowed && enableSMB)) { if (glucose_status.delta < minDelta) { - rT.reason += "Eventual BG " + convert_bg(eventualBG, profile) + " > " + convert_bg(min_bg, profile) + " but Delta " + tick + " < Exp. Delta " + expectedDelta; + rT.reason += "Eventual BG " + convert_bg(eventualBG, profile) + " > " + convert_bg(min_bg, profile) + " but Delta " + convert_bg(tick, profile) + " < Exp. Delta " + convert_bg(expectedDelta, profile); } else { - rT.reason += "Eventual BG " + convert_bg(eventualBG, profile) + " > " + convert_bg(min_bg, profile) + " but Min. Delta " + minDelta.toFixed(2) + " < Exp. Delta " + expectedDelta; + rT.reason += "Eventual BG " + convert_bg(eventualBG, profile) + " > " + convert_bg(min_bg, profile) + " but Min. Delta " + minDelta.toFixed(2) + " < Exp. Delta " + convert_bg(expectedDelta, profile); } if (currenttemp.duration > 15 && (round_basal(basal, profile) === round_basal(currenttemp.rate, profile))) { rT.reason += ", temp " + currenttemp.rate + " ~ req " + basal + "U/hr. "; @@ -730,17 +975,11 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_ } } } - // eventualBG, snoozeBG, or minPredBG is below max_bg - if (Math.min(eventualBG,snoozeBG,minPredBG) < max_bg) { - // if there is a high-temp running and eventualBG > max_bg, let it run - if (eventualBG > max_bg && round_basal(currenttemp.rate, profile) > round_basal(basal, profile) && currenttemp.duration > 5 ) { - rT.reason += eventualBG + " > " + max_bg + ": no temp required (letting high temp of " + currenttemp.rate + " run). " - return rT; - } - + // eventualBG or minPredBG is below max_bg + if (Math.min(eventualBG,minPredBG) < max_bg) { // if in SMB mode, don't cancel SMB zero temp if (! (microBolusAllowed && enableSMB )) { - rT.reason += convert_bg(eventualBG, profile)+"-"+convert_bg(Math.min(minPredBG,snoozeBG), profile)+" in range: no temp required"; + rT.reason += convert_bg(eventualBG, profile)+"-"+convert_bg(minPredBG, profile)+" in range: no temp required"; if (currenttemp.duration > 15 && (round_basal(basal, profile) === round_basal(currenttemp.rate, profile))) { rT.reason += ", temp " + currenttemp.rate + " ~ req " + basal + "U/hr. "; return rT; @@ -751,17 +990,14 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_ } } - // eventual BG is at/above target (or bolus snooze disabled for SMB) + // eventual BG is at/above target // if iob is over max, just cancel any temps - var basaliob; - if (iob_data.basaliob) { basaliob = iob_data.basaliob; } - else { basaliob = iob_data.iob - iob_data.bolussnooze; } // if we're not here because of SMB, eventual BG is at/above target if (! (microBolusAllowed && rT.COB)) { rT.reason += "Eventual BG " + convert_bg(eventualBG, profile) + " >= " + convert_bg(max_bg, profile) + ", "; } - if (basaliob > max_iob) { - rT.reason += "basaliob " + round(basaliob,2) + " > max_iob " + max_iob; + if (iob_data.iob > max_iob) { + rT.reason += "IOB " + round(iob_data.iob,2) + " > max_iob " + max_iob; if (currenttemp.duration > 15 && (round_basal(basal, profile) === round_basal(currenttemp.rate, profile))) { rT.reason += ", temp " + currenttemp.rate + " ~ req " + basal + "U/hr. "; return rT; @@ -772,19 +1008,20 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_ } else { // otherwise, calculate 30m high-temp required to get projected BG down to target // insulinReq is the additional insulin required to get minPredBG down to target_bg - //console.error(minPredBG,snoozeBG,eventualBG); - var insulinReq = round( (Math.min(minPredBG,snoozeBG,eventualBG) - target_bg) / sens, 2); + //console.error(minPredBG,eventualBG); + //var insulinReq = round( (Math.min(minPredBG,eventualBG) - target_bg) / sens, 2); + var insulinReq = round( (Math.min(minPredBG,eventualBG) - target_bg) / sens, 2); // when dropping, but not as fast as expected, reduce insulinReq proportionally // to the what fraction of expectedDelta we're dropping at - if (minDelta < 0 && minDelta > expectedDelta) { - var newinsulinReq = round(( insulinReq * (1 - (minDelta / expectedDelta)) ), 2); - //console.error("Reducing insulinReq from " + insulinReq + " to " + newinsulinReq); - insulinReq = newinsulinReq; - } + //if (minDelta < 0 && minDelta > expectedDelta) { + //var newinsulinReq = round(( insulinReq * (1 - (minDelta / expectedDelta)) ), 2); + //console.error("Reducing insulinReq from " + insulinReq + " to " + newinsulinReq + " for minDelta " + minDelta + " vs. expectedDelta " + expectedDelta); + //insulinReq = newinsulinReq; + //} // if that would put us over max_iob, then reduce accordingly - if (insulinReq > max_iob-basaliob) { + if (insulinReq > max_iob-iob_data.iob) { rT.reason += "max_iob " + max_iob + ", "; - insulinReq = max_iob-basaliob; + insulinReq = max_iob-iob_data.iob; } // rate required to deliver insulinReq more insulin over 30m: @@ -792,50 +1029,63 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_ rate = round_basal(rate, profile); insulinReq = round(insulinReq,3); rT.insulinReq = insulinReq; - rT.minPredBG = minPredBG; //console.error(iob_data.lastBolusTime); // minutes since last bolus var lastBolusAge = round(( new Date().getTime() - iob_data.lastBolusTime ) / 60000,1); //console.error(lastBolusAge); //console.error(profile.temptargetSet, target_bg, rT.COB); // only allow microboluses with COB or low temp targets, or within DIA hours of a bolus - // only microbolus if 0.1U SMB represents 20m or less of basal (0.3U/hr or higher) - if (microBolusAllowed && enableSMB && profile.current_basal >= 0.3 && bg > threshold) { - // never bolus more than 30m worth of basal - maxBolus = round(profile.current_basal/2,1); - // bolus 1/3 the insulinReq, up to maxBolus - microBolus = round(Math.min(insulinReq/3,maxBolus),1); - + if (microBolusAllowed && enableSMB && bg > threshold) { + // never bolus more than maxSMBBasalMinutes worth of basal + mealInsulinReq = round( meal_data.mealCOB / profile.carb_ratio ,3); + if (typeof profile.maxSMBBasalMinutes == 'undefined' ) { + maxBolus = round( profile.current_basal * 30 / 60 ,1); + console.error("profile.maxSMBBasalMinutes undefined: defaulting to 30m"); + // if IOB covers more than COB, limit maxBolus to 30m of basal + } else if ( iob_data.iob > mealInsulinReq && iob_data.iob > 0 ) { + console.error("IOB",iob_data.iob,"> COB",meal_data.mealCOB+"; mealInsulinReq =",mealInsulinReq); + maxBolus = round( profile.current_basal * 30 / 60 ,1); + } else { + console.error("profile.maxSMBBasalMinutes:",profile.maxSMBBasalMinutes,"profile.current_basal:",profile.current_basal); + maxBolus = round( profile.current_basal * profile.maxSMBBasalMinutes / 60 ,1); + } + // bolus 1/2 the insulinReq, up to maxBolus, rounding down to nearest 0.1U + microBolus = Math.floor(Math.min(insulinReq/2,maxBolus)*10)/10; // calculate a long enough zero temp to eventually correct back up to target var smbTarget = target_bg; var worstCaseInsulinReq = (smbTarget - (naive_eventualBG + minIOBPredBG)/2 ) / sens; var durationReq = round(60*worstCaseInsulinReq / profile.current_basal); - // if no microBolus required, snoozeBG > target_bg, and lastCOBpredBG > target_bg, don't set a zero temp - if (microBolus < 0.1 && snoozeBG > target_bg && lastCOBpredBG > target_bg) { + // if insulinReq > 0 but not enough for a microBolus, don't set an SMB zero temp + if (insulinReq > 0 && microBolus < 0.1) { durationReq = 0; } - if (durationReq < 0) { + var smbLowTempReq = 0; + if (durationReq <= 0) { durationReq = 0; // don't set a temp longer than 120 minutes - } else { + } else if (durationReq >= 30) { durationReq = round(durationReq/30)*30; durationReq = Math.min(120,Math.max(0,durationReq)); + } else { + // if SMB durationReq is less than 30m, set a nonzero low temp + smbLowTempReq = round( basal * durationReq/30 ,2); + durationReq = 30; } rT.reason += " insulinReq " + insulinReq; if (microBolus >= maxBolus) { rT.reason += "; maxBolus " + maxBolus; } if (durationReq > 0) { - rT.reason += "; setting " + durationReq + "m zero temp"; + rT.reason += "; setting " + durationReq + "m low temp of " + smbLowTempReq + "U/h"; } rT.reason += ". "; //allow SMBs every 3 minutes var nextBolusMins = round(3-lastBolusAge,1); //console.error(naive_eventualBG, insulinReq, worstCaseInsulinReq, durationReq); - console.error("naive_eventualBG",naive_eventualBG+",",durationReq+"m zero temp needed; last bolus",lastBolusAge+"m ago; maxBolus: "+maxBolus); + console.error("naive_eventualBG",naive_eventualBG+",",durationReq+"m "+smbLowTempReq+"U/h temp needed; last bolus",lastBolusAge+"m ago; maxBolus: "+maxBolus); if (lastBolusAge > 3) { if (microBolus > 0) { rT.units = microBolus; @@ -848,16 +1098,16 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_ // if no zero temp is required, don't return yet; allow later code to set a high temp if (durationReq > 0) { - rT.rate = 0; + rT.rate = smbLowTempReq; rT.duration = durationReq; return rT; } // if insulinReq is negative, snoozeBG > target_bg, and lastCOBpredBG > target_bg, set a neutral temp - if (insulinReq < 0 && snoozeBG > target_bg && lastCOBpredBG > target_bg) { - rT.reason += "; SMB bolus snooze: setting current basal of " + basal + " as temp. "; - return tempBasalFunctions.setTempBasal(basal, 30, profile, rT, currenttemp); - } + //if (insulinReq < 0 && snoozeBG > target_bg && lastCOBpredBG > target_bg) { + //rT.reason += "; SMB bolus snooze: setting current basal of " + basal + " as temp. "; + //return tempBasalFunctions.setTempBasal(basal, 30, profile, rT, currenttemp); + //} } var maxSafeBasal = tempBasalFunctions.getMaxSafeBasal(profile); @@ -890,4 +1140,4 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_ }; -module.exports = determine_basal; \ No newline at end of file +module.exports = determine_basal; diff --git a/app/src/main/java/info/nightscout/androidaps/Config.java b/app/src/main/java/info/nightscout/androidaps/Config.java index 3365f8a28e..874f75d2bf 100644 --- a/app/src/main/java/info/nightscout/androidaps/Config.java +++ b/app/src/main/java/info/nightscout/androidaps/Config.java @@ -23,7 +23,7 @@ public class Config { public static final boolean SMSCOMMUNICATORENABLED = !BuildConfig.NSCLIENTOLNY && !BuildConfig.G5UPLOADER; - public static final boolean displayDeviationSlope = false; + public static final boolean displayDeviationSlope = true; public static final boolean detailedLog = true; public static final boolean logFunctionCalls = true; diff --git a/app/src/main/java/info/nightscout/androidaps/data/GlucoseStatus.java b/app/src/main/java/info/nightscout/androidaps/data/GlucoseStatus.java index 1623028a82..4fd4bcca6a 100644 --- a/app/src/main/java/info/nightscout/androidaps/data/GlucoseStatus.java +++ b/app/src/main/java/info/nightscout/androidaps/data/GlucoseStatus.java @@ -35,6 +35,7 @@ public class GlucoseStatus { public double avgdelta = 0d; public double short_avgdelta = 0d; public double long_avgdelta = 0d; + public long date = 0L; @Override @@ -126,6 +127,7 @@ public class GlucoseStatus { GlucoseStatus status = new GlucoseStatus(); status.glucose = now.value; + status.date = now_date; status.short_avgdelta = average(short_deltas); diff --git a/app/src/main/java/info/nightscout/androidaps/data/IobTotal.java b/app/src/main/java/info/nightscout/androidaps/data/IobTotal.java index dd00978d04..34df937d14 100644 --- a/app/src/main/java/info/nightscout/androidaps/data/IobTotal.java +++ b/app/src/main/java/info/nightscout/androidaps/data/IobTotal.java @@ -24,6 +24,10 @@ public class IobTotal { public double microBolusInsulin; public double microBolusIOB; public long lastBolusTime; + public long lastTempDate; + public int lastTempDuration; + public double lastTempRate; + public IobTotal iobWithZeroTemp; public double netInsulin = 0d; // for calculations from temp basals only public double netRatio = 0d; // net ratio at start of temp basal @@ -32,6 +36,25 @@ public class IobTotal { long time; + + public IobTotal clone() { + IobTotal copy = new IobTotal(time); + copy.iob = iob; + copy.activity = activity; + copy.bolussnooze = bolussnooze; + copy.basaliob = basaliob; + copy.netbasalinsulin = netbasalinsulin; + copy.hightempinsulin = hightempinsulin; + copy.microBolusInsulin = microBolusInsulin; + copy.microBolusIOB = microBolusIOB; + copy.lastBolusTime = lastBolusTime; + copy.lastTempDate = lastTempDate; + copy.lastTempDuration = lastTempDuration; + copy.lastTempRate = lastTempRate; + copy.iobWithZeroTemp = iobWithZeroTemp; + return copy; + } + public IobTotal(long time) { this.iob = 0d; this.activity = 0d; @@ -70,6 +93,10 @@ public class IobTotal { result.microBolusInsulin = bolusIOB.microBolusInsulin + basalIob.microBolusInsulin; result.microBolusIOB = bolusIOB.microBolusIOB + basalIob.microBolusIOB; result.lastBolusTime = bolusIOB.lastBolusTime; + result.lastTempDate = basalIob.lastTempDate; + result.lastTempRate = basalIob.lastTempRate; + result.lastTempDuration = basalIob.lastTempDuration; + result.iobWithZeroTemp = basalIob.iobWithZeroTemp; return result; } @@ -107,6 +134,22 @@ public class IobTotal { json.put("activity", activity); json.put("lastBolusTime", lastBolusTime); json.put("time", DateUtil.toISOString(new Date(time))); + /* + + This is requested by SMB determine_basal but by based on Scott's info + it's MDT specific safety check only + It's causing rounding issues in determine_basal + + JSONObject lastTemp = new JSONObject(); + lastTemp.put("date", lastTempDate); + lastTemp.put("rate", lastTempRate); + lastTemp.put("duration", lastTempDuration); + json.put("lastTemp", lastTemp); + */ + if (iobWithZeroTemp != null) { + JSONObject iwzt = iobWithZeroTemp.determineBasalJson(); + json.put("iobWithZeroTemp", iwzt); + } } catch (JSONException e) { log.error("Unhandled exception", e); } diff --git a/app/src/main/java/info/nightscout/androidaps/data/MealData.java b/app/src/main/java/info/nightscout/androidaps/data/MealData.java index 4e1014431c..054c76d602 100644 --- a/app/src/main/java/info/nightscout/androidaps/data/MealData.java +++ b/app/src/main/java/info/nightscout/androidaps/data/MealData.java @@ -7,6 +7,8 @@ public class MealData { public double boluses = 0d; public double carbs = 0d; public double mealCOB = 0.0d; - public double minDeviationSlope; + public double slopeFromMaxDeviation = 0; + public double slopeFromMinDeviation = 999; public long lastBolusTime; + public long lastCarbTime = 0L; } diff --git a/app/src/main/java/info/nightscout/androidaps/db/BgReading.java b/app/src/main/java/info/nightscout/androidaps/db/BgReading.java index 58442cadd1..9109963182 100644 --- a/app/src/main/java/info/nightscout/androidaps/db/BgReading.java +++ b/app/src/main/java/info/nightscout/androidaps/db/BgReading.java @@ -46,6 +46,7 @@ public class BgReading implements DataPointWithLabelInterface { public boolean isaCOBPrediction = false; // true when drawing predictions as bg points (aCOB) public boolean isIOBPrediction = false; // true when drawing predictions as bg points (IOB) public boolean isUAMPrediction = false; // true when drawing predictions as bg points (UAM) + public boolean isZTPrediction = false; // true when drawing predictions as bg points (ZT) public BgReading() { } @@ -228,11 +229,13 @@ public class BgReading implements DataPointWithLabelInterface { return 0x80FFFFFF & MainApp.sResources.getColor(R.color.cob); if (isUAMPrediction) return MainApp.sResources.getColor(R.color.uam); + if (isZTPrediction) + return MainApp.sResources.getColor(R.color.zt); return R.color.mdtp_white; } private boolean isPrediction() { - return isaCOBPrediction || isCOBPrediction || isIOBPrediction || isUAMPrediction; + return isaCOBPrediction || isCOBPrediction || isIOBPrediction || isUAMPrediction || isZTPrediction; } } diff --git a/app/src/main/java/info/nightscout/androidaps/interfaces/InsulinInterface.java b/app/src/main/java/info/nightscout/androidaps/interfaces/InsulinInterface.java index 0803f65373..7a6ebbd701 100644 --- a/app/src/main/java/info/nightscout/androidaps/interfaces/InsulinInterface.java +++ b/app/src/main/java/info/nightscout/androidaps/interfaces/InsulinInterface.java @@ -10,16 +10,16 @@ import info.nightscout.androidaps.db.Treatment; */ public interface InsulinInterface { - final int FASTACTINGINSULIN = 0; - final int FASTACTINGINSULINPROLONGED = 1; - final int OREF_RAPID_ACTING = 2; - final int OREF_ULTRA_RAPID_ACTING = 3; - final int OREF_FREE_PEAK = 4; + int FASTACTINGINSULIN = 0; + int FASTACTINGINSULINPROLONGED = 1; + int OREF_RAPID_ACTING = 2; + int OREF_ULTRA_RAPID_ACTING = 3; + int OREF_FREE_PEAK = 4; int getId(); String getFriendlyName(); String getComment(); double getDia(); - public Iob iobCalcForTreatment(Treatment treatment, long time, Double dia); + Iob iobCalcForTreatment(Treatment treatment, long time, Double dia); } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/IobCobCalculator/AutosensData.java b/app/src/main/java/info/nightscout/androidaps/plugins/IobCobCalculator/AutosensData.java index 4e5400cbc0..dddea570be 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/IobCobCalculator/AutosensData.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/IobCobCalculator/AutosensData.java @@ -61,10 +61,11 @@ public class AutosensData { public double avgDeviation = 0d; public double autosensRatio = 1d; - public double minDeviationSlope = 999; + public double slopeFromMaxDeviation = 0; + public double slopeFromMinDeviation = 999; public String log(long time) { - return "AutosensData: " + new Date(time).toLocaleString() + " " + pastSensitivity + " Delta=" + delta + " avgDelta=" + avgDelta + " Bgi=" + bgi + " Deviation=" + deviation + " avgDeviation=" + avgDeviation + " Absorbed=" + absorbed + " CarbsFromBolus=" + carbsFromBolus + " COB=" + cob + " autosensRatio=" + autosensRatio + " minDeviationSlope=" + minDeviationSlope; + return "AutosensData: " + new Date(time).toLocaleString() + " " + pastSensitivity + " Delta=" + delta + " avgDelta=" + avgDelta + " Bgi=" + bgi + " Deviation=" + deviation + " avgDeviation=" + avgDeviation + " Absorbed=" + absorbed + " CarbsFromBolus=" + carbsFromBolus + " COB=" + cob + " autosensRatio=" + autosensRatio + " slopeFromMaxDeviation=" + slopeFromMaxDeviation + " slopeFromMinDeviation =" + slopeFromMinDeviation ; } public int minOld() { diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/IobCobCalculator/IobCobCalculatorPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/IobCobCalculator/IobCobCalculatorPlugin.java index e669f57068..6a12d77c30 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/IobCobCalculator/IobCobCalculatorPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/IobCobCalculator/IobCobCalculatorPlugin.java @@ -34,6 +34,7 @@ import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; import info.nightscout.androidaps.plugins.IobCobCalculator.events.BasalData; import info.nightscout.androidaps.plugins.IobCobCalculator.events.EventAutosensCalculationFinished; import info.nightscout.androidaps.plugins.IobCobCalculator.events.EventNewHistoryData; +import info.nightscout.androidaps.plugins.OpenAPSSMB.OpenAPSSMBPlugin; /** * Created by mike on 24.04.2017. @@ -402,8 +403,10 @@ public class IobCobCalculatorPlugin implements PluginBase { double avgDeviation = Math.round((avgDelta - bgi) * 1000) / 1000; double currentDeviation; - double minDeviationSlope = 0; + double slopeFromMaxDeviation = 0; + double slopeFromMinDeviation = 999; double maxDeviation = 0; + double minDeviation = 999; // https://github.com/openaps/oref0/blob/master/lib/determine-basal/cob-autosens.js#L169 if (i < bucketed_data.size() - 16) { // we need 1h of data to calculate minDeviationSlope @@ -416,10 +419,16 @@ public class IobCobCalculatorPlugin implements PluginBase { AutosensData ad = autosensDataTable.valueAt(initialIndex + past); double deviationSlope = (ad.avgDeviation - currentDeviation) / (ad.time - bgTime) * 1000 * 60 * 5; if (ad.avgDeviation > maxDeviation) { - minDeviationSlope = Math.min(0, deviationSlope); + slopeFromMaxDeviation = Math.min(0, deviationSlope); maxDeviation = ad.avgDeviation; } - log.debug("Deviations: " + new Date(bgTime) + new Date(ad.time) + " avgDeviation=" + avgDeviation + " deviationSlope=" + deviationSlope + " minDeviationSlope=" + minDeviationSlope); + if (avgDeviation < minDeviation) { + slopeFromMinDeviation = Math.max(0, deviationSlope); + minDeviation = avgDeviation; + } + + //if (Config.logAutosensData) + // log.debug("Deviations: " + new Date(bgTime) + new Date(ad.time) + " avgDeviation=" + avgDeviation + " deviationSlope=" + deviationSlope + " slopeFromMaxDeviation=" + slopeFromMaxDeviation + " slopeFromMinDeviation=" + slopeFromMinDeviation); } } @@ -454,7 +463,8 @@ public class IobCobCalculatorPlugin implements PluginBase { autosensData.delta = delta; autosensData.avgDelta = avgDelta; autosensData.avgDeviation = avgDeviation; - autosensData.minDeviationSlope = minDeviationSlope; + autosensData.slopeFromMaxDeviation = slopeFromMaxDeviation; + autosensData.slopeFromMinDeviation = slopeFromMinDeviation; // calculate autosens only without COB if (autosensData.cob <= 0) { @@ -477,8 +487,8 @@ public class IobCobCalculatorPlugin implements PluginBase { previous = autosensData; autosensDataTable.put(bgTime, autosensData); autosensData.autosensRatio = detectSensitivity(oldestTimeWithData, bgTime).ratio; - if (Config.logAutosensData) - log.debug(autosensData.log(bgTime)); + //if (Config.logAutosensData) + // log.debug(autosensData.log(bgTime)); } } MainApp.bus().post(new EventAutosensCalculationFinished()); @@ -511,6 +521,21 @@ public class IobCobCalculatorPlugin implements PluginBase { } IobTotal bolusIob = MainApp.getConfigBuilder().getCalculationToTimeTreatments(time).round(); IobTotal basalIob = MainApp.getConfigBuilder().getCalculationToTimeTempBasals(time).round(); + if (OpenAPSSMBPlugin.getPlugin().isEnabled(PluginBase.APS)) { + // Add expected zere temp basal for next 240 mins + IobTotal basalIobWithZeroTemp = basalIob.clone(); + TemporaryBasal t = new TemporaryBasal(); + t.date = now + 60 * 1000L; + t.durationInMinutes = 240; + t.isAbsolute = true; + t.absoluteRate = 0; + if (t.date < time) { + IobTotal calc = t.iobCalc(time); + basalIobWithZeroTemp.plus(calc); + } + + basalIob.iobWithZeroTemp = basalIobWithZeroTemp; + } IobTotal iobTotal = IobTotal.combine(bolusIob, basalIob).round(); if (time < System.currentTimeMillis()) { @@ -605,6 +630,23 @@ public class IobCobCalculatorPlugin implements PluginBase { return array; } + public static IobTotal[] calculateIobArrayForSMB() { + Profile profile = MainApp.getConfigBuilder().getProfile(); + // predict IOB out to DIA plus 30m + long time = System.currentTimeMillis(); + time = roundUpTime(time); + int len = (4 * 60) / 5; + IobTotal[] array = new IobTotal[len]; + int pos = 0; + for (int i = 0; i < len; i++) { + long t = time + i * 5 * 60000; + IobTotal iob = calculateFromTreatmentsAndTempsSynchronized(t); + array[pos] = iob; + pos++; + } + return array; + } + public static AutosensResult detectSensitivityWithLock(long fromTime, long toTime) { synchronized (dataLock) { return detectSensitivity(fromTime, toTime); diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/Loop/APSResult.java b/app/src/main/java/info/nightscout/androidaps/plugins/Loop/APSResult.java index 867fb20a92..6e81c1a619 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/Loop/APSResult.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/Loop/APSResult.java @@ -143,6 +143,16 @@ public class APSResult { array.add(bg); } } + if (predBGs.has("ZT")) { + JSONArray iob = predBGs.getJSONArray("ZT"); + for (int i = 1; i < iob.length(); i++) { + BgReading bg = new BgReading(); + bg.value = iob.getInt(i); + bg.date = startTime + i * 5 * 60 * 1000L; + bg.isZTPrediction = true; + array.add(bg); + } + } } } catch (JSONException e) { e.printStackTrace(); @@ -172,6 +182,10 @@ public class APSResult { JSONArray iob = predBGs.getJSONArray("UAM"); latest = Math.max(latest, startTime + (iob.length() - 1) * 5 * 60 * 1000L); } + if (predBGs.has("ZT")) { + JSONArray iob = predBGs.getJSONArray("ZT"); + latest = Math.max(latest, startTime + (iob.length() - 1) * 5 * 60 * 1000L); + } } } catch (JSONException e) { e.printStackTrace(); diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSAMA/DetermineBasalAdapterAMAJS.java b/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSAMA/DetermineBasalAdapterAMAJS.java index 466026e986..66d3ab2b96 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSAMA/DetermineBasalAdapterAMAJS.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSAMA/DetermineBasalAdapterAMAJS.java @@ -29,6 +29,7 @@ import info.nightscout.androidaps.db.TemporaryBasal; import info.nightscout.androidaps.plugins.IobCobCalculator.IobCobCalculatorPlugin; import info.nightscout.androidaps.plugins.Loop.ScriptReader; import info.nightscout.androidaps.plugins.OpenAPSMA.LoggerCallback; +import info.nightscout.androidaps.plugins.OpenAPSSMB.SMBDefaults; import info.nightscout.utils.SP; public class DetermineBasalAdapterAMAJS { @@ -189,8 +190,7 @@ public class DetermineBasalAdapterAMAJS { GlucoseStatus glucoseStatus, MealData mealData, double autosensDataRatio, - boolean tempTargetSet, - double min_5m_carbimpact) throws JSONException { + boolean tempTargetSet) throws JSONException { String units = profile.getUnits(); @@ -211,7 +211,7 @@ public class DetermineBasalAdapterAMAJS { mProfile.put("current_basal", basalrate); mProfile.put("temptargetSet", tempTargetSet); mProfile.put("autosens_adjust_targets", SP.getBoolean("openapsama_autosens_adjusttargets", true)); - mProfile.put("min_5m_carbimpact", SP.getDouble("openapsama_min_5m_carbimpact", 3d)); + mProfile.put("min_5m_carbimpact", SP.getInt("openapsama_min_5m_carbimpact", SMBDefaults.min_5m_carbimpact)); if (units.equals(Constants.MMOL)) { mProfile.put("out_units", "mmol/L"); diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSAMA/OpenAPSAMAPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSAMA/OpenAPSAMAPlugin.java index 4dcb174209..c318dce0d7 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSAMA/OpenAPSAMAPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSAMA/OpenAPSAMAPlugin.java @@ -233,8 +233,7 @@ public class OpenAPSAMAPlugin implements PluginBase, APSInterface { try { determineBasalAdapterAMAJS.setData(profile, maxIob, maxBasal, minBg, maxBg, targetBg, ConfigBuilderPlugin.getActivePump().getBaseBasalRate(), iobArray, glucoseStatus, mealData, lastAutosensResult.ratio, //autosensDataRatio - isTempTarget, - SafeParse.stringToDouble(SP.getString("openapsama_min_5m_carbimpact", "3.0"))//min_5m_carbimpact + isTempTarget ); } catch (JSONException e) { log.error("Unable to set data: " + e.toString()); diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSSMB/DetermineBasalAdapterSMBJS.java b/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSSMB/DetermineBasalAdapterSMBJS.java index 2663ea1e1c..05ed4d92f4 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSSMB/DetermineBasalAdapterSMBJS.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSSMB/DetermineBasalAdapterSMBJS.java @@ -17,7 +17,6 @@ import org.slf4j.LoggerFactory; import java.io.IOException; import java.lang.reflect.InvocationTargetException; -import java.util.Date; import info.nightscout.androidaps.Config; import info.nightscout.androidaps.MainApp; @@ -25,14 +24,13 @@ import info.nightscout.androidaps.R; import info.nightscout.androidaps.data.GlucoseStatus; import info.nightscout.androidaps.data.IobTotal; import info.nightscout.androidaps.data.MealData; +import info.nightscout.androidaps.data.Profile; import info.nightscout.androidaps.db.TemporaryBasal; -import info.nightscout.androidaps.interfaces.PumpInterface; import info.nightscout.androidaps.plugins.IobCobCalculator.IobCobCalculatorPlugin; import info.nightscout.androidaps.plugins.Loop.ScriptReader; -import info.nightscout.androidaps.data.Profile; -import info.nightscout.androidaps.plugins.OpenAPSAMA.DetermineBasalResultAMA; import info.nightscout.androidaps.plugins.OpenAPSMA.LoggerCallback; import info.nightscout.utils.SP; +import info.nightscout.utils.SafeParse; public class DetermineBasalAdapterSMBJS { private static Logger log = LoggerFactory.getLogger(DetermineBasalAdapterSMBJS.class); @@ -216,7 +214,7 @@ public class DetermineBasalAdapterSMBJS { String units = profile.getUnits(); mProfile = new JSONObject(); - ; + mProfile.put("max_iob", maxIob); mProfile.put("dia", profile.getDia()); mProfile.put("type", "current"); @@ -229,26 +227,32 @@ public class DetermineBasalAdapterSMBJS { mProfile.put("sens", Profile.toMgdl(profile.getIsf().doubleValue(), units)); mProfile.put("max_daily_safety_multiplier", SP.getInt("openapsama_max_daily_safety_multiplier", 3)); mProfile.put("current_basal_safety_multiplier", SP.getInt("openapsama_current_basal_safety_multiplier", 4)); - mProfile.put("skip_neutral_temps", true); + + mProfile.put("high_temptarget_raises_sensitivity", SMBDefaults.high_temptarget_raises_sensitivity); + mProfile.put("low_temptarget_lowers_sensitivity", SMBDefaults.low_temptarget_lowers_sensitivity); + mProfile.put("sensitivity_raises_target", SMBDefaults.sensitivity_raises_target); + mProfile.put("resistance_lowers_target", SMBDefaults.resistance_lowers_target); + mProfile.put("adv_target_adjustments", SMBDefaults.adv_target_adjustments); + mProfile.put("exercise_mode", SMBDefaults.exercise_mode); + mProfile.put("half_basal_exercise_target", SMBDefaults.half_basal_exercise_target); + mProfile.put("maxCOB", SMBDefaults.maxCOB); + mProfile.put("skip_neutral_temps", SMBDefaults.skip_neutral_temps); + mProfile.put("min_5m_carbimpact", SP.getInt("openapsama_min_5m_carbimpact", SMBDefaults.min_5m_carbimpact)); + mProfile.put("remainingCarbsCap", SMBDefaults.remainingCarbsCap); + mProfile.put("enableUAM", SP.getBoolean(R.string.key_use_uam, false)); + mProfile.put("A52_risk_enable", SMBDefaults.A52_risk_enable); + mProfile.put("enableSMB_with_COB", SP.getBoolean(R.string.key_use_smb, false) && SMBDefaults.enableSMB_with_COB); + mProfile.put("enableSMB_with_temptarget", SP.getBoolean(R.string.key_use_smb, false) && SMBDefaults.enableSMB_with_temptarget); + mProfile.put("enableSMB_after_carbs", SP.getBoolean(R.string.key_use_smb, false) && SMBDefaults.enableSMB_after_carbs); + mProfile.put("allowSMB_with_high_temptarget", SP.getBoolean(R.string.key_use_smb, false) && SMBDefaults.allowSMB_with_high_temptarget); + mProfile.put("maxSMBBasalMinutes", SP.getInt("key_smbmaxminutes", SMBDefaults.maxSMBBasalMinutes)); + mProfile.put("carbsReqThreshold", SMBDefaults.carbsReqThreshold); + mProfile.put("current_basal", basalrate); mProfile.put("temptargetSet", tempTargetSet); - mProfile.put("autosens_adjust_targets", SP.getBoolean("openapsama_autosens_adjusttargets", true)); - mProfile.put("min_5m_carbimpact", SP.getDouble("openapsama_min_5m_carbimpact", 3d)); - mProfile.put("enableSMB_with_bolus", SP.getBoolean(R.string.key_use_smb, false)); - mProfile.put("enableSMB_with_COB", SP.getBoolean(R.string.key_use_smb, false)); - mProfile.put("enableSMB_with_temptarget", SP.getBoolean(R.string.key_use_smb, false)); - mProfile.put("enableUAM", SP.getBoolean(R.string.key_use_uam, false)); - mProfile.put("adv_target_adjustments", true); // lower target automatically when BG and eventualBG are high - // create maxCOB and default it to 120 because that's the most a typical body can absorb over 4 hours. - // (If someone enters more carbs or stacks more; OpenAPS will just truncate dosing based on 120. - // Essentially, this just limits AMA as a safety cap against weird COB calculations) - mProfile.put("maxCOB", 120); - mProfile.put("autotune_isf_adjustmentFraction", 0.5); // keep autotune ISF closer to pump ISF via a weighted average of fullNewISF and pumpISF. 1.0 allows full adjustment, 0 is no adjustment from pump ISF. - mProfile.put("remainingCarbsFraction", 1.0d); // fraction of carbs we'll assume will absorb over 4h if we don't yet see carb absorption - mProfile.put("remainingCarbsCap", 90); // max carbs we'll assume will absorb over 4h if we don't yet see carb absorption + mProfile.put("autosens_max", SafeParse.stringToDouble(SP.getString("openapsama_autosens_max", "1.2"))); mCurrentTemp = new JSONObject(); - ; mCurrentTemp.put("temp", "absolute"); mCurrentTemp.put("duration", MainApp.getConfigBuilder().getTempBasalRemainingMinutesFromHistory()); mCurrentTemp.put("rate", MainApp.getConfigBuilder().getTempBasalAbsoluteRateHistory()); @@ -262,7 +266,6 @@ public class DetermineBasalAdapterSMBJS { mIobData = IobCobCalculatorPlugin.convertToJSONArray(iobArray); mGlucoseStatus = new JSONObject(); - ; mGlucoseStatus.put("glucose", glucoseStatus.glucose); if (SP.getBoolean("always_use_shortavg", false)) { @@ -272,14 +275,17 @@ public class DetermineBasalAdapterSMBJS { } mGlucoseStatus.put("short_avgdelta", glucoseStatus.short_avgdelta); mGlucoseStatus.put("long_avgdelta", glucoseStatus.long_avgdelta); + mGlucoseStatus.put("date", glucoseStatus.date); mMealData = new JSONObject(); - ; mMealData.put("carbs", mealData.carbs); mMealData.put("boluses", mealData.boluses); mMealData.put("mealCOB", mealData.mealCOB); - mMealData.put("minDeviationSlope", mealData.minDeviationSlope); + mMealData.put("slopeFromMaxDeviation", mealData.slopeFromMaxDeviation); + mMealData.put("slopeFromMinDeviation", mealData.slopeFromMinDeviation); mMealData.put("lastBolusTime", mealData.lastBolusTime); + mMealData.put("lastCarbTime", mealData.lastCarbTime); + if (MainApp.getConfigBuilder().isAMAModeEnabled()) { mAutosensData = new JSONObject(); diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSSMB/OpenAPSSMBFragment.java b/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSSMB/OpenAPSSMBFragment.java index 1c0af1a67d..ef515c68f2 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSSMB/OpenAPSSMBFragment.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSSMB/OpenAPSSMBFragment.java @@ -65,8 +65,8 @@ public class OpenAPSSMBFragment extends SubscriberFragment implements View.OnCli public void onClick(View view) { switch (view.getId()) { case R.id.openapsma_run: - OpenAPSSMBPlugin.getPlugin().invoke("OpenAPSAMA button"); - Answers.getInstance().logCustom(new CustomEvent("OpenAPS_AMA_Run")); + OpenAPSSMBPlugin.getPlugin().invoke("OpenAPSSMB button"); + Answers.getInstance().logCustom(new CustomEvent("OpenAPS_SMB_Run")); break; } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSSMB/OpenAPSSMBPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSSMB/OpenAPSSMBPlugin.java index d449b30a29..a0f7483f34 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSSMB/OpenAPSSMBPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSSMB/OpenAPSSMBPlugin.java @@ -110,7 +110,7 @@ public class OpenAPSSMBPlugin implements PluginBase, APSInterface { @Override public int getPreferencesId() { - return R.xml.pref_openapsama; + return R.xml.pref_openapssmb; } @Override @@ -188,7 +188,7 @@ public class OpenAPSSMBPlugin implements PluginBase, APSInterface { Date start = new Date(); Date startPart = new Date(); - IobTotal[] iobArray = IobCobCalculatorPlugin.calculateIobArrayInDia(); + IobTotal[] iobArray = IobCobCalculatorPlugin.calculateIobArrayForSMB(); Profiler.log(log, "calculateIobArrayInDia()", startPart); startPart = new Date(); @@ -229,7 +229,7 @@ public class OpenAPSSMBPlugin implements PluginBase, APSInterface { lastAutosensResult = new AutosensResult(); } Profiler.log(log, "detectSensitivityandCarbAbsorption()", startPart); - Profiler.log(log, "AMA data gathering", start); + Profiler.log(log, "SMB data gathering", start); start = new Date(); try { @@ -263,7 +263,7 @@ public class OpenAPSSMBPlugin implements PluginBase, APSInterface { try { determineBasalResultSMB.json.put("timestamp", DateUtil.toISOString(now)); } catch (JSONException e) { - e.printStackTrace(); + log.error("Unhandled exception", e); } lastDetermineBasalAdapterSMBJS = determineBasalAdapterSMBJS; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSSMB/SMBDefaults.java b/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSSMB/SMBDefaults.java new file mode 100644 index 0000000000..57fecd43f6 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSSMB/SMBDefaults.java @@ -0,0 +1,64 @@ +package info.nightscout.androidaps.plugins.OpenAPSSMB; + +/** + * Created by mike on 10.12.2017. + */ + +public class SMBDefaults { + // CALCULATED OR FROM PREFS + + // max_iob: 0 // if max_iob is not provided, will default to zero + // max_daily_safety_multiplier:3 + // current_basal_safety_multiplier:4 + // autosens_max:1.2 + // autosens_min:0.7 + + // USED IN AUTOSENS + public final static boolean rewind_resets_autosens = true; // reset autosensitivity to neutral for awhile after each pump rewind + + // USED IN TARGETS + // by default the higher end of the target range is used only for avoiding bolus wizard overcorrections + // use wide_bg_target_range: true to force neutral temps over a wider range of eventualBGs + public final static boolean wide_bg_target_range = false; // by default use only the low end of the pump's BG target range as OpenAPS target + + // USED IN AUTOTUNE + public final static double autotune_isf_adjustmentFraction = 1.0; // keep autotune ISF closer to pump ISF via a weighted average of fullNewISF and pumpISF. 1.0 allows full adjustment, 0 is no adjustment from pump ISF. + public final static double remainingCarbsFraction = 1.0; // fraction of carbs we'll assume will absorb over 4h if we don't yet see carb absorption + + // USED IN DETERMINE_BASAL + public final static boolean low_temptarget_lowers_sensitivity = false; // lower sensitivity for temptargets <= 99. + public final static boolean high_temptarget_raises_sensitivity = false; // raise sensitivity for temptargets >= 111. synonym for exercise_mode + public final static boolean sensitivity_raises_target = true; // raise BG target when autosens detects sensitivity + public final static boolean resistance_lowers_target = false; // lower BG target when autosens detects resistance + public final static boolean adv_target_adjustments = false; // lower target automatically when BG and eventualBG are high + public final static boolean exercise_mode = false; // when true, > 105 mg/dL high temp target adjusts sensitivityRatio for exercise_mode. This majorly changes the behavior of high temp targets from before. synonmym for high_temptarget_raises_sensitivity + public final static int half_basal_exercise_target = 160; // when temptarget is 160 mg/dL *and* exercise_mode=true, run 50% basal at this level (120 = 75%; 140 = 60%) + // create maxCOB and default it to 120 because that's the most a typical body can absorb over 4 hours. + // (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) + public final static int maxCOB = 120; + public final static boolean skip_neutral_temps = true; // ***** default false in oref1 ***** if true, don't set neutral temps + // unsuspend_if_no_temp:false // if true, pump will un-suspend after a zero temp finishes + // bolussnooze_dia_divisor:2 // bolus snooze decays after 1/2 of DIA + public final static int 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)) + public final static int remainingCarbsCap = 90; // max carbs we'll assume will absorb over 4h if we don't yet see carb absorption + // WARNING: use SMB with caution: it can and will automatically bolus up to max_iob worth of extra insulin + // enableUAM:true // enable detection of unannounced meal carb absorption + public final static boolean A52_risk_enable = false; + public final static boolean enableSMB_with_COB = true; // ***** default false in oref1 ***** enable supermicrobolus while COB is positive + public final static boolean enableSMB_with_temptarget = true; // ***** default false in oref1 ***** enable supermicrobolus for eating soon temp targets + // *** WARNING *** DO NOT USE enableSMB_always or enableSMB_after_carbs with xDrip+, Libre, or similar + // xDrip+, LimiTTer, etc. do not properly filter out high-noise SGVs + // Using SMB overnight with such data sources risks causing a dangerous overdose of insulin + // if the CGM sensor reads falsely high and doesn't come down as actual BG does + public final static boolean enableSMB_always = false; // always enable supermicrobolus (unless disabled by high temptarget) + // *** WARNING *** DO NOT USE enableSMB_always or enableSMB_after_carbs with xDrip+, Libre, or similar + public final static boolean enableSMB_after_carbs = false; // enable supermicrobolus for 6h after carbs, even with 0 COB + public final static boolean allowSMB_with_high_temptarget = false; // allow supermicrobolus (if otherwise enabled) even with high temp targets + public final static int maxSMBBasalMinutes = 30; // maximum minutes of basal that can be delivered as a single SMB with uncovered COB + // curve:"rapid-acting" // Supported curves: "bilinear", "rapid-acting" (Novolog, Novorapid, Humalog, Apidra) and "ultra-rapid" (Fiasp) + // useCustomPeakTime:false // allows changing insulinPeakTime + // insulinPeakTime:75 // number of minutes after a bolus activity peaks. defaults to 55m for Fiasp if useCustomPeakTime: false + public final static int carbsReqThreshold = 1; // grams of carbsReq to trigger a pushover + // offline_hotspot:false // enabled an offline-only local wifi hotspot if no Internet available +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/Overview/graphData/GraphData.java b/app/src/main/java/info/nightscout/androidaps/plugins/Overview/graphData/GraphData.java index a0c2167391..7a27546c19 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/Overview/graphData/GraphData.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/Overview/graphData/GraphData.java @@ -398,21 +398,21 @@ public class GraphData { // scale in % of vertical size (like 0.3) public void addRatio(GraphView graph, long fromTime, long toTime, boolean useForScale, double scale) { - LineGraphSeries ratioSeries; - List ratioArray = new ArrayList<>(); + LineGraphSeries ratioSeries; + List ratioArray = new ArrayList<>(); Double maxRatioValueFound = 0d; Scale ratioScale = new Scale(-1d); for (long time = fromTime; time <= toTime; time += 5 * 60 * 1000L) { AutosensData autosensData = IobCobCalculatorPlugin.getAutosensData(time); if (autosensData != null) { - ratioArray.add(new DataPoint(time, autosensData.autosensRatio)); + ratioArray.add(new ScaledDataPoint(time, autosensData.autosensRatio, ratioScale)); maxRatioValueFound = Math.max(maxRatioValueFound, Math.abs(autosensData.autosensRatio)); } } // RATIOS - DataPoint[] ratioData = new DataPoint[ratioArray.size()]; + ScaledDataPoint[] ratioData = new ScaledDataPoint[ratioArray.size()]; ratioData = ratioArray.toArray(ratioData); ratioSeries = new LineGraphSeries<>(ratioData); ratioSeries.setColor(MainApp.sResources.getColor(R.color.ratio)); @@ -428,32 +428,46 @@ public class GraphData { // scale in % of vertical size (like 0.3) public void addDeviationSlope(GraphView graph, long fromTime, long toTime, boolean useForScale, double scale) { - LineGraphSeries dsSeries; - List dsArray = new ArrayList<>(); - Double maxDSValueFound = 0d; - Scale dsScale = new Scale(); + LineGraphSeries dsMaxSeries; + LineGraphSeries dsMinSeries; + List dsMaxArray = new ArrayList<>(); + List dsMinArray = new ArrayList<>(); + Double maxFromMaxValueFound = 0d; + Double maxFromMinValueFound = 0d; + Scale dsMaxScale = new Scale(); + Scale dsMinScale = new Scale(); for (long time = fromTime; time <= toTime; time += 5 * 60 * 1000L) { AutosensData autosensData = IobCobCalculatorPlugin.getAutosensData(time); if (autosensData != null) { - dsArray.add(new DataPoint(time, autosensData.minDeviationSlope)); - maxDSValueFound = Math.max(maxDSValueFound, Math.abs(autosensData.minDeviationSlope)); + dsMaxArray.add(new ScaledDataPoint(time, autosensData.slopeFromMaxDeviation, dsMaxScale)); + dsMinArray.add(new ScaledDataPoint(time, autosensData.slopeFromMinDeviation, dsMinScale)); + maxFromMaxValueFound = Math.max(maxFromMaxValueFound, Math.abs(autosensData.slopeFromMaxDeviation)); + maxFromMinValueFound = Math.max(maxFromMinValueFound, Math.abs(autosensData.slopeFromMinDeviation)); } } - // RATIOS - DataPoint[] ratioData = new DataPoint[dsArray.size()]; - ratioData = dsArray.toArray(ratioData); - dsSeries = new LineGraphSeries<>(ratioData); - dsSeries.setColor(Color.MAGENTA); - dsSeries.setThickness(3); + // Slopes + ScaledDataPoint[] ratioMaxData = new ScaledDataPoint[dsMaxArray.size()]; + ratioMaxData = dsMaxArray.toArray(ratioMaxData); + dsMaxSeries = new LineGraphSeries<>(ratioMaxData); + dsMaxSeries.setColor(Color.MAGENTA); + dsMaxSeries.setThickness(3); + + ScaledDataPoint[] ratioMinData = new ScaledDataPoint[dsMinArray.size()]; + ratioMinData = dsMinArray.toArray(ratioMinData); + dsMinSeries = new LineGraphSeries<>(ratioMinData); + dsMinSeries.setColor(Color.YELLOW); + dsMinSeries.setThickness(3); if (useForScale) - maxY = maxDSValueFound; + maxY = Math.max(maxFromMaxValueFound, maxFromMinValueFound); - dsScale.setMultiplier(maxY * scale / maxDSValueFound); + dsMaxScale.setMultiplier(maxY * scale / maxFromMaxValueFound); + dsMinScale.setMultiplier(maxY * scale / maxFromMinValueFound); - addSeriesWithoutInvalidate(graph, dsSeries); + addSeriesWithoutInvalidate(graph, dsMaxSeries); + addSeriesWithoutInvalidate(graph, dsMinSeries); } // scale in % of vertical size (like 0.3) diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/Treatments/TreatmentsPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/Treatments/TreatmentsPlugin.java index da34488116..05c90d27c4 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/Treatments/TreatmentsPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/Treatments/TreatmentsPlugin.java @@ -250,6 +250,7 @@ public class TreatmentsPlugin implements PluginBase, TreatmentsInterface { if (t > dia_ago && t <= now) { if (treatment.carbs >= 1) { result.carbs += treatment.carbs; + result.lastCarbTime = t; } if (treatment.insulin > 0 && treatment.mealBolus) { result.boluses += treatment.insulin; @@ -260,7 +261,8 @@ public class TreatmentsPlugin implements PluginBase, TreatmentsInterface { AutosensData autosensData = IobCobCalculatorPlugin.getLastAutosensData(); if (autosensData != null) { result.mealCOB = autosensData.cob; - result.minDeviationSlope = autosensData.minDeviationSlope; + result.slopeFromMinDeviation = autosensData.slopeFromMinDeviation; + result.slopeFromMaxDeviation = autosensData.slopeFromMaxDeviation; } result.lastBolusTime = getLastBolusTime(); return result; @@ -349,6 +351,12 @@ public class TreatmentsPlugin implements PluginBase, TreatmentsInterface { IobTotal calc = t.iobCalc(time); //log.debug("BasalIOB " + new Date(time) + " >>> " + calc.basaliob); total.plus(calc); + if (!t.isEndingEvent()) { + total.lastTempDate = t.date; + total.lastTempDuration = t.durationInMinutes; + total.lastTempRate = t.tempBasalConvertedToAbsolute(t.date); + } + } } if (ConfigBuilderPlugin.getActivePump().isFakingTempsByExtendedBoluses()) { @@ -359,6 +367,12 @@ public class TreatmentsPlugin implements PluginBase, TreatmentsInterface { if (e.date > time) continue; IobTotal calc = e.iobCalc(time); totalExt.plus(calc); + TemporaryBasal t = new TemporaryBasal(e); + if (!t.isEndingEvent() && t.date > total.lastTempDate) { + total.lastTempDate = t.date; + total.lastTempDuration = t.durationInMinutes; + total.lastTempRate = t.tempBasalConvertedToAbsolute(t.date); + } } } // Convert to basal iob diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index d068062ece..67def00b04 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -6,6 +6,7 @@ #FFFFCC03 #8BC34A #ffea00 + #ff9500 #FFFFFF #00FF00 #FF0000 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 8c01030d43..d8452d480a 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -792,6 +792,9 @@ Customized APK for download Show detailed delta Show delta with one more decimal place + 45 60 75 90 105 120 + Max minutes of basal to limit SMB to + smbmaxminutes Unsupported pump firmware diff --git a/app/src/main/res/xml/pref_advanced.xml b/app/src/main/res/xml/pref_advanced.xml index 8056db4e3a..4a617c29ec 100644 --- a/app/src/main/res/xml/pref_advanced.xml +++ b/app/src/main/res/xml/pref_advanced.xml @@ -35,18 +35,6 @@ android:summary="@string/always_use_shortavg_summary" android:title="@string/always_use_shortavg" /> - - - - - + diff --git a/app/src/main/res/xml/pref_openapssmb.xml b/app/src/main/res/xml/pref_openapssmb.xml new file mode 100644 index 0000000000..c8cca93a63 --- /dev/null +++ b/app/src/main/res/xml/pref_openapssmb.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + \ No newline at end of file