/**
* Glucommander Initial Dosing Module
* @module glucommander
* @requires module:util
* @requires module:logger
* @since v1.3.1
*/
import { checkValue, displayValue } from './util.js';
import * as LOG from "./logger.js";
/**
* Total daily dose of PTA regimen.
* @type {number}
*/
let PTA_TDD;
/**
* Percent as basal of PTA regimen (0 to 100)
* @type {number}
*/
let PTA_PCT_BASAL;
/**
* Length of timeout (in ms) for event handlers that use elements from other modules
*/
const MODULE_DELAY = 150;
$(() => {
setTimeout( () => {
if ( LOG.debugEnabled ) {
$('#gm-ptaDose1').val(23);
$('#gm-ptaDose2').val(20);
$('#gm-ptaDose3').val(14);
$('#gm-ptaDose4').val(14);
$('#gm-ptaDose5').val(15);
ptaDosing();
}
}, MODULE_DELAY);
resetAll(true);
});
$('#weight').on('keyup', weightChanged);
$('#btnReset').on('click', resetClicked);
$('.gm-pta').on('change', ptaChanged);
$('#gm-multiplier').on('change', multiplierChanged);
$('.gm-custom-param').on('change', customParamChanged);
$('.gm-custom-dose').on('change', customDoseChanged);
function resetClicked(){
setTimeout( resetAll, MODULE_DELAY );
}
function resetAll(initial=false){
$('#gm-fromPta-custom0').val(20);
$('#gm-fromPta-custom1').val(25);
$('#gm-fromPta-custom2').val(30);
$('#gm-percentBasal-custom0').val(50);
$('#gm-percentBasal-custom1').val(50);
$('#gm-percentBasal-custom2').val(50);
if ( !initial ) {
$('#gm-multiplier')[0].selectedIndex = 0;
$('.gm-direction').removeClass('fa-arrow-left').addClass('fa-arrow-right');
ptaDosing();
updateBackgroundColor('#gm-fromPta-custom0');
updateBackgroundColor('#gm-fromPta-custom1');
updateBackgroundColor('#gm-fromPta-custom2');
}
}
function weightChanged(){
setTimeout( calcMultiplier, MODULE_DELAY);
}
function ptaChanged(event){
setTimeout( () => {
LOG.yellow('Input received: Glucommander - PTA');
ptaDosing();
}, 100);
}
function multiplierChanged(event){
LOG.yellow('Glucommander Multiplier Changed')
calcMultiplier();
}
function customParamChanged(event){
setTimeout( () => {
LOG.yellow(`GM CUSTOM PARAM CHANGED: #${event.target.id}`);
customFromParams(/\d/.exec(event.target.id)[0]);
}, 200);
}
function customDoseChanged(event){
setTimeout( () => {
LOG.yellow(`GM CUSTOM DOSE CHANGED: #${event.target.id}`);
customFromDose(/\d/.exec(event.target.id)[0]);
}, 200);
}
function ptaDosing(){
LOG.cyanGroupCollapsed('GLUCOMMANDER: PTA Dosing')
let basal = 0;
let bolus = 0;
PTA_TDD = 0;
for( let i = 1; i < 6; i++ ){
const txtDose = $(`#gm-ptaDose${i}`)[0];
const isBasal = txtDose.classList.contains('gm-basal');
const dose = checkValue($(txtDose).val(), 0, 500, true) || 0;
LOG.greenText(`PTA dose #${i} (${isBasal ? 'basal' : 'bolus'}) = ${dose} units`)
if ( isBasal ) {
basal += dose;
LOG.blueText(` Total basal = ${basal} units`);
} else {
bolus += dose;
LOG.blueText(` Total bolus = ${basal} units`);
}
PTA_TDD += dose;
LOG.purpleText(` TDD = ${PTA_TDD} units`)
}
if ( PTA_TDD > 0 ) {
let percentBasal = ( basal / PTA_TDD ) * 100;
LOG.log(`FINAL Basal: ${basal}; Bolus: ${bolus}; TDD: ${PTA_TDD}; ${percentBasal}% as basal`);
PTA_PCT_BASAL = Math.round( percentBasal );
} else {
PTA_TDD = undefined;
PTA_PCT_BASAL = undefined;
}
displayValue('#gm-ptaTdd', PTA_TDD);
displayValue('#gm-ptaTotalBasal', basal);
displayValue('#gm-ptaTotalBolus', bolus);
displayValue('#gm-ptaPercentBasal', PTA_PCT_BASAL);
LOG.groupEnd();
calcMultiplier();
recalculateCustom();
}
function calcMultiplier(){
LOG.cyanGroupCollapsed('GLUCOMMANDER: Multiplier')
const weight = checkValue(+$("#weight").val(), 1, 300);
const cboMultiplier = $("#gm-multiplier")[0];
const multiplier = cboMultiplier.options[cboMultiplier.selectedIndex].value;
const c = {
ident: 'multiplier',
tdd: Math.round(weight*multiplier),
percentBasal: weight > 0 ? 50 : 0,
}
calculateDoses(c);
if ( weight > 0 ) {
if ( PTA_TDD ) {
displayValue(`#gm-fromPta-${c.ident}`, c.percentReduction, 1, '', '', true, true);
} else {
displayValue(`#gm-fromPta-${c.ident}`, 0);
}
displayValue(`#gm-percentBasal-${c.ident}`, c.percentBasal);
} else {
displayValue(`#gm-fromPta-${c.ident}`, 0);
displayValue(`#gm-percentBasal-${c.ident}`, 0)
}
displayValue(`#gm-tdd-${c.ident}`, c.tdd);
displayValue(`#gm-basal-${c.ident}`, c.basal);
displayValue(`#gm-breakfast-${c.ident}`, c.breakfast);
displayValue(`#gm-lunch-${c.ident}`, c.lunch);
displayValue(`#gm-dinner-${c.ident}`, c.dinner);
updateBackgroundColor(`#gm-fromPta-${c.ident}`);
LOG.groupEnd();
}
/**
* Calculate custom dosing when dose is provided
*
* @param {Number} i Index of custom dose row that was changed
*/
function customFromDose(i){
i = parseFloat(i);
LOG.cyanGroup(`GLUCOMMANDER: Custom (row ${i+1}) from Dose`)
/** @type GlucommanderParams */
const c = {
ident: `custom${i}`,
dir: "fa-arrow-left",
oppDir: "fa-arrow-right",
tdd: 0,
};
c.basal = checkValue(+$(`#gm-basal-${c.ident}`).val());
c.breakfast = checkValue(+$(`#gm-breakfast-${c.ident}`).val());
c.lunch = checkValue(+$(`#gm-lunch-${c.ident}`).val());
c.dinner = checkValue(+$(`#gm-dinner-${c.ident}`).val());
c.tdd = c.basal + c.breakfast + c.lunch + c.dinner;
checkDirection(c);
LOG.greenText(`Basal: ${c.basal} / Breakfast: ${c.breakfast} / Lunch: ${c.lunch} / Dinner: ${c.dinner}`);
if ( c.tdd > 0 ) {
const percentBasal = 100 * c.basal / c.tdd;
LOG.blueText(`Basal is ${percentBasal}% of TDD`);
c.percentBasal = Math.round(percentBasal);
if( PTA_TDD > 0 ) {
const percentReduction = ( 1 - ( c.tdd / PTA_TDD ) ) * 100
LOG.log(`Reduced from PTA by ${PTA_TDD - c.tdd} units = ${percentReduction}% reduction`);
c.percentReduction = Math.round( percentReduction );
} else {
LOG.grayText('No PTA dosing available, unable to calculate percent reduction');
}
} else {
c.percentBasal = undefined;
c.percentReduction = undefined;
}
if ( PTA_TDD ) {
if ( c.percentReduction === 0 ) {
$(`#gm-fromPta-${c.ident}`).val("0");
} else {
displayValue(`#gm-fromPta-${c.ident}`, c.percentReduction, 0.1, '', '', true, true);
}
} else {
displayValue(`#gm-fromPta-${c.ident}`, 0);
}
displayValue(`#gm-percentBasal-${c.ident}`, c.percentBasal);
displayValue(`#gm-tdd-${c.ident}`, c.tdd);
$(`#gm-fromPta-${c.ident}`).css('background-color', getColor(c.percentReduction));
LOG.groupEnd();
}
/**
* Calculate custom dosing when parameters are provided
*
* @param {Number} i Index of custom dose row that was changed
*/
function customFromParams(i){
i = parseFloat(i);
LOG.cyanGroup(`GLUCOMMANDER: Custom (row ${i+1}) from Parameters`)
/** @type GlucommanderParams */
const c = {
ident: `custom${i}`,
dir: "fa-arrow-right",
oppDir: "fa-arrow-left",
};
checkDirection(c);
LOG.blueText(`PTA: TDD ${PTA_TDD} units (${PTA_PCT_BASAL}% as basal)`);
if( PTA_TDD > 0 ) {
c.percentReduction = checkValue(+$(`#gm-fromPta-${c.ident}`).val(), -1000, 1000, true);
if ( c.percentReduction !== undefined ) {
c.percentBasal = checkValue(+$(`#gm-percentBasal-${c.ident}`).val(),0,100);
LOG.log(`Desired: ${c.percentReduction}% reduction with ${c.percentBasal}% as basal`);
if ( c.percentBasal > 0 ) {
const newTdd = (1 - ( c.percentReduction/100 ) ) * PTA_TDD;
c.tdd = Math.round( newTdd );
LOG.log(`New TDD is ${newTdd} (rounded to ${c.tdd})`);
calculateDoses(c);
displayValue(`#gm-tdd-${c.ident}`, c.tdd);
LOG.log(`TDD: ${c.tdd} (${c.percentBasal}% as basal) = ${c.percentReduction}% reduction from PTA`);
} else {
LOG.redText('No % basal provided');
}
} else {
LOG.redText('No % reduction provided');
}
} else {
LOG.redText('No PTA dosing provided');
}
displayValue(`#gm-basal-${c.ident}`, c.basal);
displayValue(`#gm-breakfast-${c.ident}`, c.breakfast);
displayValue(`#gm-lunch-${c.ident}`, c.lunch);
displayValue(`#gm-dinner-${c.ident}`, c.dinner);
$(`#gm-fromPta-${c.ident}`).css('background-color', getColor(c.percentReduction));
LOG.groupEnd();
}
/**
* Check the arrow direction for the current row and update if it has changed
* @param {GlucommanderParams} c
*/
function checkDirection(c){
if( $(`#gm-dir-${c.ident}`).hasClass(c.oppDir) ){
LOG.purpleText(`Switched Direction for ${c.ident}`);
$(`#gm-dir-${c.ident}`).removeClass(c.oppDir).addClass(c.dir);
} else {
LOG.purpleText(`Same Direction for ${c.ident}`);
}
}
/**
* Calculate all parameters given a TDD and percent basal.
* TDD is returned unchanged; percentBasal will be adjusted if needed, so all doses are an round number of units
*
* @param {GlucommanderParams} c
*/
function calculateDoses( c ){
LOG.beginFunction('GLUCOMMANDER: Calculate Doses', arguments);
if ( c?.tdd > 0 ) {
const basal = c.tdd * ( c.percentBasal/100 );
c.basal = Math.round( basal );
c.bolus = c.tdd - c.basal;
const percentBasal = (c.basal/c.tdd)*100;
c.percentBasal = Math.round( percentBasal );
LOG.log(`Basal: ${basal} / Bolus: ${c.bolus} / Percent Basal: ${percentBasal}`)
let [ breakfast, lunch, dinner ] = distributeBolus(c.bolus);
LOG.log(`Breakfast: ${breakfast} / Lunch: ${lunch} / Dinner: ${dinner}`);
c.breakfast = breakfast;
c.lunch = lunch;
c.dinner = dinner;
if( PTA_TDD > 0 ) {
const percentReduction = ( 1 - ( c.tdd / PTA_TDD ) ) * 100
LOG.log(`Reduced from PTA by ${PTA_TDD - c.tdd} units = ${percentReduction}% reduction`);
c.percentReduction = Math.round( percentReduction );
} else {
LOG.grayText('No PTA dosing available, unable to calculate percent reduction');
}
} else {
LOG.redText('TDD = 0');
}
LOG.endResult(c);
}
function recalculateCustom(){
LOG.cyanGroupCollapsed('GLUCOMMANDER: Recalculate Custom');
for(let i=0; i<3; i++){
if( $(`#gm-dir-custom${i}`).hasClass("fa-arrow-right") ){
LOG.cyanText(`Calculating row ${i+1} based on PARAMETERS`);
customFromParams(i);
} else {
LOG.cyanText(`Calculating row ${i+1} based on DOSES`);
customFromDose(i);
}
}
LOG.groupEnd();
}
/**
* Changes the background color of the specified element based on its value
*
* @param {String} selector JQuery selector for either an output div or an input
*/
function updateBackgroundColor(selector) {
LOG.cyanGroupCollapsed(`GLUCOMMANDER: background color for ${selector}`);
const el = $(selector)[0];
const val = el.nodeName === "INPUT" ? $(el).val() : $(el).html();
LOG.cyanText(val);
if ( val.length === 0 ) {
$(el).css('background-color', '')
} else {
const clr = getColor(val);
LOG.cyanText(clr);
$(el).css('background-color', clr);
}
LOG.groupEnd()
}
function getColor(change){
if ( isNaN(change) ) return ""; // blank
if ( change < 0 || change > 100 ) return "#f9c"; // pink
if ( change > 50 ) return "#fcc"; // red
if ( change > 40 || change < 10 ) return "#fc9"; // orange
if ( change > 30 || change < 20 ) return "#ff9"; // yellow
if ( change > 19 || change < 31 ) return "#cfc"; // green
return "";
}
/**
* Distributes a total daily bolus dose between 3 meals with
* the largest at dinner, then breakfast, then lunch.
*
* @param {Number} total Total bolus dose to distribute
* @returns {Number[]} Array of breakfast, lunch, and dinner doses
*/
function distributeBolus(total){
LOG.beginFunction('GLUCOMMANDER: Distribute Bolus', arguments);
const middle = Math.round( total / 3 );
const large = Math.round( ( total - middle ) / 2);
const small = total - middle - large;
LOG.log(`Middlest dose: ${middle} --> Breakfast`);
LOG.log(`Smallest dose: ${small} --> Lunch`);
LOG.log(`Largest dose: ${large} --> Dinner`);
const res = [middle, small, large];
LOG.endResult(res);
return res;
}
/**
* @typedef {Object} GlucommanderParams
* @property {String} ident identifier to use for DOM updates
* @property {Number} tdd total daily dose
* @property {Number} percentBasal percent of daily dose that's basal
* @property {Number} percentReduction percent reduction from PTA TDD
* @property {Number} basal total daily basal dose
* @property {Number} bolus total daily bolus dose
* @property {Number} breakfast breakfast bolus dose
* @property {Number} lunch lunch bolus dose
* @property {Number} dinner dinner bolus dose
* @property {String} dir direction of calculation (for custom dose)
* @property {String} oppDir opposite direction of calculation (for custom dose)
*/