Add an option to enable IOB in the mainline #452

Merged
merged 25 commits into from Mar 8, 2015
Commits
+431 −42
Split
View
@@ -1,6 +1,9 @@
bower_components/
node_modules/
+
+bundle/bundle.out.js
+
.idea/
*.iml
my.env
View
@@ -0,0 +1,12 @@
+(function () {
+
+ window.Nightscout = window.Nightscout || {};
+
+ window.Nightscout = {
+ iob: require('../lib/iob')()
+ };
+
+ console.info("Nightscout bundle ready", window.Nightscout);
+
+})();
+
View
@@ -0,0 +1,17 @@
+'use strict';
+
+var browserify_express = require('browserify-express');
+
+function bundle() {
+ return browserify_express({
+ entry: __dirname + '/bundle.source.js',
+ watch: [__dirname + '../lib/', __dirname + '/bundle.source.js'],
+ mount: '/public/js/bundle.js',
+ verbose: true,
+ //minify: true,
+ bundle_opts: { debug: true }, // enable inline sourcemap on js files
+ write_file: __dirname + '/bundle.out.js'
+ });
+}
+
+module.exports = bundle;
View
1 env.js
@@ -41,6 +41,7 @@ function config ( ) {
env.mongo_collection = readENV('MONGO_COLLECTION', 'entries');
env.settings_collection = readENV('MONGO_SETTINGS_COLLECTION', 'settings');
env.treatments_collection = readENV('MONGO_TREATMENTS_COLLECTION', 'treatments');
+ env.profile_collection = readENV('MONGO_PROFILE_COLLECTION', 'profile');
env.devicestatus_collection = readENV('MONGO_DEVICESTATUS_COLLECTION', 'devicestatus');
env.enable = readENV('ENABLE');
View
@@ -1,6 +1,6 @@
'use strict';
-function create (env, entries, settings, treatments, devicestatus) {
+function create (env, entries, settings, treatments, profile, devicestatus) {
var express = require('express'),
app = express( )
;
@@ -47,6 +47,7 @@ function create (env, entries, settings, treatments, devicestatus) {
app.use('/', require('./entries/')(app, wares, entries));
app.use('/', require('./settings/')(app, wares, settings));
app.use('/', require('./treatments/')(app, wares, treatments));
+ app.use('/', require('./profile/')(app, wares, profile));
app.use('/', require('./devicestatus/')(app, wares, devicestatus));
// Status
View
@@ -0,0 +1,29 @@
+'use strict';
+
+var consts = require('../../constants');
+
+function configure (app, wares, profile) {
+ var express = require('express'),
+ api = express.Router( );
+
+ // invoke common middleware
+ api.use(wares.sendJSONStatus);
+ // text body types get handled as raw buffer stream
+ api.use(wares.bodyParser.raw( ));
+ // json body types get handled as parsed json
+ api.use(wares.bodyParser.json( ));
+ // also support url-encoded content-type
+ api.use(wares.bodyParser.urlencoded({ extended: true }));
+
+ // List settings available
+ api.get('/profile/', function(req, res) {
+ profile.list(function (err, attribute) {
+ return res.json(attribute);
+ });
+ });
+
+ return api;
+}
+
+module.exports = configure;
+
View
@@ -0,0 +1,80 @@
+'use strict';
+
+function calcTotal(treatments, profile, time) {
+
+ console.info("trying to calc");
+
+ var iob = 0
+ , activity = 0;
+
+ if (!treatments) return {};
+
+ if (profile === undefined) {
+ //if there is no profile default to 3 hour dia
+ profile = {dia: 3, sens: 0};
+ }
+
+ if (time === undefined) {
+ time = new Date();
+ }
+
+ treatments.forEach(function (treatment) {
+ if (new Date(treatment.created_at) < time) {
+ var tIOB = calcTreatment(treatment, profile, time);
+ if (tIOB && tIOB.iobContrib) iob += tIOB.iobContrib;
+ if (tIOB && tIOB.activityContrib) activity += tIOB.activityContrib;
+ }
+ });
+
+ return {
+ iob: iob,
+ display: iob.toFixed(2),
+ activity: activity
+ };
+}
+
+function calcTreatment(treatment, profile, time) {
+
+ var dia = profile.dia
+ , scaleFactor = 3.0 / dia
+ , peak = 75
+ , sens = profile.sens
+ , iobContrib = 0
+ , activityContrib = 0;
+
+ if (treatment.insulin) {
+ var bolusTime = new Date(treatment.created_at);
+ var minAgo = scaleFactor * (time - bolusTime) / 1000 / 60;
+
+ if (minAgo < peak) {
+ var x = minAgo / 5 + 1;
+ iobContrib = treatment.insulin * (1 - 0.001852 * x * x + 0.001852 * x);
+ activityContrib = sens * treatment.insulin * (2 / dia / 60 / peak) * minAgo;
+
+ } else if (minAgo < 180) {
+ var x = (minAgo - 75) / 5;
+ iobContrib = treatment.insulin * (0.001323 * x * x - .054233 * x + .55556);
+ activityContrib = sens * treatment.insulin * (2 / dia / 60 - (minAgo - peak) * 2 / dia / 60 / (60 * dia - peak));
+ } else {
+ iobContrib = 0;
+ activityContrib = 0;
+ }
+
+ }
+
+ return {
+ iobContrib: iobContrib,
+ activityContrib: activityContrib
+ };
+
+}
+
+function IOB(opts) {
+
+ return {
+ calcTotal: calcTotal
+ };
+
+}
+
+module.exports = IOB;
View
@@ -11,6 +11,8 @@ var DIRECTIONS = {
, 'RATE OUT OF RANGE': 9
};
+var iob = require("./iob")();
+
function directionToTrend (direction) {
var trend = 8;
if (direction in DIRECTIONS) {
@@ -23,6 +25,8 @@ function pebble (req, res) {
var ONE_DAY = 24 * 60 * 60 * 1000;
var useMetricBg = (req.query.units === "mmol");
var uploaderBattery;
+ var treatmentResults;
+ var profileResult;
function scaleBg(bg) {
if (useMetricBg) {
@@ -74,8 +78,11 @@ function pebble (req, res) {
var count = parseInt(req.query.count) || 1;
var bgs = sgvData.slice(0, count);
- //for compatibility we're keeping battery here, but it would be better somewhere else
+ //for compatibility we're keeping battery and iob here, but they would be better somewhere else
bgs[0].battery = uploaderBattery ? "" + uploaderBattery : undefined;
+ if (req.iob) {
+ bgs[0].iob = iob.calcTotal(treatmentResults.slice(0, 20), profileResult, new Date(now)).display;
+ }
var result = { status: [ {now:now}], bgs: bgs, cals: calData.slice(0, count) };
res.setHeader('content-type', 'application/json');
@@ -91,15 +98,48 @@ function pebble (req, res) {
}
var earliest_data = Date.now() - ONE_DAY;
- var q = { find: {"date": {"$gte": earliest_data}} };
- req.entries.list(q, get_latest);
+ loadTreatments(req, earliest_data, function (err, trs) {
+ treatmentResults = trs;
+ loadProfile(req, function (err, profileResults) {
+ profileResults.forEach(function(profile) {
+ if (profile) {
+ if (profile.dia) {
+ profileResult = profile;
+ }
+ }
+ });
+ var q = { find: {"date": {"$gte": earliest_data}} };
+ req.entries.list(q, get_latest);
+ });
+ });
});
}
-function configure (entries, devicestatus, env) {
+
+function loadTreatments(req, earliest_data, fn) {
+ if (req.iob) {
+ var q = { find: {"created_at": {"$gte": new Date(earliest_data).toISOString()}} };
+ req.treatments.list(q, fn);
+ } else {
+ fn(null, []);
+ }
+}
+
+function loadProfile(req, fn) {
+ if (req.iob) {
+ req.profile.list(fn);
+ } else {
+ fn(null, []);
+ }
+}
+
+function configure (entries, treatments, profile, devicestatus, env) {
function middle (req, res, next) {
req.entries = entries;
+ req.treatments = treatments;
+ req.profile = profile;
req.devicestatus = devicestatus;
req.rawbg = env.enable && env.enable.indexOf('rawbg') > -1;
+ req.iob = env.enable && env.enable.indexOf('iob') > -1;
next( );
}
return [middle, pebble];
View
@@ -0,0 +1,24 @@
+'use strict';
+
+function configure (collection, storage) {
+
+ function create (obj, fn) {
+ obj.created_at = (new Date( )).toISOString( );
+ api( ).insert(obj, function (err, doc) {
+ fn(null, doc);
+ });
+ }
+
+ function list (fn) {
+ return api( ).find({ }).sort({created_at: -1}).toArray(fn);
+ }
+
+ function api ( ) {
+ return storage.pool.db.collection(collection);
+ }
+
+ api.list = list;
+ api.create = create;
+ return api;
+}
+module.exports = configure;
View
@@ -1,5 +1,5 @@
-function websocket (env, server, entries, treatments) {
+function websocket (env, server, entries, treatments, profiles) {
"use strict";
// CONSTANTS
var ONE_HOUR = 3600000,
@@ -29,6 +29,7 @@ var dir2Char = {
treatmentData = [],
mbgData = [],
calData = [],
+ profileData = [],
patientData = [];
function start ( ) {
@@ -146,6 +147,7 @@ function update() {
cgmData = [];
treatmentData = [];
mbgData = [];
+ profileData = [];
var earliest_data = now - TWO_DAYS;
var q = { find: {"date": {"$gte": earliest_data}} };
entries.list(q, function (err, results) {
@@ -188,8 +190,19 @@ function update() {
treatment.x = timestamp.getTime();
return treatment;
});
- // all done, do loadData
- loadData( );
+
+ profiles.list(function (err, results) {
+ // There should be only one document in the profile collection with a DIA. If there are multiple, use the last one.
+ results.forEach(function(element, index, array) {
+ if (element) {
+ if (element.dia) {
+ profileData[0] = element;
+ }
+ }
+ });
+ // all done, do loadData
+ loadData( );
+ });
});
});
@@ -237,6 +250,10 @@ function loadData() {
});
}
+ if (profileData) {
+ var profile = profileData;
+ }
+
if (actualCurrent && actualCurrent < 39) errorCode = actualCurrent;
var actualLength = actual.length - 1;
@@ -271,7 +288,7 @@ function loadData() {
// consolidate and send the data to the client
var shouldEmit = is_different(actual, predicted, mbg, treatment, cal);
- patientData = [actual, predicted, mbg, treatment, cal];
+ patientData = [actual, predicted, mbg, treatment, profile, cal];
console.log('patientData', patientData.length);
if (shouldEmit) {
emitData( );
View
@@ -34,16 +34,17 @@
"dependencies": {
"body-parser": "^1.4.3",
"bower": "^1.3.8",
+ "browserify-express": "^0.1.4",
"errorhandler": "^1.1.1",
"event-stream": "~3.1.5",
"express": "^4.6.1",
"express-extension-to-accept": "0.0.2",
+ "git-rev": "git://github.com/bewest/git-rev.git",
"mongodb": "^1.4.7",
"moment": "2.8.1",
"pushover-notifications": "0.2.0",
"sgvdata": "0.0.2",
- "socket.io": "^0.9.17",
- "git-rev": "git://github.com/bewest/git-rev.git"
+ "socket.io": "^0.9.17"
},
"devDependencies": {
"istanbul": "~0.3.5",
Oops, something went wrong.