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

Predictions support to Reports #3179

Closed
wants to merge 83 commits into from
Closed
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
83 commits
Select commit Hold shift + click to select a range
87091bf
added helper run script
lukas-ondriga-kistler May 17, 2017
2177330
Added autotune tab
lukas-ondriga-kistler May 18, 2017
7c29e7a
Add table header
martinpetlus May 18, 2017
9530eaa
removed mimification
vstrisovsky May 18, 2017
45506cc
Add leading table rows
martinpetlus May 18, 2017
2e0fbae
Merge branch 'autotune-report-plugin' of github.com:lukas-ondriga/cgm…
martinpetlus May 18, 2017
5e25ce7
Add translate as param
martinpetlus May 18, 2017
39464d1
Add Basal Profile title row
martinpetlus May 18, 2017
38046a7
Select first profile
martinpetlus May 18, 2017
cf2973d
Fix variable name
martinpetlus May 18, 2017
fb788c0
prepare data test
vstrisovsky May 18, 2017
316e4d5
Show current values in table
martinpetlus May 18, 2017
43995c1
Fix typo
martinpetlus May 18, 2017
293b739
Increase precision
martinpetlus May 18, 2017
cb99cca
Added finding active profile.
May 18, 2017
ee82566
Add empty rows
martinpetlus May 18, 2017
2267e16
Merge branch 'autotune-report-plugin' of github.com:lukas-ondriga/cgm…
martinpetlus May 18, 2017
d53d507
Replace th with td
martinpetlus May 18, 2017
9166165
Add basic styles
martinpetlus May 18, 2017
c1a83f0
Add colored table rows
martinpetlus May 18, 2017
734d538
Prepare and autotune data.
vstrisovsky May 18, 2017
3abf385
Merge branch 'prepare-data' into autotune-report-plugin
vstrisovsky May 18, 2017
36ea171
Add arrow icons
martinpetlus May 18, 2017
d9e097e
Merge branch 'autotune-report-plugin' of github.com:lukas-ondriga/cgm…
martinpetlus May 18, 2017
f6ccd85
Add recomennded values
martinpetlus May 18, 2017
c8f8daa
Remove header
martinpetlus May 18, 2017
db73627
Move images
martinpetlus May 18, 2017
158d624
Add arrows
martinpetlus May 18, 2017
998885b
Show Recommended rate values.
May 18, 2017
65270d6
Add arrows to basal records
martinpetlus May 18, 2017
d9f869a
Critical changes (above 20%) are highlighted by red color.
May 18, 2017
ae69302
fixed dates
vstrisovsky May 18, 2017
1414a22
Merge branch 'autotune-report-plugin' of https://github.com/lukas-ond…
vstrisovsky May 18, 2017
06e2a49
Refine cond
martinpetlus May 18, 2017
6cb4010
Merge branch 'autotune-report-plugin' of github.com:lukas-ondriga/cgm…
martinpetlus May 18, 2017
4271562
Split autotune to libraries.
marek0o May 18, 2017
8a97da9
Add Checkbox
martinpetlus May 18, 2017
758fcee
Split autotune prepare data to libraries.
marek0o May 18, 2017
306b9f0
Autotune prepare data - add missing time format.
marek0o May 19, 2017
9d62015
Merge branch 'autotune-libs' into autotune-report-plugin
marek0o May 19, 2017
49649a1
Revert "change default back to 3HR"
scottleibrand Oct 17, 2017
c1206e7
shorten HR to H to avoid line-wrapping on mobile
scottleibrand Oct 17, 2017
49f9abd
try larger label font: 35 instead of 25 (vs. 40)
scottleibrand Oct 17, 2017
9732a73
Merge branch 'rendering' into 4h-default
scottleibrand Oct 18, 2017
ab8edda
Merge branch 'rendering' into 4h-default
scottleibrand Oct 19, 2017
ce07fef
Merge branch 'rendering' into 4h-default
scottleibrand Oct 20, 2017
50f07ac
Merge branch 'rendering' into 4h-default
scottleibrand Nov 1, 2017
9529cc4
Merge branch 'rendering' into 4h-default
scottleibrand Nov 1, 2017
0d79ddd
add comma
scottleibrand Nov 1, 2017
aca31f2
Merge remote-tracking branch 'origin/dev' into 4h-default
scottleibrand Nov 2, 2017
089f5b0
deviceInfo.recent null check
scottleibrand Nov 14, 2017
a0801e6
Merge branch 'deviceInfo-null-check' into 4h-default
scottleibrand Nov 14, 2017
18cc2b2
default back to 3, w/ 4 as an option
scottleibrand Nov 20, 2017
0b5018c
re-add H to single-digit hours
scottleibrand Nov 20, 2017
9b47edf
Merge branch 'dev' of github.com:nightscout/cgm-remote-monitor into 4…
scottleibrand Nov 29, 2017
63910d7
Merge branch 'autotune-report-plugin' of https://github.com/lukas-ond…
scottleibrand Nov 29, 2017
3191560
Merge remote-tracking branch 'origin/dev' into autotune
scottleibrand Nov 29, 2017
41f5695
declare var
scottleibrand Nov 29, 2017
4da3ea2
declare variables
scottleibrand Nov 30, 2017
fa97ffd
refactor functions into include file; suppress blank rows
scottleibrand Nov 30, 2017
a1c1eb7
update autotune libs to 0.6.1-dev
scottleibrand Nov 30, 2017
63a9ad7
add TODO
scottleibrand Nov 30, 2017
9720bc9
Initial version
Dec 18, 2017
9826c18
Added non-truncated predictions
Dec 18, 2017
284fe74
trim down IOBInputs.history to just the data for 6h prior to BGDate
scottleibrand Dec 25, 2017
3214ed0
comment out some process.stderr.write
scottleibrand Dec 25, 2017
caa86f5
Adding notes, reminder of DIY, and caution
danamlewis Dec 25, 2017
2fdb70b
paragraph breaks
scottleibrand Dec 25, 2017
58eab3e
Change output headers for clarity
danamlewis Dec 25, 2017
c49cce3
Merge branch 'autotune' of github.com:nightscout/cgm-remote-monitor i…
scottleibrand Dec 25, 2017
2c85341
remove commented code
scottleibrand Dec 25, 2017
e204200
declare variables
scottleibrand Dec 26, 2017
2fd6bae
Revert "removed mimification"
scottleibrand Dec 27, 2017
734f299
clean up trailing whitespace
scottleibrand Dec 27, 2017
04f8797
Merge branch 'dev' into autotune
sulkaharo Jan 1, 2018
4433168
Fix autotune for mmol users. Lookup the default profile so it works f…
sulkaharo Jan 1, 2018
c9fdeb5
Oops, let's not break all the other reports by changing data passed b…
sulkaharo Jan 1, 2018
71b313c
OpenAPS COB, UAM, IOB curves
Jan 15, 2018
c69357e
Merge pull request #1 from nightscout/dev
lixgbg Mar 10, 2018
584fcdb
Merge pull request #2 from nightscout/autotune
lixgbg Mar 10, 2018
20207bf
Merge pull request #5 from lixgbg/autotune
lixgbg Mar 10, 2018
3529327
Merge branch 'dev-henrik' into wip/report-predictions
lixgbg Mar 10, 2018
9053098
Revert "Merge branch 'dev-henrik' into wip/report-predictions"
Mar 10, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
232 changes: 147 additions & 85 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 @@ -231,10 +246,6 @@ daytoday.report = function report_daytoday(datastorage,sorteddaystoshow,options)
.attr('stroke', 'grey');
});

// bind up the context chart data to an array of circles
var contextCircles = context.selectAll('circle')
.data(data.sgv);

function prepareContextCircles(sel) {
var badData = [];
sel.attr('cx', function (d) {
Expand Down Expand Up @@ -283,6 +294,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 Expand Up @@ -631,80 +751,8 @@ daytoday.report = function report_daytoday(datastorage,sorteddaystoshow,options)
'translate(' + (xScale2(treatment.mills) + padding.left + 10) + ',' + (padding.top+yInsulinScale(treatment.insulin)) + ')')
.text(Number(treatment.insulin).toFixed(2)+'U');
}

// process the rest
if (treatment.insulin || treatment.carbs || treatment.eventType == 'Temp Basal' || treatment.eventType == 'Combo Bolus') {
// ignore
} else if (treatment.eventType === 'Profile Switch') {
// profile switch
appendProfileSwitch(context, treatment);
if (treatment.duration && !treatment.cuttedby) {
appendProfileSwitch(context, {
cutting: treatment.profile
, profile: client.profilefunctions.activeProfileToTime(times.mins(treatment.duration).msecs + treatment.mills + 1)
, mills: times.mins(treatment.duration).msecs + treatment.mills
, end: true
});
}
} else if (treatment.eventType === 'Exercise' && treatment.duration) {
context.append('rect')
.attr('x', xScale2(treatment.mills) + padding.left)
.attr('y', yScale2(client.utils.scaleMgdl(396)) + padding.top)
.attr('width', xScale2(treatment.mills + times.mins(treatment.duration).msecs) - xScale2(treatment.mills))
.attr('height', yScale2(client.utils.scaleMgdl(360)) - yScale2(client.utils.scaleMgdl(396)))
.attr('stroke-width', 1)
.attr('opacity', .2)
.attr('stroke', 'white')
.attr('fill', 'Violet');
context.append('text')
.style('font-size', '12px')
.style('font-weight', 'bold')
.attr('fill', 'Violet')
.attr('text-anchor', 'middle')
.attr('dy', '.35em')
.attr('y', yScale2(client.utils.scaleMgdl(378)) + padding.top)
.attr('x', xScale2(treatment.mills + times.mins(treatment.duration).msecs/2) + padding.left)
.text(treatment.notes);
} else if (treatment.eventType === 'Note' && treatment.duration) {
context.append('rect')
.attr('x', xScale2(treatment.mills) + padding.left)
.attr('y', yScale2(client.utils.scaleMgdl(360)) + padding.top)
.attr('width', xScale2(treatment.mills + times.mins(treatment.duration).msecs) - xScale2(treatment.mills))
.attr('height', yScale2(client.utils.scaleMgdl(324)) - yScale2(client.utils.scaleMgdl(360)))
.attr('stroke-width', 1)
.attr('opacity', .2)
.attr('stroke', 'white')
.attr('fill', 'Salmon');
context.append('text')
.style('font-size', '12px')
.style('font-weight', 'bold')
.attr('fill', 'Salmon')
.attr('text-anchor', 'middle')
.attr('dy', '.35em')
.attr('y', yScale2(client.utils.scaleMgdl(342)) + padding.top)
.attr('x', xScale2(treatment.mills + times.mins(treatment.duration).msecs/2) + padding.left)
.text(treatment.notes);
} else if (treatment.eventType === 'OpenAPS Offline' && treatment.duration) {
context.append('rect')
.attr('x', xScale2(treatment.mills) + padding.left)
.attr('y', yScale2(client.utils.scaleMgdl(324)) + padding.top)
.attr('width', xScale2(treatment.mills + times.mins(treatment.duration).msecs) - xScale2(treatment.mills))
.attr('height', yScale2(client.utils.scaleMgdl(288)) - yScale2(client.utils.scaleMgdl(324)))
.attr('stroke-width', 1)
.attr('opacity', .2)
.attr('stroke', 'white')
.attr('fill', 'Brown');
context.append('text')
.style('font-size', '12px')
.style('font-weight', 'bold')
.attr('fill', 'Brown')
.attr('text-anchor', 'middle')
.attr('dy', '.35em')
.attr('y', yScale2(client.utils.scaleMgdl(306)) + padding.top)
.attr('x', xScale2(treatment.mills + times.mins(treatment.duration).msecs/2) + padding.left)
.text(treatment.notes);
} else if (!treatment.duration) {
// other treatments without duration
if (!treatment.insulin && !treatment.carbs && !treatment.duration && treatment.eventType !== 'Temp Basal' && treatment.eventType !== 'Profile Switch') {
context.append('circle')
.attr('cx', xScale2(treatment.mills) + padding.left)
.attr('cy', yScale2(scaledTreatmentBG(treatment,data.sgv)) + padding.top)
Expand All @@ -720,28 +768,42 @@ daytoday.report = function report_daytoday(datastorage,sorteddaystoshow,options)
.attr('y', yScale2(scaledTreatmentBG(treatment,data.sgv)) + padding.top -10)
.attr('x', xScale2(treatment.mills) + padding.left + 10)
.text(translate(client.careportal.resolveEventName(treatment.eventType)));
} else if (treatment.duration) {
}
// other treatments with duration
if (!treatment.insulin && !treatment.carbs && treatment.duration && treatment.eventType !== 'Temp Basal' && treatment.eventType !== 'Profile Switch' && treatment.eventType !== 'Combo Bolus') {
context.append('rect')
.attr('x', xScale2(treatment.mills) + padding.left)
.attr('y', yScale2(client.utils.scaleMgdl(432)) + padding.top)
.attr('y', 0 + padding.top)
.attr('width', xScale2(treatment.mills + times.mins(treatment.duration).msecs) - xScale2(treatment.mills))
.attr('height', yScale2(client.utils.scaleMgdl(396)) - yScale2(client.utils.scaleMgdl(432)))
.attr('height', chartHeight)
//.attr('rx', 5)
//.attr('ry', 5)
.attr('stroke-width', 1)
.attr('opacity', .2)
.attr('stroke', 'white')
.attr('fill', 'black');
.attr('fill', treatment.eventType === 'Exercise' ? 'Violet' : (treatment.eventType === 'Note' ? 'Salmon' : 'black'));
context.append('text')
.style('font-size', '12px')
.style('font-weight', 'bold')
.attr('fill', 'black')
.attr('fill', treatment.eventType === 'Exercise' ? 'Violet' : (treatment.eventType === 'Note' ? 'Salmon' : 'black'))
.attr('text-anchor', 'middle')
.attr('dy', '.35em')
.attr('y', yScale2(client.utils.scaleMgdl(414)) + padding.top)
.attr('y', foodtexts * 15 + 10 + padding.top)
.attr('x', xScale2(treatment.mills + times.mins(treatment.duration).msecs/2) + padding.left)
.text(treatment.notes);
} else {
console.log("missed treatment", treatment);
foodtexts = (foodtexts + 1) % 6;
}
// profile switch
if (treatment.eventType === 'Profile Switch') {
appendProfileSwitch(context, treatment);
if (treatment.duration && !treatment.cuttedby) {
appendProfileSwitch(context, {
cutting: treatment.profile
, profile: client.profilefunctions.activeProfileToTime(times.mins(treatment.duration).msecs + treatment.mills + 1)
, mills: times.mins(treatment.duration).msecs + treatment.mills
, end: true
});
}
}
});

Expand Down