Skip to content

Commit

Permalink
Move CL->NS data transformation into its own module
Browse files Browse the repository at this point in the history
  • Loading branch information
mddub committed Oct 14, 2015
1 parent 577da16 commit 437e9fa
Show file tree
Hide file tree
Showing 3 changed files with 112 additions and 107 deletions.
105 changes: 0 additions & 105 deletions nightscout.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,111 +6,6 @@ var crypto = require('crypto'),

var logger = require('./logger');

var STALE_DATA_THRESHOLD_MINUTES = 20;
var PUMP_STATUS_ENTRY_TYPE = 'pump_status';
var SENSOR_GLUCOSE_ENTRY_TYPE = 'sgv';

function parsePumpTime(pumpTimeString, offset) {
return Date.parse(pumpTimeString + ' ' + offset);
}

function addTimeToEntry(timestamp, entry) {
entry['date'] = timestamp;
entry['dateString'] = new Date(timestamp).toISOString();
return entry;
}

var guessPumpOffset = (function() {
var lastGuess;

// From my observations, sMedicalDeviceTime is advanced by the server even when the app is
// not reporting data or the pump is not connected, so its difference from server time is
// always close to a whole number of hours, and can be used to guess the pump's timezone:
// https://gist.github.com/mddub/f673570e6427c93784bf
return function(data) {
var pumpTimeAsIfUTC = Date.parse(data['sMedicalDeviceTime'] + ' +0');
var serverTimeUTC = data['currentServerTime'];
var hours = Math.round((pumpTimeAsIfUTC - serverTimeUTC) / (60*60*1000));
var offset = (hours >= 0 ? '+' : '-') + (Math.abs(hours) < 10 ? '0' : '') + Math.abs(hours) + '00';
if (offset !== lastGuess) {
logger.log('Guessed pump timezone ' + offset + ' (pump time: "' + data['sMedicalDeviceTime'] + '"; server time: ' + new Date(data['currentServerTime']) + ')');
}
lastGuess = offset;
return offset;
};
})();

function pumpStatusEntry(data) {
var entry = {'type': PUMP_STATUS_ENTRY_TYPE};

[
'conduitBatteryLevel',
'conduitInRange',
'conduitMedicalDeviceInRange',
'reservoirLevelPercent',
'reservoirAmount',
'medicalDeviceBatteryLevelPercent'
].forEach(function(key) {
if(data[key] !== undefined) {
entry[key] = data[key];
}
});

if(data['activeInsulin'] && data['activeInsulin']['amount'] >= 0) {
entry['activeInsulin'] = data['activeInsulin']['amount'];
}

return addTimeToEntry(data['lastMedicalDeviceDataUpdateServerTime'], entry);
}

function sgvEntries(data) {
var offset = guessPumpOffset(data);

if(data['sgs'] && data['sgs'].length) {
return data['sgs'].filter(function(entry) {
return entry['kind'] === 'SG' && entry['sg'] !== 0;
}).map(function(sgv) {
return addTimeToEntry(
parsePumpTime(sgv['datetime'], offset),
{
'type': SENSOR_GLUCOSE_ENTRY_TYPE,
'sgv': sgv['sg'],
}
);
});
} else {
return [];
}
}

var transform = module.exports.transform = function(data, sgvLimit) {
var recency = (data['currentServerTime'] - data['lastMedicalDeviceDataUpdateServerTime']) / (60 * 1000);
if (recency > STALE_DATA_THRESHOLD_MINUTES) {
logger.log('Stale CareLink data: ' + recency.toFixed(2) + ' minutes old');
return [];
}

if (sgvLimit === undefined) {
sgvLimit = Infinity;
}

var entries = [];

entries.push(pumpStatusEntry(data));

var sgvs = sgvEntries(data);
// TODO: this assumes sgvs are ordered by date ascending
for(var i = Math.max(0, sgvs.length - sgvLimit); i < sgvs.length; i++) {
entries.push(sgvs[i]);
}

entries.forEach(function(entry) {
entry['device'] = 'MiniMed Connect ' + data['medicalDeviceFamily'] + ' ' + data['medicalDeviceSerialNumber'];
});

return entries;
};

var upload = module.exports.upload = function(entries, endpoint, secret, callback) {
logger.log('POST ' + endpoint + ' ' + JSON.stringify(entries));
request.post(
Expand Down
5 changes: 3 additions & 2 deletions run.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
"use strict";

var carelink = require('./carelink'),
nightscout = require('./nightscout');
nightscout = require('./nightscout'),
transform = require('./transform');

function readEnv(key, defaultVal) {
var val = process.env[key] ||
Expand Down Expand Up @@ -37,7 +38,7 @@ var endpoint = (config.nsBaseUrl ? config.nsBaseUrl : 'https://' + config.nsHost
if (err) {
throw new Error(err);
} else {
var entries = nightscout.transform(data, config.sgvLimit);
var entries = transform(data, config.sgvLimit);
if (entries.length > 0) {
nightscout.upload(entries, endpoint, config.nsSecret, function(err, response) {
if (err) {
Expand Down
109 changes: 109 additions & 0 deletions transform.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/* jshint node: true */
"use strict";

var logger = require('./logger');

var STALE_DATA_THRESHOLD_MINUTES = 20;
var PUMP_STATUS_ENTRY_TYPE = 'pump_status';
var SENSOR_GLUCOSE_ENTRY_TYPE = 'sgv';

function parsePumpTime(pumpTimeString, offset) {
return Date.parse(pumpTimeString + ' ' + offset);
}

function addTimeToEntry(timestamp, entry) {
entry['date'] = timestamp;
entry['dateString'] = new Date(timestamp).toISOString();
return entry;
}

var guessPumpOffset = (function() {
var lastGuess;

// From my observations, sMedicalDeviceTime is advanced by the server even when the app is
// not reporting data or the pump is not connected, so its difference from server time is
// always close to a whole number of hours, and can be used to guess the pump's timezone:
// https://gist.github.com/mddub/f673570e6427c93784bf
return function(data) {
var pumpTimeAsIfUTC = Date.parse(data['sMedicalDeviceTime'] + ' +0');
var serverTimeUTC = data['currentServerTime'];
var hours = Math.round((pumpTimeAsIfUTC - serverTimeUTC) / (60*60*1000));
var offset = (hours >= 0 ? '+' : '-') + (Math.abs(hours) < 10 ? '0' : '') + Math.abs(hours) + '00';
if (offset !== lastGuess) {
logger.log('Guessed pump timezone ' + offset + ' (pump time: "' + data['sMedicalDeviceTime'] + '"; server time: ' + new Date(data['currentServerTime']) + ')');
}
lastGuess = offset;
return offset;
};
})();

function pumpStatusEntry(data) {
var entry = {'type': PUMP_STATUS_ENTRY_TYPE};

[
'conduitBatteryLevel',
'conduitInRange',
'conduitMedicalDeviceInRange',
'reservoirLevelPercent',
'reservoirAmount',
'medicalDeviceBatteryLevelPercent'
].forEach(function(key) {
if(data[key] !== undefined) {
entry[key] = data[key];
}
});

if(data['activeInsulin'] && data['activeInsulin']['amount'] >= 0) {
entry['activeInsulin'] = data['activeInsulin']['amount'];
}

return addTimeToEntry(data['lastMedicalDeviceDataUpdateServerTime'], entry);
}

function sgvEntries(data) {
var offset = guessPumpOffset(data);

if(data['sgs'] && data['sgs'].length) {
return data['sgs'].filter(function(entry) {
return entry['kind'] === 'SG' && entry['sg'] !== 0;
}).map(function(sgv) {
return addTimeToEntry(
parsePumpTime(sgv['datetime'], offset),
{
'type': SENSOR_GLUCOSE_ENTRY_TYPE,
'sgv': sgv['sg'],
}
);
});
} else {
return [];
}
}

var transform = module.exports = function(data, sgvLimit) {
var recency = (data['currentServerTime'] - data['lastMedicalDeviceDataUpdateServerTime']) / (60 * 1000);
if (recency > STALE_DATA_THRESHOLD_MINUTES) {
logger.log('Stale CareLink data: ' + recency.toFixed(2) + ' minutes old');
return [];
}

if (sgvLimit === undefined) {
sgvLimit = Infinity;
}

var entries = [];

entries.push(pumpStatusEntry(data));

var sgvs = sgvEntries(data);
// TODO: this assumes sgvs are ordered by date ascending
for(var i = Math.max(0, sgvs.length - sgvLimit); i < sgvs.length; i++) {
entries.push(sgvs[i]);
}

entries.forEach(function(entry) {
entry['device'] = 'MiniMed Connect ' + data['medicalDeviceFamily'] + ' ' + data['medicalDeviceSerialNumber'];
});

return entries;
};

0 comments on commit 437e9fa

Please sign in to comment.