var tiny = tiny || { }; /** * @author: Donnal Walter * @module * DateTime: 2021-04-21 08:54 * Description: The logic module has been dubbed "The Algorithm". * The logic.recalculate() method runs each time a parameter is changed. * */ tiny.logic = (function () { "use strict"; var watchers, // object to hold pseudo-parameters products, // dictionary for enteral products adds, // dictionary for additives to TPN fluids, // dictionary for stock IV fluids // constants for plan calculations DNPSTART = 20, // DNP starting time in hours (20 is 20:00) KGIR = 10 / 60, // 1 g/dL = 10 mg/ml; 1 ml/hr = 1/60 ml/min KDXW = 3.4, // 3.4 kcal/g (dextroxe monohydrate) KPROT = 4, // 4 kcal/g (amino acids AND dietary protein) HINA = 10.0, HIK = 8.0; var DNP = function () { /** Begin order definitions ********************************************* */ function Fluid() { var d = {}; // Fluid Order is a data object d.oID = null; // order number (unique identifier) d.startDay = '0'; // === '0' for today, === '1' for tomorrow d.startHour = 20; // hour of the day (20:00 is default start) d.startMin = 0; // minutes after the hour (usually 00 or 30) d.stopDay = -1; // === -1 for NO stop date/time d.stopHour = 0; // hour of the day (00 is Midnight) d.stopMin = 0; // minutes after the hour (most often 00 or 30) d.stock = null; // Stock ID; if null, fluid is compounded d.vol = 0; // the volume (parameter or calculated) return d; // this a factory function } function Infusion() { // Infusion adds the notion of rate var d = Fluid(); // Infusion inherits from Fluid d.rate = 0; // the rate of infusion (ml/hr) // d.time = -(-24); // over a time period, often 24 hours d.time = 0; // time 0 at beginning return d; } function Schedule() { var d = Infusion(); // a Schedule may be an Infusion d.interval = 0; // or may be intermittent (at interval) d.on = 0; // hr on for intermittent d.onMin = 0; // minutes on for intermittent d.onLimit = 30; // limit time for po feed ????? d.off = 0; // hr off d.number = 0; // number of intervals return d; } function Lipid() { var d = Infusion(); // Lipid inherits from Infusion d.stock = 'ILipid20'; // 20% Intralipid is default d.gFat = 0; // amount of fat (g/kg) d.add = true; // include in total volume flag is true return d; // (though rate is calculated value) } function Stock() { // Stock fluids are NOT compounded var d = Infusion(); // IVFluid inherits from Infusion d.stock = 'D10W'; // default is D10W d.dxw = 10; // percent dextrose (g/dl) (concentration) return d; } function Scratch() { // Scratch IV fluids are compounded var d = Infusion(); // IVFluid inherits from Infusion d.dxw = 0; // percent dextrose (g/dl) (concentration) d.NaCl = 0; // amount of sodium chloride (mEq/kg) d.KCl = 0; // amount of this salt (mEq/kg) d.NaAc = 0; d.KAc = 0; d.AcToCl = 0; // acetate to chloride ratio d.ArgCl = 0; d.KPO4 = 0; d.NaPO4 = 0; d.MgSO4 = 0; d.CaGlu = 0; d.hepYN = false; return d; } function Hyperal() { // Custom parenteral nutrition is compounded var d = Scratch(); // Hyperal inherits from Scratch IV Fluid d.route = 'piv'; // default route is PIV d.gProt = 0; // amount of protein (g/kg) d.cycleHide = true; // hide cycling parameters d.cycleDn = false; // TPN cycled? false by default d.cycleUp = false; // if cycled, include UP? d.cycleXt = false; // if cycled, extend DOWN? d.cycleT = 0; // primary HAF infusion time; T - T0 - T2 - T3 d.cycleTUp = 0; // cycle up time; 0 || 1 d.cycleTDn = 0; // cycle down time; 0 || 1 d.cycleTXt = 0; // extended cycle down; 0 || 1 d.cycleLow = 0; // sum of time at lower rate d.cycleFx = 0; // fraction of volume in cycle time d.cycleRUp = 0; // rate going up; 0 || d.rate /2 d.cycleRDn = 0; // rate going down; 0 || d.rate / 2 d.cycleRXt = 0; // rate extended dn; 0 || d.rate / 4 d.albumin = 0; d.aminophylline = 0; d.ranitidine = 0; d.famotidine = 0; d.octreotide = 0; d.cysteine = 0; d.stdCysteine = true; // true standard, false custom d.carnitine = 0; d.stdCarnitine = true; // true standard, false custom d.Zn = 0; // amount of Zinc (mcg/kg) d.stdZn = true; // true standard, false custom d.Cu = 0; // part of the trace element soln d.Se = 0; // part of the trace element soln d.trace = 0; // trace element volume d.stdTrace = true; // true standard, false custom d.Mn = 0; // always custom d.Cr = 0; // always custom d.mvi = 0; // volume of multivitamin (ml) d.mviID = 'Pediatric'; // multivitamin IDentifier d.stdMVI = true; // true standard, false custom return d; } function Product() { var d = {}; d.id = ''; // id in products dictionary d.value = 0; // target kcal/oz, for example d.units = ''; // if other than kcal/oz d.gCarb = 0; d.gProt = 0; d.gFat = 0; d.mgCa = 0; d.Na = 0; d.K = 0; d.mgFe = 0; return d; } function Formulation() { var d = {}; // Formulation is a data object d.base = Product(); d.add1 = Product(); d.add2 = Product(); d.add3 = Product(); // may not need 3rd fortifier d.mod1 = Product(); d.mod2 = Product(); d.mod3 = Product(); // may not need 3rd modular d.ratio = ''; // for display only return d; } function Feeding() { // Feeding regimen var d = Schedule(); // from Schedule continuous or interval d.route = ''; // route (PO, OG, NG, TPT, GT, GJ-J, GJ-G) d.method = ''; // method of administration d.pump = ''; // is mode of administration on pump? d.tryPO = false; // allow bottle attempt with tube feeds d.tries = 0; // number of PO attempts d.clockHour = 20; // start time by clock hour (integer 0-23) d.init = 0; // start time hours after DNP start // calculated d.xVol = 0; // scaled volume // calculated pseudo-parameter d.iVol = 0; // interval volume (ml per feeding) d.form1 = Formulation(); // formulation: WHAT is to be fed d.form2 = Formulation(); // formulation to be mixed (or alternative) d.ratio = 1; // 1 = no form2; 0 = form2 option; else mix d.nursing = ''; // nursing instructions d.admin = ''; // administration instructions d.titrate = false; // titrate feedings true/false d.incr = 0; // titration increment (ml) d.every = 2; // increase every N feeds d.target = 0; // target feeding volume (ml) d.extra = 0; // extra bottles return d; } function Supplement(f) { var d = Fluid(); d.daily = 0; // daily amount (mEq/kg, mg, ml, etc.) d.freq = f || 1; // divided into how many doses d.dose = 0; d.std = false; return d; } /** Begin group plans ****************************************** */ function PNplan() { // plan for the parenteral fluid orders return { // wt: 0, // default dosing weight for parenteral haf: Hyperal(), // hyperalimentation fluid iv2: Scratch(), // secondary IV order (starter or cyclic) iv3: Stock(), // Y-in fluid to adjust GIR, Na, or xVol ilf: Lipid() // lipid emulsion associated with PN }; } function ENplan() { // plan for up to three enteral orders var d = {}; d.feed1 = Feeding(); // primary feeding order d.feed2 = Feeding(); // continuous at night, for example d.feed3 = Feeding(); // PO feeds, for example return d; } function SPplan() { // plan for enteral supplements var d = {}; d.mvi = Supplement(); // pediatric multivitamin d.mviFe = Supplement(); // multivitamin with iron d.Fe = Supplement(); // ferrous sulfate d.vitD = Supplement(); // cholecalciferol Vit D3 d.ADEK = Supplement(); // ADEKS Pediatric d.CaPO4 = Supplement(3); // Calcium Phosphate Tribasic d.NaCl = Supplement(); // Sodium Chloride d.KCl = Supplement(); // Potassium Chloride d.ArgHCl = Supplement(); // Arginine HCl d.bicitra = Supplement(); // Bicitra d.glycerin = Supplement(); // Glycerin (rectal) return d; } function IVplan() { // plan for outside IV orders var d = {}; d.art = Infusion(); // arterial line infusion order d.art.stock = "NS45"; d.drip1 = Infusion(); // sedation or pressor drips d.drip1.stock = 'D5W'; d.drip1.name = 'Drip 1'; d.drip2 = Infusion(); d.drip2.stock = 'D5W'; d.drip2.name = 'Drip 2'; d.drip3 = Infusion(); d.drip3.stock = 'D5W'; d.drip3.name = ''; d.drip4 = Infusion(); d.drip4.stock = 'D5W'; d.drip4.name = ''; d.meds = Fluid(); // estimated volume for med push d.flush = Fluid(); // estimated flush fluids d.flush.stock = 'NS90'; d.blood = Fluid(); // estimated blood products d.add = true; // include IV plan in total return d; } return { wt: 0, // dosing weight for plan (kg) yr: 0, // patient age in years date: '', // date-time of PLAN pn: PNplan(), // parenteral nutrition plan en: ENplan(), // enteral nutrition plan sp: SPplan(), // enteral supplement plan iv: IVplan() // IV fluids plan }; }; /** Copied from TinyJS ******************************************** */ function put(d, id, value) { id = id.split('.'); while (id.length > 1) {d = d[id.shift()]; } d[id] = value; } /** JavaScript Math wrappers ******************************************** */ function round(value, u) { return Math.round(value / u) * u; // nearest unit u } function about(a, b) { return Math.abs(a - b) < 0.001; // a == b (~~) } function lte(value, hi) { return Math.min(value, hi); // make =< } function gte(value, lo) { return Math.max(value, lo); // make >= } function limit(v, lo, hi) { return Math.max(Math.min(v, hi), lo); // lo =< v =< hi } function safe(v, lo, hi, d) { v = limit(v, lo, hi); return isNaN(v) ? d : v; } /** Data object level data manipulations ******************************** */ function pull(d, s, m) { var i; for (i in s) { if (isFinite(s[i])) { d[i] = s[i] * m; } } } function add(d, s) { var i; for (i in d) { if (isFinite(s[i])) { d[i] = (d[i] || 0) + s[i]; } } } function Sum() { return {xVol: 0, vol: 0, gir: 0, rate: 0, kcal: 0, carb: 0, fat: 0, prot: 0, gCarb: 0, gFat: 0, gProt: 0, Na: 0, K: 0, Ca: 0, mgCa: 0, Mg: 0, CO3: 0, NaCl: 0, NaAc: 0, Cl: 0, PO4: 0, mgFe: 0, KCl: 0, ArgCl: 0, KAc: 0, MgSO4: 0, NaPO4: 0, KPO4: 0, CaGlu: 0, Zn: 0, Cu: 0, Se: 0, Mn: 0, Cr: 0, VitD: 0, rsl: 0 }; } /** Special functions to tweak rate (0.1 ml/hr units) ******************* */ function tweak(before, after, key) { if (about(before, after)) { // if minimal change if (key === "ArrowUp") { // look for tweak key after = after + 0.1; // if so, tweak rate up } else if (key === "ArrowDown") { after = after - 0.1; // or tweak rate down } } return after; } function tweak_vol(d, vol, key) { var rate = round(vol / d.time, 0.1); // calculate new rate d.rate = tweak(d.rate, rate, key); // set tweaked rate } /** Test functions for flags *************************************************************** */ function dxwTest(d) { // d is any fluid if (d.route === 'piv' && d.dxw > 12.5) { d.dxwFlag = "warning"; d.dxwText = "PIV limit 12.5"; d.dxw = 12.5; // this is hardstop 1 } else if (d.dxw > 30) { d.dxwFlag = "warning"; d.dxwText = "Limit Exceeded"; d.dxw = 30; // this is hardstop 1 } else if (d.dxw > 25) { d.dxwFlag = "alert"; d.dxwText = "High Dextrose"; } else { d.dxwFlag = ''; d.dxwText = ''; } } function girTest(d) { // d is any fluid if (d.gir > 20) { d.girFlag = 'warning'; d.girText = "GIR > 20 mg/kg/min"; } else if (d.gir > 14) { d.girFlag = 'warning'; d.girText = "GIR > 14 mg/kg/min"; } else if (d.gir > 12) { d.girFlag = 'alert'; d.girText = "GIR > 12 mg/kg/min"; } else if (d.gir < 3) { d.girFlag = 'alert'; d.girText = "GIR < 3 mg/kg/min"; } else { d.girFlag = ''; d.girText = ''; } } function fatTest(d) { // d is data.tn if (d.fat / d.kcal > 0.6) { d.fatFlag = "warning"; d.fatText = "Fat > 60% Total Calories"; } else if (d.gFat > 4) { d.fatFlag = "alert"; d.fatText = "Fat > 4 g/kg"; } else { d.fatFlag = ''; d.fatText = ''; } } function protTest(d) { // d is data.tn if (d.gProt > 4) { d.protFlag = 'alert'; d.protText = 'Protein intake > 4 g/kg'; } else { d.protFlag = ''; d.protText = ''; } } function CaTest(d) { // d is data.pn.haf (actually any scratch IV) var concAA = d.gProt / d.xVol, limit = concAA > 0.02 ? 54 : 44; // needs reference if (d.CaPlusP > limit) { d.CaPFlag = "hardstop"; // hardstop 2 d.CaPText = "Precipitate risk! (>" + limit + ")"; } else { d.CaPFlag = ''; d.CaPText = ''; } if (d.route === 'piv' && d.Ca > 0) { d.CaFlag = "hardstop"; // hardstop 2 d.CaText = "PIV with Ca"; } else { d.CaFlag = ''; d.CaText = ''; } } function IV2test(d) { // d is data.pn if (d.haf.cycle && d.iv2.rate === 0) { d.IV2Flag = "alert"; d.IV2Text = "Cycled TPN. Order off-time fluid."; } else { d.IV2Flag = ''; d.IV2Text = ''; } } /** Ancillary calculations ***************************************************************** */ function holliday(wt) { if (wt < 5) {return wt * 140; } if (wt < 10) {return 700 + (wt - 5) * 60; } if (wt < 20) {return 1000 + (wt - 10) * 50; } return 1500 + (wt - 20) * 20; } /** Fluid object replan/recalculate ******************************************************** */ function fluid(d, wt) { d.wt = wt || d.wt; // argument overrides property d.xVol = d.vol / d.wt; // volume scaled to weight d.xVol = isFinite(d.xVol) ? d.xVol : 0; // defensive (?necessary?) if (d.stock && fluids[d.stock]) { d.dxw = fluids[d.stock].glu * 100 || 0; // mg/ml to mg/dl (0 better than null) pull(d, fluids[d.stock], d.xVol); // concentrations to amounts } dxwTest(d); // flag excessive dextrose (parameter) d.gir = KGIR * d.dxw * d.rate / d.wt; // not based on xVol (in case < 24 hr) d.gir = isFinite(d.gir) ? d.gir : 0; // defensive (?necessary?) d.gir = d.time > 12 ? d.gir : 0; // for t < 12 hr *daily* GIR meaningless girTest(d); // flag excessive GIR (value) d.gCarb = d.dxw * d.xVol / 100; // not based on GIR (in case < 24 hr) d.carb = KDXW * d.gCarb; // 3.4 kcal/g (dextroxe monohydrate) } /** Infusion object replan/recalculate ***************************************************** */ function infusion(d, wt) { d.rate = round(gte(d.rate, 0), 0.1) || 0; // round to nearest 0.1 ml/hr non-neg d.time = safe(d.time, 0, 24, 24); // is this needed? d.vol = d.rate * d.time; fluid(d, wt); } function cycleTimes(d) { var upDnXt; d.cycleTUp = d.cycleUp ? 1 : 0; d.cycleTDn = d.cycleDn ? 1 : 0; d.cycleTXt = d.cycleXt ? 1 : 0; d.cycleFx = 0.5 * (d.cycleTUp + d.cycleTDn) + 0.75 * d.cycleTXt; d.cycleT = d.time - d.cycleTUp - d.cycleTDn - d.cycleTXt; } function cycleRates(d) { d.cycleRUp = d.cycleUp ? d.rate / 2 : 0; d.cycleRDn = d.cycleDn ? d.rate / 2 : 0; d.cycleRXt = d.cycleXt ? d.rate / 4 : 0; } function cycleInfusion(d, wt) { cycleTimes(d); cycleRates(d); d.vol = d.rate * (d.time - d.cycleFx); fluid(d, wt); } /** Lipid object replan/recalculate ******************************************************** */ function lipid(d, wt, time, key) { // d is data.pn.ilf, time is data.pn.haf.time var rate; d.time = lte(d.time, time); d.cFat = fluids[d.stock].fat; d.xVol = d.gFat / d.cFat; d.vol = d.xVol * wt; rate = round(d.vol / d.time, 0.1); // calculate new temp rate d.rate = tweak(d.rate, rate, key); // set rate after tweak check d.xVol = (d.rate * d.time / wt) || 0; // recalculate for adjusted rate d.gFat = d.xVol * d.cFat; d.fat = d.xVol * fluids[d.stock].kcal; // kcal/kg d.kcal = d.fat; // total Cal = fat Cal } /** Recalculate the electrolyte component of Scratch IV ********************************** */ function lytes(d) { d.NaCl = lte(d.NaCl, HINA * 2); // hard stop at double hiNa warning d.KCl = lte(d.KCl, HIK * 2); // hard stop at double hiK warning d.concAA = d.gProt / d.xVol; d.limit = d.concAA > 0.02 ? 54 : 44; // needs reference if (d.CaPGoal) { d.KPO4 = 0; d.NaPO4 = d.limit * d.xVol / 1000; d.NaPO4 = d.NaPO4 / (2 * d.CaPGoal + 2); d.CaGlu = 2 * d.NaPO4 * d.CaPGoal; d.CaPGoal = 0; } d.PO4 = d.NaPO4 + d.KPO4; d.Ca = d.CaGlu; d.mgCa = 20 * d.Ca; // atomic wt Ca = 40, val 2 d.CaToP = d.Ca / (2 * d.PO4); // molar ratio d.CaPlusP = d.CaGlu + 2 * d.PO4; d.CaPlusP = 1000 * d.CaPlusP / d.xVol; // sum in mEq/l CaTest(d); d.Na = d.NaCl + d.NaAc + 1.33 * d.NaPO4; // 3 mM P = 4.0 mEq Na d.K = d.KCl + d.KAc + 1.47 * d.KPO4; // 3 mM P = 4.4 mEq K d.Cl = d.NaCl + d.KCl; d.CO3 = d.KAc + d.NaAc; d.Ac2Cl = d.CO3 / d.Cl || 0; // Ac:Cl ratio without Arg d.Cl = d.Cl + d.ArgCl; d.AcToCl = d.CO3 / d.Cl || 0; // Ac:Cl ratio WITH Arg d.PO4 = d.NaPO4 + d.KPO4; } function osmolarity(d) { d.rsl = d.Na + d.K + d.Cl + d.PO4; // ?? is this useful, probably not d.osm = d.gir * 7200; // 7200 = 60 * 24 * 5 d.osm += d.gProt * 10000 || 0; // 10000 = 1000 * 10 d.osm += d.NaCl * 2000; d.osm += d.KCl * 2000; d.osm += d.NaAc * 2000; d.osm += d.KAc * 2000; d.osm += d.CaGlu * 1460; d.osm = d.osm / d.xVol; } /** Scratch IV object replan/recalculate *************************************************** */ function scratch(d, wt) { if (d.cycleDn) { cycleInfusion(d, wt); } else { infusion(d, wt); } lytes(d); osmolarity(d); d.heparin = d.hepYN ? d.xVol / 2 : 0; // heparin is 0.5 units / ml or none d.kcal = d.carb; // no calories except carbohydrate } /** Hyperalimentation Fluid object calculations ************************************** */ // The bag function calculates the pump volumes for the compounder function bag(d, wt) { var i, bag = {}, v1 = d.vol, v2 = v1 + 100, v3 = 0, fx1 = v2 / v1, fx2 = wt * fx1; bag.dxw = fx2 * d.gir * 1.440 / adds.dextrose.conc; bag.eaa = fx2 * d.gProt / adds.eaa.conc; bag.NaCl = fx2 * d.NaCl / adds.NaCl.conc; bag.NaAc = fx2 * d.NaAc / adds.NaAc.conc; bag.NaPO4 = fx2 * d.NaPO4 / adds.NaPO4.conc; bag.ArgCl = fx2 * d.ArgCl / adds.ArgCl.conc; bag.KCl = fx2 * d.KCl / adds.KCl.conc; bag.KAc = fx2 * d.KAc / adds.KAc.conc; bag.KPO4 = fx2 * d.KPO4 / adds.KPO4.conc; bag.MgSO4 = fx2 * d.MgSO4 / adds.MgSO4.conc; bag.CaGlu = fx2 * d.CaGlu / adds.CaGlu.conc; bag.albumin = fx2 * d.albumin / adds.albumin.conc; bag.carnitine = fx2 * d.carnitine / adds.carnitine.conc; bag.aminophylline = fx2 * d.aminophylline / adds.aminophylline.conc; bag.ranitidine = fx2 * d.ranitidine / adds.ranitidine.conc; bag.famotidine = fx2 * d.famotidine / adds.famotidine.conc; bag.octreotide = fx2 * d.octreotide * 24 / adds.octreotide.conc; bag.trace = fx2 * d.trace / adds.tracePed.conc; bag.Zn = fx2 * d.Zn / adds.ZnPed.conc; bag.cysteine = fx1 * d.cysteine / adds.cysteine.conc; bag.heparin = fx1 * d.heparin / adds.heparin.conc; bag.mvi = fx1 * d.mvi / adds.mvi.conc; for (i in bag) {v3 = v3 + bag[i]; } bag.vol = v3; bag.h2o = v2 - v3; d.bag = bag; } // Calculate the additives component of the Hyperal function additives(d, wt, yr) { if (d.gProt > 0) { if (d.stdCarnitine) { d.carnitine = 10; } if (d.stdCysteine) { d.cysteine = 20 * d.gProt; } if (d.stdTrace) { d.trace = 0.2; d.Cu = 20; d.Se = 2; } if (d.stdZn) { if (wt < 3) { d.Zn = 400; } else if (wt < 10) { d.Zn = 200; } else if (wt < 40) { d.Zn = 100; } else { d.Zn = "limit"; } } if (yr > 10) { d.mviID = 'Adult'; if (d.stdMVI) {d.mvi = 10; } } else { d.mviID = 'Pediatric'; if (d.stdMVI) { if (wt < 1) { d.mvi = 1.5; } else { d.mvi = wt < 3 ? 3.25 : 5; } } } } } function hyperal(d, wt, yr) { if (d.stock === 'Trop4D5' || d.stock === 'Trop4D10') { d.rate = round(d.gProt * 25 * wt / d.time, 0.1); } d.cycleT = d.cycleT || d.time; scratch(d, wt); additives(d, wt, yr); bag(d, wt); d.prot = KPROT * d.gProt; d.kcal = d.prot + d.carb; } function resetNutrients(d) { d.units = ''; d.value = 0; d.vol = 0; d.xVol = 0; d.kcal = 0; d.gCarb = 0; d.gFat = 0; d.gProt = 0; d.Na = 0; d.K = 0; d.mgCa = 0; d.mgFe = 0; return d; } function getNutrients(d) { var mx = d.kcal / 100; if (d.id) { if (products[d.id]) { pull(d, products[d.id].nutr, mx); } } } function fortify(d, vol, wt, basis) { var target, source, need, take, fx; if (d.id) { if (products[d.id]) { target = d.value; source = products[d.id].density * 30; if (target > basis) { need = target - basis; take = source - target; fx = 1/(1 + take/need); d.vol = fx * vol; d.xVol = d.vol / wt; d.kcal = d.xVol * products[d.id].density; getNutrients(d); } else { resetNutrients(d); } } } else { resetNutrients(d); } return vol - d.vol; } function addModular(d, vol, wt) { if (d.id) { if (products[d.id]) { d.units = products[d.id].units; switch(d.id) { case "completeAA": d.vol = d.value * 0.75; break; case "nutrisource": d.value = 1; d.vol = 2.8 * d.value * vol / 236.6; break; case "liquidprot": d.vol = d.value * vol / 100; break; } d.xVol = d.vol / wt; d.kcal = d.xVol * products[d.id].density; getNutrients(d); } } else { resetNutrients(d); } } function formulation(d, vol, wt) { if (d.base.id !== '' && d.base.id !== 'bm') { d.formula = true; } else { d.formula = false; } if (d.mod2.id === d.mod1.id) { d.mod2.id = ''; } if (d.add2.id === d.add1.id) { if (d.add2.value > d.add1.value) { d.add1.value = d.add2.value; } d.add2.id = ''; } addModular(d.mod3, vol, wt); addModular(d.mod2, vol, wt); addModular(d.mod1, vol, wt); vol = vol - d.mod3.vol; vol = vol - d.mod2.vol; vol = vol - d.mod1.vol; vol = fortify(d.add2, vol, wt, d.add1.value); d.base.vol = fortify(d.add1, vol, wt, d.base.value); d.base.xVol = d.base.vol / wt; d.base.kcal = d.base.xVol * d.base.value / 30; // kcal/kg getNutrients(d.base); d.sum = Sum(); add(d.sum, d.base); add(d.sum, d.add1); add(d.sum, d.add2); add(d.sum, d.mod1); add(d.sum, d.mod2); } function allowMethods(d) { d.allowBottle = false; d.allowGravity = false; d.allowPump = false; d.allowIntermittent = false; d.allowContinuous = false; if (d.route === 'po') { d.allowBottle = true; } else if (d.route === 'tpt' || d.route === 'gj') { d.allowContinuous = true; } else { d.allowGravity = true; d.allowPump = true; d.allowIntermittent = true; d.allowContinuous = true; } if (!d.route) { d.allowBottle = true; } } function schedule(d, wt) { // d is en.feedN // d.init = d.clockHour - DNPSTART; // if (d.init < 0) {d.init = d.init + 24; } d.startMin = lte(d.startMin, 55); if (d.startHour > 23) {d.startHour = d.startHour - 24; } if (d.startHour < 0) {d.startHour = d.startHour + 24; } d.startMin = d.startMin || '00'; d.startHour = d.startHour || '00'; d.time = safe(d.time, 0, 24, 24); // constraints d.number = lte(d.number, 12); if (d.route === 'po') { d.tryPO = false; d.method = 'bottle'; } else if (d.route === 'tpt' || d.route === 'gj') { d.method = 'continuous'; } d.isTube = d.route !== 'po' && d.route !== '' && d.method !== 'continuous'; d.isBottle = d.method === 'bottle'; d.isGravity = d.method === 'gravity'; d.isPump = d.method === 'pump'; d.isIntermittent = d.method === 'intermittent'; d.isContinuous = d.method === 'continuous'; d.bolus = d.isBottle || d.isPump || d.isGravity; d.pump = d.isPump || d.isIntermittent || d.isContinuouos; if (d.isContinuous) { // CONTINUOUS feeding regimen d.time = d.time || 24; // ???? d.interval = d.time; // ???? d.number = 1; d.titrate = false; d.iVol0 = 0; d.incr = 0; d.on = d.time; d.off = 0; d.rate = d.rate || d.vol / d.time; // if no rate yet d.rate = round(gte(d.rate, 0), 0.1) || 0; // check rate just in case d.vol = d.rate * d.time; // THIS IS THE VOLUME CALCULATION d.iVol = d.vol; // used only for display } else { // INTERVAL feeding regimen // d.number = lte(d.number, 12); // this is checked on input // d.interval = lte(d.interval, 12); // this is checked on input if (d.interval && d.number) { // neither of these should be zero d.time = d.interval * d.number; } // may or may not be calculated elsewhere if (d.time > 24) { d.interval = 24 / d.number; d.interval = Math.floor(d.interval); d.time = d.interval * d.number; } if (d.vol && d.number) { // if either is zero do NOT change iVol d.iVol = d.iVol || d.vol / d.number; } // recalculate if needed if (d.titrate) { d.iVol0 = d.iVol0 || d.iVol; d.iVol = d.iVol0 + d.incr * (d.number / d.every); d.rate = 0; // force recalculation } else { d.iVol0 = 0; // for display purposes d.incr = 0; // for display purposes } if (d.pump) { // INTERVAL feed by PUMP d.off = gte(d.off, 1); // intermittent at least 1 hr off d.on = d.interval - d.off; d.on = gte(d.on, 0.5); // bolus at least 0.5 hr on d.off = d.interval - d.on; d.off = gte(d.off, 0); d.onMin = d.on * 60; // d.rate = d.rate || d.iVol / d.on; // recalculate if needed d.rate = round(d.rate, 0.1); // round to nearest 0.1 ml/hr d.iVol = d.rate * d.on; // rate is the parameter here } else { // by GRAVITY or by BOTTLE d.off = 0; d.on = 0; d.rate = 0; } d.vol = d.iVol * d.number; // THIS IS THE VOLUME CALCULATION } if (d.iVol > 20) { // arbitrary cutoff, but d.iVol = round(d.iVol, 1); // feeds > 20 ml made integers } // for display, not calculations d.xVol = d.vol / wt; // volume scaled to weight d.xVol = isFinite(d.xVol) ? d.xVol : 0; // defensive for wt = 0 allowMethods(d); } function feeding(d, wt) { var vol; schedule(d, wt); // calculate d.vol // d.form1.ratio = +d.ratio || 'First'; d.form1.ratio = +d.ratio * 100; d.form2.display = (d.ratio < 0.9); d.form1.pct = d.ratio > 0.1; d.form1.alt = !d.form1.pct; // console.log(d.form2.display); vol = d.vol * d.ratio || d.vol; // 0 & 1 both give d.vol formulation(d.form1, vol, wt); if (d.form1.ratio === 'First') { d.form2.ratio = 'Altn'; } else { d.form2.ratio = (100 - d.form1.ratio); vol = d.vol - vol; } formulation(d.form2, vol, wt); d.sum = Sum(); add(d.sum, d.form1.sum); if (d.form2.ratio !== 'Altn') { add(d.sum, d.form2.sum); } d.density = d.sum.kcal / d.sum.xVol * 30; d.prepare = d.sum.vol + d.extra * d.iVol; } /** IVgroup replan/recalculate **************************************************** */ function ivPlan(d, wt) { infusion(d.art, wt); infusion(d.drip1, wt); infusion(d.drip2, wt); infusion(d.drip3, wt); infusion(d.drip4, wt); fluid(d.flush, wt); fluid(d.meds, wt); fluid(d.blood, wt); d.sum = Sum(); add(d.sum, d.art); add(d.sum, d.drip1); add(d.sum, d.drip2); add(d.sum, d.drip3); add(d.sum, d.drip4); add(d.sum, d.flush); add(d.sum, d.meds); add(d.sum, d.blood); d.sum.CO3 = d.sum.Na - d.sum.Cl; d.sum.rsl = d.sum.Na + d.sum.Cl; d.sum.kcal = d.sum.carb; } function pnPlan(d, wt, yr) { // d is data.pn d.iv2.route = d.haf.route; d.iv3.route = d.haf.route; hyperal(d.haf, wt, yr); scratch(d.iv2, wt); scratch(d.iv3, wt); lipid(d.ilf, wt, d.haf.time); // keep here, even though a watcher does this d.npcN = d.haf.carb + d.ilf.fat; // NPC = carb kcal + lipid kcal d.npcN = d.npcN / (d.haf.gProt / 6.25) || 0; // NPC:N non-protein cal / nitrogen d.sum = Sum(); add(d.sum, d.haf); add(d.sum, d.iv2); add(d.sum, d.iv3); add(d.sum, d.ilf); if (!d.ilf.add) { d.sum.xVol += -d.ilf.xVol; } IV2test(d); // is there a need for IV2? d = d.sum; // **NOTE** change in object d.kcal = d.carb + d.fat + d.prot; d.carbPct = 100 * d.carb / d.kcal; d.fatPct = 100 * d.fat / d.kcal; d.protPct = 100 * d.prot / d.kcal; d.npcN = d.carb + d.fat; d.npcN = d.npcN / (d.gProt / 6.25); d.AcToCl = d.CO3 / d.Cl; // this is NOT a summed value } function enPlan(d, wt) { // d is data.en feeding(d.feed1, wt); feeding(d.feed2, wt); feeding(d.feed3, wt); d.sum = Sum(); add(d.sum, d.feed1.sum); add(d.sum, d.feed2.sum); add(d.sum, d.feed3.sum); } function spPlan(d, wt) { // d is data.sp d.mvi.daily = d.mvi.std ? 1 : d.mvi.daily; d.mvi.dose = d.mvi.daily / d.mvi.freq; d.mviFe.daily = d.mviFe.std ? 1 : d.mviFe.daily; d.mviFe.dose = d.mviFe.daily / d.mviFe.freq; d.Fe.daily = d.Fe.std ? 4 : d.Fe.daily; d.Fe.dose = d.Fe.daily / d.Fe.freq; d.vitD.daily = d.vitD.std ? 1 : d.vitD.daily; d.vitD.dose = d.vitD.daily / d.vitD.freq; d.ADEK.daily = d.ADEK.std ? 1 : d.ADEK.daily; d.ADEK.dose = d.ADEK.daily / d.ADEK.freq; d.CaPO4.dose = d.CaPO4.std ? 100 : d.CaPO4.dose; d.CaPO4.daily = d.CaPO4.dose * d.CaPO4.freq; d.NaCl.daily = d.NaCl.std ? 2 : d.NaCl.daily; d.NaCl.dose = d.NaCl.daily / d.NaCl.freq; d.KCl.daily = d.KCl.std ? 2 : d.KCl.daily; d.KCl.dose = d.KCl.daily / d.KCl.freq; d.ArgHCl.daily = d.ArgHCl.std ? 2 : d.ArgHCl.daily; d.ArgHCl.dose = d.ArgHCl.daily / d.ArgHCl.freq; d.bicitra.daily = d.bicitra.std ? 2 : d.bicitra.daily; d.bicitra.dose = d.bicitra.daily / d.bicitra.freq; d.sum = Sum(); d.sum.Na = d.NaCl.daily; d.sum.K = d.KCl.daily; d.sum.Cl = d.NaCl.daily + d.KCl.daily + d.ArgHCl.daily; d.sum.CO3 = d.bicitra.daily; d.sum.Fe = d.Fe.daily; d.sum.mgCa = d.CaPO4.daily / wt; } function tnPlan(d) { // d is data d.tn = Sum(); add(d.tn, d.pn.sum); add(d.tn, d.en.sum); add(d.tn, d.iv.sum); if (!d.iv.add) { d.sum.xVol += -d.iv.xVol; } add(d.tn, d.sp.sum); d.tn.hsVol = holliday(d.wt); // Holliday-Segar volume d.tn.pctHS = d.tn.vol / d.tn.hsVol * 100; // % Holliday-Segar maintenance d.tn.Cal = d.tn.kcal * d.wt; // absolute kcal (not scaled) fatTest(d.tn); // test for excessive fat intake protTest(d.tn); // test for excessive protein intake } /** Watcher Helpers ***************************************************************** */ function isGastric(tst) {return (tst === 'og' || tst === 'ng' || tst === 'gt'); } function isJejunal(tst) {return (tst === 'tpt' || tst === 'gj'); } // function tweak_iVol(d, vol, key) { // var rate = round(vol / d.on, 0.1); // calculate new rate // d.rate = tweak(d.rate, rate, key); // set tweaked rate // } function makeContinuous(d) { d.method = 'continuous'; d.rate = d.vol / d.time; // rate for continuous is different } /** Generic watcher for en.feedN.route */ function getRoute(d, val) { if (d.route === 'po') { if (isGastric(val)) {d.method = 'gravity'; } // a guess else if (isJejunal(val)) {makeContinuous(d);} // required } else { // route gastric or jejunal if (val === 'po') { // change to PO if (d.method === 'continuous') { d.interval = 3; // make discontinuous d.number = 8; d.iVol = 0; // force recalculation } d.method = 'bottle'; } else if (isJejunal(val)) { // or change to jejunal if (d.method !== 'continuous') { makeContinuous(d); } } } d.route = val; } /** Generic watcher for en.feedN.method */ function getMethod(d, val) { if (d.route === 'po') {return; } // ignore if route oral if (d.route === 'tpt') {return; } // ignore if route jejunal if (d.route === 'gj') {return; } // ignore if route jejunal if (val === 'bottle') { if (d.route === '') { getRoute(d, 'po'); } else { val = d.method; d.tryPO = true; } } else if (d.method === 'continuous') { if (val === 'intermittent') { d.interval = 12; // make discontinuous d.number = 2; } else { d.interval = 3; // make discontinuous d.number = 8; d.off = 2.5; // bolus on pump over 0.5 } } else if (d.method === 'intermittent') { if (val === 'pump') { d.off = d.interval - 0.5; } } else if (d.method === 'pump') { if (val === 'intermittent') { d.off = 1; } } else if (d.method === 'gravity') { if (val === 'pump') { d.off = d.interval - 0.5; } } d.rate = 0; // force recalculation!!! d.iVol = 0; // force recalculation d.method = val; } /** Tweaker for volumes and rate */ function bump_rate(d, key) { if (d.mode === 'pump') { if (key === "ArrowUp") { d.rate = d.rate + 0.1; if (d.interval) { d.iVol = d.on * d.rate; d.vol = d.iVol * d.number; } } else if (key === "ArrowDown") { d.rate = d.rate - 0.1; if (d.interval) { d.iVol = d.on * d.rate; d.vol = d.iVol * d.number; } } } } /** Generic watcher for en.feedN.xVol */ function getFeedxVol(d, val, key, wt) { if (wt) { d.vol = val * wt; // XXX need to introduce about (later) bump_rate(d, key); // if arrow keys d.iVol = 0; // force recalculation d.rate = 0; // force recalculation } } /** Generic watcher for en.feedN.iVol */ function getFeediVol(d, val, key) { if (d.titrate) { var delta = val - d.iVol0, steps = d.number / d.every; d.incr = round(delta / steps, 0.1); val = d.iVol0 + d.incr * steps; } d.iVol = val; bump_rate(d, key); // if arrow keys d.rate = 0; // force recalculation } function getInterval(d, val, key) { val = limit(val, 0, 12); if (d.time == 0) { d.number = Math.floor(24 / val); } else { if (d.time > 24) { d.number = Math.floor(24 / val); } else { d.number = Math.floor(d.time / val); } } d.interval = val; // let schedule do the rest if (d.vol) {d.iVol = 0; } // force recalculation d.rate = 0; // force recalculation } function getNumber(d, val, key) { val = limit(val, 0, 12); if (d.time == 0) { d.interval = Math.floor(24 / val); } else { d.interval = Math.floor(d.time / val); } d.number = val; d.iVol = 0; d.rate = 0; d.titrate = false; } /** Generic watcher for en.feedN.on */ function getOnPump(d, val, key) { var long, off; d.mode = 'pump'; // just in case d.on = gte(val, 0.5); off = d.interval - d.on; // temporary value if (d.interval) { long = (d.interval > 4) && (d.off === 1); if (off < 1) { if (key === "ArrowUp") { getInterval(d, d.on + 2, key); } else { getInterval(d, d.on + 1); } } else if (key === "ArrowDown" && long) { getInterval(d, d.on, key); } else { d.off = off; } } else { getInterval(d, d.on + 1); } d.rate = 0; // force rate to be recalculated } function getOnMin(d, val, key) { d.onMin = val; val = val / 60; getOnPump(d, val, key); } function getTotalxVol(data, val, key) { var d = data.pn.haf; val = val - data.iv.sum.xVol - data.en.sum.xVol; if (data.pn.ilf.add) {val = val - data.pn.ilf.xVol; } val = val * data.wt; if (d.cycleDn) {val = val * (d.time / (d.time - d.cycleFx ) ); } tweak_vol(d, val, key); } function getENxVol(data, val, key) { val = val - data.en.feed2.xVol - data.en.feed3.xVol; getFeedxVol(data.en.feed1, val, key, data.wt); } function saltsFromIons (d) { var fx = d.Ac2Cl / (d.Ac2Cl + 1); d.NaAc = d.Na * fx; d.KAc = d.K * fx; d.NaCl = d.Na - d.NaAc - d.NaPO4; d.KCl = d.K - d.KAc - d.KPO4; } watchers = { "en.feed1.route": function (data, val) { getRoute(data.en.feed1, val); }, "en.feed1.method": function (data, val) { getMethod(data.en.feed1, val); }, "en.feed1.iVol": function (data, val, key) { getFeediVol(data.en.feed1, val, key); }, "en.feed1.interval": function (data, val, key) { getInterval(data.en.feed1, val, key); }, "en.feed1.number": function (data, val, key) { getNumber(data.en.feed1, val, key); }, "en.feed1.on": function (data, val, key) { getOnPump(data.en.feed1, val,key); }, "en.feed1.onMin": function (data, val, key) { getOnMin(data.en.feed1, val, key); }, // "en.feed1.time": function (data, val, key) { // getTime(data.en.feed1, val, key); // }, "en.feed1.xVol": function (data, val, key) { getFeedxVol(data.en.feed1, val, key, data.wt); }, "en.feed2.route": function (data, val) { getRoute(data.en.feed2, val); }, "en.feed2.method": function (data, val) { getMethod(data.en.feed2, val); }, "en.feed2.iVol": function (data, val, key) { getFeediVol(data.en.feed2, val, key); }, "en.feed2.interval": function (data, val) { getInterval(data.en.feed2, val); }, "en.feed2.number": function (data, val) { getNumber(data.en.feed2, val); }, "en.feed2.on": function (data, val) { getOnPump(data.en.feed2, val); }, // "en.feed2.time": function (data, val, key) { // getTime(data.en.feed2, val, key); // }, "en.feed2.xVol": function (data, val, key) { getFeedxVol(data.en.feed2, val, key, data.wt); }, "en.feed3.route": function (data, val) { getRoute(data.en.feed3, val); }, "en.feed3.method": function (data, val) { getMethod(data.en.feed3, val); }, "en.feed3.iVol": function (data, val, key) { getFeediVol(data.en.feed3, val, key); }, "en.feed3.interval": function (data, val) { getInterval(data.en.feed3, val); }, "en.feed3.number": function (data, val) { getNumber(data.en.feed3, val); }, "en.feed3.on": function (data, val) { getOnPump(data.en.feed3, val); }, "en.feed3.time": function (data, val, key) { getTime(data.en.feed3, val, key); }, "en.feed3.xVol": function (data, val, key) { getFeedxVol(data.en.feed3, val, key, data.wt); }, "pn.haf.AcToCl": function (data, val) { var d = data.pn.haf; d.Ac2Cl = val * d.Cl / (d.Cl - d.ArgCl); saltsFromIons(d); }, "pn.haf.Na": function (data, val) { var d = data.pn.haf; d.Na = val; saltsFromIons(d); }, "pn.haf.K": function (data, val) { var d = data.pn.haf; d.K = val; saltsFromIons(d); }, "pn.iv2.AcToCl": function (data, val) { var d = data.pn.iv2; d.Ac2Cl = val * d.Cl / (d.Cl - d.ArgCl); saltsFromIons(d); }, "pn.iv2.Na": function (data, val) { var d = data.pn.iv2; d.Na = val; saltsFromIons(d); }, "pn.iv2.K": function (data, val) { var d = data.pn.iv2; d.K = val; saltsFromIons(d); }, "pn.iv3.AcToCl": function (data, val) { var d = data.pn.iv3; d.Ac2Cl = val * d.Cl / (d.Cl - d.ArgCl); saltsFromIons(d); }, "pn.iv3.Na": function (data, val) { var d = data.pn.iv3; d.Na = val; saltsFromIons(d); }, "pn.iv3.K": function (data, val) { var d = data.pn.iv3; d.K = val; saltsFromIons(d); }, "pn.haf.xVol": function (data, val, key) { var d = data.pn.haf; val = val * data.wt; if (d.cycleDn) {val = val * (d.time / (d.time - d.cycleFx ) ); } tweak_vol(d, val, key); }, "pn.haf.gir": function (data, val) { var d = data.pn.haf; d.dxw = val * data.wt / d.rate / KGIR; }, "pn.haf.cycleUp": function(data, val) { var d = data.pn.haf; d.cycleUp = val; cycleTimes(d); d.rate = d.vol / (d.time - d.cycleFx); }, "pn.haf.cycleXt": function(data, val) { var d = data.pn.haf; d.cycleXt = val; cycleTimes(d); d.rate = d.vol / (d.time - d.cycleFx); }, "pn.haf.time": function (data, val) { var d = data.pn.haf; if (val < 22) { d.cycleHide = false; d.cycleDn = true; d.cycleUp = true; } else { d.cycleHide = true; d.cycleDn = false; d.cycleUp = false; d.cycleXt = false; } d.time = safe(val, 0, 24, 24); cycleTimes(d); cycleRates(d); d.rate = d.vol / (d.time - d.cycleFx); }, "pn.ilf.gFat": function (data, val, key) { data.pn.ilf.gFat = val; lipid(data.pn.ilf, data.wt, data.pn.haf.time, key); }, "iv.flush.xVol": function (data, val) { data.iv.flush.vol = val * data.wt; }, "iv.meds.xVol": function (data, val) { data.iv.meds.vol = val * data.wt; }, "iv.blood.xVol": function (data, val) { data.iv.blood.vol = val * data.wt; }, "tn.xVol": function (data, val, key) { getTotalxVol(data, val, key); }, "tn.vol": function (data, val, key) { val = val / data.wt; getTotalxVol(data, val, key); }, "en.sum.xVol": function (data, val, key) { getENxVol(data, val,key); }, "en.sum.vol": function (data, val, key) { val = val / data.wt; getENxVol(data, val, key); }, "sp.sum.Na": function (data, val, key) { var d = data.sp.NaCl; d.daily = val; d.dose = d.daily / d.freq; } }; return { init: function (facts) { products = facts.products; // dictionary for enteral products adds = facts.adds; // dictionary for TPN additives fluids = facts.fluids; // dictionary for stock IV fluid types }, make: function () { // constructor for new data object return DNP(); }, recalculate: function (d) { if (d.wt > 250) { d.wt = d.wt / 1000; } pnPlan(d.pn, d.wt, d.yr); enPlan(d.en, d.wt); ivPlan(d.iv, d.wt); spPlan(d.sp, d.wt); tnPlan(d); // ckPlan(d); }, enter: function (d, id, value, key) { // console.log(id); if (id in watchers) { watchers[id](d, value, key); } else {put(d, id, value); } } }; }()); // immediate invocation of function as a module