Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

camelCase autotune and use pumpProfile.autosens_max and min #319

Merged
merged 18 commits into from
Jan 8, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions bin/oref0-autotune-core.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
201 changes: 101 additions & 100 deletions lib/autotune-prep/categorize.js
Original file line number Diff line number Diff line change
@@ -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) {
Expand All @@ -15,71 +15,72 @@ 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;
var maxCarbs = 0;
//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) {
Expand All @@ -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);
}
}
Expand All @@ -103,19 +104,19 @@ 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];
if (treatment) {
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);
Expand All @@ -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) {
Expand All @@ -202,58 +203,58 @@ 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.
// When BGI is positive (insulin activity is negative), we want to use that data to tune basals
// 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;

2 changes: 1 addition & 1 deletion lib/autotune-prep/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading