Skip to content

Commit

Permalink
Reimplement Predictions support to Reports (nightscout#4254)
Browse files Browse the repository at this point in the history
* reimplement nightscout#3179 (Predictions support to Reports)

* add predictions.js
  • Loading branch information
PieterGit authored and tanja3981 committed May 21, 2019
1 parent 62ae5a5 commit 23a03fc
Show file tree
Hide file tree
Showing 4 changed files with 169 additions and 2 deletions.
124 changes: 124 additions & 0 deletions lib/report_plugins/daytoday.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ daytoday.html = function html(client) {
+ '<input type="checkbox" id="rp_optionsraw"><span style="color:gray;opacity:1">'+translate('Raw')+'</span>'
+ '<input type="checkbox" id="rp_optionsiob"><span style="color:blue;opacity:0.5">'+translate('IOB')+'</span>'
+ '<input type="checkbox" id="rp_optionscob"><span style="color:red;opacity:0.5">'+translate('COB')+'</span>'
+ '<input type="checkbox" id="rp_optionspredicted"><span style="color:sienna;opacity:0.5">'+translate('Predictions')+'</span>'
+ '<input type="checkbox" id="rp_optionsopenaps"><span style="color:sienna;opacity:0.5">'+translate('OpenAPS')+'</span>'
+ '<input type="checkbox" id="rp_optionsdistribution"><span style="color:blue;opacity:0.5">'+translate('Insulin distribution')+'</span>'
+ '&nbsp;'+translate('Size')
Expand All @@ -39,13 +40,27 @@ daytoday.html = function html(client) {
+ ' <option x="1000" y="300" selected>1000x300px</option>'
+ ' <option x="1200" y="400">1200x400px</option>'
+ ' <option x="1550" y="600">1550x600px</option>'
+ ' <option x="2400" y="800">2400x800px</option>'
+ '</select>'
+ '<br>'
+ translate('Scale') + ': '
+ '<input type="radio" name="rp_scale" id="rp_linear" checked>'
+ translate('Linear')
+ '<input type="radio" name="rp_scale" id="rp_log">'
+ translate('Logarithmic')
+ '<div id="rp_predictedSettings" style="display:none">'
+ translate('Truncate predictions: ')
+ '<input type="checkbox" id="rp_optionsPredictedTruncate" checked>'
+ '<br>'
+ translate('Predictions offset') + ': '
+ '<b><label id="rp_predictedOffset"></label> minutes</b>'
+ '&nbsp;&nbsp;&nbsp;&nbsp;'
+ '<input type="button" onclick="predictMoreBackward();" value="' + translate('-30 min')+'">'
+ '<input type="button" onclick="predictBackward();" value="' + translate('-5 min')+'">'
+ '<input type="button" onclick="predictResetToZero();" value="' + translate('Zero')+'">'
+ '<input type="button" onclick="predictForward();" value="' + translate('+5 min')+'">'
+ '<input type="button" onclick="predictMoreForward();" value="' + translate('+30 min')+'">'
+ '</div>'
+ '<br>'
+ '<div id="daytodaycharts">'
+ '</div>'
Expand Down Expand Up @@ -292,6 +307,115 @@ daytoday.report = function report_daytoday(datastorage,sorteddaystoshow,options)
return sel;
}

// PREDICTIONS START
//
function preparePredictedData() {

var treatmentsTimestamps = []; // Only timestamps for (carbs and bolus insulin) treatments will be captured in this array
treatmentsTimestamps.push(dataRange[0]); // Create a fake timestamp at midnight so we can show predictions during night
for (var i in data.treatments) {
var treatment = data.treatments[i];
if (undefined != treatment.carbs && null != treatment.carbs && treatment.carbs > 0) {
if (treatment.timestamp)
treatmentsTimestamps.push(treatment.timestamp);
else if (treatment.created_at)
treatmentsTimestamps.push(treatment.created_at);
}
if (undefined != treatment.insulin && null != treatment.insulin && treatment.insulin > 0) {
if (treatment.timestamp)
treatmentsTimestamps.push(treatment.timestamp);
else if (treatment.created_at)
treatmentsTimestamps.push(treatment.created_at);
}
}

var predictions = [];
if (data && data.devicestatus) {
for (var i = data.devicestatus.length - 1; i >= 0; i--) {
if (data.devicestatus[i].loop && data.devicestatus[i].loop.predicted) {
predictions.push(data.devicestatus[i].loop.predicted);
} else if (data.devicestatus[i].openaps && data.devicestatus[i].openaps.suggested && data.devicestatus[i].openaps.suggested.predBGs) {
var entry = {};
entry.startDate = data.devicestatus[i].openaps.suggested.timestamp;
// For OpenAPS/AndroidAPS we fall back from COB if present, to UAM, then IOB
if (data.devicestatus[i].openaps.suggested.predBGs.COB) {
entry.values = data.devicestatus[i].openaps.suggested.predBGs.COB;
} else if (data.devicestatus[i].openaps.suggested.predBGs.UAM) {
entry.values = data.devicestatus[i].openaps.suggested.predBGs.UAM;
} else entry.values = data.devicestatus[i].openaps.suggested.predBGs.IOB;
predictions.push(entry);
}
}
}

var p = [];
if (predictions.length > 0 && treatmentsTimestamps.length > 0) {

// Iterate over all treatments, find the predictions for each and add them to the predicted array p
for (var treatmentsIndex = 0; treatmentsIndex < treatmentsTimestamps.length; treatmentsIndex++) {
var timestamp = treatmentsTimestamps[treatmentsIndex];
var predictedIndex = findPredicted(predictions, timestamp, predictedOffset); // Find predictions offset before or after timestamp

if (predictedIndex != null) {
var entry = predictions[predictedIndex]; // Start entry
var d = moment(entry.startDate);
var end = moment().endOf('day');
if (options.predictedTruncate) {
if (predictedOffset >= 0) {
// If we are looking forward we want to stop at the next treatment
if (treatmentsIndex < treatmentsTimestamps.length - 1) {
end = moment(treatmentsTimestamps[treatmentsIndex + 1]);
}
} else {
// If we are looking back, then we want to stop at "this" treatment
end = moment(treatmentsTimestamps[treatmentsIndex]);
}
}
for (var entryIndex in entry.values) {
if (!d.isAfter(end)) {
var value = {};
value.sgv = client.utils.scaleMgdl(entry.values[entryIndex]);
value.date = d.toDate();
value.color = 'purple';
p.push(value);
d.add(5, 'minutes');
}
}
}
}
}
return p;
}

/* Find the earliest new predicted instance that has a timestamp equal to or larger than timestamp */
/* (so if we have bolused or eaten we want to find the prediction that Loop has estimated just after that) */
/* Returns the index into the predictions array that is the predicted we are looking for */
function findPredicted(predictions, timestamp, offset) {
var ts = moment(timestamp).add(offset, 'minutes');
var predicted = null;
if (offset && offset < 0) { // If offset is negative, start searching from first prediction going forward
for (var i = 0; i < predictions.length; i++) {
if (predictions[i] && predictions[i].startDate && moment(predictions[i].startDate) <= ts) {
predicted = i;
}
}
} else { // If offset is positive or zero, start searching from last prediction going backward
for (var i = predictions.length - 1; i > 0; i--) {
if (predictions[i] && predictions[i].startDate && moment(predictions[i].startDate) >= ts) {
predicted = i;
}
}
}
return predicted;
}
//
// PREDICTIONS ENDS


// bind up the context chart data to an array of circles
var contextData = (options.predicted ? data.sgv.concat(preparePredictedData()) : data.sgv);
var contextCircles = context.selectAll('circle').data(contextData);

// if new circle then just display
prepareContextCircles(contextCircles.enter().append('circle'));

Expand Down
40 changes: 40 additions & 0 deletions static/report/js/predictions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@

var predictedOffset = 0;

function predictForward() {
predictedOffset += 5;
$("#rp_predictedOffset").html(predictedOffset);
$("#rp_show").click();
}

function predictMoreForward() {
predictedOffset += 30;
$("#rp_predictedOffset").html(predictedOffset);
$("#rp_show").click();
}

function predictBackward() {
predictedOffset -= 5;
$("#rp_predictedOffset").html(predictedOffset);
$("#rp_show").click();
}

function predictMoreBackward() {
predictedOffset -= 30;
$("#rp_predictedOffset").html(predictedOffset);
$("#rp_show").click();
}

function predictResetToZero() {
predictedOffset = 0;
$("#rp_predictedOffset").html(predictedOffset);
$("#rp_show").click();
}

$(document).on('change', '#rp_optionspredicted', function() {
if (this.checked)
$("#rp_predictedSettings").show();
else
$("#rp_predictedSettings").hide();
predictResetToZero();
});
6 changes: 4 additions & 2 deletions static/report/js/report.js
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,8 @@
options.iob = $('#rp_optionsiob').is(':checked');
options.cob = $('#rp_optionscob').is(':checked');
options.openAps = $('#rp_optionsopenaps').is(':checked');
options.predicted = $('#rp_optionspredicted').is(':checked');
options.predictedTruncate = $('#rp_optionsPredictedTruncate').is(':checked');
options.basal = $('#rp_optionsbasal').is(':checked');
options.notes = $('#rp_optionsnotes').is(':checked');
options.food = $('#rp_optionsfood').is(':checked');
Expand Down Expand Up @@ -552,7 +554,7 @@

function loadData(day, options, callback) {
// check for loaded data
if ((options.openAps || options.iob || options.cob) && datastorage[day] && !datastorage[day].devicestatus.length) {
if ((options.openAps || options.predicted || options.iob || options.cob) && datastorage[day] && !datastorage[day].devicestatus.length) {
// OpenAPS requested but data not loaded. Load anyway ...
} else if (datastorage[day] && day !== moment().format('YYYY-MM-DD')) {
callback(day);
Expand Down Expand Up @@ -673,7 +675,7 @@
data.devicestatus = [];
return $.Deferred().resolve();
}
if(options.iob || options.cob || options.openAps) {
if (options.iob || options.cob || options.openAps || options.predicted) {
$('#info-' + day).html('<b>'+translate('Loading device status data of')+' '+day+' ...</b>');
var tquery = '?find[created_at][$gte]=' + new Date(from).toISOString() + '&find[created_at][$lt]=' + new Date(to).toISOString() + '&count=10000';
return $.ajax('/api/v1/devicestatus.json'+tquery, {
Expand Down
1 change: 1 addition & 0 deletions views/reportindex.html
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ <h1><img src="/images/logo1.png"><span class="translate">Nightscout reporting</s
<script src="/socket.io/socket.io.js?v=<%= locals.cachebuster %>"></script>
<script src="/report/js/report.js?v=<%= locals.cachebuster %>"></script>
<script src="/report/js/flotcandle.js?v=<%= locals.cachebuster %>"></script>
<script src="/report/js/predictions.js?v=<%= locals.cachebuster %>"></script>
<script src="/report/js/loopalyzer.js?v=<%= locals.cachebuster %>"></script>
</body>
</html>

0 comments on commit 23a03fc

Please sign in to comment.