Skip to content

Commit

Permalink
Merge ebde66a into b25ca06
Browse files Browse the repository at this point in the history
  • Loading branch information
scottleibrand committed Apr 24, 2018
2 parents b25ca06 + ebde66a commit 5add43c
Show file tree
Hide file tree
Showing 24 changed files with 2,562 additions and 39 deletions.
14 changes: 7 additions & 7 deletions lib/client/renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -443,10 +443,10 @@ function init (client, d3) {

if ( treatment.insulin > 0) {
var dosage_units = '' + Math.round(treatment.insulin * 100)/100;

var unit_of_measurement = ' U'; // One international unit of insulin (1 IU) is shown as '1 U'
var enteredBy = '' + treatment.enteredBy;

if ( treatment.insulin < 1 && !treatment.carbs && enteredBy.indexOf('openaps') > -1) { // don't show the unit of measurement for insulin boluses < 1 without carbs (e.g. oref0 SMB's). Otherwise lot's of small insulin only dosages are often unreadable
unit_of_measurement = '';
// remove leading zeros to avoid overlap with adjacent boluses
Expand Down Expand Up @@ -858,7 +858,7 @@ function init (client, d3) {
.attr('class', 'path')
.attr('id', 'label')
.style('fill', 'white');

// reduce the treatment label font size to make it readable with SMB
var fontBaseSize = (opts.treatments >= 30) ? 40 : 50 - Math.floor((25-opts.treatments)/30 * 10);

Expand All @@ -879,13 +879,13 @@ function init (client, d3) {
}

renderer.drawTreatments = function drawTreatments(client) {

var treatmentCount = 0;

_.forEach(client.ddata.treatments, function eachTreatment (d) {
if (Number(d.insulin) > 0 || Number(d.carbs) > 0) { treatmentCount += 1; };
if (Number(d.insulin) > 0 || Number(d.carbs) > 0) { treatmentCount += 1; };
});

// add treatment bubbles
_.forEach(client.ddata.treatments, function eachTreatment (d) {
renderer.drawTreatment(d, {
Expand Down
341 changes: 341 additions & 0 deletions lib/report_plugins/autotune.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,341 @@
'use strict';

var _ = require('lodash');
var moment = window.moment;
var times = require('../times');
var d3 = (global && global.d3) || require('d3');
var tz = require('moment-timezone');
var lastResult = null;
var autotune_prep = require('./autotune/autotune-prep');
var autotune = {
name: 'autotune'
, label: 'Autotune'
, pluginType: 'report'
};

var libAutotune = require('./autotune/index');
//autotune.tune = libAutotune.tuneAllTheThings;
autotune.tune = require('./autotune/index');
autotune.percentile = libAutotune.percentile;
autotune.percentRank = libAutotune.percentRank;

function init() {
return autotune;
}

module.exports = init;
autotune.html = function html(client) {
var translate = client.translate;
var ret =
'<h2>' + translate('Autotune') + '</h2>'
+ '<p><b>WARNING</b>: Autotune is a DIY tool for recommending potential changes to (a single) ISF, basal rate, and (a single) carb ratio. If you have questions or concerns about your personal settings, you should talk with your healthcare provider about them.</p>'
+ '<p><b>NOTE</b>: The “current” basal rate, ISF, and carb ratio is based on what you manually input into the Nightscout profile. If you have updated your pump or use other tools (i.e. one of the DIY closed loops), this may not be up to date with your settings there.</p>'
+ '<p><i>More details about autotune and documentation around interpreting autotune can be found <a href="http://openaps.readthedocs.io/en/latest/docs/Customize-Iterate/autotune.html">here</a>.</i></p>'
+ '<p><input type="checkbox" id="compute_autotune">Compute autotune</p>'
+ '<div id="autotune-report"></div>'
;
return ret;
};

autotune.css =
'#autotune-report .autotune-report {'
+ ' border-spacing: 0;'
+ '}'
+ '#autotune-report .autotune-report th {'
+ ' width: 160px;'
+ ' border-bottom: 1px solid black;'
+ ' background: #b7b7b7'
+ '}'
+ '#autotune-report .autotune-report tr:nth-child(even) {'
+ ' background: #efefef;'
+ '}'
+ '#autotune-report .autotune-report td {'
+ ' width: 160px;'
+ ' padding: 4px 0;'
+ '}'

autotune.prepareHtml = function autotunePrepareHtml(sorteddaystoshow) {
};

function appendMainRecords(table, header, current, recommended, translate) {
var tr = $('<tr/>');
if (current || recommended) {
$('<td>' + translate(header) + '</td>').appendTo(tr);
$('<td>' + current + '</td>').appendTo(tr);
$('<td>' + recommended + '</td>').appendTo(tr);
}
var td = $('<td/>')
appendArrowIcon(td, current, recommended);
td.appendTo(tr);
tr.appendTo(table);
}

function getCurrentISF(store) {
return store.sens[0].value;
}

function getCurrentCSF(store) {
return getCurrentISF(store) / getCurrentCarbRatio(store);
}

function getCurrentCarbRatio(store) {
return store.carbratio[0].value;
}

function roundNum(num) {
return Number(num).toFixed(3);
}

function appendArrowIcon(td, current, recommended) {
var icon = $('<img/>');
if (recommended > current) {
$('<img src="/images/up_arrow.png" />').appendTo(td);
} else if (recommended < current) {
$('<img src="/images/down_arrow.png" />').appendTo(td);
}
}

function findActiveProfiles(profiles, sorteddaystoshow) {
var result = {};
result.activeIndex = -1;
result.manyActive = false;

var dateFrom = new Date(sorteddaystoshow[0]);
var dateTo = new Date(sorteddaystoshow[sorteddaystoshow.length-1]);
var sortedProfiles = [];
for (var i = 0; i < profiles.length; i++) {
var record = {};
record.profileIndex = i;
if (! profiles[i].startDate) { continue; }
var splittedString = profiles[i].startDate.split("T");
record.startDate = new Date(splittedString[0]);
sortedProfiles.push(record);
}
sortedProfiles.sort(function (r1, r2) {
return (r1.startDate < r2.startDate) ? -1 : ((r1.startDate > r2.startDate) ? 1 : 0);
});
for (i = 0; i < sortedProfiles.length; i++) {
var profile = sortedProfiles[i];
if (profile.startDate <= dateFrom) {
result.activeIndex = profile.profileIndex;
continue;
}
if (profile.startDate >= dateFrom && profile.startDate <= dateTo)
result.manyActive = true;
if (profile.startDate > dateFrom)
break;
}

return result;
}

function getHourMinuteText(hour, minute)
{
var text = "";
if (hour < 10)
text += "0";
text += hour.toString();
text += ":";
if (minute < 10)
text += "0";
text += minute.toString();
return text;
}

function findBasalRecord(data, time)
{
for(var i = 0; i < data.length; i++)
{
if (data[i].time == time)
return data[i];
}
return null;
}

function findRecommendedRecord(data, minutes) {
for(var i = 0; i < data.length; i++)
{
if (data[i].minutes == minutes)
return data[i];
}
return null;
}

autotune.report = function report_autotune(datastorage, sorteddaystoshow, options) {
var Nightscout = window.Nightscout;
var client = Nightscout.client;
var translate = client.translate;
var report_plugins = Nightscout.report_plugins;

if (!$('#compute_autotune').is(':checked')) return;

var profileRecords = datastorage.profiles;

var report = $('#autotune-report');
report.empty();

var foundActiveProfile = findActiveProfiles(datastorage.profiles, sorteddaystoshow);
if (foundActiveProfile.activeIndex < 0 || foundActiveProfile.manyActive)
{
// Show error
$('<div class="error">' + "Error: Many profiles are active during choosen period." + "</div>").appendTo(report);
return;
}

var table = $('<table class="centeraligned autotune-report">');
report.append(table);

var thead = $('<tr/>');
$('<th/>').appendTo(thead);
$('<th>' + translate('Current NS Profile') + '</th>').appendTo(thead);
$('<th>' + translate('Calculated') + '</th>').appendTo(thead);
$('<th/>').appendTo(thead);
thead.appendTo(table);

var profile = profileRecords[foundActiveProfile.activeIndex];
var store = profile.store[profile.defaultProfile];

const re = /\d{4}-\d{2}-\d{2}/g;
var keys = _.filter(Object.keys(datastorage), function (key) { return key.match(re)});

var convertedProfile = autotune.convertProfile(profile);
var opts = {
profile: convertedProfile //inputs.profile
// TODO: figure out how to use only the relevant treatments so it's not so slow on >1d of data
, history: _.flatMap(keys, function(key){return datastorage[key].treatments;}) //inputs.history
, glucose: _.flatMap(keys, function(key){return datastorage[key].sgv;})//inputs.glucose
, prepped_glucose: undefined// inputs.prepped_glucose
, basalprofile: convertedProfile.basalprofile
, carbs: []
};

if (Nightscout.client.settings.units == 'mmol') {
var g = _.cloneDeep(opts.glucose);
_.forEach(g, function(e) { e.sgv = e.sgv * 18; });
opts.glucose = g;
}

var prepared = autotune_prep(opts);

var inputs = {
preppedGlucose: prepared
, previousAutotune: convertedProfile
, pumpProfile: convertedProfile
};

var result = autotune.tune(inputs);

appendMainRecords(table, 'ISF', roundNum(getCurrentISF(store)), result.sens, translate);
//appendMainRecords(table, 'CSF', roundNum(getCurrentCSF(store)), result.csf, translate);
appendMainRecords(table, 'Carb Ratio', roundNum(getCurrentCarbRatio(store)), result.carb_ratio, translate);

var trBasalProfile = $('<tr/>');
$('<td>' + translate('Basal Profile') + '</td>').appendTo(trBasalProfile);
$('<td/>').appendTo(trBasalProfile);
$('<td/>').appendTo(trBasalProfile);
$('<td/>').appendTo(trBasalProfile);
trBasalProfile.appendTo(table);

var lastBasalRecord;
for (var iTime = 0; iTime < 24*2; iTime++) {
var time = getHourMinuteText(Math.floor(iTime / 2), (iTime % 2) * 30);
var basalRecord = findBasalRecord(store.basal, time);
var recommendedRecord = findRecommendedRecord(result.basalprofile, iTime*30);

if (basalRecord !== null || recommendedRecord !== null) {
var tr = $('<tr/>');
$('<td>' + time + '</td>').appendTo(tr);
if (basalRecord !== null) {
lastBasalRecord = basalRecord;
$('<td>' + basalRecord.value + '</td>').appendTo(tr);
} else {
$('<td/>').appendTo(tr);
}
if (recommendedRecord !== null) {
var criticalLimit = 1.2;
var style = "";
if (lastBasalRecord !== null) {
var wasCriticalChange = false;
if (lastBasalRecord.value < recommendedRecord.rate)
{
wasCriticalChange = (recommendedRecord.rate / lastBasalRecord.value >= criticalLimit);
}
else if (lastBasalRecord.value > recommendedRecord.rate) {
wasCriticalChange = (lastBasalRecord.value / recommendedRecord.rate >= criticalLimit);
}
if (wasCriticalChange)
style = "background-color: #FF9999";
}
$('<td style="' + style + '">' + recommendedRecord.rate + '</td>').appendTo(tr);
} else {
$('<td/>').appendTo(tr);
}
var lastTd = $('<td/>').appendTo(tr);
}

if (recommendedRecord && lastBasalRecord) {
appendArrowIcon(lastTd, lastBasalRecord.value, recommendedRecord.rate);
}

tr.appendTo(table)
}
};

autotune.convertBasal = function convertBasal(item)
{
var convertedBasal = {
"start": item.time + ":00",
"minutes": Math.round(item.timeAsSeconds / 60),
"rate": item.value
};
return convertedBasal;
}

autotune.convertProfile = function convertProfile(profile)
{
var p = profile.store[profile.defaultProfile];

var autotuneProfile =
{
"min_5m_carbimpact": 3,
"dia": Number(p.dia),
"basalprofile": _.map(p.basal, autotune.convertBasal),
"isfProfile": {
"sensitivities": [
{
"i": 0,
"start": p.sens[0].time + ":00",
"sensitivity": Number(p.sens[0].value),
"offset": 0,
"x": 0,
"endOffset": 1440
}
]
},
"carb_ratio": Number(p.carbratio[0].value),
"autosens_max": 1.2,
"autosens_min": 0.7
};
return autotuneProfile;
}

var isf = require('./autotune/profile/isf');
autotune.isfLookup = isf.isfLookup;

var history = require('./autotune/meal/history');
autotune.arrayHasElementWithSameTimestampAndProperty = history.arrayHasElementWithSameTimestampAndProperty;

var basal = require('./autotune/profile/basal');
autotune.basalLookup = basal.basalLookup;
/* Return basal rate(U / hr) at the provided timeOfDay */

autotune.maxDailyBasal = function maxDailyBasal (inputs) {
var maxRate = _.maxBy(inputs.basals,function(o) { return Number(o.rate); });
return (Number(maxRate.rate) *1000)/1000;
}

/*Return maximum daily basal rate(U / hr) from profile.basals */
autotune.maxBasalLookup = function maxBasalLookup (inputs) {
return inputs.settings.maxBasal;
}

Loading

0 comments on commit 5add43c

Please sign in to comment.