1211 lines
62 KiB
JavaScript
1211 lines
62 KiB
JavaScript
/*
|
||
Determine Basal
|
||
|
||
Released under MIT license. See the accompanying LICENSE.txt file for
|
||
full terms and conditions
|
||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||
THE SOFTWARE.
|
||
*/
|
||
|
||
|
||
var round_basal = require('../round-basal')
|
||
|
||
// Rounds value to 'digits' decimal places
|
||
function round(value, digits)
|
||
{
|
||
if (! digits) { digits = 0; }
|
||
var scale = Math.pow(10, digits);
|
||
return Math.round(value * scale) / scale;
|
||
}
|
||
|
||
// 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 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;
|
||
return /* expectedDelta */ round(bgi + (target_delta / five_min_blocks), 1);
|
||
}
|
||
|
||
|
||
function convert_bg(value, profile)
|
||
{
|
||
if (profile.out_units === "mmol/L")
|
||
{
|
||
return round(value / 18, 1).toFixed(1);
|
||
}
|
||
else
|
||
{
|
||
return Math.round(value);
|
||
}
|
||
}
|
||
|
||
function enable_smb(
|
||
profile,
|
||
microBolusAllowed,
|
||
meal_data,
|
||
target_bg
|
||
) {
|
||
// disable SMB when a high temptarget is set
|
||
if (! microBolusAllowed) {
|
||
console.error("SMB disabled (!microBolusAllowed)");
|
||
return false;
|
||
} else if (! profile.allowSMB_with_high_temptarget && profile.temptargetSet && target_bg > 100) {
|
||
console.error("SMB disabled due to high temptarget of",target_bg);
|
||
return false;
|
||
} else if (meal_data.bwFound === true && profile.A52_risk_enable === false) {
|
||
console.error("SMB disabled due to Bolus Wizard activity in the last 6 hours.");
|
||
return false;
|
||
}
|
||
|
||
// enable SMB/UAM if always-on (unless previously disabled for high temptarget)
|
||
if (profile.enableSMB_always === true) {
|
||
if (meal_data.bwFound) {
|
||
console.error("Warning: SMB enabled within 6h of using Bolus Wizard: be sure to easy bolus 30s before using Bolus Wizard");
|
||
} else {
|
||
console.error("SMB enabled due to enableSMB_always");
|
||
}
|
||
return true;
|
||
}
|
||
|
||
// enable SMB/UAM (if enabled in preferences) while we have COB
|
||
if (profile.enableSMB_with_COB === true && meal_data.mealCOB) {
|
||
if (meal_data.bwCarbs) {
|
||
console.error("Warning: SMB enabled with Bolus Wizard carbs: be sure to easy bolus 30s before using Bolus Wizard");
|
||
} else {
|
||
console.error("SMB enabled for COB of",meal_data.mealCOB);
|
||
}
|
||
return 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)
|
||
if (profile.enableSMB_after_carbs === true && meal_data.carbs ) {
|
||
if (meal_data.bwCarbs) {
|
||
console.error("Warning: SMB enabled with Bolus Wizard carbs: be sure to easy bolus 30s before using Bolus Wizard");
|
||
} else {
|
||
console.error("SMB enabled for 6h after carb entry");
|
||
}
|
||
return true;
|
||
}
|
||
|
||
// enable SMB/UAM (if enabled in preferences) if a low temptarget is set
|
||
if (profile.enableSMB_with_temptarget === true && (profile.temptargetSet && target_bg < 100)) {
|
||
if (meal_data.bwFound) {
|
||
console.error("Warning: SMB enabled within 6h of using Bolus Wizard: be sure to easy bolus 30s before using Bolus Wizard");
|
||
} else {
|
||
console.error("SMB enabled for temptarget of",convert_bg(target_bg, profile));
|
||
}
|
||
return true;
|
||
}
|
||
|
||
console.error("SMB disabled (no enableSMB preferences active or no condition satisfied)");
|
||
return false;
|
||
}
|
||
|
||
var determine_basal = function determine_basal(glucose_status, currenttemp, iob_data, profile, autosens_data, meal_data, tempBasalFunctions, microBolusAllowed, reservoir_data, currentTime, flatBGsDetected) {
|
||
var rT = {}; //short for requestedTemp
|
||
|
||
var deliverAt = new Date();
|
||
if (currentTime) {
|
||
deliverAt = new Date(currentTime);
|
||
}
|
||
|
||
if (typeof profile === 'undefined' || typeof profile.current_basal === 'undefined') {
|
||
rT.error ='Error: could not get current basal rate';
|
||
return rT;
|
||
}
|
||
var profile_current_basal = round_basal(profile.current_basal, profile);
|
||
var basal = profile_current_basal;
|
||
|
||
var systemTime = new Date();
|
||
if (currentTime) {
|
||
systemTime = currentTime;
|
||
}
|
||
var bgTime = new Date(glucose_status.date);
|
||
var minAgo = round( (systemTime - bgTime) / 60 / 1000 ,1);
|
||
|
||
var bg = glucose_status.glucose;
|
||
var noise = glucose_status.noise;
|
||
// 38 is an xDrip error state that usually indicates sensor failure
|
||
// all other BG values between 11 and 37 mg/dL reflect non-error-code BG values, so we should zero temp for those
|
||
if (bg <= 10 || bg === 38 || noise >= 3) { //Dexcom is in ??? mode or calibrating, or xDrip reports high noise
|
||
rT.reason = "CGM is calibrating, in ??? state, or noise is high";
|
||
}
|
||
if (minAgo > 12 || minAgo < -5) { // Dexcom data is too old, or way in the future
|
||
rT.reason = "If current system time "+systemTime+" is correct, then BG data is too old. The last BG data was read "+minAgo+"m ago at "+bgTime;
|
||
// if BG is too old/noisy, or is changing less than 1 mg/dL/5m for 45m, cancel any high temps and shorten any long zero temps
|
||
} else if ( bg > 60 && flatBGsDetected) {
|
||
if ( glucose_status.last_cal && glucose_status.last_cal < 3 ) {
|
||
rT.reason = "CGM was just calibrated";
|
||
} else {
|
||
rT.reason = "Error: CGM data is unchanged for the past ~45m";
|
||
}
|
||
}
|
||
if (bg <= 10 || bg === 38 || noise >= 3 || minAgo > 12 || minAgo < -5 || ( bg > 60 && flatBGsDetected )) {
|
||
if (currenttemp.rate > basal) { // high temp is running
|
||
rT.reason += ". Replacing high temp basal of "+currenttemp.rate+" with neutral temp of "+basal;
|
||
rT.deliverAt = deliverAt;
|
||
rT.temp = 'absolute';
|
||
rT.duration = 30;
|
||
rT.rate = basal;
|
||
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 " + round(basal, 2) + "U/hr; doing nothing. ";
|
||
return rT;
|
||
}
|
||
}
|
||
|
||
var max_iob = profile.max_iob; // maximum amount of non-bolus IOB OpenAPS will ever deliver
|
||
|
||
// if min and max are set, then set target to their average
|
||
var target_bg;
|
||
var min_bg;
|
||
var max_bg;
|
||
if (typeof profile.min_bg !== 'undefined') {
|
||
min_bg = profile.min_bg;
|
||
}
|
||
if (typeof profile.max_bg !== 'undefined') {
|
||
max_bg = profile.max_bg;
|
||
}
|
||
if (typeof profile.min_bg !== 'undefined' && typeof profile.max_bg !== 'undefined') {
|
||
target_bg = (profile.min_bg + profile.max_bg) / 2;
|
||
} else {
|
||
rT.error ='Error: could not determine target_bg. ';
|
||
return rT;
|
||
}
|
||
|
||
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 target (which might change)
|
||
if ( profile.half_basal_exercise_target ) {
|
||
var halfBasalTarget = profile.half_basal_exercise_target;
|
||
} else {
|
||
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)
|
||
}
|
||
|
||
//*********************************************************************************
|
||
//** Start of Dynamic ISF code for predictions **
|
||
//*********************************************************************************
|
||
|
||
console.error("---------------------------------------------------------");
|
||
console.error( " Dynamic ISF version Beta 2.0 ");
|
||
console.error("---------------------------------------------------------");
|
||
|
||
var variable_sens = profile.variable_sens;
|
||
var TDD = profile.TDD;
|
||
var insulinDivisor = profile.insulinDivisor;
|
||
|
||
//*********************************************************************************
|
||
//** End of Dynamic ISF code for predictions **
|
||
//*********************************************************************************
|
||
|
||
|
||
if ( high_temptarget_raises_sensitivity && profile.temptargetSet && target_bg > normalTarget
|
||
|| 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.log("Sensitivity ratio set to "+sensitivityRatio+" based on temp target of "+target_bg+"; ");
|
||
} else if (typeof autosens_data !== 'undefined' && autosens_data) {
|
||
sensitivityRatio = autosens_data.ratio;
|
||
console.log("Autosens ratio: "+sensitivityRatio+"; ");
|
||
}
|
||
if (sensitivityRatio) {
|
||
basal = profile.current_basal * sensitivityRatio;
|
||
basal = round_basal(basal, profile);
|
||
if (basal !== profile_current_basal) {
|
||
console.log("Adjusting basal from "+profile_current_basal+" to "+basal+"; ");
|
||
} else {
|
||
console.log("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.log("Temp Target set, not adjusting with autosens; ");
|
||
} else if (typeof autosens_data !== 'undefined' && autosens_data) {
|
||
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;
|
||
var 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.log("target_bg unchanged: "+new_target_bg+"; ");
|
||
} else {
|
||
console.log("target_bg from "+target_bg+" to "+new_target_bg+"; ");
|
||
}
|
||
target_bg = new_target_bg;
|
||
}
|
||
}
|
||
|
||
if (typeof iob_data === 'undefined' ) {
|
||
rT.error ='Error: iob_data undefined. ';
|
||
return rT;
|
||
}
|
||
|
||
var iobArray = iob_data;
|
||
if (typeof(iob_data.length) && iob_data.length > 1) {
|
||
iob_data = iobArray[0];
|
||
//console.error(JSON.stringify(iob_data[0]));
|
||
}
|
||
|
||
if (typeof iob_data.activity === 'undefined' || typeof iob_data.iob === 'undefined' ) {
|
||
rT.error ='Error: iob_data missing some property. ';
|
||
return rT;
|
||
}
|
||
|
||
var tick;
|
||
|
||
if (glucose_status.delta > -0.5) {
|
||
tick = "+" + round(glucose_status.delta,0);
|
||
} else {
|
||
tick = round(glucose_status.delta,0);
|
||
}
|
||
//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 sens = variable_sens
|
||
|
||
// 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(systemTime).getTime() - iob_data.lastTemp.date ) / 60000); // in minutes
|
||
} else {
|
||
lastTempAge = 0;
|
||
}
|
||
//console.error("currenttemp:",currenttemp,"lastTemp:",JSON.stringify(iob_data.lastTemp),"lastTempAge:",lastTempAge,"m");
|
||
var tempModulus = (lastTempAge + currenttemp.duration) % 30;
|
||
console.error("currenttemp:",round(currenttemp.rate,2),"lastTempAge:",lastTempAge,"m","tempModulus:",tempModulus,"m");
|
||
rT.temp = 'absolute';
|
||
rT.deliverAt = deliverAt;
|
||
if ( microBolusAllowed && currenttemp && iob_data.lastTemp && currenttemp.rate !== iob_data.lastTemp.rate && lastTempAge > 10 && currenttemp.duration ) {
|
||
rT.reason = "Warning: currenttemp rate "+currenttemp.rate+" != lastTemp rate "+iob_data.lastTemp.rate+" from pumphistory; canceling temp";
|
||
return tempBasalFunctions.setTempBasal(0, 0, 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 && lastTempAge > 10 ) {
|
||
rT.reason = "Warning: currenttemp running but lastTemp from pumphistory ended "+lastTempEnded+"m ago; canceling temp";
|
||
//console.error(currenttemp, round(iob_data.lastTemp,1), round(lastTempAge,1));
|
||
return tempBasalFunctions.setTempBasal(0, 0, 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);
|
||
//}
|
||
}
|
||
|
||
//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);
|
||
// project deviations for 30 minutes
|
||
var deviation = round( 30 / 5 * ( minDelta - bgi ) );
|
||
// 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
|
||
if (iob_data.iob > 0) {
|
||
var naive_eventualBG = round( bg - (iob_data.iob * sens) );
|
||
} else { // if IOB is negative, be more conservative and use the lower of sens, profile.sens
|
||
naive_eventualBG = round( bg - (iob_data.iob * sens ) );
|
||
}
|
||
// and adjust it for the deviation above
|
||
var eventualBG = naive_eventualBG + deviation;
|
||
|
||
// raise target for noisy / raw CGM data
|
||
if (glucose_status.noise >= 2) {
|
||
// increase target at least 10% (default 30%) for raw / noisy data
|
||
var noisyCGMTargetMultiplier = Math.max( 1.1, profile.noisyCGMTargetMultiplier );
|
||
// don't allow maxRaw above 250
|
||
var maxRaw = Math.min( 250, profile.maxRaw );
|
||
var adjustedMinBG = round(Math.min(200, min_bg * noisyCGMTargetMultiplier ));
|
||
var adjustedTargetBG = round(Math.min(200, target_bg * noisyCGMTargetMultiplier ));
|
||
var adjustedMaxBG = round(Math.min(200, max_bg * noisyCGMTargetMultiplier ));
|
||
console.log("Raising target_bg for noisy / raw CGM data, from "+target_bg+" to "+adjustedTargetBG+"; ");
|
||
min_bg = adjustedMinBG;
|
||
target_bg = adjustedTargetBG;
|
||
max_bg = adjustedMaxBG;
|
||
// adjust target BG range if configured to bring down high BG faster
|
||
} else 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
|
||
adjustedMinBG = round(Math.max(80, min_bg - (bg - min_bg)/3 ),0);
|
||
adjustedTargetBG =round( Math.max(80, target_bg - (bg - target_bg)/3 ),0);
|
||
adjustedMaxBG = round(Math.max(80, max_bg - (bg - max_bg)/3 ),0);
|
||
// if eventualBG, naive_eventualBG, and target_bg aren't all above adjustedMinBG, don’t use it
|
||
//console.error("naive_eventualBG:",naive_eventualBG+", eventualBG:",eventualBG);
|
||
if (eventualBG > adjustedMinBG && naive_eventualBG > adjustedMinBG && min_bg > adjustedMinBG) {
|
||
console.log("Adjusting targets for high BG: min_bg from "+min_bg+" to "+adjustedMinBG+"; ");
|
||
min_bg = adjustedMinBG;
|
||
} else {
|
||
console.log("min_bg unchanged: "+min_bg+"; ");
|
||
}
|
||
// if eventualBG, naive_eventualBG, and target_bg aren't all above adjustedTargetBG, don’t use it
|
||
if (eventualBG > adjustedTargetBG && naive_eventualBG > adjustedTargetBG && target_bg > adjustedTargetBG) {
|
||
console.log("target_bg from "+target_bg+" to "+adjustedTargetBG+"; ");
|
||
target_bg = adjustedTargetBG;
|
||
} else {
|
||
console.log("target_bg unchanged: "+target_bg+"; ");
|
||
}
|
||
// if eventualBG, naive_eventualBG, and max_bg aren't all above adjustedMaxBG, don’t use it
|
||
if (eventualBG > adjustedMaxBG && naive_eventualBG > adjustedMaxBG && max_bg > adjustedMaxBG) {
|
||
console.error("max_bg from "+max_bg+" to "+adjustedMaxBG);
|
||
max_bg = adjustedMaxBG;
|
||
} else {
|
||
console.error("max_bg unchanged: "+max_bg);
|
||
}
|
||
}
|
||
|
||
var expectedDelta = calculate_expected_delta(target_bg, eventualBG, bgi);
|
||
if (typeof eventualBG === 'undefined' || isNaN(eventualBG)) {
|
||
rT.error ='Error: could not calculate eventualBG. ';
|
||
return rT;
|
||
}
|
||
|
||
// min_bg of 90 -> threshold of 65, 100 -> 70 110 -> 75, and 130 -> 85, or if specified by user, take that value
|
||
var lgsThreshold = profile.lgsThreshold;
|
||
var threshold = min_bg - 0.5*(min_bg-40);
|
||
var oldThreshold = threshold;
|
||
if (lgsThreshold >= 65 && lgsThreshold <= 120 && lgsThreshold > threshold) {
|
||
threshold = lgsThreshold;
|
||
}
|
||
console.error("Threshold set from " + convert_bg(oldThreshold, profile) + " to " + convert_bg(threshold, profile) + "; ");
|
||
|
||
//console.error(reservoir_data);
|
||
|
||
rT = {
|
||
'temp': 'absolute'
|
||
, 'bg': bg
|
||
, 'tick': tick
|
||
, 'eventualBG': eventualBG
|
||
, 'targetBG': target_bg
|
||
, 'insulinReq': 0
|
||
, '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
|
||
, 'sensitivityRatio' : sensitivityRatio // autosens ratio (fraction of normal basal)
|
||
, 'variable_sens' : variable_sens
|
||
};
|
||
|
||
// 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);
|
||
|
||
var enableSMB = enable_smb(
|
||
profile,
|
||
microBolusAllowed,
|
||
meal_data,
|
||
target_bg
|
||
);
|
||
|
||
// enable UAM (if enabled in preferences)
|
||
var enableUAM=(profile.enableUAM);
|
||
|
||
|
||
//console.error(meal_data);
|
||
// carb impact and duration are 0 unless changed below
|
||
var ci = 0;
|
||
var cid = 0;
|
||
// 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);
|
||
var uci = round((minDelta - bgi),1);
|
||
// ISF (mg/dL/U) / CR (g/U) = CSF (mg/dL/g)
|
||
|
||
// TODO: remove commented-out code for old behavior
|
||
//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;
|
||
//}
|
||
// 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 TT or autosens
|
||
// this avoids overdosing insulin for large meals when low temp targets are active
|
||
csf = sens / profile.carb_ratio;
|
||
console.error("profile.sens:",profile.sens,"sens:",sens,"CSF:",csf);
|
||
|
||
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
|
||
var 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;
|
||
}
|
||
var remainingCATimeMin = 3; // h; duration of expected not-yet-observed carb absorption
|
||
// adjust remainingCATime (instead of CR) for autosens if sensitivityRatio defined
|
||
if (sensitivityRatio){
|
||
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 = remainingCATimeMin;
|
||
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(systemTime).getTime() - meal_data.lastCarbTime ) / 60000);
|
||
//console.error(meal_data.lastCarbTime, lastCarbAge);
|
||
|
||
var 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 * remainingCATime / 2);
|
||
// totalCI (mg/dL) / CSF (mg/dL/g) = total carbs absorbed (g)
|
||
var totalCA = totalCI / csf;
|
||
var remainingCarbsCap = 90; // default to 90
|
||
var remainingCarbsFraction = 1;
|
||
if (profile.remainingCarbsCap) { remainingCarbsCap = Math.min(90,profile.remainingCarbsCap); }
|
||
if (profile.remainingCarbsFraction) { remainingCarbsFraction = Math.min(1,profile.remainingCarbsFraction); }
|
||
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 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);
|
||
|
||
// calculate peak deviation in last hour, and slope from that to current deviation
|
||
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);
|
||
|
||
var 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)
|
||
// 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 ));
|
||
}
|
||
var 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 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;
|
||
var maxIOBPredBG = bg;
|
||
var maxCOBPredBG = bg;
|
||
var maxUAMPredBG = bg;
|
||
//var maxPredBG = bg;
|
||
var eventualPredBG = bg;
|
||
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);
|
||
var predBGI = round(( -iobTick.activity * sens * 5 ), 2);
|
||
var 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)
|
||
var predDev = ci * ( 1 - Math.min(1,IOBpredBGs.length/(60/5)) );
|
||
//IOBpredBG = IOBpredBGs[IOBpredBGs.length-1] + predBGI + predDev;
|
||
IOBpredBG = IOBpredBGs[IOBpredBGs.length-1] + (round(( -iobTick.activity * (1800 / ( TDD * (Math.log((Math.max( IOBpredBGs[IOBpredBGs.length-1],39) / insulinDivisor ) + 1 ) ) ))
|
||
* 5 ),2)) + predDev;
|
||
|
||
// calculate predBGs with long zero temp without deviations
|
||
//var ZTpredBG = ZTpredBGs[ZTpredBGs.length-1] + predZTBGI;
|
||
var ZTpredBG = ZTpredBGs[ZTpredBGs.length-1] + (round(( -iobTick.iobWithZeroTemp.activity * (1800 / ( TDD * (Math.log(( Math.max(ZTpredBGs[ZTpredBGs.length-1],39) /
|
||
insulinDivisor ) + 1 ) ) )) * 5 ), 2));
|
||
// 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)
|
||
var predCI = Math.max(0, Math.max(0,ci) * ( 1 - COBpredBGs.length/Math.max(cid*2,1) ) );
|
||
var predACI = Math.max(0, Math.max(0,aci) * ( 1 - COBpredBGs.length/Math.max(acid*2,1) ) );
|
||
// 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.log(round(predCI,1)+"+"+round(remainingCI,1)+" ");
|
||
COBpredBG = COBpredBGs[COBpredBGs.length-1] + predBGI + Math.min(0,predDev) + predCI + remainingCI;
|
||
var aCOBpredBG = aCOBpredBGs[aCOBpredBGs.length-1] + predBGI + Math.min(0,predDev) + predACI;
|
||
// for UAMpredBGs, predicted carb impact drops at slopeFromDeviations
|
||
// calculate predicted CI from UAM based on slopeFromDeviations
|
||
var 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)
|
||
var 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
|
||
var predUCI = Math.min(predUCIslope, predUCImax);
|
||
if(predUCI>0) {
|
||
//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;
|
||
UAMpredBG = UAMpredBGs[UAMpredBGs.length-1] + (round(( -iobTick.activity * (1800 / ( TDD
|
||
* (Math.log(( Math.max(UAMpredBGs[UAMpredBGs.length-1],39) / insulinDivisor ) + 1 ) ) )) * 5 ),2)) + Math.min(0, predDev) + predUCI;
|
||
//console.error(predBGI, predCI, predUCI);
|
||
// 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 insulin 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 > insulinPeak5m && (IOBpredBG < minIOBPredBG) ) { minIOBPredBG = round(IOBpredBG); }
|
||
if ( IOBpredBG > maxIOBPredBG ) { maxIOBPredBG = IOBpredBG; }
|
||
// 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; }
|
||
});
|
||
// set eventualBG to include effect of carbs
|
||
//console.error("PredBGs:",JSON.stringify(predBGs));
|
||
} catch (e) {
|
||
console.error("Problem with iobArray. Optional feature Advanced Meal Assist disabled");
|
||
}
|
||
if (meal_data.mealCOB) {
|
||
console.error("predCIs (mg/dL/5m):",predCIs.join(" "));
|
||
console.error("remainingCIs: ",remainingCIs.join(" "));
|
||
}
|
||
rT.predBGs = {};
|
||
IOBpredBGs.forEach(function(p, i, theArray) {
|
||
theArray[i] = round(Math.min(401,Math.max(39,p)));
|
||
});
|
||
for (var i=IOBpredBGs.length-1; i > 12; i--) {
|
||
if (IOBpredBGs[i-1] !== IOBpredBGs[i]) { break; }
|
||
else { IOBpredBGs.pop(); }
|
||
}
|
||
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 (i=ZTpredBGs.length-1; i > 6; i--) {
|
||
// 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)));
|
||
});
|
||
for (i=aCOBpredBGs.length-1; i > 12; i--) {
|
||
if (aCOBpredBGs[i-1] !== aCOBpredBGs[i]) { break; }
|
||
else { aCOBpredBGs.pop(); }
|
||
}
|
||
}
|
||
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)));
|
||
});
|
||
for (i=COBpredBGs.length-1; i > 12; i--) {
|
||
if (COBpredBGs[i-1] !== COBpredBGs[i]) { break; }
|
||
else { COBpredBGs.pop(); }
|
||
}
|
||
rT.predBGs.COB = COBpredBGs;
|
||
lastCOBpredBG=round(COBpredBGs[COBpredBGs.length-1]);
|
||
eventualBG = Math.max(eventualBG, round(COBpredBGs[COBpredBGs.length-1]) );
|
||
}
|
||
if (ci > 0 || remainingCIpeak > 0) {
|
||
if (enableUAM) {
|
||
UAMpredBGs.forEach(function(p, i, theArray) {
|
||
theArray[i] = round(Math.min(401,Math.max(39,p)));
|
||
});
|
||
for (i=UAMpredBGs.length-1; i > 12; i--) {
|
||
if (UAMpredBGs[i-1] !== UAMpredBGs[i]) { break; }
|
||
else { UAMpredBGs.pop(); }
|
||
}
|
||
rT.predBGs.UAM = UAMpredBGs;
|
||
lastUAMpredBG=round(UAMpredBGs[UAMpredBGs.length-1]);
|
||
if (UAMpredBGs[UAMpredBGs.length-1]) {
|
||
eventualBG = Math.max(eventualBG, round(UAMpredBGs[UAMpredBGs.length-1]) );
|
||
}
|
||
}
|
||
|
||
// set eventualBG based on COB or UAM predBGs
|
||
rT.eventualBG = eventualBG;
|
||
}
|
||
|
||
console.error("UAM Impact:",uci,"mg/dL per 5m; UAM Duration:",UAMduration,"hours");
|
||
|
||
console.log("EventualBG is" +eventualBG+" ;");
|
||
|
||
minIOBPredBG = Math.max(39,minIOBPredBG);
|
||
minCOBPredBG = Math.max(39,minCOBPredBG);
|
||
minUAMPredBG = Math.max(39,minUAMPredBG);
|
||
minPredBG = round(minIOBPredBG);
|
||
|
||
var fSensBG = Math.min(minPredBG,bg);
|
||
|
||
if (bg > target_bg && glucose_status.delta < 3 && glucose_status.delta > -3 && glucose_status.short_avgdelta > -3 && glucose_status.short_avgdelta < 3 && eventualBG > target_bg && eventualBG < bg ) {
|
||
var future_sens = ( 1800 / (Math.log((((fSensBG * 0.5) + (bg * 0.5))/insulinDivisor)+1)*TDD));
|
||
//var future_sens_old = ( 277700 / (TDD * ((bg * 0.5) + (eventualBG * 0.5 ))));
|
||
console.log("Future state sensitivity is " +future_sens+" based on eventual and current bg due to flat glucose level above target");
|
||
rT.reason += "Dosing sensitivity: " +future_sens+" using eventual BG;";
|
||
}
|
||
|
||
else if( glucose_status.delta > 0 && eventualBG > target_bg || eventualBG > bg) {
|
||
var future_sens = ( 1800 / (Math.log((bg/insulinDivisor)+1)*TDD));
|
||
//var future_sens_old = ( 277700 / (TDD * bg));
|
||
console.log("Future state sensitivity is " +future_sens+" using current bg due to small delta or variation");
|
||
rT.reason += "Dosing sensitivity: " +future_sens+" using current BG;";
|
||
}
|
||
|
||
else {
|
||
var future_sens = ( 1800 / (Math.log((fSensBG/insulinDivisor)+1)*TDD));
|
||
//var future_sens_old = ( 277700 / (TDD * eventualBG));
|
||
console.log("Future state sensitivity is " +future_sens+" based on eventual bg due to -ve delta");
|
||
rT.reason += "Dosing sensitivity: " +future_sens+" using eventual BG;";
|
||
}
|
||
future_sens = round(future_sens,1);
|
||
|
||
|
||
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 < 999 ) {
|
||
avgPredBG = round( (IOBpredBG + COBpredBG)/2 );
|
||
// 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) {
|
||
|
||
// if UAM is disabled, use max of minIOBPredBG, minCOBPredBG
|
||
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 < 999 ) {
|
||
// calculate blendedMinPredBG based on how many carbs remain as COB
|
||
var 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 minUAMPredBG
|
||
} else if ( enableUAM ) {
|
||
minPredBG = minZTUAMPredBG;
|
||
} else {
|
||
minPredBG = minGuardBG;
|
||
}
|
||
// in pure UAM mode, use the higher of minIOBPredBG,minUAMPredBG
|
||
} else if ( enableUAM ) {
|
||
minPredBG = round(Math.max(minIOBPredBG,minZTUAMPredBG));
|
||
}
|
||
|
||
// make sure minPredBG isn't higher than avgPredBG
|
||
minPredBG = Math.min( minPredBG, avgPredBG );
|
||
|
||
console.log("minPredBG: "+minPredBG+" minIOBPredBG: "+minIOBPredBG+" minZTGuardBG: "+minZTGuardBG);
|
||
if (minCOBPredBG < 999) {
|
||
console.log(" minCOBPredBG: "+minCOBPredBG);
|
||
}
|
||
if (minUAMPredBG < 999) {
|
||
console.log(" minUAMPredBG: "+minUAMPredBG);
|
||
}
|
||
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);
|
||
}
|
||
|
||
rT.COB=meal_data.mealCOB;
|
||
rT.IOB=iob_data.iob;
|
||
rT.reason="COB: " + round(meal_data.mealCOB, 1) + ", 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);
|
||
}
|
||
if (lastUAMpredBG > 0) {
|
||
rT.reason += ", UAMpredBG " + convert_bg(lastUAMpredBG, profile)
|
||
}
|
||
rT.reason += "; ";
|
||
// use naive_eventualBG if above 40, but switch to minGuardBG if both eventualBGs hit floor of 39
|
||
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;
|
||
var minutesAboveThreshold = 240;
|
||
if (meal_data.mealCOB > 0 && ( ci > 0 || remainingCIpeak > 0 )) {
|
||
for (i=0; i<COBpredBGs.length; i++) {
|
||
//console.error(COBpredBGs[i], min_bg);
|
||
if ( COBpredBGs[i] < min_bg ) {
|
||
minutesAboveMinBG = 5*i;
|
||
break;
|
||
}
|
||
}
|
||
for (i=0; i<COBpredBGs.length; i++) {
|
||
//console.error(COBpredBGs[i], threshold);
|
||
if ( COBpredBGs[i] < threshold ) {
|
||
minutesAboveThreshold = 5*i;
|
||
break;
|
||
}
|
||
}
|
||
} else {
|
||
for (i=0; i<IOBpredBGs.length; i++) {
|
||
//console.error(IOBpredBGs[i], min_bg);
|
||
if ( IOBpredBGs[i] < min_bg ) {
|
||
minutesAboveMinBG = 5*i;
|
||
break;
|
||
}
|
||
}
|
||
for (i=0; i<IOBpredBGs.length; i++) {
|
||
//console.error(IOBpredBGs[i], threshold);
|
||
if ( IOBpredBGs[i] < threshold ) {
|
||
minutesAboveThreshold = 5*i;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
if (enableSMB && minGuardBG < threshold) {
|
||
console.error("minGuardBG",convert_bg(minGuardBG, profile),"projected below", convert_bg(threshold, profile) ,"- disabling SMB");
|
||
//rT.reason += "minGuardBG "+minGuardBG+"<"+threshold+": SMB disabled; ";
|
||
enableSMB = false;
|
||
}
|
||
if ( maxDelta > 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 minutesAboveThreshold 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 = 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;
|
||
// 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("naive_eventualBG:",naive_eventualBG,"bgUndershoot:",bgUndershoot,"zeroTempDuration:",zeroTempDuration,"zeroTempEffect:",zeroTempEffect,"carbsReq:",carbsReq);
|
||
if ( carbsReq >= profile.carbsReqThreshold && minutesAboveThreshold <= 45 ) {
|
||
rT.carbsReq = carbsReq;
|
||
rT.carbsReqWithin = minutesAboveThreshold;
|
||
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 " + 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);
|
||
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 not in LGS mode, cancel temps before the top of the hour to reduce beeping/vibration
|
||
// console.error(profile.skip_neutral_temps, rT.deliverAt.getMinutes());
|
||
if ( profile.skip_neutral_temps && rT.deliverAt.getMinutes() >= 55 ) {
|
||
rT.reason += "; Canceling temp at " + rT.deliverAt.getMinutes() + "m past the hour. ";
|
||
return tempBasalFunctions.setTempBasal(0, 0, 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 && !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 " + convert_bg(tick, profile) + " > expectedDelta " + convert_bg(expectedDelta, profile);
|
||
} else {
|
||
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 " + round(basal, 2) + "U/hr. ";
|
||
return rT;
|
||
} else {
|
||
rT.reason += "; setting current basal of " + round(basal, 2) + " as temp. ";
|
||
return tempBasalFunctions.setTempBasal(basal, 30, profile, rT, currenttemp);
|
||
}
|
||
}
|
||
|
||
// calculate 30m low-temp required to get projected BG up to target
|
||
// multiply by 2 to low-temp faster for increased hypo safety
|
||
var insulinReq = 2 * Math.min(0, (eventualBG - target_bg) / future_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
|
||
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 " + round(rate, 2) + "U/hr. ";
|
||
return rT;
|
||
} else {
|
||
// calculate a long enough zero temp to eventually correct back up to target
|
||
if ( rate <=0 ) {
|
||
bgUndershoot = target_bg - naive_eventualBG;
|
||
worstCaseInsulinReq = bgUndershoot / sens;
|
||
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);
|
||
if (durationReq > 0) {
|
||
rT.reason += ", setting " + durationReq + "m zero temp. ";
|
||
return tempBasalFunctions.setTempBasal(rate, durationReq, profile, rT, currenttemp);
|
||
}
|
||
} else {
|
||
rT.reason += ", setting " + round(rate, 2) + "U/hr. ";
|
||
}
|
||
return tempBasalFunctions.setTempBasal(rate, 30, profile, rT, currenttemp);
|
||
}
|
||
}
|
||
|
||
// 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 " + 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 " + convert_bg(expectedDelta, profile);
|
||
}
|
||
if (currenttemp.duration > 15 && (round_basal(basal, profile) === round_basal(currenttemp.rate, profile))) {
|
||
rT.reason += ", temp " + currenttemp.rate + " ~ req " + round(basal, 2) + "U/hr. ";
|
||
return rT;
|
||
} else {
|
||
rT.reason += "; setting current basal of " + round(basal, 2) + " as temp. ";
|
||
return tempBasalFunctions.setTempBasal(basal, 30, profile, rT, currenttemp);
|
||
}
|
||
}
|
||
}
|
||
// 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(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 " + round(basal, 2) + "U/hr. ";
|
||
return rT;
|
||
} else {
|
||
rT.reason += "; setting current basal of " + round(basal, 2) + " as temp. ";
|
||
return tempBasalFunctions.setTempBasal(basal, 30, profile, rT, currenttemp);
|
||
}
|
||
}
|
||
}
|
||
|
||
// eventual BG is at/above target
|
||
// if iob is over max, just cancel any temps
|
||
if ( eventualBG >= max_bg ) {
|
||
rT.reason += "Eventual BG " + convert_bg(eventualBG, profile) + " >= " + convert_bg(max_bg, profile) + ", ";
|
||
}
|
||
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 " + round(basal, 2) + "U/hr. ";
|
||
return rT;
|
||
} else {
|
||
rT.reason += "; setting current basal of " + round(basal, 2) + " as temp. ";
|
||
return tempBasalFunctions.setTempBasal(basal, 30, profile, rT, currenttemp);
|
||
}
|
||
} 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,eventualBG);
|
||
insulinReq = round( (Math.min(minPredBG,eventualBG) - target_bg) / future_sens, 2);
|
||
// if that would put us over max_iob, then reduce accordingly
|
||
if (insulinReq > max_iob-iob_data.iob) {
|
||
rT.reason += "max_iob " + max_iob + ", ";
|
||
insulinReq = max_iob-iob_data.iob;
|
||
}
|
||
|
||
// rate required to deliver insulinReq more insulin over 30m:
|
||
rate = basal + (2 * insulinReq);
|
||
rate = round_basal(rate, profile);
|
||
insulinReq = round(insulinReq,3);
|
||
rT.insulinReq = insulinReq;
|
||
//console.error(iob_data.lastBolusTime);
|
||
// minutes since last bolus
|
||
var lastBolusAge = round(( new Date(systemTime).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
|
||
if (microBolusAllowed && enableSMB && bg > threshold) {
|
||
// never bolus more than maxSMBBasalMinutes worth of basal
|
||
var mealInsulinReq = round( meal_data.mealCOB / profile.carb_ratio ,3);
|
||
if (typeof profile.maxSMBBasalMinutes === 'undefined' ) {
|
||
var 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);
|
||
if (profile.maxUAMSMBBasalMinutes) {
|
||
console.error("profile.maxUAMSMBBasalMinutes:",profile.maxUAMSMBBasalMinutes,"profile.current_basal:",profile.current_basal);
|
||
maxBolus = round( profile.current_basal * profile.maxUAMSMBBasalMinutes / 60 ,1);
|
||
} else {
|
||
console.error("profile.maxUAMSMBBasalMinutes undefined: defaulting to 30m");
|
||
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 bolus increment
|
||
var roundSMBTo = 1 / profile.bolus_increment;
|
||
var microBolus = Math.floor(Math.min(insulinReq/2,maxBolus)*roundSMBTo)/roundSMBTo;
|
||
// calculate a long enough zero temp to eventually correct back up to target
|
||
var smbTarget = target_bg;
|
||
worstCaseInsulinReq = (smbTarget - (naive_eventualBG + minIOBPredBG)/2 ) / sens;
|
||
durationReq = round(60*worstCaseInsulinReq / profile.current_basal);
|
||
|
||
// if insulinReq > 0 but not enough for a microBolus, don't set an SMB zero temp
|
||
if (insulinReq > 0 && microBolus < profile.bolus_increment) {
|
||
durationReq = 0;
|
||
}
|
||
|
||
var smbLowTempReq = 0;
|
||
if (durationReq <= 0) {
|
||
durationReq = 0;
|
||
// don't set an SMB zero temp longer than 60 minutes
|
||
} else if (durationReq >= 30) {
|
||
durationReq = round(durationReq/30)*30;
|
||
durationReq = Math.min(60,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 low temp of " + smbLowTempReq + "U/h";
|
||
}
|
||
rT.reason += ". ";
|
||
|
||
//allow SMBs every 3 minutes by default
|
||
var SMBInterval = 3;
|
||
if (profile.SMBInterval) {
|
||
// allow SMBIntervals between 1 and 10 minutes
|
||
SMBInterval = Math.min(10,Math.max(1,profile.SMBInterval));
|
||
}
|
||
var nextBolusMins = round(SMBInterval-lastBolusAge,0);
|
||
var nextBolusSeconds = round((SMBInterval - lastBolusAge) * 60, 0) % 60;
|
||
//console.error(naive_eventualBG, insulinReq, worstCaseInsulinReq, durationReq);
|
||
console.error("naive_eventualBG",naive_eventualBG+",",durationReq+"m "+smbLowTempReq+"U/h temp needed; last bolus",lastBolusAge+"m ago; maxBolus: "+maxBolus);
|
||
if (lastBolusAge > SMBInterval) {
|
||
if (microBolus > 0) {
|
||
rT.units = microBolus;
|
||
rT.reason += "Microbolusing " + microBolus + "U. ";
|
||
}
|
||
} else {
|
||
rT.reason += "Waiting " + nextBolusMins + "m " + nextBolusSeconds + "s to microbolus again. ";
|
||
}
|
||
//rT.reason += ". ";
|
||
|
||
// if no zero temp is required, don't return yet; allow later code to set a high temp
|
||
if (durationReq > 0) {
|
||
rT.rate = smbLowTempReq;
|
||
rT.duration = durationReq;
|
||
return rT;
|
||
}
|
||
|
||
}
|
||
|
||
var maxSafeBasal = tempBasalFunctions.getMaxSafeBasal(profile);
|
||
|
||
if (rate > maxSafeBasal) {
|
||
rT.reason += "adj. req. rate: "+round(rate, 2)+" to maxSafeBasal: "+maxSafeBasal+", ";
|
||
rate = round_basal(maxSafeBasal, profile);
|
||
}
|
||
|
||
insulinScheduled = currenttemp.duration * (currenttemp.rate - basal) / 60;
|
||
if (insulinScheduled >= insulinReq * 2) { // if current temp would deliver >2x more than the required insulin, lower the rate
|
||
rT.reason += currenttemp.duration + "m@" + (currenttemp.rate).toFixed(2) + " > 2 * insulinReq. Setting temp basal of " + round(rate, 2) + "U/hr. ";
|
||
return tempBasalFunctions.setTempBasal(rate, 30, profile, rT, currenttemp);
|
||
}
|
||
|
||
if (typeof currenttemp.duration === 'undefined' || currenttemp.duration === 0) { // no temp is set
|
||
rT.reason += "no temp, setting " + round(rate, 2) + "U/hr. ";
|
||
return tempBasalFunctions.setTempBasal(rate, 30, profile, rT, currenttemp);
|
||
}
|
||
|
||
if (currenttemp.duration > 5 && (round_basal(rate, profile) <= round_basal(currenttemp.rate, profile))) { // if required temp <~ existing temp basal
|
||
rT.reason += "temp " + (currenttemp.rate).toFixed(2) + " >~ req " + round(rate, 2) + "U/hr. ";
|
||
return rT;
|
||
}
|
||
|
||
// required temp > existing temp basal
|
||
rT.reason += "temp " + (currenttemp.rate).toFixed(2) + " < " + round(rate, 2) + "U/hr. ";
|
||
return tempBasalFunctions.setTempBasal(rate, 30, profile, rT, currenttemp);
|
||
}
|
||
|
||
};
|
||
|
||
module.exports = determine_basal;
|