/*!
* Multipurpose Calculator
* Copyright 2020-2024 Andy Briggs (https://github.com/pharmot)
* Licensed under MIT (https://github.com/pharmot/multipurpose-calculator/LICENSE)
*/
// eslint-disable-next-line no-global-assign
$ = require("jquery");
import "bootstrap";
import "../scss/main.scss";
import { displayDate, displayValue, checkValue, roundTo, getDateTime, getHoursBetweenDates, checkTimeInput, parseAge, displayTime } from "./util.js";
import { default as ivig } from "./ivig.js";
import { getSecondDose } from "./seconddose.js";
import * as arial from "./arial.js";
import { default as setupValidation } from "./formValidation.js";
import * as vanco from "./vanco.js";
import * as amg from "./amg.js";
import * as LOG from "./logger.js";
require("./heparin.js");
require("./kcentra.js");
require("./pca.js");
require("./nextdose.js");
require("./qtc.js");
require("./alligation.js");
require("./warf.js");
// require("./glucommander.js");
let debug = false;
// let debugDefaultTab = "more";
// let debugDefaultMoreTab = "amg";
const tape = {};
let validatedFields;
//---------------------------------------------------------------
// ON PAGE LOAD
$(() => {
if ( /debug/.test(location.search) ) {
debug = true;
LOG.enable();
} else if ( /log/.test(location.search) ) {
LOG.enable();
}
$("[data-toggle=\"popover\"]").popover({ html: true });
$("[data-toggle=\"tooltip\"]").tooltip();
$(".hidden").addClass('hidden');
$("#amg-warning").addClass('hidden');
if ( debug ) {
LOG.presetValues();
$("#ptage").val(60);
$("#sex").val("M");
$("#height").val(170.2);
$("#weight").val(123.1);
$("#scr").val(0.9);
$("#vancoAUCPeakTime").val(5);
$("#vancoAUCTroughTime").val(11.5);
$("#auc-curPeak").val(20);
$("#auc-curTrough").val(11);
$("#auc-curDose").val(1000);
$("#auc-curFreq").val(12);
$("#twolevelDate1").val("2021-01-16");
$("#twolevelDate2").val("2021-01-16");
$("#twolevelTime1").val("0000");
$("#twolevelTime2").val("1500");
$("#twolevelLevel1").val(20);
$("#twolevelLevel2").val(12);
$("#revision-curDose").val(1000);
$("#revision-curFreq").val(12);
$("#revision-curTrough").val(11);
$("#revision-curTroughTime").val(0.5);
$("#amg-goalPeak").val(20);
$("#amg-currentDose").val(500);
$("#amg-doseDate").val("2023-05-27");
$("#amg-doseTime").val("2100");
$("#amg-level1").val(17);
$("#amg-level1Date").val("2023-05-28");
$("#amg-level1Time").val("0000");
$("#amg-level2").val(7);
$("#amg-level2Date").val("2023-05-28");
$("#amg-level2Time").val("1200");
$("#amg-customDate").val("2023-05-29");
$("#amg-customTime").val("0200");
$("#amg-warning").addClass('hidden');
calculate.syncCurrentDFT("revision");
} else {
resetDates();
}
validatedFields = setupValidation([
{ selector: "#ptage", inputType: "age", min: pt.config.check.ageMin, max: pt.config.check.ageMax },
{ selector: "#sex", match: /^[MmFf]$/ },
{ selector: "#height", min: pt.config.check.htMin, max: pt.config.check.htMax },
{ selector: "#weight", min: pt.config.check.wtMin, max: pt.config.check.wtMax },
{ selector: "#scr", min: pt.config.check.scrMin, max: pt.config.check.scrMax },
{ selector: ".validate-dose", min: vanco.config.check.doseMin, max: vanco.config.check.doseMax },
{ selector: ".validate-freq", min: vanco.config.check.freqMin, max: vanco.config.check.freqMax },
{ selector: ".validate-level", min: vanco.config.check.levelMin, max: vanco.config.check.levelMax },
{ selector: ".validate-amgDose", min: amg.config.check.levelMin, max: amg.config.check.levelMax },
{ selector: ".validate-amgFreq", min: amg.config.check.freqMin, max: amg.config.check.freqMax },
{ selector: ".validate-amgPeak", min: amg.config.check.goalPeakMin, max: amg.config.check.goalPeakMax },
{ selector: ".validate-amgLevel", min: amg.config.check.levelMin, max: amg.config.check.levelMax },
{ selector: ".validate-time", inputType: "time" },
]);
calculate.allForPatient();
$("#ptage").get(0).focus();
});
//---------------------------------------------------------------
// EVENT LISTENERS
// Reset Button
$("#btnReset").on("click", () => {
LOG.green('Reset');
$("input[type='text']").val("");
$("input[type='number']").val("");
calculate.syncCurrentDFT("revision");
resetDates();
$("#aucDates-apply").removeClass("datesApplied");
$("#hd")[0].selectedIndex = 0;
$("#top-container").removeClass("hd-0 hd-1 hd-2 hd-3 hd-4");
$("#top-container").addClass("hd-0");
$("#revision-levelTiming")[0].selectedIndex = 0;
$('#revision-curTrough-label').html('Trough');
$("#vancoIndication")[0].selectedIndex = 0;
pt.outlierSelected = false;
/** Add methods to calculate.allForPatient if dependant on input-patient fields */
calculate.allForPatient();
/** Methods that aren't dependant on input-patient fields */
calculate.secondDose();
calculate.ivig();
$("#top-container").removeClass("age-adult age-child age-infant");
$("#top-container").addClass("age-adult");
$(validatedFields).removeClass("invalid");
// Remove from manually marked invalid fields
$(".invalid").removeClass("invalid");
$("#vancoAki").prop( "checked", false );
$("#vancoOutlier").prop( "checked", false );
$("#amg-Cf").prop( "checked", false );
$("#amg-PrePostpartum").prop( "checked", false );
changedAmgMethod();
$("#amg-postAbxEffect")[0].selectedIndex = 4;
$(".output").html("");
$("#ptage").get(0).focus();
$("input[type='date']").val("");
$('.btn-group--nextdose-freq label').removeClass('active');
$('.btn-group--nextdose-time label').removeClass('active');
$('input[name="nextdose-freq"]:checked').prop('checked', false);
$('input[name="nextdose-time"]:checked').prop('checked', false);
$('.highlighted').removeClass('highlighted');
$('#nextdose-result').addClass('hidden');
});
// Patient
$(".input-patient").on("keyup", () => {
setTimeout( () => {
LOG.yellow('Input received: Patient')
calculate.allForPatient();
}, 50);
});
$('#weight').on('keyup', () => {
setTimeout( () => {
LOG.yellow('Input received: Weight')
calculate.allForPatient();
calculate.ivig();
}, 50)
})
$("#ptage").on("keyup", () => {
// LOG.yellow('Input received: Age')
setTimeout(() => {
$("#top-container").removeClass("age-adult age-child age-infant");
$("#top-container").addClass(`age-${pt.ageContext}`);
}, 1000);
});
$("#hd").on("change", (e) => {
LOG.yellow('HD status changed')
const hd = e.target.selectedIndex;
$("#top-container").removeClass("hd-0 hd-1 hd-2 hd-3 hd-4");
$("#top-container").addClass(`hd-${hd}`);
if ( hd === 0 ) {
$('#revision-curTrough-label').html('Trough');
} else {
$('#revision-curTrough-label').html('Level');
}
calculate.patientData();
calculate.vancoInitial();
calculate.vancoRevision();
calculate.vancoAUC();
});
$("#vancoAki").on("click keyup", () => {
LOG.yellow('AKI status changed');
calculate.vancoInitial();
})
$("#schwartz-k-infant").on("change", () => {
LOG.yellow('Schwartz k value changed')
calculate.patientData();
calculate.vancoInitial();
calculate.vancoRevision();
calculate.vancoAUC();
});
// Vancomycin
$("#vancoIndication").on("change", () => {
LOG.yellow('Vanco Indication Changed')
calculate.patientData();
calculate.vancoInitial();
});
$(".input-initialPK").on("keyup", () => {
LOG.yellow('Input received: Initial PK')
calculate.vancoInitial();
});
$("#peakTiming-dose").on("keyup", () => {
LOG.yellow('Input received: Peak Timing Dose')
calculate.peakTimingDuration();
});
$(".input-peakTiming").on("keyup", () => {
LOG.yellow('Input received: Peak Timing')
calculate.peakTiming()
});
$(".input-auc").on("keyup", () => {
LOG.yellow('Input received: AUC')
calculate.syncCurrentDFT("auc");
calculate.vancoAUC();
calculate.vancoRevision();
});
$(".input-auc-interval").on("keyup", () => {
LOG.yellow('Input received: AUC Interval')
calculate.vancoAUC(false);
});
$("#auc-reset").on("click", () => {
LOG.green('Reset (AUC)');
calculate.vancoAUC();
});
$(".input-revision").on("keyup", () => {
LOG.yellow('Input received: Revision');
calculate.syncCurrentDFT("revision");
calculate.vancoRevision();
calculate.vancoAUC();
});
$(".input-aucDates").on("keyup", () => {
LOG.yellow('Input received: AUC Dates')
calculate.vancoAUCDates();
});
$(".input-twolevel").on("keyup", () => {
LOG.yellow('Input received: Two-Level')
calculate.vancoTwolevel();
});
$(".input-twolevel-interval").on("keyup", () => {
LOG.yellow('Input received: Two-Level Interval')
calculate.vancoTwolevel(false);
});
$("#twolevel-reset").on("click", () => {
LOG.green('Reset (Two-Level)')
calculate.vancoTwolevel();
});
$("#revision-goalTrough").on("change", () => {
LOG.yellow('Input Received: Revision/Goal Trough')
calculate.vancoRevision();
});
$("#revision-levelTiming").on("change", () => {
LOG.yellow('Input Received: Revision/Level Timing')
calculate.vancoRevision();
});
$(".input-steadystate").on("keyup", () => {
LOG.yellow('Input Received: Steady State Check')
calculate.vancoSteadyStateCheck();
});
$("#seconddose-time1").on("keyup", () => {
LOG.yellow('Input Received: Second Dose Time')
calculate.secondDose();
});
$("[name='seconddose-freq']").on("change", () => {
LOG.yellow('Input Received: Second Dose Frequency')
calculate.secondDose();
});
// Aminoglycosides
$(".input-amg").on("keyup", () => {
LOG.yellow('Input Received: AMG')
calculate.amg();
});
$("#amg-medication").on("change", () => {
LOG.yellow('Input Received: AMG Medication')
calculate.amg();
});
$("#amg-postAbxEffect").on("change", () => {
LOG.yellow('Input Received: AMG Post Abx Effect')
calculate.amg();
});
$("#amg-Cf").on("change", () => {
LOG.yellow('AMG Method Changed')
changedAmgMethod();
calculate.amgWeight();
calculate.amg();
});
$("#vancoOutlier").on("click keyup", () => {
LOG.yellow('Input Received: Vanco/Outlier')
// Mark as selected if it is checked manually
if ( $("#vancoOutlier").prop( "checked" ) ) {
LOG.purpleText('User marked as an outlier')
pt.outlierSelected = true;
} else {
LOG.purpleText('User marked as not an outlier')
pt.outlierSelected = false;
}
calculate.vancoInitial();
});
/**
* Check CF box status and show/hide DOM elements based on dosing method
*/
function changedAmgMethod() {
if ( $("#amg-Cf").is(":checked") ) {
$("#row--amgTab").removeClass("amg-ext").addClass("amg-cf");
$("#label--amg-currentDose").html("Current Dose");
/* Change to tobramycin if gentamicin is selected */
if ( $("#amg-medication option:selected").val() === "G" ) {
$("#amg-medication").val("T");
}
} else {
$("#row--amgTab").removeClass("amg-cf").addClass("amg-ext");
$("#label--amg-currentDose").html("Dose Administered");
}
}
$("#amg-PrePostpartum").on("change", () => {
LOG.yellow('AMG - Pre/Postpartum Changed')
calculate.amgWeight();
calculate.amg();
});
// IVIG module
$("#ivig-product").on("change", () => {
LOG.yellow('IVIG product changed');
calculate.ivig();
});
$("#ivig-dose").on("keyup", () => {
LOG.yellow('Input Received: IVIG');
calculate.ivig();
});
// Vancomycin AUC Dates Modal
$("#aucDates-sameInterval").on("change", e => {
LOG.yellow('Input Received: AUC Dates - Same Interval?');
const tr = document.getElementById("aucDates-row--trough");
const pk = document.getElementById("aucDates-row--peak");
const spacer = document.getElementById("aucDates-row--spacer");
// const last = document.getElementById("aucDates-row--last");
const par = tr.parentNode;
if ( e.target.checked ) {
$("#aucDates-label--dose1").html("Dose prior to levels");
par.insertBefore(pk, tr);
par.insertBefore(tr, spacer);
$("#aucDates-row--dose2").addClass('hidden');
} else {
$("#aucDates-label--dose1").html("Dose before trough");
$("#aucDates-row--dose2").removeClass('hidden');
par.insertBefore(tr, pk);
par.insertBefore(pk, spacer);
}
calculate.vancoAUCDates();
});
$("#aucDates-apply").on("click", e => {
$(e.target).addClass("datesApplied");
$("#vancoAUCPeakTime").val($("#aucDates-peakResult").html());
$("#vancoAUCTroughTime").val($("#aucDates-troughResult").html());
LOG.green('AUC Dates Applied')
$("#aucDatesModal").modal("hide");
calculate.vancoAUC();
});
/**
* Resets date input fields to today's date.
*/
function resetDates() {
LOG.green('Reset Dates');
const today = new Date();
$(".dt-date").val(`${today.getFullYear()}-${`0${ today.getMonth() + 1}`.slice(-2)}-${`0${ today.getDate()}`.slice(-2)}`);
}
/**
* Object representing the patient.
*
* @namespace
* @property {string|number} _sex patient's sex, or 0 if invalid input
* @property {number} _age petiant's age in years, or 0 if invalid input
* @property {number} _wt patient's weight in kg, or 0 if invalid input
* @property {number} _ht petiant's height in cm, or 0 if invalid input
* @property {number} _scr patient's serum creatinine, or 0 if invalid input
* @property {boolean} outlierSelected PK outlier checkbox was clicked
*/
const pt = {
/**
* Contains the min and max values for input validation
* @const
* @readonly
*/
config: {
check: {
wtMin: 1,
wtMax: 300,
htMin: 60,
htMax: 250,
ageMin: 0.25, // minimum 3 months old
ageMax: 120,
scrMin: 0.1,
scrMax: 20,
},
},
/**
* Gets/sets the sex of the patient. Accepts a single letter - m or f - and
* stores as uppercase. If invalid, stores 0.
* @function
* @param {any} [x] Patient's sex
* @returns {string|number} Patient's sex as `M`, `F`, or 0 if invalid
*/
set sex(x) {
this._sex = /^[MmFf]$/.test(x) ? x.toUpperCase() : 0;
},
get sex() { return this._sex || 0; },
/**
* Gets/sets the weight of the patient.
* @function
* @requires module:util
* @param {number} [x] Patient's weight
* @returns {number} Patient's weight in kg, or 0 if invalid
*/
set wt(x) { this._wt = checkValue(x, this.config.check.wtMin, this.config.check.wtMax); },
get wt() { return this._wt || 0; },
/**
* Gets/sets the height of the patient.
* @function
* @requires module:util
* @param {number} [x] Patient's height
* @returns {number} Patient's height in cm, or 0 if invalid
*/
set ht(x) { this._ht = checkValue(x, this.config.check.htMin, this.config.check.htMax); },
get ht() { return this._ht || 0; },
/**
* Gets/sets the age of the patient.
* Accepts in years, months, days, or months/days.
* @function
* @requires module:util
* @param {string|number} [x] Patient's age in days, months, or years
* @returns {number} Patient's height in cm, or 0 if invalid
* @example
* pt.age("50"); // sets patient's age to 50 years
* pt.age("23m"); // sets patient's age to 23 months (getter returns in years)
* pt.age("16m3d"); // sets patient's age to 16 months, 3 days (getter returns in years)
* pt.age("300d"); // sets patient's age to 300 days (getter returns in years)
*/
set age(x) {
const ageInYears = parseAge(x);
if ( ageInYears ) {
this._age = checkValue(ageInYears, this.config.check.ageMin, this.config.check.ageMax);
} else {
this._age = undefined;
}
},
get age() { return this._age || 0; },
/**
* Gets the age context of the patient.
* Possible values are `adult` (default), `child`, and `infant`
* @function
* @returns {string} Patient's age context (adult, child, or infant)
*/
get ageContext() {
if ( this.age < 1 && this.age > 0 ) return "infant";
if ( this.age < 18 && this.age !== 0 ) return "child";
return "adult";
},
/**
* Gets/sets the patient's serum creatinine.
* @function
* @requires module:util
* @param {number} [x] Patient's SCr
* @returns {number} Patient's SCr in mg/dL, or 0 if invalid
*/
set scr(x) {
this._scr = checkValue(x, this.config.check.scrMin, this.config.check.scrMax);
},
get scr() { return this._scr || 0; },
/**
* Gets the patient's body mass index
* @function
* @returns {number} Patient's BMI in kg/m^2, or 0 if insufficient input
* @see [equations.md](https://pharmot.github.io/multipurpose-calculator/docs/equations.md/#body-mass-index)
*/
get bmi() {
if ( this.wt > 0 && this.ht > 0 ) {
return this.wt / ( this.ht / 100 * ( this.ht / 100 ));
}
return 0;
},
/**
* Gets the patient's ideal body weight. Returns 0 if age < 18.
* @function
* @returns {number} Patient's ideal body weight BMI in kg/m^2, or 0 if insufficient input
* @see [equations.md](https://pharmot.github.io/multipurpose-calculator/docs/equations.md/#ideal-body-weight)
*/
get ibw() {
if ( this.age < 18 ) return 0;
if ( this.ht > 0 && this.wt > 0 && this._sex ) {
return ( this.sex === "M" ? 50 : 45.5 ) + 2.3 * ( this.ht / 2.54 - 60 );
}
return 0;
},
/**
* Gets the patient's adjusted body weight, using a factor of 0.4. Returns 0 if age < 18.
* @function
* @returns {number} Patient's ideal body weight BMI in kg/m^2, or 0 if insufficient input
* @see [equations.md](https://pharmot.github.io/multipurpose-calculator/docs/equations.md/#adjusted-body-weight)
*/
get adjBW() {
if ( this.age < 18 ) return 0;
if ( this.ht > 0 && this.wt > 0 && this._sex ) {
if ( this.wt <= this.ibw ) return this.wt;
return 0.4 * (this.wt - this.ibw) + this.ibw;
}
return 0;
},
/**
* Gets the percent the patient is over or under ideal body weight. Returns 0 if age < 18.
* @function
* @returns {number} Percent over or under ideal body weight, or 0 if insufficient input
* @example
* If patient is 30% above their IBW, returns `30`
* @see [equations.md](https://pharmot.github.io/multipurpose-calculator/docs/equations.md#percent-over-or-under-ibw)
*/
get overUnder() {
if ( this.age < 18 ) return 0;
if ( this.ht > 0 && this.wt > 0 && this._sex && this.adjBW > 0 ) {
return (this.wt / this.ibw - 1) * 100;
}
return 0;
},
/**
* Gets the patient's lean body weight. Returns 0 if age < 18.
* @function
* @returns {number} Patient's lean body weight in kg, or 0 if insufficient input
* @see [equations.md](https://pharmot.github.io/multipurpose-calculator/docs/equations.md#lean-body-weight)
*/
get lbw() {
if ( this.age < 18 ) return 0;
if ( this.ht > 0 && this.wt > 0 && this._sex ) {
if ( this.sex === "F" ) {
return 9270 * this.wt / ( 8780 + 244 * this.bmi );
}
return 9270 * this.wt / ( 6680 + 216 * this.bmi );
}
return 0;
},
/**
* Gets the patient's Cockroft-Gault creatinine clearance using actual body weight.
* @function
* @returns {number} Patient's CrCl (C-G ABW) in mL/min, or 0 if insufficient input
* @see [equations.md](https://pharmot.github.io/multipurpose-calculator/docs/equations.md#cockroft-gault)
*/
get cgActual() {
if ( this.wt > 0 && this.age > 0 && this.scr > 0 && this._sex ) {
return this.cg(this.wt);
}
return 0;
},
/**
* Gets the patient's Cockroft-Gault creatinine clearance using adjusted body weight.
* @function
* @returns {number} Patient's CrCl (C-G AdjBW) in mL/min, or 0 if insufficient input
* @see [equations.md](https://pharmot.github.io/multipurpose-calculator/docs/equations.md#cockroft-gault)
*/
get cgAdjusted() {
if ( this.adjBW > 0 && this.age > 0 && this.scr > 0 ) {
return this.cg(this.adjBW);
}
return 0;
},
/**
* Gets the patient's Cockroft-Gault creatinine clearance using ideal body weight.
* @function
* @returns {number} Patient's CrCl (C-G IBW) in mL/min, or 0 if insufficient input
* @see [equations.md](https://pharmot.github.io/multipurpose-calculator/docs/equations.md#cockroft-gault)
*/
get cgIdeal() {
if ( this.ibw > 0 && this.age > 0 && this.scr > 0 ) {
return this.cg(this.ibw);
}
return 0;
},
/**
* Gets the patient's Protocol CrCl (equation and weight depend on age and percent over/under IBW.
* @function
* @returns {number} Patient's Protocol CrCl in mL/min, or 0 if insufficient input
* @see [equations.md](https://pharmot.github.io/multipurpose-calculator/docs/equations.md#protocol-crcl)
*/
get crcl() {
if ( this.age < 18 ) return this.schwartz;
if ( this.ibw === 0 || this.age === 0 || this.scr === 0 ) return 0;
if ( this.wt < this.ibw ) return this.cgActual;
if ( this.overUnder > 30 ) return this.cgAdjusted;
return this.cgIdeal;
},
/**
* Calculates the patient's Cockroft-Gault creatinine clearance
* @function
* @param {number} weight weight in kg to use for calculation
* @returns {number} Calculated CrCl in mL/min, or 0 if insufficient input
*/
cg(weight) {
return (140 - this.age) * weight / (this.scr * 72) * (pt.sex === "F" ? 0.85 : 1);
},
/**
* Gets/sets the k value for the Schwartz CrCl equation.
* @function
* @see [equations.md](https://pharmot.github.io/multipurpose-calculator/docs/equations.md#schwartz)
* @param {number} [x] selectedIndex of k value input element where 0 is "term infant" and 1 is "LBW infant"
* @returns {number} k value, or 0 if age > 18 or insufficient input
*/
set schwartzK(term) {
if ( this.age === 0 || this.age >= 18 || this.age >= 13 && this.sex === 0 ) {
this._schwartzK = 0;
} else if ( this.age <= 1 ) {
this._schwartzK = term === 0 ? 0.45 : 0.33;
} else if ( this.age >= 13 && this.sex === "M" ) {
this._schwartzK = 0.7;
} else {
this._schwartzK = 0.55;
}
// return this._schwartzK;
},
get schwartzK() {
return this._schwartzK || 0;
},
/**
* Gets the patient's Schwartz CrCl
* @see [equations.md](https://pharmot.github.io/multipurpose-calculator/docs/equations.md#schwartz)
* @function
* @returns {number} Patient's CrCl in mL/min using the Schwartz equation, or 0 if insufficient input
*/
get schwartz() {
const k = this.schwartzK;
if ( k === 0 || this.ht === 0 || this.scr === 0 ) return 0;
return k * this.ht / this.scr;
},
/**
* Gets the patient's body surface area using the Mosteller formula if patient
* is 14 years of age or older.
*
* @function
* @returns {number} Patient's BSA in m^2, or 0 if insufficient input
* @see [equations.md](https://pharmot.github.io/multipurpose-calculator/docs/equations.md/#body-surface-area)
*/
get bsa() {
if ( this.age < 14 ) return 0;
if ( this.ht > 0 && this.wt > 0 ) {
return Math.sqrt( this.wt * this.ht / 3600 );
}
return 0;
},
};
/**
* Functions called by event listeners to calculate and display results
* @namespace
*/
const calculate = {
/** Call all methods that depend on patient data */
allForPatient() {
this.patientData();
this.vancoInitial();
this.vancoRevision();
this.vancoTwolevel();
this.vancoAUC();
this.amgWeight();
this.amg();
},
/**
* Input and output for patient parameters including CrCl, weights, etc.
* @requires module:util
* @returns {undefined}
*/
patientData() {
LOG.cyanGroupCollapsed('CALCULATE: Patient Data')
// Remove highlighting on CrCls
$(".outCrCl").removeClass("use-this");
// Set pt properties from inputs
pt.age = $("#ptage").val();
pt.sex = $("#sex").val();
pt.ht = +$("#height").val();
pt.wt = +$("#weight").val();
pt.scr = +$("#scr").val();
pt.schwartzK = $("#schwartz-k-infant")[0].selectedIndex;
displayValue("#schwartz-crcl", pt.schwartz, 0.1, " mL/min");
// Display weights and CrCl
displayValue("#ibw", pt.ibw, 0.1, " kg");
displayValue("#overUnder", pt.overUnder, 0.1, "%", "", true);
displayValue("#adjBW", pt.adjBW, 0.1, " kg");
displayValue("#lbw", pt.lbw, 0.1, " kg");
displayValue("#bmi", pt.bmi, 0.1, " kg/m²");
displayValue("#bsa", pt.bsa, 0.01, " m²");
if ( pt.bmi > 30 ) {
$("#bmi").addClass("text-danger font-weight-bold");
} else {
$("#bmi").removeClass("text-danger font-weight-bold");
}
displayValue("#cgIdeal", pt.cgIdeal, 0.1, " mL/min");
displayValue("#cgActual", pt.cgActual, 0.1, " mL/min");
displayValue("#cgAdjusted", pt.cgAdjusted, 0.1, " mL/min");
// Highlight CrCl to use per protocol
if ( pt.cgAdjusted > 0 ) {
if ( pt.wt < pt.ibw ) {
LOG.log("ABW < IBW, use ABW for CrCl");
$("#cgActual").addClass("use-this");
} else if ( pt.overUnder > 30 ) {
LOG.log("ABW > 130% of IBW, use AdjBW for CrCl");
$("#cgAdjusted").addClass("use-this");
} else {
LOG.log("ABW between IBW and 130% of IBW, use IBW for CrCl");
$("#cgIdeal").addClass("use-this");
}
}
if ( pt.bmi > 40 ) {
LOG.purpleText('Automatically marked as outlier for BMI > 40');
$("#vancoOutlier").prop( "checked", true );
} else if ( pt.scr < 0.4 && pt.scr > 0 ) {
LOG.purpleText('Automatically marked as outlier for SCr < 0.4');
$("#vancoOutlier").prop( "checked", true );
} else if ( pt.crcl > 130 ) {
LOG.purpleText('Automatically marked as outlier for CrCl > 130');
$("#vancoOutlier").prop( "checked", true );
} else if ( pt.age < 35 && pt.age > 18 ) {
LOG.purpleText('Automatically marked as outlier for Age < 35');
$("#vancoOutlier").prop( "checked", true );
} else {
LOG.purpleText('Not an outlier based on age, BMI, SCr or CrCl');
if ( $("#vancoOutlier").prop( "checked" ) ) {
if ( !pt.outlierSelected ) {
LOG.purpleText('User has not manually marked as an outlier; box unchecked')
$("#vancoOutlier").prop( "checked", false );
} else {
LOG.purpleText('User marked as an outlier so status not automatically changed')
}
}
}
const cboHD = $("#hd")[0];
const cboIndication = $("#vancoIndication")[0];
pt.hd = cboHD.selectedIndex;
pt.vancoIndication = cboIndication.selectedIndex;
tape.pt = [[
["Age", displayValue("", pt.age, 0.01, " years")],
["Sex", pt.sex === 0 ? "" : pt.sex],
["Weight", displayValue("", pt.wt, 0.0001, " kg")],
["Height", displayValue("", pt.ht, 0.0001, " cm")],
["SCr", displayValue("", pt.scr, 0.0001, " mg/dL")],
["Dialysis", cboHD.options[pt.hd].innerHTML],
]];
if ( pt.ageContext === "adult" ) {
tape.pt.push([
["Ideal body weight", displayValue("", pt.ibw, 0.1, " kg")],
["Over/Under IBW", displayValue("", pt.overUnder, 0.1, "%", "", true)],
["Adjusted body weight", displayValue("", pt.adjBW, 0.1, " kg")],
["Lean body weight", displayValue("", pt.lbw, 0.1, " kg")],
["Body mass index", displayValue("", pt.bmi, 0.1, " kg/m²")],
]);
tape.pt.push([
["CrCl (C-G Actual)", displayValue("", pt.cgActual, 0.1, " mL/min")],
["CrCl (C-G Ideal)", displayValue("", pt.cgIdeal, 0.1, " mL/min")],
["CrCl (C-G Adjusted)", displayValue("", pt.cgAdjusted, 0.1, " mL/min")],
["CrCl (Protocol)", displayValue("", pt.crcl, 0.1, " mL/min")],
]);
} else {
tape.pt.push([
["Body mass index", displayValue("", pt.bmi, 0.1, " kg/m^2")],
["CrCl (Schwartz)", displayValue("", pt.schwartz, 0.1, " mL/min")],
]);
}
$("#tape--pt").html(LOG.outputTape(tape.pt, "Patient Info"));
$("#tapeAmg--pt").html(LOG.outputTape(tape.pt, "Patient Info"));
LOG.groupEnd();
},
/**
* Sync dose, frequency, and trough inputs between tabs
* @requires module:util
* @param {String} src id prefix of element to use as the source
* @returns {undefined}
*/
syncCurrentDFT(src) {
LOG.cyanGroupCollapsed('CALCULATE: Sync dose/frequency/trough')
pt.curDose = checkValue(+$(`#${src}-curDose`).val(), vanco.config.check.doseMin, vanco.config.check.doseMax);
pt.curFreq = checkValue(+$(`#${src}-curFreq`).val(), vanco.config.check.freqMin, vanco.config.check.freqMax);
pt.curTrough = checkValue(+$(`#${src}-curTrough`).val(), vanco.config.check.levelMin, vanco.config.check.levelMax);
$(".current-dose").filter($(`:not(.input-${src})`)).val(pt.curDose > 0 ? pt.curDose : "");
$(".current-freq").filter($(`:not(.input-${src})`)).val(pt.curFreq > 0 ? pt.curFreq : "");
$(".current-trough").filter($(`:not(.input-${src})`)).val(pt.curTrough > 0 ? pt.curTrough : "");
LOG.groupEnd();
},
/**
* Input and output for aminoglycoside dosing
* @requires module:amg
*/
amgWeight() {
LOG.cyanGroupCollapsed('CALCULATE: AMG dosing weight')
/* Get dosing weight */
const { dosingWeightString } = amg.getDosingWeight({
age: pt.age,
wt: pt.wt,
ibw: pt.ibw,
adjBW: pt.adjBW,
overUnder: pt.overUnder,
alt: $("#amg-Cf").is(":checked") || $("#amg-PrePostpartum").is(":checked"),
});
$("#amg-dosingWeight").html(dosingWeightString);
LOG.groupEnd();
},
amg() {
LOG.cyanGroupCollapsed('CALCULATE: Aminoglycosides')
/* Get limits for input checking */
const { goalPeakMin, goalPeakMax, freqMin, freqMax, doseMin, doseMax } = amg.config.check;
/* Determine whether CF calculation method should be used */
const cf = $("#amg-Cf").is(":checked");
/* Get dosing weight */
const { dosingWeight, dosingWeightType, dosingWeightReason } = amg.getDosingWeight({
age: pt.age,
wt: pt.wt,
ibw: pt.ibw,
adjBW: pt.adjBW,
overUnder: pt.overUnder,
alt: cf || $("#amg-PrePostpartum").is(":checked"),
});
/* First letter (uppercase) of selected drug */
const selectedDrug = $("#amg-medication option:selected").val();
/* Selected post-antibiotic effect time */
const pae = +$("#amg-postAbxEffect option:selected").text();
/* Get input values */
const params = {
cf: cf,
prePostpartum: $("#amg-prePostpartum").is(":checked"),
drug: selectedDrug,
goalPeak: checkValue(+$("#amg-goalPeak").val(), goalPeakMin, goalPeakMax),
dose: checkValue(+$("#amg-currentDose").val(), doseMin, doseMax),
freq: checkValue(+$("#amg-currentFreq").val(), freqMin, freqMax),
doseTime: getDateTime($("#amg-doseDate").val(), $("#amg-doseTime").val()),
postAbxEffect: pae,
level1: checkValue(+$("#amg-level1").val(), 0),
level1Time: getDateTime($("#amg-level1Date").val(), $("#amg-level1Time").val()),
level2: checkValue(+$("#amg-level2").val(), 0),
level2Time: getDateTime($("#amg-level2Date").val(), $("#amg-level2Time").val()),
customTime: getDateTime($("#amg-customDate").val(), $("#amg-customTime").val()),
};
//TODO: add input validation for amg-currentDose, amg-goalPeak, amg-currentFreq -- add to HTML
const wtBasedDose = dosingWeight > 0 && params.dose > 0 ? params.dose / dosingWeight : 0;
displayValue("#amg-currentWtBasedDose", wtBasedDose, 0.1, " mg/kg");
/* Check to make sure timing of dose/levels are in the correct sequence and highlight if not */
params.valid = true;
if ( params.level1Time > 0 && params.doseTime > 0 && params.level1Time <= params.doseTime ) {
$("#amg-level1DateTime").addClass("invalid");
params.valid = false;
} else {
$("#amg-level1DateTime").removeClass("invalid");
}
if ( params.level2Time > 0 && ( params.level1Time > 0 && params.level2Time <= params.level1Time || params.doseTime > 0 && params.level2Time <= params.doseTime ) ) {
$("#amg-level2DateTime").addClass("invalid");
params.valid = false;
} else {
$("#amg-level2DateTime").removeClass("invalid");
}
if ( params.customTime > 0 && params.doseTime > 0 && params.customTime <= params.doseTime ) {
$("#amg-customLevelDateTime").addClass("invalid");
} else {
$("#amg-customLevelDateTime").removeClass("invalid");
}
const wtTape = dosingWeight > 0 ? `${displayValue("", dosingWeight, 0.1, " kg")} (${dosingWeightType} weight)` : "";
/* Save common items to tape */
tape.amg = [
[
["Dosing Weight", wtTape],
["Used because", dosingWeightReason],
],
[
["Drug", $("#amg-medication option:selected").text()],
["Cystic Fibrosis?", params.cf ? "Yes" : "No"],
["Pre-/Postpartum?", params.prePostpartum ? "Yes" : "No"],
["Dose administered", displayValue("", params.dose, 0.1, " mg")],
["Weight-based", displayValue("", wtBasedDose, 0.1, " mg/kg")],
["Time of dose", displayDate(params.doseTime)],
["First level", displayValue("", params.level1, 0.01, " mcg/mL")],
["First level time", displayDate(params.level1Time)],
["Second level", displayValue("", params.level2, 0.01, " mcg/mL")],
["Second level time", displayDate(params.level2Time)],
],
];
if ( cf ) {
/* Reset non-cf items */
$("#amg-recDose").html("");
$("#amg-recDoseStart").html("");
$("#amg-customLevel").html("");
$("#amg-goalTrough").html("");
$("#amg-warning").addClass('hidden');
const { goalTrough, goalPeak, goalAuc, ke, predPeak, predTrough, auc, halflife } = amg.cf(params);
displayValue("#amg-predTrough", predTrough, 0.1, " mcg/mL");
displayValue("#amg-predPeak", predPeak, 0.1, " mcg/mL");
displayValue("#amg-auc", auc, 1, " mg·hr/L");
displayValue("#amg-predAuc", auc, 1, " mg·hr/L");
displayValue("#amg-predHalflife", halflife, 0.1, " hr");
$("#amg-goalTroughOutput").html(goalTrough);
$("#amg-goalPeakOutput").html(goalPeak);
$("#amg-goalAuc").html(goalAuc);
if ( ke > 0 ) {
tape.amg.push([
["Goal trough", goalTrough],
["Goal peak", goalPeak],
["Goal AUC", goalAuc],
],
[
["Calculated ke", displayValue("", ke, 0.001, "/hr")],
["Halflife", displayValue("", halflife, 0.01, " hr")],
["Predicted trough", displayValue("", predTrough, 0.001, " mcg/mL")],
["Predicted peak", displayValue("", predPeak, 0.01, " mcg/mL")],
["Calculated AUC", displayValue("", auc, 0.1, " mg·hr/L")],
]);
}
} else {
/* Reset cf-only items */
$("#amg-goalPeakOutput").html("");
$("#amg-goalAuc").html("");
$("#amg-predTrough").html("");
$("#amg-predPeak").html("");
$("#amg-auc").html("");
$("#amg-predAuc").html("");
$("#amg-predHalflife").html("");
const { goalTrough, ke, truePeak, troughDT, redoseDT, redoseLevel, newDoseToRedoseTime, vd, recDose, recDoseRounded, recFreq, levelAtCustom, warn } = amg.extended(params);
/* Show warning if indicated */
if ( warn ) {
$("#amg-warning").removeClass('hidden');
} else {
$("#amg-warning").addClass('hidden');
}
displayValue("#amg-goalTrough", goalTrough, 1, " mcg/mL");
const recDoseAndFreq = `${displayValue("", recDoseRounded, 0.1, " mg" )} ${displayValue("", recFreq, 1, "h", "q")}`;
$("#amg-recDose").html(recDoseAndFreq);
$("#amg-recDoseStart").html(displayDate(redoseDT));
displayValue("#amg-customLevel", levelAtCustom, 0.1, " mcg/mL");
if ( truePeak > 0 ) {
tape.amg.push([
["Goal peak", displayValue("", params.goalPeak, 0.1, " mcg/mL")],
["Goal trough", displayValue("", goalTrough, 1, " mcg/mL")],
["Post-antibiotic effect", displayValue("", pae, 1, " hours", "", false, true)],
],
[
["Calculated ke", displayValue("", ke, 0.001, "/hr")],
["Estimated Vd", displayValue("", vd, 0.01, " L")],
["Calculated (true) Peak", displayValue("", truePeak, 0.01, " mcg/mL")],
["Time to goal trough (MIC)", displayDate(troughDT)],
["Redose point", displayDate(redoseDT)],
["Level at redose point", displayValue("", redoseLevel, 0.01, " mcg/mL", "", false, true)],
["Time to redose point", displayValue("", newDoseToRedoseTime, 0.01, " hrs", "", true)],
],
[
["Recommended dose", displayValue("", recDose, 0.1, " mg")],
["Recommended frequency", displayValue("", recFreq, 1, "h", "q")],
]);
if ( levelAtCustom > 0 ) {
tape.amg.push([
["Point level estimate at", displayDate(params.customTime)],
["Estimated level", displayValue("", levelAtCustom, 0.01, " mcg/mL", false, true)],
]);
}
}
}
if ( tape.amg.length > 0 ) {
$("#tapeAmg--calc").html(LOG.outputTape(tape.amg, "Aminoglycoside Extended Interval Dosing"));
} else {
$("#tapeAmg--calc").html("");
}
LOG.groupEnd();
},
/**
* Input and output for initial protocol dosing and initial PK dosing
* @requires module:util
* @requires module:vanco
* @returns {undefined}
*/
vancoInitial() {
LOG.cyanGroupCollapsed('CALCULATE: Vanco Initial')
$("#vancoInitialLoad").html(vanco.loadingDose({
ht: pt.ht,
wt: pt.wt,
age: pt.age,
sex: pt.sex,
bmi: pt.bmi,
hd: pt.hd,
vancoIndication: pt.vancoIndication,
}));
const { doseMin, doseMax, freqMin, freqMax } = vanco.config.check;
const { maintText, freq } = vanco.getMaintenanceDose({
age: pt.age,
wt: pt.wt,
ibw: pt.ibw,
scr: pt.scr,
hd: pt.hd,
indication: pt.vancoIndication,
crcl: pt.crcl,
aki: $('#vancoAki').prop('checked'),
outlier: $('#vancoOutlier').prop('checked'),
});
$("#vancoInitialMaintenance").html(maintText);
let maintTextTooltip = "";
if ( maintText.length > 0 ) {
if ( /^Must order/.test(maintText) ) {
maintTextTooltip = maintText;
} else {
maintTextTooltip = `<b>Calculated weight-based dose is</b> <br>${maintText}<br><b>but InsightRx should be used if not in AKI or ESRD/on dialysis.</b>`;
}
}
$("#tooltip--vanco-md-bayesian").attr("data-original-title", maintTextTooltip);
$('#vancoInitialMaintenance-PrintOnly').html(maintTextTooltip);
if ( maintText.length > 0 && pt.hd === 0 && !$('#vancoAki').prop('checked') ) {
$("#row--vanco-md-default").css("display", "none");
$("#row--vanco-md-bayesian").css("display", "flex");
$('#row--vanco-md-bayesian-print').addClass('d-none d-print-flex');
} else {
$("#row--vanco-md-default").css("display", "flex");
$("#row--vanco-md-bayesian").css("display", "none");
$('#row--vanco-md-bayesian-print').removeClass('d-print-flex');
$('#row--vanco-md-bayesian-print').addClass('d-none');
}
const { monitoring, targetLevelText, targetMin, targetMax, goalTroughIndex, method } = vanco.getMonitoringRecommendation({
freq: freq,
hd: pt.hd,
crcl: pt.crcl,
scr: pt.scr,
bmi: pt.bmi,
indication: pt.vancoIndication,
age: pt.age,
aki: $('#vancoAki').prop('checked'),
outlier: $('#vancoOutlier').prop('checked'),
});
$("#vancoInitialMonitoring").html(monitoring);
$("#vancoInitialTargetLevel").html(targetLevelText);
$("#vancoInitialMethod").html(method);
// Set goal trough selection on the single level tab
if ( goalTroughIndex >= 0 ) {
$("#revision-goalTrough")[0].selectedIndex = goalTroughIndex;
}
// Initial PK Dosing
const selectedDose = checkValue(+$("#vancoInitialPK-dose").val(), doseMin, doseMax);
const selectedFrequency = checkValue(+$("#vancoInitialPK-interval").val(), freqMin, freqMax);
const { arrDose, arrViable, arrLevel, pkLevel, pkRecLevel, pkRecDose, pkFreq, pkRecFreq, pkHalflife } = vanco.getInitialDosing({
crcl: pt.crcl,
age: pt.age,
scr: pt.scr,
sex: pt.sex,
wt: pt.wt,
bmi: pt.bmi,
goalMin: targetMin,
goalMax: targetMax,
selDose: selectedDose,
selFreq: selectedFrequency,
});
displayValue("#vancoInitialPK-halflife", pkHalflife, 0.1, " hrs");
displayValue("#vancoInitialPK-recDose", pkRecDose, 1, " mg");
displayValue("#vancoInitialPK-recFreq", pkRecFreq, 0.1, " hrs");
displayValue("#vancoInitialPK-recLevel", pkRecLevel, 0.1, ' mcg/mL');
displayValue("#vancoInitialPK-level", pkLevel, 0.1, ' mcg/mL');
const tableHtml = this.createVancoTable({
rows: [{ title: 'Dose', data: arrDose, roundTo: 1, units: " mg" },
{ title: 'Frequency', data: pkFreq, units: " hrs" },
{ title: 'Est. Trough (mcg/mL)', data: arrLevel, roundTo: 0.1 }],
highlightColumns: arrViable,
});
$("#vancoInitialPK-table").html(tableHtml);
const tableText = [['Est. Trough', []]];
for ( let i = 0; i < arrDose.length; i++ ) {
tableText[0][1].push(
[
`${arrDose[i]} mg q${pkFreq}h`,
arial.padArray(["9999.99", displayValue("", arrLevel[i], 0.1, ' mcg/mL')], 0)[1],
]
);
}
LOG.groupEnd();
},
/**
* Input and output for linear and kinetic single-level dose adjustments
* @requires module:vanco
* @requires module:util
* @returns {undefined}
*/
vancoRevision() {
LOG.cyanGroupCollapsed('CALCULATE: Vanco Revision')
const cboGoal = $("#revision-goalTrough")[0];
const { goalmin, goalmax, goaltrough } = cboGoal.options[cboGoal.selectedIndex].dataset;
const { doseMin, doseMax, freqMin, freqMax } = vanco.config.check;
const selectedLinearDose = checkValue(+$("#revision-linearTestDose").val(), doseMin, doseMax);
const selectedLinearInterval = checkValue(+$("#revision-linearTestFreq").val(), freqMin, freqMax);
const selectedDose = checkValue(+$("#revision-pkTestDose").val(), doseMin, doseMax);
const selectedInterval = checkValue(+$("#revision-pkTestFreq").val(), freqMin, freqMax);
const troughTime = checkValue($("#revision-curTroughTime").val(), 0, freqMax, true);
const { linearDose, linearFreq, linearTrough, testLinearDose, testLinearFreq, testLinearTrough } = vanco.getLinearAdjustment({
curDose: pt.curDose,
curFreq: pt.curFreq,
curTrough: pt.curTrough,
testDose: selectedLinearDose,
testFreq: selectedLinearInterval,
goalTrough: goaltrough,
});
$("#revision-linearDose").html( linearDose === 0 ? "" : `${roundTo(linearDose, 1)} mg q ${linearFreq} h`);
displayChange("#revision-linearDoseChange", linearDose, linearFreq);
displayValue("#revision-linearTrough", linearTrough, 0.1, " mcg/mL");
displayChange("#revision-testLinearDoseChange", testLinearDose, testLinearFreq);
displayValue("#revision-testLinearTrough", testLinearTrough, 0.1, " mcg/mL");
const { halflife, newDose, newFreq, newTrough, newViable, recDose, recTrough, recFreq, selTrough, selDose, selFreq } = vanco.getSingleLevelAdjustment({
bmi: pt.bmi,
wt: pt.wt,
curDose: pt.curDose,
curFreq: pt.curFreq,
curTrough: pt.curTrough,
troughTime: troughTime,
goalTrough: goaltrough,
goalMin: goalmin,
goalMax: goalmax,
goalPeak: 35,
selFreq: selectedInterval,
selDose: selectedDose,
});
pt.adjHalflife = halflife;
displayValue("#revision-halflife", halflife, 0.1, " hours");
$("#revision-pkDose").html( recDose === 0 ? "" : `${roundTo(recDose, 1)} mg q ${roundTo(recFreq, 0.1)} h`);
displayChange("#revision-pkDoseChange", recDose, recFreq);
displayValue("#revision-pkTrough", recTrough, 0.1, " mcg/mL");
displayChange("#revision-pkTestDoseChange", selDose, selFreq);
displayValue("#revision-pkTestTrough", selTrough, 0.1, " mcg/mL");
this.vancoSteadyStateCheck();
const tableHtml = this.createVancoTable({
rows: [{ title: "Maint. dose", data: newDose, roundTo: 1, units: " mg" },
{ title: "Interval", data: newFreq, units: " hrs" },
{ title: "Est. Trough (mcg/mL)", data: newTrough, roundTo: 0.1 }],
highlightColumns: newViable,
});
$("#revision-pkTable").html(tableHtml);
const levelTiming = $('#revision-levelTiming')[0].selectedIndex;
const revisionGoal = $('#revision-goalTrough')[0].selectedIndex;
$("#vancoHdAdj").html(vanco.hdRevision({ wt: pt.wt, trough: pt.curTrough, timing: levelTiming, goal: revisionGoal, hd: pt.hd }));
LOG.groupEnd();
},
/**
* Input and output for Steady State Check on single-level tab
* @requires module:vanco
* @returns {undefined}
*/
vancoSteadyStateCheck() {
LOG.blueGroupCollapsed('CALCULATE: Vanco Steady State')
const firstDT = getDateTime($("#steadystate-dateFirst").val(), $("#steadystate-timeFirst").val());
LOG.log(`First ${firstDT}`);
const troughDT = getDateTime($("#steadystate-dateTrough").val(), $("#steadystate-timeTrough").val());
LOG.log(`Trough ${troughDT}`);
if ( firstDT !== 0 && troughDT !== 0 && pt.adjHalflife > 0 ) {
const timeDiff = getHoursBetweenDates(firstDT, troughDT);
const halflives = roundTo(timeDiff / pt.adjHalflife, 0.1);
$("#steadystate-timeDiff").html(`${roundTo(timeDiff, 0.1)} hrs (${halflives} ${halflives === 1 ? "half-life" : "half-lives"})`);
$("#steadystate-atSS").html(`${halflives < 4 ? "Not at" : "At"} steady state.`);
} else {
LOG.redText('insufficient or invalid input')
$("#steadystate-atSS").html("");
$("#steadystate-timeDiff").html("");
}
LOG.groupEnd();
},
/**
* For peak level timing, calculate and output infusion time for the selected dose
* @requires module:vanco
*
* @since v1.1.1
*/
peakTimingDuration() {
LOG.cyanGroupCollapsed('CALCULATE: Vanco Peak Timing Duration')
const dose = checkValue(+$("#peakTiming-dose").val(), vanco.config.check.doseMin, vanco.config.check.doseMax);
const infTime = dose > 0 ? vanco.getInfusionTime(dose) * 60 : "";
LOG.log(`Dose: ${dose}; Infusion time: ${infTime}`);
$("#peakTiming-infTime").val(infTime);
this.peakTiming();
LOG.groupEnd();
},
/**
* For peak level timing, calculate and output time to draw peak
* @requires module:vanco
* @requires module:util
*
* @since v1.1.1
*/
peakTiming() {
LOG.cyanGroupCollapsed('CALCULATE: Vanco Peak Timing')
const infTime = checkValue(+$("#peakTiming-infTime").val(), vanco.config.check.infTimeMin, vanco.config.check.infTimeMax);
const startTime = getDateTime("1900-01-01", $("#peakTiming-startTime").val());
LOG.log({infTime, startTime});
if ( startTime instanceof Date ) {
startTime.setMinutes(startTime.getMinutes() + 60 + infTime );
const time1 = displayTime(startTime);
LOG.cyanText(`TIME 1: ${startTime}`);
LOG.blueText(`TIME 1: ${time1}`);
startTime.setMinutes(startTime.getMinutes() + 60 );
const time2 = displayTime(startTime);
LOG.cyanText(`TIME 2: ${startTime}`);
LOG.blueText(`TIME 2: ${time2}`);
if ( infTime > 0 && startTime instanceof Date) {
$("#peakTiming-peak").html(`Draw peak between ${time1} and ${time2}.`);
} else {
$("#peakTiming-peak").html("");
LOG.redText('infTime not valid')
}
} else {
LOG.redText('startTime is not an instanceof Date');
}
LOG.groupEnd();
},
/**
* Input and output for AUC date/time calculation modal
* @requires module:vanco
* @returns {undefined}
*/
vancoAUCDates() {
LOG.cyanGroupCollapsed('CALCULATE: Vanco AUC Dates')
const sameInterval = $("#aucDates-sameInterval").is(":checked");
const dose1 = getDateTime($("#aucDates-doseDate-1").val(), $("#aucDates-doseTime-1").val());
const dose2 = sameInterval ? dose1 : getDateTime($("#aucDates-doseDate-2").val(), $("#aucDates-doseTime-2").val());
const trough = getDateTime($("#aucDates-troughDate").val(), $("#aucDates-troughTime").val());
const peak = getDateTime($("#aucDates-peakDate").val(), $("#aucDates-peakTime").val());
const troughHrs = roundTo(checkValue(getHoursBetweenDates(dose1, trough), 0, 48), 0.1);
const peakHrs = roundTo(checkValue(getHoursBetweenDates(dose2, peak), 0, 48), 0.1);
displayValue("#aucDates-troughResult", troughHrs, 0.1);
displayValue("#aucDates-peakResult", peakHrs, 0.1);
LOG.groupEnd();
},
/**
* Input and output for AUC calculation
* @requires module:vanco
* @param {Boolean} [resetInterval=true] whether recommended interval should override user input
* @returns {undefined}
*/
vancoAUC(resetInterval = true) {
LOG.cyanGroupCollapsed('CALCULATE: Vanco AUC');
if ( !resetInterval) {
LOG.log('Interval not reset, user input preserved')
}
const params = {
dose: pt.curDose,
interval: pt.curFreq,
peak: checkValue(+$("#auc-curPeak").val(), vanco.config.check.levelMin, vanco.config.check.levelMax),
peakTime: checkValue(+$("#vancoAUCPeakTime").val(), vanco.config.check.timeMin, vanco.config.check.timeMax),
troughTime: checkValue(+$("#vancoAUCTroughTime").val(), vanco.config.check.timeMin, vanco.config.check.timeMax),
trough: pt.curTrough,
};
const aucCurrent = vanco.calculateAUC(params);
const auc24 = aucCurrent === undefined ? 0 : aucCurrent.auc24;
// const oldInterval = aucCurrent === undefined ? 0 : aucCurrent.oldInterval;
const goalTroughLow = aucCurrent === undefined ? 0 : aucCurrent.goalTroughLow;
const goalTroughHigh = aucCurrent === undefined ? 0 : aucCurrent.goalTroughHigh;
displayValue("#vancoAUC24", auc24 || 0, 0.1);
const goalTrough = aucCurrent === undefined ? "" : `${roundTo(goalTroughLow, 0.1)} – ${roundTo(goalTroughHigh, 0.1)} mcg/mL`;
$("#vancoAUCTroughGoal").html(goalTrough);
if ( $("#vancoAUC24").html() !== "" ) {
$("#vancoAUC24").append(` (${aucCurrent.therapeutic})`);
}
if ( resetInterval && $("#vancoCurrentInterval") !== "" && aucCurrent !== undefined ) {
$("#vancoAUCNewInterval").val(aucCurrent.oldInterval);
}
// done with first step here, have outputted new interval, auc24
const newInterval = checkValue(+$("#vancoAUCNewInterval").val(), vanco.config.check.freqMin, vanco.config.check.freqMax);
const aucNew = vanco.calculateAUCNew(aucCurrent, newInterval);
// create AUC table
const tableHtml = this.createVancoTable({
rows: [
{ title: "Maint. dose", data: aucNew.dose, units: " mg" },
{ title: "Predicted AUC", data: aucNew.auc, roundTo: 0.1 },
{ title: "Est. Trough (mcg/mL)", data: aucNew.trough, roundTo: 0.1 },
],
highlightColumns: aucNew.therapeutic,
});
$("#aucTable").html(tableHtml);
const tableText = [["Predicted AUC (est. trough) for New Dose", []]];
for ( let i = 0; i < aucNew.dose.length; i++ ) {
tableText[0][1].push(
[
`${aucNew.dose[i]} mg q${newInterval}h`,
`${arial.padArray(["9999.99", roundTo(aucNew.auc[i], 0.1)], 0)[1]} (${roundTo(aucNew.trough[i], 0.1)} mcg/mL)`,
]
);
}
if ( aucCurrent !== undefined ) {
// was the date-time calculator modal used?
const usedDateTimeCalculator = $("#aucDates-apply").hasClass("datesApplied");
tape.auc = [
[
["Current dose", `${params.dose} mg q${params.interval}h`],
["Peak level", displayValue("", params.peak, 0.1, " mcg/mL")],
["Trough level", displayValue("", params.trough, 0.1, " mcg/mL")],
["Time to peak", displayValue("", params.peakTime, 0.01, " hrs")],
["Time to trough", displayValue("", params.troughTime, 0.01, " hrs")],
],
[
["Vd", displayValue("", aucCurrent.vd, 0.01, " L")],
["Infusion time", displayValue("", 60 * aucCurrent.tInf, 1, " min")],
["ke", displayValue("", aucCurrent.ke, 0.0001, ` hr^-1`)],
["Halflife", displayValue("", aucCurrent.halflife, 0.1, " hr")],
["True peak", displayValue("", aucCurrent.truePeak, 0.1, " mcg/mL")],
["True trough", displayValue("", aucCurrent.trueTrough, 0.1, " mcg/mL")],
["AUC (inf)", displayValue("", aucCurrent.aucInf, 0.1)],
["AUC (elim)", displayValue("", aucCurrent.aucElim, 0.1)],
],
[
["AUC24", `${displayValue("", aucCurrent.auc24, 0.1)} (${aucCurrent.therapeutic})`],
[
`Trough goal`,
`${roundTo(aucCurrent.goalTroughLow, 0.1)} – ${roundTo(aucCurrent.goalTroughHigh, 0.1)} mcg/mL`,
],
],
];
if ( usedDateTimeCalculator ) {
const sameInterval = $("#aucDates-sameInterval").is(":checked");
const addToTape = [];
addToTape.push([
"Drawn in same interval?",
sameInterval ? "Yes" : "No",
]);
addToTape.push([
sameInterval ? "Dose before levels" : "Dose before trough",
displayDate( getDateTime( $("#aucDates-doseDate-1").val(), $("#aucDates-doseTime-1").val() ) ),
]);
addToTape.push([
"Trough time",
displayDate( getDateTime($("#aucDates-troughDate").val(), $("#aucDates-troughTime").val() ) ),
]);
if ( !sameInterval ) {
addToTape.push([
"Dose before peak",
displayDate( getDateTime($("#aucDates-doseDate-2").val(), $("#aucDates-doseTime-2").val() ) ),
]);
}
addToTape.push([
"Peak time",
displayDate( getDateTime($("#aucDates-peakDate").val(), $("#aucDates-peakTime").val() ) ),
]);
tape.auc.unshift(addToTape);
}
$("#tape--auc").html(LOG.outputTape(tape.auc, "AUC Dosing Calculation"));
$("#tape--auc").append(LOG.outputTape(tableText));
} else {
tape.auc = [];
$("#tape--auc").html("");
}
LOG.groupEnd();
},
/**
* Input and output for Two-Level kinetic calculation
* @requires module:util
* @requires module:vanco
* @param {Boolean} [resetInterval=true] whether recommended interval should override user input
* @returns {undefined}
*/
vancoTwolevel(resetInterval = true) {
LOG.cyanGroupCollapsed('CALCULATE: Vanco Two Level');
if ( !resetInterval) {
LOG.log('Interval not reset, user input preserved')
}
const {
levelMin,
levelMax,
// doseMin,
// doseMax,
freqMin,
freqMax,
} = vanco.config.check;
const time1 = getDateTime($("#twolevelDate1").val(), $("#twolevelTime1").val());
const time2 = getDateTime($("#twolevelDate2").val(), $("#twolevelTime2").val());
const level1 = checkValue(+$("#twolevelLevel1").val(), levelMin, levelMax);
const level2 = checkValue(+$("#twolevelLevel2").val(), levelMin, levelMax);
let ke = -1;
if ( time1 !== 0 && time2 !== 0 && level1 > 0 && level2 > 0 ) {
const timeDiff = getHoursBetweenDates(time1, time2);
ke = Math.log(level1 / level2) / timeDiff;
}
const selectedInterval = resetInterval ? 0 : checkValue(+$("#twolevelInterval").val(), freqMin, freqMax);
const {
pkDose,
pkFreq,
pkTrough,
halflife,
// newPeak,
newTrough,
newViable,
newDose,
} = vanco.calculateTwoLevelPK({
wt: pt.wt,
bmi: pt.bmi,
ke: ke,
selectedInterval: selectedInterval,
});
displayValue("#twolevelHalflife", halflife, 0.1, " hours");
displayValue("#twolevelNewDose", pkDose, 1, " mg");
if ( resetInterval ) {
$("#twolevelInterval").val(pkFreq === 0 ? "" : pkFreq);
}
displayValue("#twolevelNewTrough", pkTrough, 0.1, " mcg/mL");
const tableHtml = this.createVancoTable({
rows: [{ title: "Maint. dose", data: newDose, units: " mg" },
{ title: "Interval", data: pkFreq, units: " hrs" },
{ title: "Est. Trough (mcg/mL)", data: newTrough, roundTo: 0.1 }],
highlightColumns: newViable,
});
$("#twolevelTable").html(tableHtml);
LOG.groupEnd();
},
/**
* Generate HTML for a table of dosing options. Data can be provided as an
* array to display different values in each column, or as a number to repeat
* the same number in each column.
* @requires module:util
* @requires module:vanco
* @param {Object} obj Input parameters
* @param {Object[]} obj.rows Table row parameters
* @param {String} [obj.rows.title] Row header
* @param {String} [obj.rows.units] Units for values
* @param {Number} [obj.rows.roundTo] Rounding factor if values are to be rounded
* @param {Number|Number[]} [obj.rows.data] Data for cells
* @param {Boolean[]} [obj.highlightColumns] Whether column is to be highlighted
* @returns {String} HTML tbody tag with class 'isTherapeutic' on highlighted columns
*/
createVancoTable({ rows, highlightColumns } = {}) {
LOG.beginFunction('CALCULATE: createVancoTable', arguments);
let rowHtml = "";
if ( rows[0].data.length === 0 ) {
LOG.exitFunction();
return "";
}
for ( const row of rows ) {
rowHtml += `<tr><th scope="row">${row.title}</th>`;
if ( row.units === undefined ) { row.units = ""; }
if ( row.roundTo === undefined ) { row.roundTo = -1; }
for ( let i = 0; i < vanco.config.doses.length; i++ ) {
let value = "";
if ( Array.isArray(row.data) ) {
if ( row.data.length > 0 ) { value = row.data[i]; }
} else {
value = row.data;
}
let rowClass = "";
if ( Array.isArray(highlightColumns) && highlightColumns.length > 0 ) {
if ( highlightColumns[i] ) {
rowClass = "isTherapeutic";
}
}
rowHtml += `<td class="${rowClass}">${displayValue("", value, row.roundTo, row.units)}</td>`;
}
rowHtml += `</tr>`;
}
LOG.groupEnd();
return `<tbody>${rowHtml}</tbody>`;
},
/**
* Input and output for Second Dose tab
* @requires module:util
* @requires module:seconddose
* @returns {undefined}
*/
secondDose() {
LOG.cyanGroupCollapsed('CALCULATE: Vanco Second Dose')
const fd = checkTimeInput($("#seconddose-time1").val());
let freqId = $("[name='seconddose-freq']:checked")[0].id;
freqId = freqId.replace("seconddose-", "");
const sd = getSecondDose({ fd: fd, freqId: freqId });
if ( sd === undefined ) {
$(".output[id^='seconddose']").html("");
$("#seconddose-row-1").removeClass('hidden');
} else {
sd.forEach( (me, i) => {
$(`#seconddose-text-${i}`).html(`${me.hours} hours (${me.diff} hours ${me.direction})`);
me.times.forEach( (time, j) => {
$(`#seconddose-${i}-${j}`).html(time);
});
});
if ( sd.length === 1 ) {
$("#seconddose-row-1").addClass('hidden');
} else {
$("#seconddose-row-1").removeClass('hidden');
}
}
LOG.groupEnd();
},
/**
* Input and output for IVIG calculation
* @requires module:util
* @requires module:ivig
* @returns {undefined}
*/
ivig() {
LOG.cyanGroupCollapsed('CALCULATE: IVIG')
const dose = checkValue(+$("#ivig-dose").val());
const selected = $("#ivig-product")[0].selectedIndex;
$("#ivig-text").html(ivig.getText(selected, pt.wt, dose));
LOG.groupEnd();
},
};
/**
* Provides a color to highlight the percent change of total daily vancomycin dose.
* Color stops are fixed at : red at 35%, yellow at 30%, and green at 20%
* Values in between color stops are scaled using R, G, and B values
* @param {Number} x percent change (from -100 to 100)
* @returns {String} color as rgb(__, __, __)
*/
function vancoColorScale(x) {
let arr = [];
x = Math.abs(x);
if (x >= 35) {
arr = [248, 105, 107];
} else if (x < 20) {
arr = [100, 221, 67];
} else if (x <= 30) {
arr = [
Math.floor(100 + 155 * ((x - 20) / 10)),
Math.floor(221 + 14 * ((x - 20) / 10)),
Math.floor(67 + 65 * ((x - 20) / 10)),
];
} else {
arr = [
Math.ceil(255 - 7 * ((x - 30) / 5)),
Math.ceil(235 - 130 * ((x - 30) / 5)),
Math.ceil(132 - 25 * ((x - 30) / 5)),
];
}
return `rgb(${arr[0]}, ${arr[1]}, ${arr[2]})`;
}
/**
* Calculate halflife from ke
* @param {Number} ke elimination rate constant
* @returns {Number} halflife in hours
*/
// function getHalflife(ke) {
// return Math.log(2) / ke;
// }
/**
* From a proposed dose and frequency, calculate and display
* the percent change from the patient's current total daily dose.
* @requires module:util
* @param {(String|HTMLElement)} el jQuery selector for element that will display result
* @param {(Number|String)} d the dose in mg
* @param {(Number|String)} f the frequency (every __ hours)
* @returns {HTMLElement} The original DOM element, for chaining
*/
function displayChange(el, d = 0, f = 0) {
const newDose = checkValue(d);
const newFreq = checkValue(f);
const oldTdd = 24 / pt.curFreq * pt.curDose;
let arrow = "";
if ( newDose > 0 && newFreq > 0 && pt.curDose > 0 && pt.curFreq > 0 ) {
const newTdd = 24 / newFreq * newDose;
const change = (newTdd - oldTdd) / oldTdd;
if (change === 0) {
arrow = " ";
} else if (change < 0) {
arrow = "↓ ";
} else {
arrow = "↑ ";
}
$(el).html(`${arrow + roundTo(Math.abs(change * 100), 0.1) }%`);
$(el).css("background-color", vancoColorScale(Math.abs(change * 100)));
} else {
$(el).html("");
$(el).css("background-color", "#f2f7fa");
}
return el;
}