Split the Bedside clinical reference section out of calculators.js (~1220 lines) into 20 focused ES modules under public/js/bedside/. Each module owns one clinical topic (cardiac, seizure, sepsis, burns, etc.) and exports an init() wired up by bedside/index.js. Shared helpers live in shared.js and also set window._EM for back-compat with the 4 remaining call sites in calculators.js. Load order: classic defer calculators.js first, then module script bedside/index.js. Handlers bind at DOM-ready; runtime _EM lookups resolve after both are evaluated. Makes bedside content editable per-section, shrinks calculators.js from 4111 to 2891 lines, and keeps the existing Playwright smoke suite (e2e/tests/bedside-smoke.spec.js) as the behavioral contract.
174 lines
15 KiB
JavaScript
174 lines
15 KiB
JavaScript
// ============================================================
|
|
// bedside/respiratory.js
|
|
// RESPIRATORY MANAGEMENT (Asthma, PRAM, Croup, Bronchiolitis).
|
|
// Keeps its own local dose/doseStr/drugRow/drugTable helpers —
|
|
// the layout differs from S.drugTable (no 'Notes' column alignment).
|
|
// ============================================================
|
|
|
|
import { S } from './shared.js';
|
|
|
|
function dose(wt, mgPerKg, maxMg) { var d = Math.round(wt * mgPerKg * 10) / 10; return maxMg ? Math.min(d, maxMg) : d; }
|
|
// Delegates to the shared S.dStr so the per-kg math is visible to the prescriber.
|
|
function doseStr(wt, mgPerKg, maxMg, unit) {
|
|
return S.dStr(wt, mgPerKg, maxMg, unit);
|
|
}
|
|
function drugRow(name, doseText, route, notes) {
|
|
return '<tr><td style="font-weight:600;">' + name + '</td><td>' + doseText + '</td><td>' + route + '</td><td style="font-size:12px;color:var(--g500);">' + (notes || '') + '</td></tr>';
|
|
}
|
|
function drugTable(rows) {
|
|
return '<div style="overflow-x:auto;-webkit-overflow-scrolling:touch;margin:8px 0;"><table style="width:100%;min-width:500px;border-collapse:collapse;font-size:13px;"><thead><tr style="background:var(--g100);"><th style="text-align:left;padding:6px 8px;">Drug</th><th style="padding:6px 8px;">Dose</th><th style="padding:6px 8px;">Route</th><th style="padding:6px 8px;">Notes</th></tr></thead><tbody>' + rows + '</tbody></table></div>';
|
|
}
|
|
function severityBadge(label, color) { return '<span style="display:inline-block;padding:4px 12px;border-radius:6px;background:' + color + '20;color:' + color + ';font-weight:700;font-size:14px;">' + label + '</span>'; }
|
|
|
|
function asthmaManagement(severity) {
|
|
var wt = parseFloat(document.getElementById('resp-weight').value);
|
|
var resultDiv = document.getElementById('asthma-result');
|
|
if (!wt) { resultDiv.innerHTML = '<p style="color:var(--red);font-size:13px;">Enter weight (kg).</p>'; return; }
|
|
|
|
var html = '';
|
|
if (severity === 'mild') {
|
|
html += severityBadge('Mild Exacerbation', '#10b981');
|
|
html += '<p style="font-size:13px;margin:8px 0;">Speaks in sentences, no accessory muscle use, SpO2 ≥94%</p>';
|
|
html += drugTable(
|
|
drugRow('Albuterol (MDI)', '4-8 puffs via spacer', 'Inhaled', 'q20min x 3 doses, then q1-4h') +
|
|
drugRow('Albuterol (neb)', doseStr(wt, 0.15, 5, ' mg') + ' (min 2.5 mg)', 'Nebulized', 'q20min x 3 doses') +
|
|
drugRow('Dexamethasone', doseStr(wt, 0.6, 16), 'PO/IV', 'Single dose, or 2 days') +
|
|
drugRow('Prednisolone', doseStr(wt, 1, 60) + '/day', 'PO', 'Alternative: 3-5 day course')
|
|
);
|
|
html += '<div style="margin-top:8px;padding:8px 12px;background:#d1fae5;border-radius:6px;font-size:12px;color:#065f46;"><strong>Reassess after 1 hour.</strong> If improving → discharge with albuterol MDI + spacer + oral steroid course. If not improving → escalate to moderate.</div>';
|
|
} else if (severity === 'moderate') {
|
|
html += severityBadge('Moderate Exacerbation', '#f59e0b');
|
|
html += '<p style="font-size:13px;margin:8px 0;">Speaks in phrases, some accessory muscle use, SpO2 90-93%</p>';
|
|
html += drugTable(
|
|
drugRow('Albuterol (neb)', doseStr(wt, 0.15, 5, ' mg') + ' (min 2.5 mg)', 'Nebulized', 'q20min x 3 doses, then continuous if needed') +
|
|
drugRow('Ipratropium', wt < 20 ? '250 mcg' : '500 mcg', 'Nebulized', 'q20min x 3 doses with albuterol') +
|
|
drugRow('Dexamethasone', doseStr(wt, 0.6, 16), 'PO/IV/IM', 'Single dose') +
|
|
drugRow('O2 supplemental', 'Target SpO2 ≥94%', 'Nasal cannula/mask', 'Titrate to effect')
|
|
);
|
|
html += '<div style="margin-top:8px;padding:8px 12px;background:#fef3c7;border-radius:6px;font-size:12px;color:#92400e;"><strong>Reassess after 1-2 hours.</strong> If improving → step down to mild protocol. If worsening or no improvement → escalate to severe.</div>';
|
|
} else {
|
|
html += severityBadge('Severe / Life-Threatening', '#ef4444');
|
|
html += '<p style="font-size:13px;margin:8px 0;">Speaks in words only, significant accessory muscle use, SpO2 <90%. Consider ICU.</p>';
|
|
html += drugTable(
|
|
drugRow('Albuterol continuous', doseStr(wt, 0.5, 20, ' mg') + '/hr', 'Continuous neb', 'Or 0.15-0.3 mg/kg q20min') +
|
|
drugRow('Ipratropium', wt < 20 ? '250 mcg' : '500 mcg', 'Nebulized', 'q20min x 3 doses with albuterol') +
|
|
drugRow('Dexamethasone', doseStr(wt, 0.6, 16), 'IV', 'Or methylprednisolone 2 mg/kg IV (max 60 mg)') +
|
|
drugRow('Magnesium sulfate', doseStr(wt, 50, 2000) + ' IV over 20 min', 'IV', 'Single dose, monitor BP') +
|
|
drugRow('Epinephrine (IM)', doseStr(wt, 0.01, 0.5) + ' (1:1000)', 'IM', 'If impending arrest / no IV access') +
|
|
drugRow('Terbutaline', doseStr(wt, 0.01, 0.4) + ' SC/IV', 'SC or IV bolus', 'Then 0.1-10 mcg/kg/min infusion') +
|
|
drugRow('O2 supplemental', 'Target SpO2 ≥94%', 'High flow / NIPPV', 'Consider BiPAP/CPAP')
|
|
);
|
|
html += '<div style="margin-top:8px;padding:8px 12px;background:#fee2e2;border-radius:6px;font-size:12px;color:#991b1b;"><strong>Continuous monitoring.</strong> Consider ICU admission. If no response to magnesium → terbutaline infusion. If impending respiratory failure → intubation (ketamine preferred induction agent).</div>';
|
|
|
|
// Clinical decision points specific to severe asthma
|
|
html += '<div style="margin-top:10px;display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:8px;">';
|
|
|
|
// ABG
|
|
html += '<div style="padding:10px 12px;background:white;border:1px solid var(--g200);border-radius:8px;font-size:12px;line-height:1.5;">' +
|
|
'<div style="font-weight:700;color:var(--blue-dark);margin-bottom:4px;"><i class="fas fa-flask"></i> When to obtain a blood gas</div>' +
|
|
'• Severe exacerbation <strong>not responding</strong> after 1-2 h aggressive therapy<br>' +
|
|
'• Impending respiratory failure — altered mentation, fatigue, silent chest<br>' +
|
|
'• SpO2 <92% despite supplemental O2<br>' +
|
|
'• Suspected CO2 retention (rising PCO2 in a previously tachypneic patient)<br>' +
|
|
'<strong style="color:#991b1b;">Interpretation:</strong> asthmatics hyperventilate → expect <em>low</em> PCO2. A <strong>"normal" or rising PCO2 = impending failure</strong>. Don\'t delay treatment for the gas.' +
|
|
'</div>';
|
|
|
|
// Intubation
|
|
html += '<div style="padding:10px 12px;background:white;border:1px solid var(--g200);border-radius:8px;font-size:12px;line-height:1.5;">' +
|
|
'<div style="font-weight:700;color:#dc2626;margin-bottom:4px;"><i class="fas fa-stethoscope"></i> When to intubate (mostly clinical)</div>' +
|
|
'• <strong>Absolute:</strong> apnea, cardiac arrest, coma, inability to protect airway<br>' +
|
|
'• Progressive fatigue despite maximal therapy (NIV, mag, terbutaline, heliox)<br>' +
|
|
'• Refractory hypoxemia / hypercapnia with acidosis<br>' +
|
|
'• Silent chest + deteriorating consciousness<br>' +
|
|
'<strong>RSI choice:</strong> <span style="color:#065f46;font-weight:600;">ketamine 1-2 mg/kg</span> (bronchodilator, preserves BP) + <span style="color:#065f46;font-weight:600;">rocuronium</span>. Avoid succs if hyperkalemic. Permissive hypercapnia after tube; allow expiration (low rate, I:E 1:3-4).' +
|
|
'</div>';
|
|
|
|
// Heliox
|
|
html += '<div style="padding:10px 12px;background:white;border:1px solid var(--g200);border-radius:8px;font-size:12px;line-height:1.5;">' +
|
|
'<div style="font-weight:700;color:#7c3aed;margin-bottom:4px;"><i class="fas fa-wind"></i> Heliox (He/O2)</div>' +
|
|
'• Lower gas density → less turbulence → reduced work of breathing through narrowed airways.<br>' +
|
|
'• <strong>Consider in:</strong> severe asthma not responding to max therapy (bridge), upper airway obstruction (croup, post-extubation stridor, FB partial).<br>' +
|
|
'• Typical mix: <strong>70:30 He:O2</strong> (or 80:20). Delivered via tight non-rebreather or in-line with neb.<br>' +
|
|
'• <strong>Limitation:</strong> requires FiO2 ≤ 30-40%. If patient needs higher FiO2, heliox can\'t help.<br>' +
|
|
'• Evidence in asthma is mixed — use as adjunct, not substitute.' +
|
|
'</div>';
|
|
|
|
html += '</div>';
|
|
}
|
|
html += '<p style="font-size:11px;color:var(--g400);margin:12px 0 0;">NAEPP/GINA guidelines. Always use clinical judgment. Verify doses against institutional protocols.</p>';
|
|
resultDiv.innerHTML = html;
|
|
}
|
|
|
|
function calcPRAM() {
|
|
var total = ['pram-spo2','pram-retractions','pram-scalene','pram-air','pram-wheeze'].reduce(function(s, id) {
|
|
return s + parseInt(document.getElementById(id).value);
|
|
}, 0);
|
|
var severity = total <= 3 ? 'Mild' : total <= 7 ? 'Moderate' : 'Severe';
|
|
var color = total <= 3 ? '#10b981' : total <= 7 ? '#f59e0b' : '#ef4444';
|
|
document.getElementById('pram-result').innerHTML = '<div style="padding:10px 14px;border-radius:8px;background:' + color + '10;border:1.5px solid ' + color + '30;"><span style="font-size:18px;font-weight:700;color:' + color + ';">PRAM Score: ' + total + '/12 — ' + severity + '</span><p style="font-size:12px;color:var(--g600);margin:4px 0 0;">Mild (0-3): outpatient management. Moderate (4-7): consider oral steroids + frequent bronchodilators. Severe (8-12): aggressive treatment, consider ICU.</p></div>';
|
|
}
|
|
|
|
function calcCroup() {
|
|
var wt = parseFloat(document.getElementById('resp-weight').value) || 10;
|
|
var total = ['croup-conscious','croup-cyanosis','croup-stridor','croup-air','croup-retractions'].reduce(function(s, id) {
|
|
return s + parseInt(document.getElementById(id).value);
|
|
}, 0);
|
|
var severity, color, treatment;
|
|
if (total <= 2) { severity = 'Mild'; color = '#10b981'; treatment = drugTable(drugRow('Dexamethasone', doseStr(wt, 0.6, 16), 'PO (single dose)', 'Preferred corticosteroid') + drugRow('Supportive care', 'Cool mist, comfort measures', '', 'Discharge if tolerating PO')); }
|
|
else if (total <= 5) { severity = 'Moderate'; color = '#f59e0b'; treatment = drugTable(drugRow('Dexamethasone', doseStr(wt, 0.6, 16), 'PO/IM', 'Single dose') + drugRow('Racemic epinephrine', '0.5 mL of 2.25% solution', 'Nebulized', 'Observe 2-4 hrs after for rebound') + drugRow('Nebulized epinephrine', '0.5 mL/kg of 1:1000 (max 5 mL)', 'Nebulized', 'Alternative to racemic')); }
|
|
else if (total <= 11) { severity = 'Severe'; color = '#ef4444'; treatment = drugTable(drugRow('Dexamethasone', doseStr(wt, 0.6, 16), 'IV/IM', 'Immediate') + drugRow('Racemic epinephrine', '0.5 mL of 2.25% solution', 'Nebulized', 'May repeat q15-20min, observe 2-4 hrs') + drugRow('Nebulized epinephrine', '0.5 mL/kg of 1:1000 (max 5 mL)', 'Nebulized', 'Alternative') + drugRow('Heliox', '70:30 or 80:20', 'Face mask', 'Consider if not responding') + drugRow('O2 supplemental', 'Target SpO2 ≥94%', 'Blow-by preferred', 'Minimize agitation')); }
|
|
else { severity = 'Impending Respiratory Failure'; color = '#dc2626'; treatment = '<div style="padding:10px;background:#fee2e2;border-radius:6px;font-size:13px;color:#991b1b;font-weight:600;">Immediate airway management. Prepare for intubation (use ETT 0.5-1 size smaller than predicted). Call anesthesia/ENT. Continue nebulized epinephrine and dexamethasone IV.</div>'; }
|
|
|
|
document.getElementById('croup-result').innerHTML = '<div style="padding:10px 14px;border-radius:8px;background:' + color + '10;border:1.5px solid ' + color + '30;margin-bottom:12px;"><span style="font-size:18px;font-weight:700;color:' + color + ';">Westley Score: ' + total + '/17 — ' + severity + '</span></div>' + treatment + '<p style="font-size:11px;color:var(--g400);margin:8px 0 0;">Westley WJ et al. Nebulized racemic epinephrine by IPPB for the treatment of croup. Am J Dis Child 1978. Mild ≤2, Moderate 3-5, Severe 6-11, Impending failure ≥12.</p>';
|
|
}
|
|
|
|
function calcBronchiolitis() {
|
|
var wt = parseFloat(document.getElementById('resp-weight').value) || 5;
|
|
var age = document.getElementById('bronch-age').value;
|
|
var spo2 = document.getElementById('bronch-spo2').value;
|
|
var hydration = document.getElementById('bronch-hydration').value;
|
|
var distress = document.getElementById('bronch-distress').value;
|
|
|
|
var html = '<h4 style="font-size:15px;font-weight:700;color:var(--g800);margin:0 0 8px;">Bronchiolitis Management</h4>';
|
|
var admit = distress === 'severe' || spo2 === 'low' || hydration === 'poor' || age === '<12w';
|
|
|
|
if (admit) {
|
|
html += severityBadge('Admit / Observe', '#ef4444');
|
|
html += '<div style="margin:10px 0;">';
|
|
if (age === '<12w') html += '<p style="font-size:13px;color:var(--red);font-weight:600;">⚠ Age <12 weeks — high risk for apnea. Monitor closely.</p>';
|
|
html += '</div>';
|
|
} else {
|
|
html += severityBadge('Likely Safe for Discharge', '#10b981');
|
|
}
|
|
|
|
html += '<div style="margin:12px 0;"><strong style="font-size:13px;">Supportive Care (evidence-based):</strong></div>';
|
|
html += drugTable(
|
|
drugRow('Nasal suctioning', 'Bulb suction or NasalClear', 'Nasal', 'Before feeds and as needed') +
|
|
drugRow('O2 supplemental', 'Target SpO2 ≥90%', 'NC / high flow', spo2 === 'low' ? 'Required' : 'If needed') +
|
|
drugRow('Hypertonic saline 3%', '4 mL nebulized', 'Nebulized', 'Consider in inpatients (AAP weak recommendation)') +
|
|
(hydration === 'poor' ? drugRow('IV fluids', 'NS/LR bolus ' + doseStr(wt, 20, null, ' mL'), 'IV', 'Then maintenance D5 0.45NS') + drugRow('NG feeds', 'If unable to feed orally', 'NG tube', 'Preferred over IV if gut functional') : '')
|
|
);
|
|
|
|
html += '<div style="margin:12px 0;padding:10px 12px;background:var(--amber-light);border-radius:6px;font-size:12px;color:#92400e;"><strong>NOT recommended (AAP 2014/2023):</strong> Albuterol/salbutamol (no benefit in bronchiolitis), epinephrine (no evidence of benefit), systemic corticosteroids (no benefit), antibiotics (unless secondary bacterial infection), chest physiotherapy.</div>';
|
|
html += '<p style="font-size:11px;color:var(--g400);margin:8px 0 0;">AAP Clinical Practice Guideline: Management of Bronchiolitis in Infants and Children (2014, reaffirmed 2023). RSV is the most common cause (50-80%).</p>';
|
|
document.getElementById('bronch-result').innerHTML = html;
|
|
}
|
|
|
|
export function init() {
|
|
// Sub-pill navigation
|
|
document.addEventListener('click', function(e) {
|
|
var pill = e.target.closest('[data-resp]');
|
|
if (pill) {
|
|
document.querySelectorAll('[data-resp]').forEach(function(p) { p.classList.remove('active'); });
|
|
pill.classList.add('active');
|
|
['asthma','croup','bronchiolitis'].forEach(function(s) {
|
|
var el = document.getElementById('resp-' + s);
|
|
if (el) el.style.display = pill.dataset.resp === s ? '' : 'none';
|
|
});
|
|
}
|
|
if (e.target.closest('[data-asthma-severity]')) asthmaManagement(e.target.closest('[data-asthma-severity]').dataset.asthmaSeverity);
|
|
if (e.target.id === 'btn-pram-calc' || e.target.closest('#btn-pram-calc')) calcPRAM();
|
|
if (e.target.id === 'btn-croup-calc' || e.target.closest('#btn-croup-calc')) calcCroup();
|
|
if (e.target.id === 'btn-bronch-calc' || e.target.closest('#btn-bronch-calc')) calcBronchiolitis();
|
|
});
|
|
}
|