diff --git a/bin/oref0-autotune-core.js b/bin/oref0-autotune-core.js index 5f9c1381..068e7dda 100755 --- a/bin/oref0-autotune-core.js +++ b/bin/oref0-autotune-core.js @@ -51,9 +51,9 @@ if (!module.parent) { } var inputs = { - prepped_glucose: prepped_glucose_data - , previous_autotune: previous_autotune_data - , pumpprofile: pumpprofile_data + preppedGlucose: prepped_glucose_data + , previousAutotune: previous_autotune_data + , pumpProfile: pumpprofile_data }; var autotune_output = autotune(inputs); diff --git a/lib/autotune-prep/categorize.js b/lib/autotune-prep/categorize.js index 2288c2bf..941e9cdc 100644 --- a/lib/autotune-prep/categorize.js +++ b/lib/autotune-prep/categorize.js @@ -1,12 +1,12 @@ var tz = require('timezone'); var calcMealCOB = require('oref0/lib/determine-basal/cob-autosens'); var basal = require('oref0/lib/profile/basal'); -var get_iob = require('oref0/lib/iob'); -var isf = require('../profile/isf'); +var getIOB = require('oref0/lib/iob'); +var ISF = require('../profile/isf'); -// main function categorizeBGdatums. ;) categorize to ISF, CSF, or basals. +// main function categorizeBGDatums. ;) categorize to ISF, CSF, or basals. -function categorizeBGdatums(opts) { +function categorizeBGDatums(opts) { var treatments = opts.treatments; // this sorts the treatments collection in order. treatments.sort(function (a, b) { @@ -15,17 +15,17 @@ function categorizeBGdatums(opts) { var bDate = new Date(tz(b.timestamp)); return bDate.getTime() - aDate.getTime(); }); - var profile_data = opts.profile; + var profileData = opts.profile; if (typeof(opts.glucose) !== 'undefined') { - //var glucose_data = opts.glucose; - var glucose_data = opts.glucose.map(function prepGlucose (obj) { + //var glucoseData = opts.glucose; + var glucoseData = opts.glucose.map(function prepGlucose (obj) { //Support the NS sgv field to avoid having to convert in a custom way obj.glucose = obj.glucose || obj.sgv; return obj; }); } - if (typeof(opts.prepped_glucose) !== 'undefined') { - var prepped_glucose_data = opts.prepped_glucose; + if (typeof(opts.preppedGlucose) !== 'undefined') { + var preppedGlucoseData = opts.preppedGlucose; } //starting variable at 0 var boluses = 0; @@ -33,53 +33,54 @@ function categorizeBGdatums(opts) { //console.error(treatments); if (!treatments) return {}; - //console.error(glucose_data); - var iob_inputs = { - profile: profile_data - , history: opts.pumphistory + //console.error(glucoseData); + var IOBInputs = { + profile: profileData + , history: opts.pumpHistory }; - var COB_inputs = { - glucose_data: glucose_data - , iob_inputs: iob_inputs + // TODO: verify this is safe to remove, and do so + var COBInputs = { + glucoseData: glucoseData + , IOBInputs: IOBInputs , basalprofile: opts.basalprofile }; var mealCOB = 0; - var csf_glucose_data = []; - var isf_glucose_data = []; - var basal_glucose_data = []; + var CSFGlucoseData = []; + var ISFGlucoseData = []; + var basalGlucoseData = []; - var bucketed_data = []; - bucketed_data[0] = glucose_data[0]; + var bucketedData = []; + bucketedData[0] = glucoseData[0]; j=0; //for loop to validate and bucket the data - for (var i=1; i < glucose_data.length; ++i) { - var bgTime; - var lastbgTime; - if (glucose_data[i].display_time) { - bgTime = new Date(glucose_data[i].display_time.replace('T', ' ')); - } else if (glucose_data[i].dateString) { - bgTime = new Date(glucose_data[i].dateString); + for (var i=1; i < glucoseData.length; ++i) { + var BGTime; + var lastBGTime; + if (glucoseData[i].displayTime) { + BGTime = new Date(glucoseData[i].displayTime.replace('T', ' ')); + } else if (glucoseData[i].dateString) { + BGTime = new Date(glucoseData[i].dateString); } else { console.error("Could not determine BG time"); } - if (glucose_data[i-1].display_time) { - lastbgTime = new Date(glucose_data[i-1].display_time.replace('T', ' ')); - } else if (glucose_data[i-1].dateString) { - lastbgTime = new Date(glucose_data[i-1].dateString); + if (glucoseData[i-1].displayTime) { + lastBGTime = new Date(glucoseData[i-1].displayTime.replace('T', ' ')); + } else if (glucoseData[i-1].dateString) { + lastBGTime = new Date(glucoseData[i-1].dateString); } else { console.error("Could not determine last BG time"); } - if (glucose_data[i].glucose < 39 || glucose_data[i-1].glucose < 39) { + if (glucoseData[i].glucose < 39 || glucoseData[i-1].glucose < 39) { continue; } - var elapsed_minutes = (bgTime - lastbgTime)/(60*1000); - if(Math.abs(elapsed_minutes) > 2) { + var elapsedMinutes = (BGTime - lastBGTime)/(60*1000); + if(Math.abs(elapsedMinutes) > 2) { j++; - bucketed_data[j]=glucose_data[i]; - bucketed_data[j].date = bgTime.getTime(); + bucketedData[j]=glucoseData[i]; + bucketedData[j].date = BGTime.getTime(); } else { // if duplicate, average the two - bucketed_data[j].glucose = (bucketed_data[j].glucose + glucose_data[i].glucose)/2; + bucketedData[j].glucose = (bucketedData[j].glucose + glucoseData[i].glucose)/2; } } - //console.error(bucketed_data); - //console.error(bucketed_data[bucketed_data.length-1]); + //console.error(bucketedData); + //console.error(bucketedData[bucketedData.length-1]); // go through the treatments and remove any that are older than the oldest glucose value //console.error(treatments); for (var i=treatments.length-1; i>0; --i) { @@ -88,11 +89,11 @@ function categorizeBGdatums(opts) { if (treatment) { var treatmentDate = new Date(tz(treatment.timestamp)); var treatmentTime = treatmentDate.getTime(); - var glucose_datum = bucketed_data[bucketed_data.length-1]; - //console.error(glucose_datum); - var bgDate = new Date(glucose_datum.date); - var bgTime = bgDate.getTime(); - if ( treatmentTime < bgTime ) { + var glucoseDatum = bucketedData[bucketedData.length-1]; + //console.error(glucoseDatum); + var BGDate = new Date(glucoseDatum.date); + var BGTime = BGDate.getTime(); + if ( treatmentTime < BGTime ) { treatments.splice(i,1); } } @@ -103,11 +104,11 @@ function categorizeBGdatums(opts) { mealCarbs = 0; var type=""; // main for loop - for (var i=bucketed_data.length-5; i > 0; --i) { - var glucose_datum = bucketed_data[i]; - //console.error(glucose_datum); - var bgDate = new Date(glucose_datum.date); - var bgTime = bgDate.getTime(); + for (var i=bucketedData.length-5; i > 0; --i) { + var glucoseDatum = bucketedData[i]; + //console.error(glucoseDatum); + var BGDate = new Date(glucoseDatum.date); + var BGTime = BGDate.getTime(); // As we're processing each data point, go through the treatment.carbs and see if any of them are older than // the current BG data point. If so, add those carbs to COB. var treatment = treatments[treatments.length-1]; @@ -115,7 +116,7 @@ function categorizeBGdatums(opts) { var treatmentDate = new Date(tz(treatment.timestamp)); var treatmentTime = treatmentDate.getTime(); //console.error(treatmentDate); - if ( treatmentTime < bgTime ) { + if ( treatmentTime < BGTime ) { if (treatment.carbs >= 1) { mealCOB += parseFloat(treatment.carbs); mealCarbs += parseFloat(treatment.carbs); @@ -124,71 +125,71 @@ function categorizeBGdatums(opts) { } } - var bg; + var BG; var avgDelta; var delta; // TODO: re-implement interpolation to avoid issues here with gaps // calculate avgDelta as last 4 datapoints to better catch more rises after COB hits zero - if (typeof(bucketed_data[i].glucose) != 'undefined') { - //console.error(bucketed_data[i]); - bg = bucketed_data[i].glucose; - if ( bg < 40 || bucketed_data[i+4].glucose < 40) { + if (typeof(bucketedData[i].glucose) != 'undefined') { + //console.error(bucketedData[i]); + BG = bucketedData[i].glucose; + if ( BG < 40 || bucketedData[i+4].glucose < 40) { process.stderr.write("!"); continue; } - avgDelta = (bg - bucketed_data[i+4].glucose)/4; - delta = (bg - bucketed_data[i+1].glucose); + avgDelta = (BG - bucketedData[i+4].glucose)/4; + delta = (BG - bucketedData[i+1].glucose); } else { console.error("Could not find glucose data"); } avgDelta = avgDelta.toFixed(2); - glucose_datum.avgDelta = avgDelta; + glucoseDatum.avgDelta = avgDelta; - //sens = isf - var sens = isf.isfLookup(iob_inputs.profile.isfProfile,bgDate); - iob_inputs.clock=bgDate.toISOString(); + //sens = ISF + var sens = ISF.isfLookup(IOBInputs.profile.isfProfile,BGDate); + IOBInputs.clock=BGDate.toISOString(); // use the average of the last 4 hours' basals to help convergence; // this helps since the basal this hour could be different from previous, especially if with autotune they start to diverge. - currentBasal = basal.basalLookup(opts.basalprofile, bgDate); - bgDate1hAgo = new Date(bgTime-1*60*60*1000); - bgDate2hAgo = new Date(bgTime-2*60*60*1000); - bgDate3hAgo = new Date(bgTime-3*60*60*1000); - basal1hAgo = basal.basalLookup(opts.basalprofile, bgDate1hAgo); - basal2hAgo = basal.basalLookup(opts.basalprofile, bgDate2hAgo); - basal3hAgo = basal.basalLookup(opts.basalprofile, bgDate3hAgo); + currentBasal = basal.basalLookup(opts.basalprofile, BGDate); + BGDate1hAgo = new Date(BGTime-1*60*60*1000); + BGDate2hAgo = new Date(BGTime-2*60*60*1000); + BGDate3hAgo = new Date(BGTime-3*60*60*1000); + basal1hAgo = basal.basalLookup(opts.basalprofile, BGDate1hAgo); + basal2hAgo = basal.basalLookup(opts.basalprofile, BGDate2hAgo); + basal3hAgo = basal.basalLookup(opts.basalprofile, BGDate3hAgo); var sum = [currentBasal,basal1hAgo,basal2hAgo,basal3hAgo].reduce(function(a, b) { return a + b; }); - iob_inputs.profile.current_basal = Math.round((sum/4)*1000)/1000; + IOBInputs.profile.currentBasal = Math.round((sum/4)*1000)/1000; - //console.error(currentBasal,basal1hAgo,basal2hAgo,basal3hAgo,iob_inputs.profile.current_basal); + //console.error(currentBasal,basal1hAgo,basal2hAgo,basal3hAgo,IOBInputs.profile.currentBasal); // basalBGI is BGI of basal insulin activity. - basalBgi = Math.round(( currentBasal * sens / 60 * 5 )*100)/100; // U/hr * mg/dL/U * 1 hr / 60 minutes * 5 = mg/dL/5m - //console.log(JSON.stringify(iob_inputs.profile)); + basalBGI = Math.round(( currentBasal * sens / 60 * 5 )*100)/100; // U/hr * mg/dL/U * 1 hr / 60 minutes * 5 = mg/dL/5m + //console.log(JSON.stringify(IOBInputs.profile)); // call iob since calculated elsewhere - var iob = get_iob(iob_inputs)[0]; + var iob = getIOB(IOBInputs)[0]; //console.error(JSON.stringify(iob)); // activity times ISF times 5 minutes is BGI - var bgi = Math.round(( -iob.activity * sens * 5 )*100)/100; + var BGI = Math.round(( -iob.activity * sens * 5 )*100)/100; // datum = one glucose data point (being prepped to store in output) - glucose_datum.bgi = bgi; + glucoseDatum.BGI = BGI; // calculating deviation - deviation = avgDelta-bgi; + deviation = avgDelta-BGI; // rounding and storing deviation deviation = deviation.toFixed(2); - glucose_datum.deviation = deviation; + glucoseDatum.deviation = deviation; // Then, calculate carb absorption for that 5m interval using the deviation. if ( mealCOB > 0 ) { - var profile = profile_data; + var profile = profileData; ci = Math.max(deviation, profile.min_5m_carbimpact); absorbed = ci * profile.carb_ratio / sens; mealCOB = Math.max(0, mealCOB-absorbed); } // Store the COB, and use it as the starting point for the next data point. - // If mealCOB is zero but all deviations since hitting COB=0 are positive, assign those data points to csf_glucose_data + // If mealCOB is zero but all deviations since hitting COB=0 are positive, assign those data points to CSFGlucoseData // Once deviations go negative for at least one data point after COB=0, we can use the rest of the data to tune ISF or basals if (mealCOB > 0 || absorbing || mealCarbs > 0) { if (deviation > 0) { @@ -202,18 +203,18 @@ function categorizeBGdatums(opts) { // check previous "type" value, and if it wasn't csf, set a mealAbsorption start flag //console.error(type); if ( type != "csf" ) { - glucose_datum.mealAbsorption = "start"; - console.error(glucose_datum.mealAbsorption,"carb absorption"); + glucoseDatum.mealAbsorption = "start"; + console.error(glucoseDatum.mealAbsorption,"carb absorption"); } type="csf"; - glucose_datum.mealCarbs = mealCarbs; - //if (i == 0) { glucose_datum.mealAbsorption = "end"; } - csf_glucose_data.push(glucose_datum); + glucoseDatum.mealCarbs = mealCarbs; + //if (i == 0) { glucoseDatum.mealAbsorption = "end"; } + CSFGlucoseData.push(glucoseDatum); } else { // check previous "type" value, and if it was csf, set a mealAbsorption end flag if ( type === "csf" ) { - csf_glucose_data[csf_glucose_data.length-1].mealAbsorption = "end"; - console.error(csf_glucose_data[csf_glucose_data.length-1].mealAbsorption,"carb absorption"); + CSFGlucoseData[CSFGlucoseData.length-1].mealAbsorption = "end"; + console.error(CSFGlucoseData[CSFGlucoseData.length-1].mealAbsorption,"carb absorption"); } // Go through the remaining time periods and divide them into periods where scheduled basal insulin activity dominates. This would be determined by calculating the BG impact of scheduled basal insulin (for example 1U/hr * 48 mg/dL/U ISF = 48 mg/dL/hr = 5 mg/dL/5m), and comparing that to BGI from bolus and net basal insulin activity. @@ -221,39 +222,39 @@ function categorizeBGdatums(opts) { // When BGI is smaller than about 1/4 of basalBGI, we want to use that data to tune basals // When BGI is negative and more than about 1/4 of basalBGI, we can use that data to tune ISF, // unless avgDelta is positive: then that's some sort of unexplained rise we don't want to use for ISF, so that means basals - if (basalBgi > -4 * bgi) { + if (basalBGI > -4 * BGI) { // attempting to prevent basal from being calculated as negative; should help prevent basals from going below 0 - var minPossibleDeviation = -( basalBgi + Math.max(0,bgi) ); - //var minPossibleDeviation = -basalBgi; + var minPossibleDeviation = -( basalBGI + Math.max(0,BGI) ); + //var minPossibleDeviation = -basalBGI; if ( deviation < minPossibleDeviation ) { console.error("Adjusting deviation",deviation,"to",minPossibleDeviation.toFixed(2)); deviation = minPossibleDeviation; deviation = deviation.toFixed(2); - glucose_datum.deviation = deviation; + glucoseDatum.deviation = deviation; } type="basal"; - basal_glucose_data.push(glucose_datum); + basalGlucoseData.push(glucoseDatum); } else { if (avgDelta > 0 ) { //type="unknown" type="basal" - basal_glucose_data.push(glucose_datum); + basalGlucoseData.push(glucoseDatum); } else { - type="isf"; - isf_glucose_data.push(glucose_datum); + type="ISF"; + ISFGlucoseData.push(glucoseDatum); } } } // debug line to print out all the things - console.error(absorbing.toString(),"mealCOB:",mealCOB.toFixed(1),"mealCarbs:",mealCarbs,"basalBgi:",basalBgi.toFixed(1),"bgi:",bgi.toFixed(1),"at",bgDate,"dev:",deviation,"avgDelta:",avgDelta,type); + console.error(absorbing.toString(),"mealCOB:",mealCOB.toFixed(1),"mealCarbs:",mealCarbs,"basalBGI:",basalBGI.toFixed(1),"BGI:",BGI.toFixed(1),"at",BGDate,"dev:",deviation,"avgDelta:",avgDelta,type); } return { - csf_glucose_data: csf_glucose_data, - isf_glucose_data: isf_glucose_data, - basal_glucose_data: basal_glucose_data + CSFGlucoseData: CSFGlucoseData, + ISFGlucoseData: ISFGlucoseData, + basalGlucoseData: basalGlucoseData }; } -exports = module.exports = categorizeBGdatums; +exports = module.exports = categorizeBGDatums; diff --git a/lib/autotune-prep/index.js b/lib/autotune-prep/index.js index 36695628..f98d6b39 100644 --- a/lib/autotune-prep/index.js +++ b/lib/autotune-prep/index.js @@ -13,7 +13,7 @@ function generate (inputs) { var opts = { treatments: treatments , profile: inputs.profile - , pumphistory: inputs.history + , pumpHistory: inputs.history , glucose: inputs.glucose , prepped_glucose: inputs.prepped_glucose , basalprofile: inputs.profile.basalprofile diff --git a/lib/autotune/index.js b/lib/autotune/index.js index 230eff00..40531f7c 100644 --- a/lib/autotune/index.js +++ b/lib/autotune/index.js @@ -2,85 +2,86 @@ function tuneAllTheThings (inputs) { - var previous_autotune = inputs.previous_autotune; - var pumpprofile = inputs.pumpprofile; - var pumpbasalprofile = pumpprofile.basalprofile; - //console.error(pumpbasalprofile); - var basalprofile = previous_autotune.basalprofile; - //console.error(basalprofile); - var isf_profile = previous_autotune.isfProfile; - //console.error(isf_profile); - var isf = isf_profile.sensitivities[0].sensitivity; - //console.error(isf); - var carb_ratio = previous_autotune.carb_ratio; - //console.error(carb_ratio); - var csf = isf / carb_ratio; + var previousAutotune = inputs.previousAutotune; + //console.error(previousAutotune); + var pumpProfile = inputs.pumpProfile; + var pumpBasalProfile = pumpProfile.basalprofile; + //console.error(pumpBasalProfile); + var basalProfile = previousAutotune.basalprofile; + //console.error(basalProfile); + var isfProfile = previousAutotune.isfProfile; + //console.error(isfProfile); + var ISF = isfProfile.sensitivities[0].sensitivity; + //console.error(ISF); + var carbRatio = previousAutotune.carb_ratio; + //console.error(carbRatio); + var CSF = ISF / carbRatio; // conditional on there being a pump profile; if not then skip - if (pumpprofile) { pump_isf_profile = pumpprofile.isfProfile; } - if (pump_isf_profile && pump_isf_profile.sensitivities[0]) { - pumpISF = pump_isf_profile.sensitivities[0].sensitivity; - pump_carb_ratio = pumpprofile.carb_ratio; - pumpCSF = pumpISF / pump_carb_ratio; + if (pumpProfile) { pumpISFProfile = pumpProfile.isfProfile; } + if (pumpISFProfile && pumpISFProfile.sensitivities[0]) { + pumpISF = pumpISFProfile.sensitivities[0].sensitivity; + pumpCarbRatio = pumpProfile.carb_ratio; + pumpCSF = pumpISF / pumpCarbRatio; } - //console.error(csf); - var prepped_glucose = inputs.prepped_glucose; - var csf_glucose = prepped_glucose.csf_glucose_data; - //console.error(csf_glucose[0]); - var isf_glucose = prepped_glucose.isf_glucose_data; - //console.error(isf_glucose[0]); - var basal_glucose = prepped_glucose.basal_glucose_data; - //console.error(basal_glucose[0]); + //console.error(CSF); + var preppedGlucose = inputs.preppedGlucose; + var CSFGlucose = preppedGlucose.CSFGlucoseData; + //console.error(CSFGlucose[0]); + var ISFGlucose = preppedGlucose.ISFGlucoseData; + //console.error(ISFGlucose[0]); + var basalGlucose = preppedGlucose.basalGlucoseData; + //console.error(basalGlucose[0]); // convert the basal profile to hourly if it isn't already - hourlybasalprofile = []; - hourlypumpprofile = []; + hourlyBasalProfile = []; + hourlyPumpProfile = []; for (var i=0; i < 24; i++) { // aututuned basal profile - for (var j=0; j < basalprofile.length; ++j) { - if (basalprofile[j].minutes <= i * 60) { - hourlybasalprofile[i] = JSON.parse(JSON.stringify(basalprofile[j])); + for (var j=0; j < basalProfile.length; ++j) { + if (basalProfile[j].minutes <= i * 60) { + hourlyBasalProfile[i] = JSON.parse(JSON.stringify(basalProfile[j])); } } - hourlybasalprofile[i].i=i; - hourlybasalprofile[i].minutes=i*60; + hourlyBasalProfile[i].i=i; + hourlyBasalProfile[i].minutes=i*60; var zeroPadHour = ("000"+i).slice(-2); - hourlybasalprofile[i].start=zeroPadHour + ":00:00"; - hourlybasalprofile[i].rate=Math.round(hourlybasalprofile[i].rate*1000)/1000 + hourlyBasalProfile[i].start=zeroPadHour + ":00:00"; + hourlyBasalProfile[i].rate=Math.round(hourlyBasalProfile[i].rate*1000)/1000 // pump basal profile - if (pumpbasalprofile && pumpbasalprofile[0]) { - for (var j=0; j < pumpbasalprofile.length; ++j) { - //console.error(pumpbasalprofile[j]); - if (pumpbasalprofile[j].minutes <= i * 60) { - hourlypumpprofile[i] = JSON.parse(JSON.stringify(pumpbasalprofile[j])); + if (pumpBasalProfile && pumpBasalProfile[0]) { + for (var j=0; j < pumpBasalProfile.length; ++j) { + //console.error(pumpBasalProfile[j]); + if (pumpBasalProfile[j].minutes <= i * 60) { + hourlyPumpProfile[i] = JSON.parse(JSON.stringify(pumpBasalProfile[j])); } } - hourlypumpprofile[i].i=i; - hourlypumpprofile[i].minutes=i*60; - hourlypumpprofile[i].rate=Math.round(hourlypumpprofile[i].rate*1000)/1000 + hourlyPumpProfile[i].i=i; + hourlyPumpProfile[i].minutes=i*60; + hourlyPumpProfile[i].rate=Math.round(hourlyPumpProfile[i].rate*1000)/1000 } } - //console.error(hourlypumpprofile); - //console.error(hourlybasalprofile); + //console.error(hourlyPumpProfile); + //console.error(hourlyBasalProfile); // look at net deviations for each hour for (var hour=0; hour < 24; hour++) { var deviations = 0; - for (var i=0; i < basal_glucose.length; ++i) { - //console.error(basal_glucose[i].dateString); - splitString = basal_glucose[i].dateString.split("T"); + for (var i=0; i < basalGlucose.length; ++i) { + //console.error(basalGlucose[i].dateString); + splitString = basalGlucose[i].dateString.split("T"); timeString = splitString[1]; splitTime = timeString.split(":"); myHour = parseInt(splitTime[0]); if (hour == myHour) { - //console.error(basal_glucose[i].deviation); - deviations += parseFloat(basal_glucose[i].deviation); + //console.error(basalGlucose[i].deviation); + deviations += parseFloat(basalGlucose[i].deviation); } } deviations = Math.round( deviations * 1000 ) / 1000 //console.error("Hour",hour.toString(),"total deviations:",deviations,"mg/dL"); // calculate how much less or additional basal insulin would have been required to eliminate the deviations // only apply 20% of the needed adjustment to keep things relatively stable - basalNeeded = 0.2 * deviations / isf; + basalNeeded = 0.2 * deviations / ISF; basalNeeded = Math.round( basalNeeded * 1000 ) / 1000 // if basalNeeded is positive, adjust each of the 1-3 hour prior basals by 10% of the needed adjustment console.error("Hour",hour,"basal adjustment needed:",basalNeeded,"U/hr"); @@ -89,8 +90,8 @@ function tuneAllTheThings (inputs) { offsetHour = hour + offset; if (offsetHour < 0) { offsetHour += 24; } //console.error(offsetHour); - hourlybasalprofile[offsetHour].rate += basalNeeded / 3; - hourlybasalprofile[offsetHour].rate=Math.round(hourlybasalprofile[offsetHour].rate*1000)/1000 + hourlyBasalProfile[offsetHour].rate += basalNeeded / 3; + hourlyBasalProfile[offsetHour].rate=Math.round(hourlyBasalProfile[offsetHour].rate*1000)/1000 } // otherwise, figure out the percentage reduction required to the 1-3 hour prior basals // and adjust all of them downward proportionally @@ -99,86 +100,92 @@ function tuneAllTheThings (inputs) { for (var offset=-3; offset < 0; offset++) { offsetHour = hour + offset; if (offsetHour < 0) { offsetHour += 24; } - threeHourBasal += hourlybasalprofile[offsetHour].rate; + threeHourBasal += hourlyBasalProfile[offsetHour].rate; } var adjustmentRatio = 1.0 + basalNeeded / threeHourBasal; - console.error(adjustmentRatio); + //console.error(adjustmentRatio); for (var offset=-3; offset < 0; offset++) { offsetHour = hour + offset; if (offsetHour < 0) { offsetHour += 24; } - hourlybasalprofile[offsetHour].rate = hourlybasalprofile[offsetHour].rate * adjustmentRatio; - hourlybasalprofile[offsetHour].rate=Math.round(hourlybasalprofile[offsetHour].rate*1000)/1000 + hourlyBasalProfile[offsetHour].rate = hourlyBasalProfile[offsetHour].rate * adjustmentRatio; + hourlyBasalProfile[offsetHour].rate=Math.round(hourlyBasalProfile[offsetHour].rate*1000)/1000 } } } - if (pumpbasalprofile && pumpbasalprofile[0]) { + if (pumpBasalProfile && pumpBasalProfile[0]) { for (var hour=0; hour < 24; hour++) { - //console.error(hourlybasalprofile[hour],hourlypumpprofile[hour].rate*1.2); - // 20% caps - var maxrate = hourlypumpprofile[hour].rate * 1.2; - var minrate = hourlypumpprofile[hour].rate / 1.2; - if (hourlybasalprofile[hour].rate > maxrate ) { - console.error("Limiting hour",hour,"basal to",maxrate.toFixed(2),"(which is 20% above pump basal of",hourlypumpprofile[hour].rate,")"); - hourlybasalprofile[hour].rate = maxrate; - } else if (hourlybasalprofile[hour].rate < minrate ) { - console.error("Limiting hour",hour,"basal to",minrate.toFixed(2),"(which is 20% below pump basal of",hourlypumpprofile[hour].rate,")"); - hourlybasalprofile[hour].rate = minrate; + //console.error(hourlyBasalProfile[hour],hourlyPumpProfile[hour].rate*1.2); + // cap adjustments at autosens_max and autosens_min + autotuneMax = pumpProfile.autosens_max; + autotuneMin = pumpProfile.autosens_min; + var maxRate = hourlyPumpProfile[hour].rate * autotuneMax; + var minRate = hourlyPumpProfile[hour].rate * autotuneMin; + if (hourlyBasalProfile[hour].rate > maxRate ) { + console.error("Limiting hour",hour,"basal to",maxRate.toFixed(2),"(which is",autotuneMax,"* pump basal of",hourlyPumpProfile[hour].rate,")"); + //console.error("Limiting hour",hour,"basal to",maxRate.toFixed(2),"(which is 20% above pump basal of",hourlyPumpProfile[hour].rate,")"); + hourlyBasalProfile[hour].rate = maxRate; + } else if (hourlyBasalProfile[hour].rate < minRate ) { + console.error("Limiting hour",hour,"basal to",minRate.toFixed(2),"(which is",autotuneMin,"* pump basal of",hourlyPumpProfile[hour].rate,")"); + //console.error("Limiting hour",hour,"basal to",minRate.toFixed(2),"(which is 20% below pump basal of",hourlyPumpProfile[hour].rate,")"); + hourlyBasalProfile[hour].rate = minRate; } - hourlybasalprofile[hour].rate = Math.round(hourlybasalprofile[hour].rate*1000)/1000; + hourlyBasalProfile[hour].rate = Math.round(hourlyBasalProfile[hour].rate*1000)/1000; } } - console.error(hourlybasalprofile); - basalprofile = hourlybasalprofile; + console.error(hourlyBasalProfile); + basalProfile = hourlyBasalProfile; // calculate median deviation and bgi in data attributable to ISF var deviations = []; - var bgis = []; + var BGIs = []; var avgDeltas = []; var ratios = []; var count = 0; - for (var i=0; i < isf_glucose.length; ++i) { - deviation = parseFloat(isf_glucose[i].deviation); + for (var i=0; i < ISFGlucose.length; ++i) { + deviation = parseFloat(ISFGlucose[i].deviation); deviations.push(deviation); - bgi = parseFloat(isf_glucose[i].bgi); - bgis.push(bgi); - avgDelta = parseFloat(isf_glucose[i].avgDelta); + BGI = parseFloat(ISFGlucose[i].BGI); + BGIs.push(BGI); + avgDelta = parseFloat(ISFGlucose[i].avgDelta); avgDeltas.push(avgDelta); - ratio = 1 + deviation / bgi; - //console.error("Deviation:",deviation,"BGI:",bgi,"avgDelta:",avgDelta,"ratio:",ratio); + ratio = 1 + deviation / BGI; + //console.error("Deviation:",deviation,"BGI:",BGI,"avgDelta:",avgDelta,"ratio:",ratio); ratios.push(ratio); count++; } avgDeltas.sort(function(a, b){return a-b}); - bgis.sort(function(a, b){return a-b}); + BGIs.sort(function(a, b){return a-b}); deviations.sort(function(a, b){return a-b}); ratios.sort(function(a, b){return a-b}); p50deviation = percentile(deviations, 0.50); - p50bgi = percentile(bgis, 0.50); + p50BGI = percentile(BGIs, 0.50); p50ratios = Math.round( percentile(ratios, 0.50) * 1000)/1000; // calculate what adjustments to ISF would have been necessary to bring median deviation to zero - fullNewISF = isf * p50ratios; + fullNewISF = ISF * p50ratios; fullNewISF = Math.round( fullNewISF * 1000 ) / 1000; // and apply 10% of that adjustment - var newISF = ( 0.9 * isf ) + ( 0.1 * fullNewISF ); + var newISF = ( 0.9 * ISF ) + ( 0.1 * fullNewISF ); if (typeof(pumpISF) !== 'undefined') { - var maxISF = pumpISF * 1.2; - var minISF = pumpISF / 1.2; + // low autosens ratio = high ISF + var maxISF = pumpISF / autotuneMin; + // high autosens ratio = low ISF + var minISF = pumpISF / autotuneMax; if (newISF > maxISF) { - console.error("Limiting ISF to",maxISF.toFixed(2),"(which is 20% above pump ISF of",pumpISF,")"); + console.error("Limiting ISF of",newISF.toFixed(2),"to",maxISF.toFixed(2),"(which is pump ISF of",pumpISF,"/",autotuneMin,")"); newISF = maxISF; } else if (newISF < minISF) { - console.error("Limiting ISF to",minISF.toFixed(2),"(which is 20% below pump ISF of",pumpISF,")"); + console.error("Limiting ISF of",newISF.toFixed(2),"to",minISF.toFixed(2),"(which is pump ISF of",pumpISF,"/",autotuneMax,")"); newISF = minISF; } } newISF = Math.round( newISF * 1000 ) / 1000; //console.error(avgRatio); //console.error(newISF); - console.error("p50deviation:",p50deviation,"p50bgi",p50bgi,"p50ratios:",p50ratios,"Old ISF:",isf,"fullNewISF:",fullNewISF,"newISF:",newISF); + console.error("p50deviation:",p50deviation,"p50BGI",p50BGI,"p50ratios:",p50ratios,"Old ISF:",ISF,"fullNewISF:",fullNewISF,"newISF:",newISF); - isf = newISF; + ISF = newISF; // calculate net deviations while carbs are absorbing // measured from carb entry until COB and deviations both drop to zero @@ -188,26 +195,26 @@ function tuneAllTheThings (inputs) { var totalMealCarbs = 0; var totalDeviations = 0; var fullNewCSF; - //console.error(csf_glucose[0].mealAbsorption); - //console.error(csf_glucose[0]); - for (var i=0; i < csf_glucose.length; ++i) { - //console.error(csf_glucose[i].mealAbsorption, i); - if ( csf_glucose[i].mealAbsorption === "start" ) { + //console.error(CSFGlucose[0].mealAbsorption); + //console.error(CSFGlucose[0]); + for (var i=0; i < CSFGlucose.length; ++i) { + //console.error(CSFGlucose[i].mealAbsorption, i); + if ( CSFGlucose[i].mealAbsorption === "start" ) { deviations = 0; - mealCarbs = parseInt(csf_glucose[i].mealCarbs); - } else if (csf_glucose[i].mealAbsorption === "end") { - deviations += parseFloat(csf_glucose[i].deviation); + mealCarbs = parseInt(CSFGlucose[i].mealCarbs); + } else if (CSFGlucose[i].mealAbsorption === "end") { + deviations += parseFloat(CSFGlucose[i].deviation); // compare the sum of deviations from start to end vs. current CSF * mealCarbs - //console.error(csf,mealCarbs); - csfRise = csf * mealCarbs; - //console.error(deviations,isf); - console.error("csfRise:",csfRise,"deviations:",deviations); + //console.error(CSF,mealCarbs); + csfRise = CSF * mealCarbs; + //console.error(deviations,ISF); + //console.error("csfRise:",csfRise,"deviations:",deviations); totalMealCarbs += mealCarbs; totalDeviations += deviations; } else { - deviations += Math.max(0*previous_autotune.min_5m_carbimpact,parseFloat(csf_glucose[i].deviation)); - mealCarbs = Math.max(mealCarbs, parseInt(csf_glucose[i].mealCarbs)); + deviations += Math.max(0*previousAutotune.min_5m_carbimpact,parseFloat(CSFGlucose[i].deviation)); + mealCarbs = Math.max(mealCarbs, parseInt(CSFGlucose[i].mealCarbs)); } } // at midnight, write down the mealcarbs as total meal carbs (to prevent special case of when only one meal and it not finishing absorbing by midnight) @@ -217,42 +224,42 @@ function tuneAllTheThings (inputs) { //console.error(totalDeviations, totalMealCarbs); if (totalMealCarbs == 0) { // if no meals today, CSF is unchanged - fullNewCSF = csf; + fullNewCSF = CSF; } else { // how much change would be required to account for all of the deviations fullNewCSF = Math.round( (totalDeviations / totalMealCarbs)*100 )/100; } // only adjust by 10% - newCSF = ( 0.9 * csf ) + ( 0.1 * fullNewCSF ); + newCSF = ( 0.9 * CSF ) + ( 0.1 * fullNewCSF ); // safety cap CSF if (typeof(pumpCSF) !== 'undefined') { - var maxCSF = pumpCSF * 1.2; - var minCSF = pumpCSF / 1.2; + var maxCSF = pumpCSF * autotuneMax; + var minCSF = pumpCSF * autotuneMin; if (newCSF > maxCSF) { - console.error("Limiting CSF to",maxCSF.toFixed(2),"(which is 20% above pump CSF of",pumpCSF,")"); + console.error("Limiting CSF to",maxCSF.toFixed(2),"(which is",autotuneMax,"* pump CSF of",pumpCSF,")"); newCSF = maxCSF; } else if (newCSF < minCSF) { - console.error("Limiting CSF to",minCSF.toFixed(2),"(which is 20% below pump CSF of",pumpCSF,")"); + console.error("Limiting CSF to",minCSF.toFixed(2),"(which is",autotuneMin,"* pump CSF of",pumpCSF,")"); newCSF = minCSF; - } else { console.error("newCSF",newCSF,"is within 20% of",pumpCSF); } + } //else { console.error("newCSF",newCSF,"is close enough to",pumpCSF); } } newCSF = Math.round( newCSF * 1000 ) / 1000; console.error("totalMealCarbs:",totalMealCarbs,"totalDeviations:",totalDeviations,"fullNewCSF:",fullNewCSF,"newCSF:",newCSF); - // this is where csf is set based on the outputs - csf = newCSF; + // this is where CSF is set based on the outputs + CSF = newCSF; - // reconstruct updated version of previous_autotune as autotune_output - autotune_output = previous_autotune; - autotune_output.basalprofile = basalprofile; - isf_profile.sensitivities[0].sensitivity = isf; - autotune_output.isfProfile = isf_profile; - autotune_output.sens = isf; - autotune_output.csf = csf; - carb_ratio = isf / csf; - carb_ratio = Math.round( carb_ratio * 1000 ) / 1000; - autotune_output.carb_ratio = carb_ratio; + // reconstruct updated version of previousAutotune as autotuneOutput + autotuneOutput = previousAutotune; + autotuneOutput.basalprofile = basalProfile; + isfProfile.sensitivities[0].sensitivity = ISF; + autotuneOutput.isfProfile = isfProfile; + autotuneOutput.sens = ISF; + autotuneOutput.CSF = CSF; + carbRatio = ISF / CSF; + carbRatio = Math.round( carbRatio * 1000 ) / 1000; + autotuneOutput.carb_ratio = carbRatio; - return autotune_output; + return autotuneOutput; } exports = module.exports = tuneAllTheThings; diff --git a/lib/oref0-setup/alias.json b/lib/oref0-setup/alias.json index 8d1c6dcb..ed648fa2 100644 --- a/lib/oref0-setup/alias.json +++ b/lib/oref0-setup/alias.json @@ -176,7 +176,7 @@ }, { "pump-loop": { - "command": "! bash -c \"sleep $[ ( $RANDOM / 2048 ) ]s; until(echo Starting pump-loop at $(date): && openaps wait-for-silence && openaps refresh-old-pumphistory && openaps refresh-old-pumphistory-24h && openaps refresh-old-profile && openaps refresh-temp-and-enact && openaps refresh-pumphistory-and-enact && openaps refresh-profile && openaps refresh-pumphistory-24h && echo Completed pump-loop at $(date) && echo); do echo Error, retrying && [[ $RANDOM > 28000 ]] && openaps wait-for-long-silence && openaps mmtune; sleep 5; done\"" + "command": "! bash -c \"sleep $[ ( $RANDOM / 2048 ) ]s; until(echo Starting pump-loop at $(date): && openaps wait-for-silence && openaps refresh-old-pumphistory && openaps refresh-old-pumphistory-24h && openaps refresh-old-profile && openaps refresh-temp-and-enact && openaps refresh-pumphistory-and-enact && openaps refresh-profile && openaps refresh-pumphistory-24h && echo Completed pump-loop at $(date) && echo); do echo Error, retrying && [[ $RANDOM > 25000 ]] && openaps wait-for-long-silence && openaps mmtune; sleep 5; done\"" }, "type": "alias", "name": "pump-loop"