From a89487a515d947dbe7fa737382259179052540d4 Mon Sep 17 00:00:00 2001 From: Jeremy Cunningham Date: Tue, 13 Feb 2018 21:23:52 -0600 Subject: [PATCH 001/274] Fixed raw bg calculations to use noise level to determine filters vs. unfiltered. --- lib/plugins/rawbg.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/plugins/rawbg.js b/lib/plugins/rawbg.js index a3853f88e11..a15bd777918 100644 --- a/lib/plugins/rawbg.js +++ b/lib/plugins/rawbg.js @@ -55,11 +55,10 @@ function init (ctx) { if (cleaned.slope === 0 || cleaned.unfiltered === 0 || cleaned.scale === 0) { raw = 0; - } else if (cleaned.filtered === 0 || sgv.mgdl < 40) { + } else if (parseInt(sgv.noise) < 2 || sgv.mgdl < 40) { raw = cleaned.scale * (cleaned.unfiltered - cleaned.intercept) / cleaned.slope; } else { - var ratio = cleaned.scale * (cleaned.filtered - cleaned.intercept) / cleaned.slope / sgv.mgdl; - raw = cleaned.scale * (cleaned.unfiltered - cleaned.intercept) / cleaned.slope / ratio; + raw = cleaned.scale * (cleaned.filtered - cleaned.intercept) / cleaned.slope; } return Math.round(raw); From 5643aa0df86361b7e1a7c77f101a881fcb1bf6a5 Mon Sep 17 00:00:00 2001 From: Jeremy Cunningham Date: Tue, 13 Feb 2018 21:57:27 -0600 Subject: [PATCH 002/274] Changed test to use the updated rawbg calculation values. --- tests/rawbg.test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/rawbg.test.js b/tests/rawbg.test.js index 4697bea900a..4a2ceac019b 100644 --- a/tests/rawbg.test.js +++ b/tests/rawbg.test.js @@ -27,7 +27,7 @@ describe('Raw BG', function ( ) { sbx.offerProperty = function mockedOfferProperty (name, setter) { name.should.equal('rawbg'); var result = setter(); - result.mgdl.should.equal(113); + result.mgdl.should.equal(115); result.noiseLabel.should.equal('Clean'); done(); }; @@ -47,7 +47,7 @@ describe('Raw BG', function ( ) { rawbg.alexa.intentHandlers[0].intentHandler(function next(title, response) { title.should.equal('Current Raw BG'); - response.should.equal('Your raw bg is 113'); + response.should.equal('Your raw bg is 115'); done(); }, [], sbx); From 056928385f6f806eaedc66b02f13264ffec2dbb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Sj=C3=B6strand?= Date: Tue, 29 May 2018 21:41:15 +0200 Subject: [PATCH 003/274] First test deploy New branch dev -> loopalyzer-dev, cloned to Mac, edited index.js and pushing. --- lib/report_plugins/index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/report_plugins/index.js b/lib/report_plugins/index.js index 12cd024bdd2..7cd32a78f91 100644 --- a/lib/report_plugins/index.js +++ b/lib/report_plugins/index.js @@ -19,6 +19,7 @@ function init() { , require('./calibrations')() , require('./treatments')() , require('./profiles')() +// , require('./loopalyzer')() ]; consts.scaleYFromSettings = function scaleYFromSettings (client) { From 166af844485b87004eb0b2340f809bcad8c148b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Sj=C3=B6strand?= Date: Tue, 29 May 2018 22:26:09 +0200 Subject: [PATCH 004/274] Test --- lib/report_plugins/index.js | 2 +- lib/report_plugins/loopalyzer.js | 940 +++++++++++++++++++++++++++++++ static/report/js/loopalyzer.js | 52 ++ 3 files changed, 993 insertions(+), 1 deletion(-) create mode 100644 lib/report_plugins/loopalyzer.js create mode 100644 static/report/js/loopalyzer.js diff --git a/lib/report_plugins/index.js b/lib/report_plugins/index.js index 7cd32a78f91..58f798bb27a 100644 --- a/lib/report_plugins/index.js +++ b/lib/report_plugins/index.js @@ -19,7 +19,7 @@ function init() { , require('./calibrations')() , require('./treatments')() , require('./profiles')() -// , require('./loopalyzer')() + , require('./loopalyzer')() ]; consts.scaleYFromSettings = function scaleYFromSettings (client) { diff --git a/lib/report_plugins/loopalyzer.js b/lib/report_plugins/loopalyzer.js new file mode 100644 index 00000000000..0e41f667f6c --- /dev/null +++ b/lib/report_plugins/loopalyzer.js @@ -0,0 +1,940 @@ +'use strict'; + +//var _ = require('lodash'); +var moment = window.moment; +//var times = require('../times'); +//var d3 = (global && global.d3) || require('d3'); + +var loopalyzer = { + name: 'loopalyzer' + , label: 'Loopalyzer' + , pluginType: 'report' +}; + +function init() { + return loopalyzer; +} + +module.exports = init; + +var minutewindow = 5; //minute-window should be a divisor of 60 + +loopalyzer.html = function html(client) { + var translate = client.translate; + var ret = '' + ret += '

' + translate('Loopalyzer') + '  

' + ret += '' + translate('To see this report, press SHOW while in this view') + '' + ret += '' + ret += '
' + ret += '
' + ret += '
' + ret += '
' + ret += '
' + ret += '
' + ret += '
' + ret += '
' + ret += '
' + ; + return ret; +}; + +loopalyzer.css = + '#loopalyzer-basal, #loopalyzer-bg, #loopalyzer-tempbasal, #loopalyzer-iob, #loopalyzer-cob {' + + ' width: 100%;' + + ' height: 100%;' + + '}' + ; + +loopalyzer.prepareHtml = function loopalyzerPrepareHtml() { +// $('#loopalyzer-carbTreatmentsTable').html(''); +// $('#loopalyzer-insulinTreatmentsTable').html(''); +// $('#loopalyzer-charts').append($('
')); +}; + +loopalyzer.ss = require('simple-statistics'); + +loopalyzer.getSGVs = function(datastorage, daysToShow) { + var data = datastorage.allstatsrecords; + var bins = []; + + // Fill the bins array with the timestamp, one per 5 minutes + var date = moment(); + date.set({'hours':0, 'minutes':0, 'seconds':0, 'milliseconds':0}); + for (var i=0; i<288; i++) { + bins.push([date.toDate(),[]]); + date.add(5, 'minutes'); + } + + // Loop thru the days to show, for each day find the matching SGVs and insert into the bins entry array + daysToShow.forEach(function(day) { + var entries = []; // Array with all SGVs for this day, we'll fill this and then insert into the bins later + for (var i=0; i<288; i++) entries.push(NaN); // Fill the array with NaNs so we have something in case we don't find an SGV + var fromDate = moment(day); + var toDate = moment(day); + fromDate.set({'hours':0, 'minutes':0, 'seconds':0, 'milliseconds':0}); + toDate.set({'hours':0, 'minutes':5, 'seconds':0, 'milliseconds':0}); // toDate is 5 mins ahead + for (var i=0; i<288; i++) { + var found = false; + data.some(function(record) { + var recDate = moment(record.displayTime); + if (!found && recDate.isAfter(fromDate) && recDate.isBefore(toDate)) { + entries[i]=record.sgv; + found = true; + } + return found; // Breaks .some loop if found is true + }) + fromDate.add(5, 'minutes'); + toDate.add(5, 'minutes'); + } + for (var i=0; i<288; i++) { + bins[i][1].push(entries[i]); + } + }); + + return bins; +} + + +loopalyzer.getBasals = function(datastorage, daysToShow, profile) { + var bins = []; + + // Loop through all hours & minutes and days and get the basal from profile + for (var hour = 0; hour < 24; hour++) { + for (var minute = 0; minute < 60; minute = minute + minutewindow) { + var basalValues = []; + for (var m = 0; m < minutewindow; m = m + 5) { + daysToShow.forEach(function(d){ + var date = new Date(d); + date.setHours(hour); + date.setMinutes(minute + m); + date.setSeconds(0); + date.setMilliseconds(0); + var basalValue = profile.getTempBasal(date); + basalValues.push(basalValue.basal); + }); + } + var date = new Date(); + date.setHours(hour); + date.setMinutes(minute); + date.setSeconds(0); + date.setMilliseconds(0); + bins.push([date, basalValues]); + } + } + return bins; +} + +loopalyzer.getTempBasalDeltas = function(datastorage, daysToShow, profile) { + var bins = []; + + // Loop through all hours & minutes and days and get the basal from profile, + // and compute the plus/minus delta temp basal and push to bins + var date = moment(); + date.set({'hours':0, 'minutes':0, 'seconds':0, 'milliseconds':0}); + + for (var i=0; i<288; i++) { + var basalValues = []; + daysToShow.forEach(function(day){ + var d = moment(day); + d.set({'hours':date.hours(), 'minutes':date.minutes(), 'seconds':0, 'milliseconds':0}); + var basalValue = profile.getTempBasal(d.toDate()); + basalValues.push(basalValue.tempbasal - basalValue.basal); + }); + bins.push([date.toDate(), basalValues]); + date.add(5, 'minutes') + } + return bins; +} + + +loopalyzer.getIOBs = function(datastorage, daysToShow) { + + // If no IOB available return empty array + if (!client.plugins('iob').isDeviceStatusAvailable(datastorage.devicestatus)) + return []; + + // Create a single array of empty IOB entries to hold all IOBs for all days, one entry per 5 minutes + var iobs=[]; + var numberOfEntries = 288 * daysToShow.length; + for (var i=0; i0}).forEach(function(treatment){ + if (moment(treatment.created_at).isBetween(startDate, endDate)) { + treatments.push({date:treatment.created_at, amount:treatment.carbs}); + } + }) + return treatments; +} + +/* Returns the insulin treatments array as [date, amount] */ +loopalyzer.getInsulinTreatments = function(datastorage, daysToShow) { + var treatments = []; // Holds the treatments [date, amount] + var startDate = moment(daysToShow[0]); + var endDate = moment(daysToShow[daysToShow.length-1]).add(1, 'days'); + + datastorage.treatments.filter(function(treatment){return treatment.insulin && treatment.insulin >0}).forEach(function(treatment){ + if (moment(treatment.created_at).isBetween(startDate, endDate)) { + treatments.push({date:treatment.created_at, amount:treatment.insulin}); + } + }) + return treatments; +} + +// PREDICTIONS START +// +loopalyzer.getAllTreatmentTimestampsForADay = function(datastorage, day) { + var timestamps = []; + var dayStart = moment(day).startOf('day'); + timestamps.push(dayStart.toDate()); // Create a fake timestamp at midnight so we can show predictions during night + + var carbTreatments = loopalyzer.getCarbTreatments(datastorage,[day]); + var insulinTreatments = loopalyzer.getInsulinTreatments(datastorage,[day]); + carbTreatments.forEach(function(entry) { timestamps.push(entry.date)}); + insulinTreatments.forEach(function(entry) { timestamps.push(entry.date)}); + timestamps.sort(function(a,b) { return (a= 0; i--) { + if (datastorage.devicestatus[i].loop && datastorage.devicestatus[i].loop.predicted) { + var predicted = datastorage.devicestatus[i].loop.predicted; + if (moment(predicted.startDate).isSame(dayStart,'day')) + predictions.push(datastorage.devicestatus[i].loop.predicted); + } + /* else if (datastorage.devicestatus[i].openaps && datastorage.devicestatus[i].openaps.suggested && datastorage.devicestatus[i].openaps.suggested.predBGs) { + var entry = {}; + entry.startDate = datastorage.devicestatus[i].openaps.suggested.timestamp; + // For OpenAPS/AndroidAPS we fall back from COB if present, to UAM, then IOB + if (datastorage.devicestatus[i].openaps.suggested.predBGs.COB) { + entry.values = datastorage.devicestatus[i].openaps.suggested.predBGs.COB; + } else if (datastorage.devicestatus[i].openaps.suggested.predBGs.UAM) { + entry.values = datastorage.devicestatus[i].openaps.suggested.predBGs.UAM; + } else entry.values = datastorage.devicestatus[i].openaps.suggested.predBGs.IOB; + predictions.push(entry); + } + */ + } + // Remove duplicates before we're done + var p = []; + predictions.forEach(function(prediction) { + if (p.length === 0 || prediction.startDate !== p[p.length-1].startDate) + p.push(prediction); + }) +// console.log('PREDICTIONS for ' + day, p); + 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 */ +loopalyzer.findPredicted = function(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; +} + + +loopalyzer.getPredictions = function(datastorage, daysToShow, client) { + + if (!datastorage.devicestatus) + return []; + + var predictedOffset = 0; + var truncatePredictions = true; + + // Fill the bins array with the timestamp, one per 5 minutes + var bins = []; + var date = moment(); + date.set({'hours':0, 'minutes':0, 'seconds':0, 'milliseconds':0}); + for (var i=0; i<288; i++) { + bins.push([date.toDate(),[]]); + date.add(5, 'minutes'); + } + + daysToShow.forEach(function(day) { + var p = []; // Array with all prediction SGVs for this day, we'll fill this and then insert into the bins later + for (var i=0; i<288; i++) p.push(NaN); + var treatmentTimestamps = loopalyzer.getAllTreatmentTimestampsForADay(datastorage, day); + var predictions = loopalyzer.getAllPredictionsForADay(datastorage, day); + + if (predictions.length > 0 && treatmentTimestamps.length > 0) { + + // Iterate over all treatments, find the predictions for each and add them to the entries array p, aligned on timestamp + for (var treatmentsIndex = 0; treatmentsIndex < treatmentTimestamps.length; treatmentsIndex++) { + var timestamp = treatmentTimestamps[treatmentsIndex]; + var predictedIndex = loopalyzer.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(day).endOf('day'); // Default to stop and end of the day + if (truncatePredictions) { + if (predictedOffset >= 0) { + // But if we are looking forward we want to stop at the next treatment + if (treatmentsIndex < treatments.length - 1) { + end = moment(treatmentTimestamps[treatmentsIndex + 1]); + } + } else { + // And if we are looking backward then we want to stop at "this" treatment + end = moment(treatmentTimestamps[treatmentsIndex]); + } + } + for (var entryIndex in entry.values) { + if (!d.isAfter(end)) { + var dayStart = moment(d).startOf('day'); + var minutesAfterMidnight = moment(d).diff(dayStart, 'minutes'); + var index = Math.floor(minutesAfterMidnight/5); + p[index] = client.utils.scaleMgdl(entry.values[entryIndex]); + d.add(5, 'minutes'); + } + } + } + } + } + for (var i=0; i<288; i++) { + bins[i][1].push(p[i]); + } + }) + return bins; + } + // + // PREDICTIONS ENDS + + +loopalyzer.min = function(xBins) { + var min = xBins[0][1]; + for (var i=0; imax) max = xBins[i][1]; + } + return max; +} + +loopalyzer.avg = function(xBins) { + var out=[]; + xBins.forEach(function(entry){ + var sum = 0; + var count = 0; + entry[1].forEach(function(value){ + if (value && value != NaN) { + sum += value; + count++; + } + }) + var avg = sum / count; + out.push([entry[0], avg]); + }) + return out; +} + +// Timeshifts a bins array with subarrays for multiple days +loopalyzer.timeShiftBins = function(bins, timeShift) { + if (bins && bins.length>0) { + timeShift.forEach(function(minutes, dayIndex){ + if (minutes !==0) { + var tempBin = []; + bins.forEach(function(){ + tempBin.push(NaN); // Fill tempBin with NaNs + }) + var minutesBy5 = Math.floor(minutes/5); + if (minutesBy5>0) { + var count = 288-minutesBy5; + // If minutes>0 it means we should shift forward in time + // Example: Shift by 15 mins = 3 buckets + // bin : 0 1 2 3 4 5 6 7 8 9 10 + // tempBin: NaN NaN NaN 0 1 2 3 4 5 6 7 + for (var i=0; i0) { + daysToShow.forEach(function(day, dayIndex){ + var minutesToAdd = timeShift[dayIndex]; + var date = moment(day); + bin.forEach(function(entry, entryIndex){ + var entryDate = moment(entry.date); + if (entryDate.isSame(date, 'day')) { + entryDate.add(minutesToAdd, 'minutes'); + bin[entryIndex].date=entryDate.toDate(); + } + }) + }) + } +} + +// Main method +loopalyzer.report = function report_loopalyzer(datastorage,sorteddaystoshow,options) { + var Nightscout = window.Nightscout; + var client = Nightscout.client; + var translate = client.translate; + var profile = client.sbx.data.profile; + var report_plugins = Nightscout.report_plugins; + // var scaledTreatmentBG = report_plugins.utils.scaledTreatmentBG; + + var today = new Date(); + var todayJSON = {'year':today.getFullYear(), 'month':today.getMonth(), 'date':today.getDate()}; + + // Copy the sorteddaystoshow into new array (clone) and re-sort ascending (so we don't mess with original array) + var daysToShow = []; + sorteddaystoshow.forEach(function(day){daysToShow.push(day)}); + daysToShow.sort(function(a,b) { return (a1) dateInfo += ' - ' + moment(daysToShow[daysToShow.length-1]).format('ddd MMM D'); // .split(',')[0]; + $("#dateinfo").html(dateInfo); + + loopalyzer.prepareHtml(); + $("#loopalyzer-help").hide(); + $("#loopalyzer-buttons").show(); + if (daysToShow.length==1) + $("#rp_loopalyzertimeshiftinput").hide(); /* Hide timeShift if single day */ + else + $("#rp_loopalyzertimeshiftinput").show(); /* and show if multi days */ + + + // Pull all necessary treatment information + profile.updateTreatments(datastorage.profileSwitchTreatments, datastorage.tempbasalTreatments, datastorage.combobolusTreatments); + + var carbTreatments = loopalyzer.getCarbTreatments(datastorage,daysToShow); + var insulinTreatments = loopalyzer.getInsulinTreatments(datastorage,daysToShow); + var sgvBin = loopalyzer.getSGVs(datastorage,daysToShow); + var basalsBin = loopalyzer.getBasals(datastorage,daysToShow,profile); + var tempBasalsBin = loopalyzer.getTempBasalDeltas(datastorage,daysToShow,profile); + var iobBin = loopalyzer.getIOBs(datastorage,daysToShow); + var cobBin = loopalyzer.getCOBs(datastorage,daysToShow); + var predictionsBin = []; + + if ($("#rp_loopalyzerpredictions").is(":checked")) { + predictionsBin = loopalyzer.getPredictions(datastorage,daysToShow,client); + } + + // Prepare an array with the minutes to timeShift each day (0 as default since timeShift is off by default) + var timeShifts = []; + var firstCarbs = []; + var timeShiftStartTime = null; // If timeShifting this is the average time the meals were eaten + var timeShiftStopTime = null; // and this is the start + DIA according to profile + var doTimeShift = false; + daysToShow.forEach(function(){ timeShifts.push(0); firstCarbs.push(NaN) }); + + // Check to see if we are doing timeShift or not + if ($("#rp_loopalyzertimeshift").is(":checked") && daysToShow.length>1) { + var mealMinCarbs = $("#rp_loopalyzermincarbs").val(); + var t1 = $("#rp_loopalyzert1").val(); + var t2 = $("#rp_loopalyzert2").val(); + + if (t2>t1) { + var h1 = t1.split(':')[0]; + var m1 = t1.split(':')[1]; + var h2 = t2.split(':')[0]; + var m2 = t2.split(':')[1]; + + var timeShiftBegin = moment(); + timeShiftBegin.set({'hours':h1, 'minutes':m1, 'seconds':0}); + + var timeShiftEnd = moment(); + timeShiftEnd.set({'hours':h2, 'minutes':m2, 'seconds':0}); + + //Loop through the carb treatments and find the first meal each day + daysToShow.forEach(function(day, dayIndex){ + var timeShiftBegin = moment(day); + var timeShiftEnd = moment(day); + timeShiftBegin.set({'hours':h1, 'minutes':m1, 'seconds':0}); + timeShiftEnd.set({'hours':h2, 'minutes':m2, 'seconds':0}); + + var found = false; + carbTreatments.forEach(function(entry){ + if (!found && entry.amount >= mealMinCarbs) { + var date = moment(entry.date); + if ( (date.isSame(timeShiftBegin,'minute') || date.isAfter(timeShiftBegin,'minute')) && + (date.isSame(timeShiftEnd,'minute') || date.isBefore(timeShiftEnd,'minute')) ) { + var startOfDay = moment(entry.date); + startOfDay.set({'hours':0, 'minutes':0, 'seconds':0}); + var minutesAfterMidnight = date.diff(startOfDay, 'minutes'); + firstCarbs[dayIndex]=minutesAfterMidnight; + found = true; + doTimeShift = true; + } + } + }) + }) + + // Calculate the average starting time, in minutes after midnight + var averageMinutesAfterMidnight = 0, sum = 0, count = 0; + firstCarbs.forEach(function(minutesAfterMidnight){ + if (minutesAfterMidnight) { // Avoid NaN + sum += minutesAfterMidnight; + count++; + } + }); + var averageMinutesAfterMidnight = Math.round(sum / count); + + var dia = profile.getDIA(); + if (!dia || dia <= 0) + dia=6; // Default to 6h if DIA not set in profile + timeShiftStartTime = moment(todayJSON); + timeShiftStartTime.minutes(averageMinutesAfterMidnight); + timeShiftStopTime = moment(todayJSON); + if (averageMinutesAfterMidnight + dia*60 < 24*60) + timeShiftStopTime.minutes(averageMinutesAfterMidnight + dia*60); // If not beyond midnight, stop at end of DIA + else + timeShiftStopTime.minutes(24*60-1); // If beyond midnight, stop at midnight + + // Compute the timeShift (+ / -) that we should add to each entry (sgv, iob, carbs, etc) for each day + firstCarbs.forEach(function(minutesAfterMidnight,index){ + if (minutesAfterMidnight) { // Avoid NaN + var delta = Math.round(averageMinutesAfterMidnight - minutesAfterMidnight); + timeShifts[index]=delta; + } + }); + + if (doTimeShift) { + loopalyzer.timeShiftBins(sgvBin, timeShifts); + loopalyzer.timeShiftBins(basalsBin, timeShifts); + loopalyzer.timeShiftBins(tempBasalsBin, timeShifts); + loopalyzer.timeShiftBins(iobBin, timeShifts); + loopalyzer.timeShiftBins(cobBin, timeShifts); + loopalyzer.timeShiftBins(predictionsBin, timeShifts); + loopalyzer.timeShiftSingleBin(carbTreatments, daysToShow, timeShifts); + loopalyzer.timeShiftSingleBin(insulinTreatments, daysToShow, timeShifts); + } + } else { + console.log('Loopalyzer - Timeshift end must be later than beginning.'); + } + } + + // After timeShift code block, get the average values + var sgvAvg = loopalyzer.avg(sgvBin); + var basalsAvg = loopalyzer.avg(basalsBin); + var tempBasalsAvg = loopalyzer.avg(tempBasalsBin); + var iobAvg = loopalyzer.avg(iobBin); + var cobAvg = loopalyzer.avg(cobBin); + var predictionsAvg = loopalyzer.avg(predictionsBin); + + var high = options.targetHigh; + var low = options.targetLow; + + // Set up the charts basics + function tickFormatter(val,axis) { + if (val <= axis.min) { return ''; } + if (val >= axis.max) { return ''; } + return val + ''; + } + + var tickColor = '#DDDDDD'; + var basalColor = '#33A0FF'; + var glucoseColor = '#33AA33'; + var predictionsColor = '#8E1578'; + var glucoseRangeColor = '#D6FFD6'; + var insulinColor = '#FF7000'; + var carbColor = '#23D820'; + var timeShiftBackgroundColor = "#F3F3F3"; + var barWidth = (24 * 60 * 60 * 1000 / 288); + var borderWidth = 1; + var labelWidth = 25; + var xaxisCfg = { + mode: 'time', + timezone: 'browser', + timeformat: '%H:%M', + tickColor: tickColor, + tickSize: [1, "hour"], + font: { size: 0 } + }; + + var hiddenAxis = { + position: "right", + show: true, + labelWidth: 10, + tickColor: "#FFFFFF", + font: { size: 0} + } + + // For drawing the carbs and insulin treatments + var markings = []; + var markingColor = "#000000"; + + + // Chart 1: Basal + markings = []; + if (doTimeShift) + markings.push( { xaxis: { from: timeShiftStartTime.toDate(), to: timeShiftStopTime.toDate()}, color: timeShiftBackgroundColor } ); + var chartBasalData = [{ + data: basalsAvg, + label: translate('Basal profile'), + id: 'basals', + color: basalColor, + points: { show: false }, + bars: { show: true, fill: true, barWidth: barWidth }, + yaxis: 1 + }]; + var chartBasalOptions = { + xaxis: xaxisCfg, + yaxes: [{ + tickColor: tickColor, + labelWidth: labelWidth, + tickFormatter: function(val,axis) { return tickFormatter(val,axis); } + }, + hiddenAxis], + grid: { + borderWidth: borderWidth, + markings: markings + } + }; + $.plot( '#loopalyzer-basal', chartBasalData, chartBasalOptions ); + + + // Chart 2: Blood glucose + markings = []; + if (doTimeShift) + markings.push( { xaxis: { from: timeShiftStartTime.toDate(), to: timeShiftStopTime.toDate()}, color: timeShiftBackgroundColor } ); + markings.push({ yaxis: { from: low, to: high }, color: glucoseRangeColor }); + + var chartBGData = [{ + label: translate('Blood glucose'), + data: sgvAvg, + id: 'glucose', + color: glucoseColor, + points: { show: false }, + lines: { show: true } + },{ + label: translate('Predictions'), + data: predictionsAvg, + id: 'predictions', + color: predictionsColor, + points: { show: true, fill: true, radius: 0.75, fillColor: predictionsColor }, + lines: { show: false } + }]; + var chartBGOptions = { + xaxis: xaxisCfg, + yaxes: [{ + min: 0, + max: options.units === 'mmol' ? 20 : 400, + tickColor: tickColor, + labelWidth: labelWidth, + tickFormatter: function(val,axis) { return tickFormatter(val,axis); } + }, + hiddenAxis], + grid: { + borderWidth: borderWidth, + markings: markings + } + }; + $.plot( '#loopalyzer-bg', chartBGData, chartBGOptions ); + + + // Chart 3: Delta temp basals + markings = []; + if (doTimeShift) + markings.push( { xaxis: { from: timeShiftStartTime.toDate(), to: timeShiftStopTime.toDate()}, color: timeShiftBackgroundColor } ); + markings.push( { yaxis: { from: 0, to: 0 }, color: insulinColor, lineWidth: 2 }); + + var chartTempBasalData = [{ + data: tempBasalsAvg, + label: translate('Temp basal delta'), + id: 'tempBasals', + color: insulinColor, + points: { show: false }, + bars: { show: true, barWidth: barWidth } + }]; + var chartTempBasalOptions = { + xaxis: xaxisCfg, + yaxes: [{ + tickColor: tickColor, + labelWidth: labelWidth, + tickFormatter: function(val,axis) { return tickFormatter(val,axis); } + }, + hiddenAxis], + grid: { + borderWidth: borderWidth, + markings: markings + } + }; + $.plot( '#loopalyzer-tempbasal', chartTempBasalData, chartTempBasalOptions ); + + + // Chart 4: IOB + markings = []; + if (doTimeShift) + markings.push( { xaxis: { from: timeShiftStartTime.toDate(), to: timeShiftStopTime.toDate()}, color: timeShiftBackgroundColor } ); + if ($("#rp_loopalyzertreatments").is(":checked")) { + insulinTreatments.forEach(function(treatment){ + var startDate = moment(treatment.date); + var endDate = moment(treatment.date); + startDate.set(todayJSON); + endDate.set(todayJSON); + endDate.add(5, 'minutes'); + markings.push( { xaxis: { from: startDate.toDate(), to: endDate.toDate()}, yaxis: { from: 0, to: treatment.amount }, color: markingColor } ); + }) + } + + var chartIOBData = [{ + data: iobAvg, + label: translate('IOB'), + id: 'iobs', + color: insulinColor, + points: { show: false }, + bars: { show: true, fill: true, barWidth: barWidth } + }]; + var chartIOBOptions = { + xaxis: xaxisCfg, + yaxes: [{ + tickColor: tickColor, + labelWidth: labelWidth, + tickFormatter: function(val,axis) { return tickFormatter(val,axis); } + }, + hiddenAxis], + grid: { + borderWidth: borderWidth, + markings: markings + } + }; + $.plot( '#loopalyzer-iob', chartIOBData, chartIOBOptions ); + + + // Chart 5: COB + markings = []; + if (doTimeShift) + markings.push( { xaxis: { from: timeShiftStartTime.toDate(), to: timeShiftStopTime.toDate()}, color: timeShiftBackgroundColor } ); + if ($("#rp_loopalyzertreatments").is(":checked")) { + carbTreatments.forEach(function(treatment){ + var startDate = moment(treatment.date); + var endDate = moment(treatment.date); + startDate.set(todayJSON); + endDate.set(todayJSON); + endDate.add(5, 'minutes'); + markings.push( { xaxis: { from: startDate.toDate(), to: endDate.toDate()}, yaxis: { from: 0, to: treatment.amount }, color: markingColor } ); + }) + } + delete xaxisCfg.font; // Remove the font config so HH:MM is shown on the last chart + + var chartCOBData = [{ + data: cobAvg, + label: translate('COB'), + id: 'cobs', + color: carbColor, + points: { show: false }, + bars: { show: true, fil: true, barWidth: barWidth } + }]; + var chartCOBOptions = { + xaxis: xaxisCfg, + yaxes: [{ + tickColor: tickColor, + labelWidth: labelWidth, + tickFormatter: function(val,axis) { return tickFormatter(val,axis); } + }, + hiddenAxis], + grid: { + borderWidth: borderWidth, + markings: markings + } + }; + $.plot( '#loopalyzer-cob', chartCOBData, chartCOBOptions ); + +}; diff --git a/static/report/js/loopalyzer.js b/static/report/js/loopalyzer.js new file mode 100644 index 00000000000..ebe434f2e64 --- /dev/null +++ b/static/report/js/loopalyzer.js @@ -0,0 +1,52 @@ +// Moves backward one day +function loopalyzerBackward() { + var moment = window.moment; + var from = moment($("#rp_from").val()).subtract(1,'day'); + var to = moment($("#rp_to").val()).subtract(1,'day'); + $("#rp_from").val(from.format('YYYY-MM-DD')); + $("#rp_to").val(to.format('YYYY-MM-DD')); + $("#rp_show").click(); +} + +// Moves backward same amount as shown (e.g. whole week) +function loopalyzerMoreBackward() { + var moment = window.moment; + var from = moment($("#rp_from").val()) + var to = moment($("#rp_to").val()) + var diff = to.diff(from, 'days') + 1; + from.subtract(diff, 'days'); + to.subtract(diff, 'days'); + $("#rp_from").val(from.format('YYYY-MM-DD')); + $("#rp_to").val(to.format('YYYY-MM-DD')); + $("#rp_show").click(); +} + +// Moves forward one day +function loopalyzerForward() { + var moment = window.moment; + var from = moment($("#rp_from").val()).add(1,'day'); + var to = moment($("#rp_to").val()).add(1,'day'); + if (to <= moment()) { + $("#rp_from").val(from.format('YYYY-MM-DD')); + $("#rp_to").val(to.format('YYYY-MM-DD')); + $("#rp_show").click(); + } +} + +// Moves forward same amount as shown (e.g. whole week) +function loopalyzerMoreForward() { + var moment = window.moment; + var from = moment($("#rp_from").val()) + var to = moment($("#rp_to").val()) + var diff = to.diff(from, 'days') + 1; + from.add(diff, 'days'); + to.add(diff, 'days'); + if (to > moment()) { + to = moment(); + from = moment(); + from.subtract(diff-1, 'days'); + } + $("#rp_from").val(from.format('YYYY-MM-DD')); + $("#rp_to").val(to.format('YYYY-MM-DD')); + $("#rp_show").click(); +} From d1667481d9d6c0feef10c407a1a5da9b2145802f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Sj=C3=B6strand?= Date: Tue, 29 May 2018 23:09:37 +0200 Subject: [PATCH 005/274] Loopalyzer works --- lib/report_plugins/loopalyzer.js | 16 ++++------------ static/report/js/report.js | 5 +++-- views/reportindex.html | 5 +++-- 3 files changed, 10 insertions(+), 16 deletions(-) diff --git a/lib/report_plugins/loopalyzer.js b/lib/report_plugins/loopalyzer.js index 0e41f667f6c..7c6cd66af10 100644 --- a/lib/report_plugins/loopalyzer.js +++ b/lib/report_plugins/loopalyzer.js @@ -178,10 +178,6 @@ loopalyzer.getTempBasalDeltas = function(datastorage, daysToShow, profile) { loopalyzer.getIOBs = function(datastorage, daysToShow) { - // If no IOB available return empty array - if (!client.plugins('iob').isDeviceStatusAvailable(datastorage.devicestatus)) - return []; - // Create a single array of empty IOB entries to hold all IOBs for all days, one entry per 5 minutes var iobs=[]; var numberOfEntries = 288 * daysToShow.length; @@ -242,10 +238,6 @@ loopalyzer.getIOBs = function(datastorage, daysToShow) { }; loopalyzer.getCOBs = function(datastorage, daysToShow) { - - // If no COB available return empty array - if (!client.plugins('cob').isDeviceStatusAvailable(datastorage.devicestatus)) - return []; // Create a single array of empty COB entries to hold all COBs for all days, one entry per 5 minutes var cobs=[]; @@ -385,7 +377,7 @@ loopalyzer.getAllPredictionsForADay = function(datastorage, day) { if (p.length === 0 || prediction.startDate !== p[p.length-1].startDate) p.push(prediction); }) -// console.log('PREDICTIONS for ' + day, p); + // console.log('PREDICTIONS for ' + day, p); return p; } @@ -585,7 +577,7 @@ loopalyzer.report = function report_loopalyzer(datastorage,sorteddaystoshow,opti // Copy the sorteddaystoshow into new array (clone) and re-sort ascending (so we don't mess with original array) var daysToShow = []; sorteddaystoshow.forEach(function(day){daysToShow.push(day)}); - daysToShow.sort(function(a,b) { return (a1) dateInfo += ' - ' + moment(daysToShow[daysToShow.length-1]).format('ddd MMM D'); // .split(',')[0]; @@ -608,8 +600,8 @@ loopalyzer.report = function report_loopalyzer(datastorage,sorteddaystoshow,opti var sgvBin = loopalyzer.getSGVs(datastorage,daysToShow); var basalsBin = loopalyzer.getBasals(datastorage,daysToShow,profile); var tempBasalsBin = loopalyzer.getTempBasalDeltas(datastorage,daysToShow,profile); - var iobBin = loopalyzer.getIOBs(datastorage,daysToShow); - var cobBin = loopalyzer.getCOBs(datastorage,daysToShow); + var iobBin = (client.plugins('iob').isDeviceStatusAvailable(datastorage.devicestatus) ? loopalyzer.getIOBs(datastorage,daysToShow) : []); + var cobBin = (client.plugins('cob').isDeviceStatusAvailable(datastorage.devicestatus) ? loopalyzer.getCOBs(datastorage,daysToShow) : []); var predictionsBin = []; if ($("#rp_loopalyzerpredictions").is(":checked")) { diff --git a/static/report/js/report.js b/static/report/js/report.js index ba637365dcb..9a083247759 100644 --- a/static/report/js/report.js +++ b/static/report/js/report.js @@ -229,6 +229,7 @@ options.order = ( $('#rp_oldestontop').is(':checked') ? report_plugins.consts.ORDER_OLDESTONTOP : report_plugins.consts.ORDER_NEWESTONTOP ); options.width = parseInt($('#rp_size :selected').attr('x')); options.height = parseInt($('#rp_size :selected').attr('y')); + options.loopalyzer = true; var matchesneeded = 0; @@ -545,7 +546,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.loopalyzer || 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); @@ -665,7 +666,7 @@ data.devicestatus = []; return $.Deferred().resolve(); } - if(options.iob || options.cob || options.openAps) { + if(options.iob || options.cob || options.openAps || options.loopalyzer) { $('#info-' + day).html(''+translate('Loading device status data of')+' '+day+' ...'); 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, { diff --git a/views/reportindex.html b/views/reportindex.html index 47f1e808cda..29a83176923 100644 --- a/views/reportindex.html +++ b/views/reportindex.html @@ -121,6 +121,7 @@

Nightscout reporting - - + + + From 51ac3167efc3057f7593a12c4b0693ebbb4ab2e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Sj=C3=B6strand?= Date: Wed, 30 May 2018 22:56:06 +0200 Subject: [PATCH 006/274] Works ok (minor bug in predictions) --- lib/report_plugins/loopalyzer.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/report_plugins/loopalyzer.js b/lib/report_plugins/loopalyzer.js index 7c6cd66af10..06c682154f4 100644 --- a/lib/report_plugins/loopalyzer.js +++ b/lib/report_plugins/loopalyzer.js @@ -438,10 +438,14 @@ loopalyzer.getPredictions = function(datastorage, daysToShow, client) { var entry = predictions[predictedIndex]; // Start entry var d = moment(entry.startDate); var end = moment(day).endOf('day'); // Default to stop and end of the day +// console.log('...predictedIndex is ' + predictedIndex); +// console.log('...d is ' + d.toDate()); +// console.log('...end is' + end.toDate()); if (truncatePredictions) { if (predictedOffset >= 0) { +// console.log('treatmentTimestamps', treatmentTimestamps); // But if we are looking forward we want to stop at the next treatment - if (treatmentsIndex < treatments.length - 1) { + if (treatmentsIndex < treatmentTimestamps.length - 1) { end = moment(treatmentTimestamps[treatmentsIndex + 1]); } } else { @@ -449,7 +453,9 @@ loopalyzer.getPredictions = function(datastorage, daysToShow, client) { end = moment(treatmentTimestamps[treatmentsIndex]); } } +// console.log('...final end is ' + end.toDate()); for (var entryIndex in entry.values) { + console.log('...comparing d ' + d.toDate() + ' with end ' + end.toDate()); if (!d.isAfter(end)) { var dayStart = moment(d).startOf('day'); var minutesAfterMidnight = moment(d).diff(dayStart, 'minutes'); From a09c434b496af73eb5779c8fa2839d81529c4b03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Sj=C3=B6strand?= Date: Thu, 31 May 2018 19:22:55 +0200 Subject: [PATCH 007/274] v0.5 works well OK --- lib/report_plugins/loopalyzer.js | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/lib/report_plugins/loopalyzer.js b/lib/report_plugins/loopalyzer.js index 06c682154f4..b62039a3fd9 100644 --- a/lib/report_plugins/loopalyzer.js +++ b/lib/report_plugins/loopalyzer.js @@ -338,14 +338,12 @@ loopalyzer.getInsulinTreatments = function(datastorage, daysToShow) { loopalyzer.getAllTreatmentTimestampsForADay = function(datastorage, day) { var timestamps = []; var dayStart = moment(day).startOf('day'); - timestamps.push(dayStart.toDate()); // Create a fake timestamp at midnight so we can show predictions during night - var carbTreatments = loopalyzer.getCarbTreatments(datastorage,[day]); var insulinTreatments = loopalyzer.getInsulinTreatments(datastorage,[day]); carbTreatments.forEach(function(entry) { timestamps.push(entry.date)}); insulinTreatments.forEach(function(entry) { timestamps.push(entry.date)}); timestamps.sort(function(a,b) { return (a= 0) { -// console.log('treatmentTimestamps', treatmentTimestamps); // But if we are looking forward we want to stop at the next treatment if (treatmentsIndex < treatmentTimestamps.length - 1) { end = moment(treatmentTimestamps[treatmentsIndex + 1]); @@ -453,9 +446,7 @@ loopalyzer.getPredictions = function(datastorage, daysToShow, client) { end = moment(treatmentTimestamps[treatmentsIndex]); } } -// console.log('...final end is ' + end.toDate()); for (var entryIndex in entry.values) { - console.log('...comparing d ' + d.toDate() + ' with end ' + end.toDate()); if (!d.isAfter(end)) { var dayStart = moment(d).startOf('day'); var minutesAfterMidnight = moment(d).diff(dayStart, 'minutes'); From 7c92b2ea539e0e00bfebf4e913550d0122e765e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Sj=C3=B6strand?= Date: Thu, 31 May 2018 22:04:16 +0200 Subject: [PATCH 008/274] Fixed so Predictions legend is not shown unless Predictions are shown --- lib/report_plugins/loopalyzer.js | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/lib/report_plugins/loopalyzer.js b/lib/report_plugins/loopalyzer.js index b62039a3fd9..66aa6748b4e 100644 --- a/lib/report_plugins/loopalyzer.js +++ b/lib/report_plugins/loopalyzer.js @@ -792,14 +792,17 @@ loopalyzer.report = function report_loopalyzer(datastorage,sorteddaystoshow,opti color: glucoseColor, points: { show: false }, lines: { show: true } - },{ - label: translate('Predictions'), - data: predictionsAvg, - id: 'predictions', - color: predictionsColor, - points: { show: true, fill: true, radius: 0.75, fillColor: predictionsColor }, - lines: { show: false } }]; + if (predictionsAvg && predictionsAvg.length>0) { + chartBGData.push({ + label: translate('Predictions'), + data: predictionsAvg, + id: 'predictions', + color: predictionsColor, + points: { show: true, fill: true, radius: 0.75, fillColor: predictionsColor }, + lines: { show: false } + }); + } var chartBGOptions = { xaxis: xaxisCfg, yaxes: [{ From 19f2dcce7cd8b2d485263ff56244d79084580b90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Sj=C3=B6strand?= Date: Fri, 1 Jun 2018 21:56:28 +0200 Subject: [PATCH 009/274] Fixed hang if no COB/IOB --- lib/report_plugins/loopalyzer.js | 16 +- npm-shrinkwrap.json | 8406 ++++++++++++++++++++---------- 2 files changed, 5646 insertions(+), 2776 deletions(-) diff --git a/lib/report_plugins/loopalyzer.js b/lib/report_plugins/loopalyzer.js index 66aa6748b4e..49cc81505ef 100644 --- a/lib/report_plugins/loopalyzer.js +++ b/lib/report_plugins/loopalyzer.js @@ -183,6 +183,8 @@ loopalyzer.getIOBs = function(datastorage, daysToShow) { var numberOfEntries = 288 * daysToShow.length; for (var i=0; i Date: Sun, 3 Jun 2018 00:47:41 +0200 Subject: [PATCH 010/274] Changed to Nightscouts default way of reading IOB/COBs --- lib/report_plugins/loopalyzer.js | 51 ++++++++++++++++++++++++++++++-- 1 file changed, 48 insertions(+), 3 deletions(-) diff --git a/lib/report_plugins/loopalyzer.js b/lib/report_plugins/loopalyzer.js index 49cc81505ef..4ff851a7f45 100644 --- a/lib/report_plugins/loopalyzer.js +++ b/lib/report_plugins/loopalyzer.js @@ -244,7 +244,52 @@ loopalyzer.getIOBs = function(datastorage, daysToShow) { return bins; }; -loopalyzer.getCOBs = function(datastorage, daysToShow) { +loopalyzer.getCOBs = function(datastorage, daysToShow, profile, client) { + var cobStatusAvailable = client.plugins('cob').isDeviceStatusAvailable(datastorage.devicestatus); + console.log('cobStatusAvailable=' + cobStatusAvailable); + + // Create a bins array with date entries for entire today + var bins=[]; + var todayStart = moment().startOf('day'); + var todayEnd = moment().endOf('day'); + for (var dt=todayStart; dt < todayEnd; dt.add(5, 'minutes')) { + bins.push([dt.toDate(), []]); + } + + daysToShow.forEach(function(day, dayIndex) { + var dayStart = moment(day).startOf('day'); + var dayEnd = moment(day).endOf('day'); + var cobs = []; + if (cobStatusAvailable) { + var dayStartMills = dayStart.milliseconds(); + for (var i=0; i<288; i++) cobs.push(NaN); // Clear the COBs by filling with NaNs + var cobArray = client.plugins('cob').COBDeviceStatusesInTimeRange(datastorage.devicestatus, dayStart.valueOf(), dayEnd.valueOf()); + console.log('cobArray', cobArray); + cobArray.forEach(function(entry){ + var index = Math.floor(moment(entry.mills).diff(dayStart,'minutes') / 5); + cobs[index] = entry.cob; + }); + } else { + for (var dt=dayStart; dt < dayEnd; dt.add(5, 'minutes')) { + var cob = client.plugins('cob').cobTotal(datastorage.treatments,datastorage.devicestatus,profile,dt.toDate()).cob; + cobs.push(cob); + console.log(dt.format() + ' cob=' + cob); + } + } + // cobs array are now 288 values, one per 5 minute, with COBs. Map them into the bins array. + console.log('COBS', cobs); + for (var i=0; i<288; i++) { + bins[i][1].push(cobs[i]); + } + console.log('bins', bins); + }); + return bins; +} + + +loopalyzer.getCOBsOLD = function(datastorage, daysToShow) { + + var cobStatusAvailable = client.plugins('cob').isDeviceStatusAvailable(datastorage.devicestatus); // Create a single array of empty COB entries to hold all COBs for all days, one entry per 5 minutes var cobs=[]; @@ -611,8 +656,8 @@ loopalyzer.report = function report_loopalyzer(datastorage,sorteddaystoshow,opti var sgvBin = loopalyzer.getSGVs(datastorage,daysToShow); var basalsBin = loopalyzer.getBasals(datastorage,daysToShow,profile); var tempBasalsBin = loopalyzer.getTempBasalDeltas(datastorage,daysToShow,profile); - var iobBin = (client.plugins('iob').isDeviceStatusAvailable(datastorage.devicestatus) ? loopalyzer.getIOBs(datastorage,daysToShow) : []); - var cobBin = (client.plugins('cob').isDeviceStatusAvailable(datastorage.devicestatus) ? loopalyzer.getCOBs(datastorage,daysToShow) : []); + var iobBin = loopalyzer.getIOBs(datastorage,daysToShow); + var cobBin = loopalyzer.getCOBs(datastorage,daysToShow, profile, client); var predictionsBin = []; if ($("#rp_loopalyzerpredictions").is(":checked")) { From 3fe8e8b2a07f9c187ec9561c1f33486da7d855cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Sj=C3=B6strand?= Date: Sun, 3 Jun 2018 09:03:02 +0200 Subject: [PATCH 011/274] Fixed new COB compute (refactor, simpler) --- lib/report_plugins/loopalyzer.js | 67 ++++++++++++++++++++++++++------ 1 file changed, 55 insertions(+), 12 deletions(-) diff --git a/lib/report_plugins/loopalyzer.js b/lib/report_plugins/loopalyzer.js index 4ff851a7f45..a05782567a6 100644 --- a/lib/report_plugins/loopalyzer.js +++ b/lib/report_plugins/loopalyzer.js @@ -244,17 +244,64 @@ loopalyzer.getIOBs = function(datastorage, daysToShow) { return bins; }; -loopalyzer.getCOBs = function(datastorage, daysToShow, profile, client) { - var cobStatusAvailable = client.plugins('cob').isDeviceStatusAvailable(datastorage.devicestatus); - console.log('cobStatusAvailable=' + cobStatusAvailable); - - // Create a bins array with date entries for entire today +/* Create an empty bins array with date stamps for today */ +loopalyzer.getEmptyBins = function() { var bins=[]; var todayStart = moment().startOf('day'); var todayEnd = moment().endOf('day'); for (var dt=todayStart; dt < todayEnd; dt.add(5, 'minutes')) { bins.push([dt.toDate(), []]); } + return bins; +} + +/* Takes an array of 288 values and adds to the bins */ +loopalyzer.addArrayToBins = function(bins, values) { + if (bins && bins.length === 288 && values && values.length === 288) { + values.forEach(function(value,index) { + bins[index][1].push(value); + }); + } else + console.log('addArrayToBins - array must have 288 items', values); +} + +/* Fill all NaNs in an array by interpolating between adjacent values */ +loopalyzer.interpolateArray = function(values) { + var startIndex=0, stopIndex=0, k=0, m=0; + + while (isNaN(values[startIndex])) { + startIndex++; // Advance start to the first real number + } + stopIndex = startIndex+1; + while (stopIndex Date: Sun, 3 Jun 2018 09:24:32 +0200 Subject: [PATCH 012/274] Fixed IOB calculations as well --- lib/report_plugins/loopalyzer.js | 42 +++++++++++++++++++++++++++++--- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/lib/report_plugins/loopalyzer.js b/lib/report_plugins/loopalyzer.js index a05782567a6..de70e536816 100644 --- a/lib/report_plugins/loopalyzer.js +++ b/lib/report_plugins/loopalyzer.js @@ -175,8 +175,8 @@ loopalyzer.getTempBasalDeltas = function(datastorage, daysToShow, profile) { return bins; } - -loopalyzer.getIOBs = function(datastorage, daysToShow) { +/* +loopalyzer.getIOBsOLD = function(datastorage, daysToShow) { // Create a single array of empty IOB entries to hold all IOBs for all days, one entry per 5 minutes var iobs=[]; @@ -243,6 +243,7 @@ loopalyzer.getIOBs = function(datastorage, daysToShow) { } return bins; }; +*/ /* Create an empty bins array with date stamps for today */ loopalyzer.getEmptyBins = function() { @@ -297,6 +298,39 @@ loopalyzer.interpolateArray = function(values) { } } +loopalyzer.getIOBs = function(datastorage, daysToShow, profile, client) { + var iobStatusAvailable = client.plugins('iob').isDeviceStatusAvailable(datastorage.devicestatus); + console.log('iobStatusAvailable=' + iobStatusAvailable); + + var bins = loopalyzer.getEmptyBins(); + + daysToShow.forEach(function(day, dayIndex) { + var dayStart = moment(day).startOf('day'); + var dayEnd = moment(day).endOf('day'); + var iobs = []; + if (iobStatusAvailable) { + var dayStartMills = dayStart.milliseconds(); + for (var i=0; i<288; i++) iobs.push(NaN); // Clear the IOBs by filling with NaNs + var iobArray = client.plugins('iob').IOBDeviceStatusesInTimeRange(datastorage.devicestatus, dayStart.valueOf(), dayEnd.valueOf()); + console.log('iobArray', iobArray); + iobArray.forEach(function(entry){ + var index = Math.floor(moment(entry.mills).diff(dayStart,'minutes') / 5); + iobs[index] = entry.iob; + }); + loopalyzer.interpolateArray(iobs); + } else { + for (var dt=dayStart; dt < dayEnd; dt.add(5, 'minutes')) { + var iob = client.plugins('iob').iobTotal(datastorage.treatments,datastorage.devicestatus,profile,dt.toDate()).iob; + iobs.push(iob); + console.log(dt.format() + ' iob=' + iob); + } + } + console.log('getIOBs.. ' + day, iobs); + loopalyzer.addArrayToBins(bins, iobs); + }); + return bins; +} + loopalyzer.getCOBs = function(datastorage, daysToShow, profile, client) { var cobStatusAvailable = client.plugins('cob').isDeviceStatusAvailable(datastorage.devicestatus); console.log('cobStatusAvailable=' + cobStatusAvailable); @@ -330,6 +364,7 @@ loopalyzer.getCOBs = function(datastorage, daysToShow, profile, client) { return bins; } +/* loopalyzer.getCOBsOLD = function(datastorage, daysToShow) { var cobStatusAvailable = client.plugins('cob').isDeviceStatusAvailable(datastorage.devicestatus); @@ -406,6 +441,7 @@ loopalyzer.getCOBsOLD = function(datastorage, daysToShow) { } return bins; }; +*/ /* Returns the carbs treatments array as [date, amount] */ loopalyzer.getCarbTreatments = function(datastorage, daysToShow) { @@ -699,7 +735,7 @@ loopalyzer.report = function report_loopalyzer(datastorage,sorteddaystoshow,opti var sgvBin = loopalyzer.getSGVs(datastorage,daysToShow); var basalsBin = loopalyzer.getBasals(datastorage,daysToShow,profile); var tempBasalsBin = loopalyzer.getTempBasalDeltas(datastorage,daysToShow,profile); - var iobBin = loopalyzer.getIOBs(datastorage,daysToShow); + var iobBin = loopalyzer.getIOBs(datastorage,daysToShow, profile, client); var cobBin = loopalyzer.getCOBs(datastorage,daysToShow, profile, client); var predictionsBin = []; From c87c24c02e0e76bc9d32e24859750e9feeb92290 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Sj=C3=B6strand?= Date: Sun, 3 Jun 2018 10:03:28 +0200 Subject: [PATCH 013/274] Refactored getBasals and getTempBasals --- lib/report_plugins/loopalyzer.js | 383 ++++++++++--------------------- 1 file changed, 127 insertions(+), 256 deletions(-) diff --git a/lib/report_plugins/loopalyzer.js b/lib/report_plugins/loopalyzer.js index de70e536816..337558ebc45 100644 --- a/lib/report_plugins/loopalyzer.js +++ b/lib/report_plugins/loopalyzer.js @@ -18,6 +18,7 @@ function init() { module.exports = init; var minutewindow = 5; //minute-window should be a divisor of 60 +var laDebug = true; // If we should print console.logs loopalyzer.html = function html(client) { var translate = client.translate; @@ -82,48 +83,40 @@ loopalyzer.prepareHtml = function loopalyzerPrepareHtml() { loopalyzer.ss = require('simple-statistics'); +// +// Functions to pull data from datastorage and prepare in bins +// loopalyzer.getSGVs = function(datastorage, daysToShow) { var data = datastorage.allstatsrecords; - var bins = []; + var bins = loopalyzer.getEmptyBins(); - // Fill the bins array with the timestamp, one per 5 minutes - var date = moment(); - date.set({'hours':0, 'minutes':0, 'seconds':0, 'milliseconds':0}); + // Loop thru the days to show, for each day find the matching SGVs and insert into the bins entry array + daysToShow.forEach(function(day) { + var entries = []; // Array with all SGVs for this day, we'll fill this and then insert into the bins later + for (var i=0; i<288; i++) entries.push(NaN); // Fill the array with NaNs so we have something in case we don't find an SGV + var fromDate = moment(day); + var toDate = moment(day); + fromDate.set({'hours':0, 'minutes':0, 'seconds':0, 'milliseconds':0}); + toDate.set({'hours':0, 'minutes':5, 'seconds':0, 'milliseconds':0}); // toDate is 5 mins ahead for (var i=0; i<288; i++) { - bins.push([date.toDate(),[]]); - date.add(5, 'minutes'); + var found = false; + data.some(function(record) { + var recDate = moment(record.displayTime); + if (!found && recDate.isAfter(fromDate) && recDate.isBefore(toDate)) { + entries[i]=record.sgv; + found = true; + } + return found; // Breaks .some loop if found is true + }) + fromDate.add(5, 'minutes'); + toDate.add(5, 'minutes'); } - - // Loop thru the days to show, for each day find the matching SGVs and insert into the bins entry array - daysToShow.forEach(function(day) { - var entries = []; // Array with all SGVs for this day, we'll fill this and then insert into the bins later - for (var i=0; i<288; i++) entries.push(NaN); // Fill the array with NaNs so we have something in case we don't find an SGV - var fromDate = moment(day); - var toDate = moment(day); - fromDate.set({'hours':0, 'minutes':0, 'seconds':0, 'milliseconds':0}); - toDate.set({'hours':0, 'minutes':5, 'seconds':0, 'milliseconds':0}); // toDate is 5 mins ahead - for (var i=0; i<288; i++) { - var found = false; - data.some(function(record) { - var recDate = moment(record.displayTime); - if (!found && recDate.isAfter(fromDate) && recDate.isBefore(toDate)) { - entries[i]=record.sgv; - found = true; - } - return found; // Breaks .some loop if found is true - }) - fromDate.add(5, 'minutes'); - toDate.add(5, 'minutes'); - } - for (var i=0; i<288; i++) { - bins[i][1].push(entries[i]); - } - }); - - return bins; + loopalyzer.addArrayToBins(bins, entries); + }); + return bins; } - +/* loopalyzer.getBasals = function(datastorage, daysToShow, profile) { var bins = []; @@ -152,155 +145,54 @@ loopalyzer.getBasals = function(datastorage, daysToShow, profile) { } return bins; } +*/ -loopalyzer.getTempBasalDeltas = function(datastorage, daysToShow, profile) { - var bins = []; - - // Loop through all hours & minutes and days and get the basal from profile, - // and compute the plus/minus delta temp basal and push to bins - var date = moment(); - date.set({'hours':0, 'minutes':0, 'seconds':0, 'milliseconds':0}); +loopalyzer.getBasals = function(datastorage, daysToShow, profile) { + var bins = loopalyzer.getEmptyBins(); - for (var i=0; i<288; i++) { - var basalValues = []; - daysToShow.forEach(function(day){ - var d = moment(day); - d.set({'hours':date.hours(), 'minutes':date.minutes(), 'seconds':0, 'milliseconds':0}); - var basalValue = profile.getTempBasal(d.toDate()); - basalValues.push(basalValue.tempbasal - basalValue.basal); - }); - bins.push([date.toDate(), basalValues]); - date.add(5, 'minutes') - } + daysToShow.forEach(function(day, dayIndex) { + var dayStart = moment(day).startOf('day'); + var dayEnd = moment(day).endOf('day'); + var basals = []; + for (var i=0; i<288; i++) basals.push(NaN); // Clear the basals by filling with NaNs + + var index = 0; + for (var dt=dayStart; dt < dayEnd; dt.add(5, 'minutes')) { + var basal = profile.getTempBasal(dt.toDate()); + if (basal) + basals[index++] = basal.basal; + } + console.log('getBasals ' + day, basals); + loopalyzer.addArrayToBins(bins, basals); + }); return bins; } -/* -loopalyzer.getIOBsOLD = function(datastorage, daysToShow) { - - // Create a single array of empty IOB entries to hold all IOBs for all days, one entry per 5 minutes - var iobs=[]; - var numberOfEntries = 288 * daysToShow.length; - for (var i=0; i Date: Sun, 3 Jun 2018 10:43:59 +0200 Subject: [PATCH 014/274] Refactored the interpolation back into the IOB/COB functions --- lib/report_plugins/loopalyzer.js | 96 ++++++++++++++++++++------------ 1 file changed, 60 insertions(+), 36 deletions(-) diff --git a/lib/report_plugins/loopalyzer.js b/lib/report_plugins/loopalyzer.js index 337558ebc45..8b3366283bb 100644 --- a/lib/report_plugins/loopalyzer.js +++ b/lib/report_plugins/loopalyzer.js @@ -116,37 +116,6 @@ loopalyzer.getSGVs = function(datastorage, daysToShow) { return bins; } -/* -loopalyzer.getBasals = function(datastorage, daysToShow, profile) { - var bins = []; - - // Loop through all hours & minutes and days and get the basal from profile - for (var hour = 0; hour < 24; hour++) { - for (var minute = 0; minute < 60; minute = minute + minutewindow) { - var basalValues = []; - for (var m = 0; m < minutewindow; m = m + 5) { - daysToShow.forEach(function(d){ - var date = new Date(d); - date.setHours(hour); - date.setMinutes(minute + m); - date.setSeconds(0); - date.setMilliseconds(0); - var basalValue = profile.getTempBasal(date); - basalValues.push(basalValue.basal); - }); - } - var date = new Date(); - date.setHours(hour); - date.setMinutes(minute); - date.setSeconds(0); - date.setMilliseconds(0); - bins.push([date, basalValues]); - } - } - return bins; -} -*/ - loopalyzer.getBasals = function(datastorage, daysToShow, profile) { var bins = loopalyzer.getEmptyBins(); @@ -189,7 +158,6 @@ loopalyzer.getTempBasalDeltas = function(datastorage, daysToShow, profile) { return bins; } - loopalyzer.getIOBs = function(datastorage, daysToShow, profile, client) { var iobStatusAvailable = client.plugins('iob').isDeviceStatusAvailable(datastorage.devicestatus); console.log('getIOBs iobStatusAvailable=' + iobStatusAvailable); @@ -209,7 +177,31 @@ loopalyzer.getIOBs = function(datastorage, daysToShow, profile, client) { var index = Math.floor(moment(entry.mills).diff(dayStart,'minutes') / 5); iobs[index] = entry.iob; }); - loopalyzer.interpolateArray(iobs); + + // Loop thru these entries and where no IOB has been found, interpolate between nearby to get a continuous array + var startIndex = 0, stopIndex = 0; + while (isNaN(iobs[startIndex])) { + startIndex++; // Advance start to the first real number + } + stopIndex = startIndex+1; + while (stopIndex Date: Sun, 3 Jun 2018 18:29:25 +0200 Subject: [PATCH 015/274] Added OpenAPS (and AndroidAPS?) to Predictions --- lib/report_plugins/loopalyzer.js | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/lib/report_plugins/loopalyzer.js b/lib/report_plugins/loopalyzer.js index 8b3366283bb..ab28f8754ce 100644 --- a/lib/report_plugins/loopalyzer.js +++ b/lib/report_plugins/loopalyzer.js @@ -131,7 +131,7 @@ loopalyzer.getBasals = function(datastorage, daysToShow, profile) { if (basal) basals[index++] = basal.basal; } - console.log('getBasals ' + day, basals); + if (laDebug) console.log('getBasals ' + day, basals); loopalyzer.addArrayToBins(bins, basals); }); return bins; @@ -160,7 +160,7 @@ loopalyzer.getTempBasalDeltas = function(datastorage, daysToShow, profile) { loopalyzer.getIOBs = function(datastorage, daysToShow, profile, client) { var iobStatusAvailable = client.plugins('iob').isDeviceStatusAvailable(datastorage.devicestatus); - console.log('getIOBs iobStatusAvailable=' + iobStatusAvailable); + if (laDebug) console.log('getIOBs iobStatusAvailable=' + iobStatusAvailable); var bins = loopalyzer.getEmptyBins(); @@ -172,7 +172,7 @@ loopalyzer.getIOBs = function(datastorage, daysToShow, profile, client) { var dayStartMills = dayStart.milliseconds(); for (var i=0; i<288; i++) iobs.push(NaN); // Clear the IOBs by filling with NaNs var iobArray = client.plugins('iob').IOBDeviceStatusesInTimeRange(datastorage.devicestatus, dayStart.valueOf(), dayEnd.valueOf()); - console.log('getIOBs iobArray', iobArray); + if (laDebug) console.log('getIOBs iobArray', iobArray); iobArray.forEach(function(entry){ var index = Math.floor(moment(entry.mills).diff(dayStart,'minutes') / 5); iobs[index] = entry.iob; @@ -200,16 +200,14 @@ loopalyzer.getIOBs = function(datastorage, daysToShow, profile, client) { stopIndex++; } } - - // loopalyzer.interpolateArray(iobs, true); // IOBs can be negative } else { for (var dt=dayStart; dt < dayEnd; dt.add(5, 'minutes')) { var iob = client.plugins('iob').iobTotal(datastorage.treatments,datastorage.devicestatus,profile,dt.toDate()).iob; iobs.push(iob); - console.log('getIOBs ' + dt.format() + ' iob=' + iob); + if (laDebug) console.log('getIOBs ' + dt.format() + ' iob=' + iob); } } - console.log('getIOBs ' + day, iobs); + if (laDebug) console.log('getIOBs ' + day, iobs); loopalyzer.addArrayToBins(bins, iobs); }); return bins; @@ -217,7 +215,7 @@ loopalyzer.getIOBs = function(datastorage, daysToShow, profile, client) { loopalyzer.getCOBs = function(datastorage, daysToShow, profile, client) { var cobStatusAvailable = client.plugins('cob').isDeviceStatusAvailable(datastorage.devicestatus); - console.log('getCOBs cobStatusAvailable=' + cobStatusAvailable); + if (laDebug) console.log('getCOBs cobStatusAvailable=' + cobStatusAvailable); var bins = loopalyzer.getEmptyBins(); @@ -229,7 +227,7 @@ loopalyzer.getCOBs = function(datastorage, daysToShow, profile, client) { var dayStartMills = dayStart.milliseconds(); for (var i=0; i<288; i++) cobs.push(NaN); // Clear the COBs by filling with NaNs var cobArray = client.plugins('cob').COBDeviceStatusesInTimeRange(datastorage.devicestatus, dayStart.valueOf(), dayEnd.valueOf()); - console.log('getCOBs cobArray', cobArray); + if (laDebug) console.log('getCOBs cobArray', cobArray); cobArray.forEach(function(entry){ var index = Math.floor(moment(entry.mills).diff(dayStart,'minutes') / 5); cobs[index] = entry.cob; @@ -265,16 +263,14 @@ loopalyzer.getCOBs = function(datastorage, daysToShow, profile, client) { } } - // loopalyzer.interpolateArray(cobs, false); // COBs can never be negative - } else { for (var dt=dayStart; dt < dayEnd; dt.add(5, 'minutes')) { var cob = client.plugins('cob').cobTotal(datastorage.treatments,datastorage.devicestatus,profile,dt.toDate()).cob; cobs.push(cob); - console.log('getCOBs ' + dt.format() + ' cob=' + cob); + if (laDebug) console.log('getCOBs ' + dt.format() + ' cob=' + cob); } } - console.log('getCOBs ' + day, cobs); + if (laDebug) console.log('getCOBs ' + day, cobs); loopalyzer.addArrayToBins(bins, cobs); }); return bins; @@ -330,8 +326,7 @@ loopalyzer.getAllPredictionsForADay = function(datastorage, day) { var predicted = datastorage.devicestatus[i].loop.predicted; if (moment(predicted.startDate).isSame(dayStart,'day')) predictions.push(datastorage.devicestatus[i].loop.predicted); - } - /* else if (datastorage.devicestatus[i].openaps && datastorage.devicestatus[i].openaps.suggested && datastorage.devicestatus[i].openaps.suggested.predBGs) { + } else if (datastorage.devicestatus[i].openaps && datastorage.devicestatus[i].openaps.suggested && datastorage.devicestatus[i].openaps.suggested.predBGs) { var entry = {}; entry.startDate = datastorage.devicestatus[i].openaps.suggested.timestamp; // For OpenAPS/AndroidAPS we fall back from COB if present, to UAM, then IOB @@ -342,7 +337,6 @@ loopalyzer.getAllPredictionsForADay = function(datastorage, day) { } else entry.values = datastorage.devicestatus[i].openaps.suggested.predBGs.IOB; predictions.push(entry); } - */ } // Remove duplicates before we're done var p = []; From db8039c70cda2e991e23a61fc7786efa96fd1c4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Sj=C3=B6strand?= Date: Sun, 3 Jun 2018 19:00:06 +0200 Subject: [PATCH 016/274] Added version info to log output --- lib/report_plugins/loopalyzer.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/report_plugins/loopalyzer.js b/lib/report_plugins/loopalyzer.js index ab28f8754ce..4ba320f2812 100644 --- a/lib/report_plugins/loopalyzer.js +++ b/lib/report_plugins/loopalyzer.js @@ -588,6 +588,7 @@ loopalyzer.timeShiftSingleBin = function(bin, daysToShow, timeShift) { // Main method loopalyzer.report = function report_loopalyzer(datastorage,sorteddaystoshow,options) { + if (laDebug) console.log('Loopalyzer 2018-06-03 v1'); var Nightscout = window.Nightscout; var client = Nightscout.client; var translate = client.translate; From aad095e6f7cdedb27297d09f2217a138cc4ec2d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Sj=C3=B6strand?= Date: Sun, 3 Jun 2018 22:18:01 +0200 Subject: [PATCH 017/274] Added one week time limit to limit CPU * Skipping Loopalyzer unless it is the selected tab * Showing message if more than one week duration selected --- lib/report_plugins/loopalyzer.js | 49 +++++++++++++++++++++----------- static/report/js/report.js | 12 ++++++-- 2 files changed, 42 insertions(+), 19 deletions(-) diff --git a/lib/report_plugins/loopalyzer.js b/lib/report_plugins/loopalyzer.js index 4ba320f2812..237739ca198 100644 --- a/lib/report_plugins/loopalyzer.js +++ b/lib/report_plugins/loopalyzer.js @@ -17,14 +17,14 @@ function init() { module.exports = init; -var minutewindow = 5; //minute-window should be a divisor of 60 var laDebug = true; // If we should print console.logs loopalyzer.html = function html(client) { var translate = client.translate; var ret = '' - ret += '

' + translate('Loopalyzer') + '  

' + ret += '

' + translate('Loopalyzer') + '  

' ret += '' + translate('To see this report, press SHOW while in this view') + '' + ret += '' ret += '' - ret += '
' - ret += '
' ; return ret; }; @@ -76,8 +74,6 @@ loopalyzer.css = ; loopalyzer.prepareHtml = function loopalyzerPrepareHtml() { -// $('#loopalyzer-carbTreatmentsTable').html(''); -// $('#loopalyzer-insulinTreatmentsTable').html(''); // $('#loopalyzer-charts').append($('
')); }; @@ -152,7 +148,7 @@ loopalyzer.getTempBasalDeltas = function(datastorage, daysToShow, profile) { if (basal) temps[index++] = basal.tempbasal - basal.basal; } - console.log('getTempBasalDeltas ' + day, temps); + if (laDebug) console.log('getTempBasalDeltas ' + day, temps); loopalyzer.addArrayToBins(bins, temps); }); return bins; @@ -587,8 +583,35 @@ loopalyzer.timeShiftSingleBin = function(bin, daysToShow, timeShift) { } // Main method -loopalyzer.report = function report_loopalyzer(datastorage,sorteddaystoshow,options) { - if (laDebug) console.log('Loopalyzer 2018-06-03 v1'); +loopalyzer.report = function(datastorage,sorteddaystoshow,options) { + // Copy the sorteddaystoshow into new array (clone) and re-sort ascending (so we don't mess with original array) + var daysToShow = []; + sorteddaystoshow.forEach(function(day){daysToShow.push(day)}); + daysToShow.sort(function(a,b) { return (a1) dateInfo += ' - ' + moment(daysToShow[daysToShow.length-1]).format('ddd MMM D'); // .split(',')[0]; - $("#dateinfo").html(dateInfo); + $("#loopalyzer-dateinfo").html(dateInfo); loopalyzer.prepareHtml(); - $("#loopalyzer-help").hide(); $("#loopalyzer-buttons").show(); if (daysToShow.length==1) $("#rp_loopalyzertimeshiftinput").hide(); /* Hide timeShift if single day */ diff --git a/static/report/js/report.js b/static/report/js/report.js index 9a083247759..1a2c76aeb78 100644 --- a/static/report/js/report.js +++ b/static/report/js/report.js @@ -229,7 +229,12 @@ options.order = ( $('#rp_oldestontop').is(':checked') ? report_plugins.consts.ORDER_OLDESTONTOP : report_plugins.consts.ORDER_NEWESTONTOP ); options.width = parseInt($('#rp_size :selected').attr('x')); options.height = parseInt($('#rp_size :selected').attr('y')); - options.loopalyzer = true; + options.loopalyzer = $("#loopalyzer").hasClass( "selected" ); // We only want to run through Loopalyzer if that tab is selected + if (options.loopalyzer) { + options.iob = true; + options.cob = true; + options.openAps = true; + } var matchesneeded = 0; @@ -511,6 +516,7 @@ if (plugin.name == 'daytoday' && ! $('#daytoday').hasClass('selected')) skipRender = true; if (plugin.name == 'treatments' && ! $('#treatments').hasClass('selected')) skipRender = true; + if (plugin.name == 'loopalyzer' && ! $('#loopalyzer').hasClass('selected')) skipRender = true; if (skipRender) { console.log('Skipping ',plugin.name); @@ -546,7 +552,7 @@ function loadData(day, options, callback) { // check for loaded data - if ((options.openAps || options.loopalyzer || options.iob || options.cob) && datastorage[day] && !datastorage[day].devicestatus.length) { + if ((options.openAps || 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); @@ -666,7 +672,7 @@ data.devicestatus = []; return $.Deferred().resolve(); } - if(options.iob || options.cob || options.openAps || options.loopalyzer) { + if(options.iob || options.cob || options.openAps) { $('#info-' + day).html(''+translate('Loading device status data of')+' '+day+' ...'); 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, { From c588796cc8d55ce60b682072dcdaadf755031514 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Sj=C3=B6strand?= Date: Mon, 4 Jun 2018 07:54:34 +0200 Subject: [PATCH 018/274] Fixed bug calculation IOB total when not using deviceStatus --- lib/report_plugins/loopalyzer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/report_plugins/loopalyzer.js b/lib/report_plugins/loopalyzer.js index 237739ca198..7d74d751f20 100644 --- a/lib/report_plugins/loopalyzer.js +++ b/lib/report_plugins/loopalyzer.js @@ -198,7 +198,7 @@ loopalyzer.getIOBs = function(datastorage, daysToShow, profile, client) { } } else { for (var dt=dayStart; dt < dayEnd; dt.add(5, 'minutes')) { - var iob = client.plugins('iob').iobTotal(datastorage.treatments,datastorage.devicestatus,profile,dt.toDate()).iob; + var iob = client.plugins('iob').calcTotal(datastorage.treatments,datastorage.devicestatus,profile,dt.toDate()).iob; iobs.push(iob); if (laDebug) console.log('getIOBs ' + dt.format() + ' iob=' + iob); } From 8a0d09d932c7456db6b086f655d28f97bbb7145c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Sj=C3=B6strand?= Date: Mon, 4 Jun 2018 08:18:43 +0200 Subject: [PATCH 019/274] Added info text and improve finding NaNs --- lib/report_plugins/loopalyzer.js | 75 +++++++++++++++++--------------- 1 file changed, 39 insertions(+), 36 deletions(-) diff --git a/lib/report_plugins/loopalyzer.js b/lib/report_plugins/loopalyzer.js index 7d74d751f20..41d3f9ee05b 100644 --- a/lib/report_plugins/loopalyzer.js +++ b/lib/report_plugins/loopalyzer.js @@ -23,7 +23,7 @@ loopalyzer.html = function html(client) { var translate = client.translate; var ret = '' ret += '

' + translate('Loopalyzer') + '  

' - ret += '' + translate('To see this report, press SHOW while in this view') + '' + ret += '' + translate('Depending on which uploader you use, how frequent it is able to capture your data and upload, and how it backfills missing data some graphs may be missing data. Always ensure the graphs look reasonable (best is to view one day at a time first to see).


To see this report, press SHOW while in this view') + '
' ret += '' ret += '