UNPKG

nightscout

Version:

Nightscout acts as a web-based CGM (Continuous Glucose Monitor) to allow multiple caregivers to remotely view a patients glucose data in realtime.

215 lines (193 loc) 6.75 kB
'use strict'; var times = require('../times'); var success = { name: 'success' , label: 'Weekly Distribution' , pluginType: 'report' }; function init () { return success; } module.exports = init; success.html = function html (client) { var translate = client.translate; var ret = '<h2>' + translate('Weekly Distribution') + '</h2>' + '<div id="success-grid"></div>'; return ret; }; success.css = `#success-placeholder td { border: 1px #ccc solid; margin: 0; padding: 1px; text-align:center; } #success-placeholder .bad { background-color: #fcc; } #success-placeholder .good { background-color: #cfc; } #success-placeholder th:first-child { width: 30%; } #success-placeholder th { width: 10%; } #success-placeholder table { width: 100%; }`; success.report = function report_success (datastorage, sorteddaystoshow, options) { var Nightscout = window.Nightscout; var client = Nightscout.client; var translate = client.translate; var ss = require('simple-statistics'); var low = options.targetLow , high = options.targetHigh; var data = datastorage.allstatsrecords; var now = Date.now(); var period = 7 * times.hours(24).msecs; var firstDataPoint = data.reduce(function(min, record) { return Math.min(min, record.displayTime); }, Number.MAX_VALUE); if (firstDataPoint < 1390000000000) { firstDataPoint = 1390000000000; } var quarters = Math.floor((Date.now() - firstDataPoint) / period); var grid = $('#success-grid'); grid.empty(); var table = $('<table/>'); if (quarters === 0) { // insufficent data grid.append('<p>' + translate('There is not sufficient data to run this report. Select more days.') + '</p>'); return; } var dim = function(n) { var a = []; for (var i = 0; i < n; i++) { a[i] = 0; } return a; }; var sum = function(a) { return a.reduce(function(sum, v) { return sum + v; }, 0); }; var averages = { percentLow: 0 , percentInRange: 0 , percentHigh: 0 , standardDeviation: 0 , lowerQuartile: 0 , upperQuartile: 0 , average: 0 }; quarters = dim(quarters).map(function(blank, n) { var starting = new Date(now - (n + 1) * period) , ending = new Date(now - n * period); return { starting: starting , ending: ending , records: data.filter(function(record) { return record.displayTime > starting && record.displayTime <= ending; }) }; }).filter(function(quarter) { return quarter.records.length > 0; }).map(function(quarter, ix, all) { var bgValues = quarter.records.map(function(record) { return record.sgv; }); quarter.standardDeviation = ss.standard_deviation(bgValues); quarter.average = bgValues.length > 0 ? (sum(bgValues) / bgValues.length) : 'N/A'; quarter.lowerQuartile = ss.quantile(bgValues, 0.25); quarter.upperQuartile = ss.quantile(bgValues, 0.75); quarter.numberLow = bgValues.filter(function(bg) { return bg < low; }).length; quarter.numberHigh = bgValues.filter(function(bg) { return bg >= high; }).length; quarter.numberInRange = bgValues.length - (quarter.numberHigh + quarter.numberLow); quarter.percentLow = (quarter.numberLow / bgValues.length) * 100; quarter.percentInRange = (quarter.numberInRange / bgValues.length) * 100; quarter.percentHigh = (quarter.numberHigh / bgValues.length) * 100; averages.percentLow += quarter.percentLow / all.length; averages.percentInRange += quarter.percentInRange / all.length; averages.percentHigh += quarter.percentHigh / all.length; averages.lowerQuartile += quarter.lowerQuartile / all.length; averages.upperQuartile += quarter.upperQuartile / all.length; averages.average += quarter.average / all.length; averages.standardDeviation += quarter.standardDeviation / all.length; return quarter; }); var lowComparison = function(quarter, averages, field, invert) { if (quarter[field] < averages[field] * 0.8) { return (invert ? 'bad' : 'good'); } else if (quarter[field] > averages[field] * 1.2) { return (invert ? 'good' : 'bad'); } else { return ''; } }; var lowQuartileEvaluation = function(quarter, averages) { if (quarter.lowerQuartile < low) { return 'bad'; } else { return lowComparison(quarter, averages, 'lowerQuartile'); } }; var upperQuartileEvaluation = function(quarter, averages) { if (quarter.upperQuartile > high) { return 'bad'; } else { return lowComparison(quarter, averages, 'upperQuartile'); } }; table.append('<thead><tr><th>' + translate('Period') + '</th><th>' + translate('Low') + '</th><th>' + translate('In Range') + '</th><th>' + translate('High') + '</th><th>' + translate('Standard Deviation') + '</th><th>' + translate('Low Quartile') + '</th><th>' + translate('Average') + '</th><th>' + translate('Upper Quartile') + '</th></tr></thead>'); table.append('<tbody>' + quarters.filter(function(quarter) { return quarter.records.length > 0; }).map(function(quarter) { var INVERT = true; return '<tr>' + [ quarter.starting.toLocaleDateString() + ' - ' + quarter.ending.toLocaleDateString() , { klass: lowComparison(quarter, averages, 'percentLow') , text: Math.round(quarter.percentLow) + '%' } , { klass: lowComparison(quarter, averages, 'percentInRange', INVERT) , text: Math.round(quarter.percentInRange) + '%' } , { klass: lowComparison(quarter, averages, 'percentHigh') , text: Math.round(quarter.percentHigh) + '%' } , { klass: lowComparison(quarter, averages, 'standardDeviation') , text: (quarter.standardDeviation > 10 ? Math.round(quarter.standardDeviation) : quarter.standardDeviation.toFixed(1)) } , { klass: lowQuartileEvaluation(quarter, averages) , text: quarter.lowerQuartile } , { klass: lowComparison(quarter, averages, 'average') , text: quarter.average.toFixed(1) } , { klass: upperQuartileEvaluation(quarter, averages) , text: quarter.upperQuartile } ].map(function(v) { if (typeof v === 'object') { return '<td class="' + v.klass + '">' + v.text + '</td>'; } else { return '<td>' + v + '</td>'; } }).join('') + '</tr>'; }).join('') + '</tbody>'); table.appendTo(grid); };