mirror of
https://github.com/bckelley/cgm-remote-monitor.git
synced 2026-08-24 03:14:12 -05:00
* Experimental branch that replaces momentjs with dayjs in the client * Revert unintentional change * feat * Turns out dayjs is a no-go, but this has some good restructuring so submitting that
294 lines
8.4 KiB
JavaScript
294 lines
8.4 KiB
JavaScript
'use strict';
|
|
|
|
const _ = require('lodash')
|
|
const times = require('../times');
|
|
|
|
function init(ctx) {
|
|
var moment = ctx.moment;
|
|
var translate = ctx.language.translate;
|
|
var utils = require('../utils')(ctx);
|
|
|
|
var iob = {
|
|
name: 'iob'
|
|
, label: 'Insulin-on-Board'
|
|
, pluginType: 'pill-major'
|
|
};
|
|
|
|
iob.RECENCY_THRESHOLD = times.mins(30).msecs;
|
|
|
|
iob.setProperties = function setProperties(sbx) {
|
|
sbx.offerProperty('iob', function setIOB ( ) {
|
|
return iob.calcTotal(sbx.data.treatments, sbx.data.devicestatus, sbx.data.profile, sbx.time);
|
|
});
|
|
};
|
|
|
|
iob.calcTotal = function calcTotal(treatments, devicestatus, profile, time, spec_profile) {
|
|
if (time === undefined) {
|
|
time = Date.now();
|
|
}
|
|
|
|
var result = iob.lastIOBDeviceStatus(devicestatus, time);
|
|
|
|
var treatmentResult = (treatments !== undefined && treatments.length) ? iob.fromTreatments(treatments, profile, time, spec_profile) : {};
|
|
|
|
if (_.isEmpty(result)) {
|
|
result = treatmentResult;
|
|
} else if (treatmentResult.iob) {
|
|
result.treatmentIob = +(Math.round(treatmentResult.iob + "e+3") + "e-3");
|
|
}
|
|
if (result.iob) result.iob = +(Math.round(result.iob + "e+3") + "e-3");
|
|
return addDisplay(result);
|
|
};
|
|
|
|
function addDisplay(iob) {
|
|
if (_.isEmpty(iob) || iob.iob === undefined) {
|
|
return {};
|
|
}
|
|
var display = utils.toFixed(iob.iob);
|
|
return _.merge(iob, {
|
|
display: display
|
|
, displayLine: 'IOB: ' + display + 'U'
|
|
});
|
|
}
|
|
|
|
iob.isDeviceStatusAvailable = function isDeviceStatusAvailable (devicestatus) {
|
|
|
|
return _.chain(devicestatus)
|
|
.map(iob.fromDeviceStatus)
|
|
.reject(_.isEmpty)
|
|
.value()
|
|
.length > 0;
|
|
};
|
|
|
|
iob.lastIOBDeviceStatus = function lastIOBDeviceStatus(devicestatus, time) {
|
|
if (time && time.getTime) {
|
|
time = time.getTime();
|
|
}
|
|
var futureMills = time + times.mins(5).msecs; //allow for clocks to be a little off
|
|
var recentMills = time - iob.RECENCY_THRESHOLD;
|
|
|
|
// All IOBs
|
|
var iobs = _.chain(devicestatus)
|
|
.filter(function (iobStatus) {
|
|
return iobStatus.mills <= futureMills && iobStatus.mills >= recentMills;
|
|
})
|
|
.map(iob.fromDeviceStatus)
|
|
.reject(_.isEmpty)
|
|
.sortBy('mills');
|
|
|
|
// Loop IOBs
|
|
var loopIOBs = iobs.filter(function (iobStatus) {
|
|
return iobStatus.source === 'Loop';
|
|
});
|
|
|
|
// Loop uploads both Loop IOB and pump-reported IOB, prioritize Loop IOB if available
|
|
return loopIOBs.last().value() || iobs.last().value();
|
|
};
|
|
|
|
iob.IOBDeviceStatusesInTimeRange = function IOBDeviceStatusesInTimeRange (devicestatus, from, to) {
|
|
|
|
return _.chain(devicestatus)
|
|
.filter(function (iobStatus) {
|
|
return iobStatus.mills > from && iobStatus.mills < to;
|
|
})
|
|
.map(iob.fromDeviceStatus)
|
|
.reject(_.isEmpty)
|
|
.sortBy('mills')
|
|
.value();
|
|
};
|
|
|
|
iob.fromDeviceStatus = function fromDeviceStatus(devicestatusEntry) {
|
|
var iobOpenAPS = _.get(devicestatusEntry, 'openaps.iob');
|
|
var iobLoop = _.get(devicestatusEntry, 'loop.iob');
|
|
var iobPump = _.get(devicestatusEntry, 'pump.iob');
|
|
|
|
if (_.isObject(iobOpenAPS)) {
|
|
|
|
//hacks to support AMA iob array with time fields instead of timestamp fields
|
|
iobOpenAPS = _.isArray(iobOpenAPS) ? iobOpenAPS[0] : iobOpenAPS;
|
|
|
|
// array could still be empty, handle as null
|
|
if (_.isEmpty(iobOpenAPS)) {
|
|
return {};
|
|
}
|
|
|
|
if (iobOpenAPS.time) {
|
|
iobOpenAPS.timestamp = iobOpenAPS.time;
|
|
}
|
|
|
|
return {
|
|
iob: iobOpenAPS.iob
|
|
, basaliob: iobOpenAPS.basaliob
|
|
, activity: iobOpenAPS.activity
|
|
, source: 'OpenAPS'
|
|
, device: devicestatusEntry.device
|
|
, mills: moment(iobOpenAPS.timestamp).valueOf( )
|
|
};
|
|
} else if (_.isObject(iobLoop)) {
|
|
return {
|
|
iob: iobLoop.iob
|
|
, source: 'Loop'
|
|
, device: devicestatusEntry.device
|
|
, mills: moment(iobLoop.timestamp).valueOf( )
|
|
};
|
|
} else if (_.isObject(iobPump)) {
|
|
return {
|
|
iob: iobPump.iob || iobPump.bolusiob
|
|
, source: devicestatusEntry.connect !== undefined ? 'MM Connect' : undefined
|
|
, device: devicestatusEntry.device
|
|
, mills: devicestatusEntry.mills
|
|
};
|
|
} else {
|
|
return {};
|
|
}
|
|
};
|
|
|
|
iob.fromTreatments = function fromTreatments(treatments, profile, time, spec_profile) {
|
|
var totalIOB = 0
|
|
, totalActivity = 0;
|
|
|
|
var lastBolus = null;
|
|
|
|
_.each(treatments, function eachTreatment(treatment) {
|
|
if (treatment.mills <= time) {
|
|
var tIOB = iob.calcTreatment(treatment, profile, time, spec_profile);
|
|
if (tIOB.iobContrib > 0) {
|
|
lastBolus = treatment;
|
|
}
|
|
if (tIOB && tIOB.iobContrib) { totalIOB += tIOB.iobContrib; }
|
|
// units: BG (mg/dL or mmol/L)
|
|
if (tIOB && tIOB.activityContrib) { totalActivity += tIOB.activityContrib; }
|
|
}
|
|
});
|
|
|
|
return {
|
|
iob: +(Math.round(totalIOB + "e+3") + "e-3")
|
|
, activity: totalActivity
|
|
, lastBolus: lastBolus
|
|
, source: translate('Care Portal')
|
|
};
|
|
};
|
|
|
|
iob.calcTreatment = function calcTreatment(treatment, profile, time, spec_profile) {
|
|
|
|
var dia = 3
|
|
, sens = 0;
|
|
|
|
if (profile !== undefined) {
|
|
dia = profile.getDIA(time, spec_profile) || 3;
|
|
sens = profile.getSensitivity(time, spec_profile);
|
|
}
|
|
|
|
var scaleFactor = 3.0 / dia
|
|
, peak = 75
|
|
, result = {
|
|
iobContrib: 0
|
|
, activityContrib: 0
|
|
};
|
|
|
|
if (treatment.insulin) {
|
|
var bolusTime = treatment.mills;
|
|
var minAgo = scaleFactor * (time - bolusTime) / 1000 / 60;
|
|
|
|
if (minAgo < peak) {
|
|
var x1 = minAgo / 5 + 1;
|
|
result.iobContrib = treatment.insulin * (1 - 0.001852 * x1 * x1 + 0.001852 * x1);
|
|
// units: BG (mg/dL) = (BG/U) * U insulin * scalar
|
|
result.activityContrib = sens * treatment.insulin * (2 / dia / 60 / peak) * minAgo;
|
|
|
|
} else if (minAgo < 180) {
|
|
var x2 = (minAgo - 75) / 5;
|
|
result.iobContrib = treatment.insulin * (0.001323 * x2 * x2 - 0.054233 * x2 + 0.55556);
|
|
result.activityContrib = sens * treatment.insulin * (2 / dia / 60 - (minAgo - peak) * 2 / dia / 60 / (60 * 3 - peak));
|
|
}
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
};
|
|
|
|
iob.updateVisualisation = function updateVisualisation(sbx) {
|
|
var info = [];
|
|
|
|
var prop = sbx.properties.iob;
|
|
|
|
if (prop.lastBolus) {
|
|
var when = new Date(prop.lastBolus.mills).toLocaleTimeString();
|
|
var amount = sbx.roundInsulinForDisplayFormat(Number(prop.lastBolus.insulin)) + 'U';
|
|
info.push({ label: translate('Last Bolus'), value: amount + ' @ ' + when });
|
|
}
|
|
if (prop.basaliob !== undefined) {
|
|
info.push({ label: translate('Basal IOB'), value: prop.basaliob.toFixed(2) });
|
|
}
|
|
if (prop.source !== undefined) {
|
|
info.push({ label: translate('Source'), value: prop.source });
|
|
}
|
|
if (prop.device !== undefined) {
|
|
info.push({ label: translate('Device'), value: prop.device });
|
|
}
|
|
|
|
if (prop.treatmentIob !== undefined) {
|
|
info.push({label: '------------', value: ''});
|
|
info.push({ label: translate('Careportal IOB'), value: prop.treatmentIob.toFixed(2) });
|
|
}
|
|
|
|
var value = (prop.display !== undefined ? sbx.roundInsulinForDisplayFormat(prop.display) : '---') + 'U';
|
|
|
|
sbx.pluginBase.updatePillText(iob, {
|
|
value: value
|
|
, label: translate('IOB')
|
|
, info: info
|
|
});
|
|
|
|
};
|
|
|
|
function virtAsstIOBIntentHandler (callback, slots, sbx) {
|
|
|
|
var message = translate('virtAsstIobIntent', {
|
|
params: [
|
|
getIob(sbx)
|
|
]
|
|
});
|
|
callback(translate('virtAsstTitleCurrentIOB'), message);
|
|
}
|
|
|
|
function virtAsstIOBRollupHandler (slots, sbx, callback) {
|
|
var iob = getIob(sbx);
|
|
var message = translate('virtAsstIob', {
|
|
params: [iob]
|
|
});
|
|
callback(null, {results: message, priority: 2});
|
|
}
|
|
|
|
function getIob(sbx) {
|
|
var iob = _.get(sbx, 'properties.iob.iob');
|
|
if (iob !== 0) {
|
|
return translate('virtAsstIobUnits', {
|
|
params: [
|
|
utils.toFixed(iob)
|
|
]
|
|
});
|
|
}
|
|
return translate('virtAsstNoInsulin');
|
|
}
|
|
|
|
iob.virtAsst = {
|
|
rollupHandlers: [{
|
|
rollupGroup: 'Status'
|
|
, rollupName: 'current iob'
|
|
, rollupHandler: virtAsstIOBRollupHandler
|
|
}]
|
|
, intentHandlers: [{
|
|
intent: 'MetricNow'
|
|
, metrics: ['iob', 'insulin on board']
|
|
, intentHandler: virtAsstIOBIntentHandler
|
|
}]
|
|
};
|
|
|
|
return iob;
|
|
|
|
}
|
|
|
|
module.exports = init;
|