feat: C — extract Bedside reference into ES modules
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.
This commit is contained in:
parent
c4e30b4f3e
commit
ddf9ca805b
23 changed files with 1978 additions and 1220 deletions
|
|
@ -18,6 +18,7 @@
|
|||
<script defer src="/js/calc-math.js"></script>
|
||||
<script defer src="/js/drugs-loader.js"></script>
|
||||
<script defer src="/js/calculators.js"></script>
|
||||
<script type="module" src="/js/bedside/index.js"></script>
|
||||
<script defer src="/js/e2e-bootstrap.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -447,6 +447,7 @@
|
|||
<script defer src="/js/calc-math.js"></script>
|
||||
<script defer src="/js/drugs-loader.js"></script>
|
||||
<script defer src="/js/calculators.js"></script>
|
||||
<script type="module" src="/js/bedside/index.js"></script>
|
||||
<script defer src="/js/learningHub.js"></script>
|
||||
<script defer src="/js/admin.js"></script>
|
||||
<script defer src="/js/adminMilestones.js"></script>
|
||||
|
|
|
|||
80
public/js/bedside/age-weight.js
Normal file
80
public/js/bedside/age-weight.js
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
// ============================================================
|
||||
// bedside/age-weight.js
|
||||
// BEDSIDE — AGE → WEIGHT (single source for all sub-sections).
|
||||
// Top estimator wiring: #bedside-age, #bedside-formula,
|
||||
// #bedside-weight, #btn-bedside-clear.
|
||||
// ============================================================
|
||||
|
||||
export function init() {
|
||||
document.addEventListener('input', function(e) {
|
||||
if (e.target && e.target.id === 'bedside-age') recomputeFromAge();
|
||||
if (e.target && e.target.id === 'bedside-weight') {
|
||||
e.target.dataset.userTyped = '1';
|
||||
}
|
||||
});
|
||||
document.addEventListener('change', function(e) {
|
||||
if (e.target && e.target.id === 'bedside-formula') recomputeFromAge(true);
|
||||
});
|
||||
document.addEventListener('click', function(e) {
|
||||
if (e.target.closest && e.target.closest('#btn-bedside-clear')) {
|
||||
document.getElementById('bedside-age').value = '';
|
||||
var wtField = document.getElementById('bedside-weight');
|
||||
wtField.value = '';
|
||||
delete wtField.dataset.userTyped;
|
||||
var note = document.getElementById('bedside-estimate-note');
|
||||
if (note) note.innerHTML = '';
|
||||
}
|
||||
});
|
||||
|
||||
// Recompute weight from age using the currently selected formula.
|
||||
// If forceFromFormula is true (formula dropdown changed), overwrite even user-typed weight.
|
||||
function recomputeFromAge(forceFromFormula) {
|
||||
var raw = document.getElementById('bedside-age').value;
|
||||
var note = document.getElementById('bedside-estimate-note');
|
||||
if (!raw || !raw.trim()) { if (note) note.innerHTML = ''; return; }
|
||||
var months = window._parseAgeMonths ? window._parseAgeMonths(raw) : null;
|
||||
if (months == null || isNaN(months) || months < 0) {
|
||||
if (note) note.innerHTML = '<span style="color:var(--red);">Could not parse age. Try "3y", "18 months", "15 days".</span>';
|
||||
return;
|
||||
}
|
||||
var formula = document.getElementById('bedside-formula').value;
|
||||
var est = window._PED_MATH.estimateWeightFromAgeMonths(months);
|
||||
var pickedWeight = formula === 'bestguess' ? est.all.bestGuess : est.all.apls;
|
||||
var pickedLabel = formula === 'bestguess' ? bestGuessBand(months) : aplsBand(months);
|
||||
|
||||
var wtField = document.getElementById('bedside-weight');
|
||||
if (forceFromFormula || !wtField.dataset.userTyped) {
|
||||
wtField.value = pickedWeight;
|
||||
delete wtField.dataset.userTyped; // treat as computed again
|
||||
}
|
||||
var ageTxt = months < 12
|
||||
? (Math.round(months * 10) / 10) + ' months'
|
||||
: (Math.round(months / 12 * 10) / 10) + ' years';
|
||||
if (note) note.innerHTML = '<strong>' + pickedWeight + ' kg</strong> ' +
|
||||
'<span style="color:var(--g500);">(' + ageTxt + ', ' + pickedLabel + ')</span> · ' +
|
||||
'APLS: ' + est.all.apls + ' kg · Best Guess: ' + est.all.bestGuess + ' kg. <em>You can override the weight field.</em>';
|
||||
}
|
||||
|
||||
function aplsBand(m) {
|
||||
if (m < 12) return 'APLS 0-12 mo';
|
||||
var y = m / 12;
|
||||
if (y <= 5) return 'APLS 1-5 yr';
|
||||
return 'APLS 6-12 yr';
|
||||
}
|
||||
function bestGuessBand(m) {
|
||||
if (m < 12) return 'Best Guess 1-11 mo';
|
||||
var y = m / 12;
|
||||
if (y <= 5) return 'Best Guess 1-4 yr';
|
||||
return 'Best Guess 5-14 yr';
|
||||
}
|
||||
|
||||
// Top bedside weight is estimator-only. Sections have their own weight fields and
|
||||
// read them directly (no auto-propagation). Expose the reader on window for
|
||||
// safety (some older code paths may look it up).
|
||||
if (typeof window !== 'undefined') {
|
||||
window._getBedsideWeight = function() {
|
||||
var el = document.getElementById('bedside-weight');
|
||||
return el ? parseFloat(el.value) || null : null;
|
||||
};
|
||||
}
|
||||
}
|
||||
83
public/js/bedside/agitation.js
Normal file
83
public/js/bedside/agitation.js
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
// ============================================================
|
||||
// bedside/agitation.js
|
||||
// AGITATION — step 1/2/3 pathway, drugs.json-backed.
|
||||
// ============================================================
|
||||
|
||||
import { S } from './shared.js';
|
||||
|
||||
// Fallback inline data — mirrors drugs.json agitation section, split into
|
||||
// step-2 (PO/IN) and step-3 (IV/IM) subgroups by the route field.
|
||||
// Display-name column intentionally drops the route suffix from the JSON
|
||||
// name so the table still shows "Lorazepam" / "Midazolam" / "Ketamine".
|
||||
var AGIT_FALLBACK = [
|
||||
// Step 2 — PO / IN
|
||||
{ name: 'Lorazepam PO', display: 'Lorazepam', step: 2, dose_mg_per_kg: 0.05, max_mg: 2, unit: 'mg', route: 'PO', notes: '0.05 mg/kg. May repeat in 30 min.' },
|
||||
{ name: 'Midazolam PO', display: 'Midazolam', step: 2, dose_mg_per_kg: 0.5, max_mg: 10, unit: 'mg', route: 'PO', notes: '0.5 mg/kg (max 10 mg)' },
|
||||
{ name: 'Midazolam IN', display: 'Midazolam', step: 2, dose_mg_per_kg: 0.3, max_mg: 10, unit: 'mg', route: 'IN (5 mg/mL)', notes: '0.3 mg/kg split between nares (max 10 mg)' },
|
||||
{ name: 'Olanzapine (ODT)', display: 'Olanzapine (ODT)', step: 2, dose_mg_per_kg: null, max_mg: null, unit: 'mg', route: 'PO / ODT', notes: 'Age ≥6 yr. Avoid IM + benzo combo (risk of resp depression).', dose_display_if_under_30kg: '2.5-5 mg', dose_display_if_30kg_or_more: '5-10 mg' },
|
||||
{ name: 'Diphenhydramine PO', display: 'Diphenhydramine', step: 2, dose_mg_per_kg: 1, max_mg: 50, unit: 'mg', route: 'PO', notes: '1 mg/kg. Adjunct only; sedating.' },
|
||||
// Step 3 — IV / IM
|
||||
{ name: 'Midazolam IV/IM', display: 'Midazolam', step: 3, dose_mg_per_kg: 0.1, max_mg: 5, unit: 'mg', route: 'IV / IM', notes: '0.05-0.1 mg/kg IV, 0.1-0.15 mg/kg IM (max 10 mg)' },
|
||||
{ name: 'Lorazepam IV/IM', display: 'Lorazepam', step: 3, dose_mg_per_kg: 0.1, max_mg: 4, unit: 'mg', route: 'IV / IM', notes: '0.05-0.1 mg/kg (max 4 mg). May cause resp depression.' },
|
||||
{ name: 'Haloperidol', display: 'Haloperidol', step: 3, dose_mg_per_kg: 0.05, max_mg: null, unit: 'mg', route: 'IM / IV', notes: 'Avoid <3 yr. Risk: QT, EPS, NMS. ECG if repeated.', dose_display_if_under_40kg: '0.025-0.075 mg/kg', dose_display_if_40kg_or_more: '2.5-5 mg' },
|
||||
{ name: 'Olanzapine IM', display: 'Olanzapine', step: 3, dose_mg_per_kg: null, max_mg: null, unit: 'mg', route: 'IM', notes: 'Avoid benzo co-administration (resp depression, hypotension)', dose_display_if_under_40kg: '2.5-5 mg', dose_display_if_40kg_or_more: '5-10 mg' },
|
||||
{ name: 'Ketamine IM', display: 'Ketamine', step: 3, dose_mg_per_kg: 4, max_mg: 500, unit: 'mg', route: 'IM', notes: '4-5 mg/kg IM (rescue for severe excited delirium). Monitor airway.' },
|
||||
{ name: 'Droperidol', display: 'Droperidol', step: 3, dose_mg_per_kg: 0.05, max_mg: null, unit: 'mg', route: 'IM / IV', notes: 'Effective but QT concern — get ECG.', dose_display_prefix: '0.03-0.07 mg/kg' }
|
||||
];
|
||||
|
||||
function agitDrugs() {
|
||||
var s = window._DRUGS && window._DRUGS.sections && window._DRUGS.sections.agitation;
|
||||
if (s && s.drugs && s.drugs.length) {
|
||||
// Merge per-drug hints from fallback (display name, step) when the
|
||||
// JSON doesn't carry them — keeps rendering identical.
|
||||
return s.drugs.map(function(dg) {
|
||||
var fb = AGIT_FALLBACK.filter(function(x) { return x.name === dg.name; })[0] || {};
|
||||
return Object.assign({}, fb, dg);
|
||||
});
|
||||
}
|
||||
return AGIT_FALLBACK;
|
||||
}
|
||||
|
||||
// Render one drug row, handling the agitation-specific display quirks:
|
||||
// weight-conditional dose strings (Olanzapine, Haloperidol) and hand-
|
||||
// computed "prefix = value mg" strings (Droperidol, Haloperidol <40 kg).
|
||||
function agitRowFor(wt, dg) {
|
||||
var dose;
|
||||
if (dg.dose_display_if_under_30kg && dg.dose_display_if_30kg_or_more) {
|
||||
dose = wt < 30 ? dg.dose_display_if_under_30kg : dg.dose_display_if_30kg_or_more;
|
||||
} else if (dg.dose_display_if_under_40kg && dg.dose_display_if_40kg_or_more) {
|
||||
dose = wt < 40 ? dg.dose_display_if_under_40kg + ' = ' + S.d(wt, dg.dose_mg_per_kg) + ' mg' : dg.dose_display_if_40kg_or_more;
|
||||
} else if (dg.dose_display_prefix) {
|
||||
dose = dg.dose_display_prefix + ' = ' + S.d(wt, dg.dose_mg_per_kg) + ' mg';
|
||||
} else if (dg.dose_mg_per_kg != null) {
|
||||
dose = S.dStr(wt, dg.dose_mg_per_kg, dg.max_mg, ' ' + dg.unit);
|
||||
} else {
|
||||
dose = dg.dose_display || '';
|
||||
}
|
||||
return S.drugRow(dg.display || dg.name, dose, dg.route, dg.notes);
|
||||
}
|
||||
|
||||
function calcAgit() {
|
||||
var wt = parseFloat(document.getElementById('agit-weight').value);
|
||||
var el = document.getElementById('agit-result');
|
||||
if (!wt) { el.innerHTML = '<p style="color:var(--red);font-size:13px;">Enter weight (kg).</p>'; return; }
|
||||
var drugs = agitDrugs();
|
||||
var step2 = drugs.filter(function(dg) { return dg.step === 2; });
|
||||
var step3 = drugs.filter(function(dg) { return dg.step === 3; });
|
||||
|
||||
var html = '<div style="padding:10px 14px;border-radius:8px;background:var(--purple-light);border:1.5px solid var(--purple);margin-bottom:10px;"><strong style="color:var(--purple);">Agitation management — ' + wt + ' kg</strong></div>';
|
||||
html += S.stepBox('#10b981', 'Step 1 — Non-pharmacologic', 'Dim lights, quiet room, familiar caregiver present, reduce stimulation. Rule out hypoglycemia, hypoxia, pain, ↑ICP, toxic ingestion, infection.');
|
||||
html += S.stepBox('#3b82f6', 'Step 2 — Oral / intranasal (cooperative)', 'First-line for moderate agitation if the patient will accept PO/IN.');
|
||||
html += S.drugTable(step2.map(function(dg) { return agitRowFor(wt, dg); }).join(''));
|
||||
html += S.stepBox('#f59e0b', 'Step 3 — IM / IV (severe, uncooperative, or safety risk)', 'Require continuous monitoring. Consider restraints only as last resort.');
|
||||
html += S.drugTable(step3.map(function(dg) { return agitRowFor(wt, dg); }).join(''));
|
||||
html += '<div style="padding:10px;background:#fef3c7;border-radius:6px;font-size:12px;color:#92400e;margin-top:10px;"><strong>Monitoring:</strong> Continuous SpO2 + HR after parenteral sedation. Have airway equipment, flumazenil, naloxone, and IV access ready.</div>';
|
||||
html += S.ref('AAP Clinical Report on Pediatric Agitation. ACEP Guidelines for Acute Agitation.');
|
||||
el.innerHTML = html;
|
||||
}
|
||||
|
||||
export function init() {
|
||||
document.addEventListener('click', function(e) {
|
||||
if (e.target.id === 'btn-agit-calc' || e.target.closest('#btn-agit-calc')) calcAgit();
|
||||
});
|
||||
}
|
||||
90
public/js/bedside/airway.js
Normal file
90
public/js/bedside/airway.js
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
// ============================================================
|
||||
// bedside/airway.js
|
||||
// AIRWAY / RSI.
|
||||
// ============================================================
|
||||
|
||||
import { S } from './shared.js';
|
||||
|
||||
function calcAirway() {
|
||||
var wt = parseFloat(document.getElementById('airway-weight').value);
|
||||
var age = parseFloat(document.getElementById('airway-age').value);
|
||||
var el = document.getElementById('airway-result');
|
||||
if (!wt) { el.innerHTML = '<p style="color:var(--red);font-size:13px;">Enter weight (kg).</p>'; return; }
|
||||
|
||||
// Equipment sizing
|
||||
var ettUncuff = age ? Math.round(((age/4) + 4) * 2) / 2 : null;
|
||||
var ettCuff = age ? Math.round(((age/4) + 3.5) * 2) / 2 : null;
|
||||
var ettDepth = ettUncuff ? Math.round(ettUncuff * 3 * 10) / 10 : null;
|
||||
var bladeSize = age != null ? (age < 1 ? 'Miller 0-1' : age < 2 ? 'Miller 1 / Mac 1' : age < 8 ? 'Miller 2 / Mac 2' : 'Miller/Mac 3') : '—';
|
||||
var lmaSize = wt < 5 ? '1' : wt < 10 ? '1.5' : wt < 20 ? '2' : wt < 30 ? '2.5' : wt < 50 ? '3' : wt < 70 ? '4' : '5';
|
||||
|
||||
var html = '<div style="padding:10px 14px;border-radius:8px;background:var(--blue-light);border:1.5px solid var(--blue);margin-bottom:10px;"><strong style="color:var(--blue-dark);">RSI doses & equipment — ' + wt + ' kg' + (age ? ', ~' + age + ' yr' : '') + '</strong></div>';
|
||||
|
||||
// Pre-medication
|
||||
html += '<h5 style="font-size:13px;font-weight:700;color:var(--g700);margin:10px 0 4px;">Pre-medication (optional)</h5>';
|
||||
html += S.drugTable(
|
||||
S.drugRow('Atropine', S.dStr(wt, 0.02, 1, ' mg') + ' (min 0.1 mg)', 'IV', 'Consider <1 yr or if using succinylcholine, or bradycardia risk') +
|
||||
S.drugRow('Lidocaine 2%', S.dStr(wt, 1.5, 100, ' mg'), 'IV over 1 min', 'For ↑ICP, asthma; give 2-3 min pre-induction') +
|
||||
S.drugRow('Fentanyl', S.dStr(wt, 2, 150, ' mcg') + ' (2-3 mcg/kg)', 'IV over 1 min', 'Blunts sympathetic response; avoid if hypotensive')
|
||||
);
|
||||
|
||||
// Induction
|
||||
html += '<h5 style="font-size:13px;font-weight:700;color:var(--g700);margin:14px 0 4px;">Induction</h5>';
|
||||
html += S.drugTable(
|
||||
S.drugRow('Ketamine', S.dStr(wt, 1.5, 150, ' mg') + ' (1-2 mg/kg)', 'IV', 'Preserves BP. First-line for shock, asthma, sepsis') +
|
||||
S.drugRow('Etomidate', S.dStr(wt, 0.3, 30, ' mg'), 'IV', 'Hemodynamically neutral. Avoid in septic shock (adrenal suppression)') +
|
||||
S.drugRow('Propofol', S.dStr(wt, 2, 200, ' mg') + ' (1-2 mg/kg)', 'IV', 'Causes hypotension. Avoid in shock') +
|
||||
S.drugRow('Midazolam', S.dStr(wt, 0.2, 10, ' mg'), 'IV', 'Less optimal induction — slower onset')
|
||||
);
|
||||
|
||||
// Paralytics
|
||||
html += '<h5 style="font-size:13px;font-weight:700;color:var(--g700);margin:14px 0 4px;">Paralytics</h5>';
|
||||
html += S.drugTable(
|
||||
S.drugRow('Rocuronium', S.dStr(wt, 1.2, 100, ' mg') + ' (1-1.2 mg/kg)', 'IV', '30-60 sec onset; 30-45 min duration. First-line non-depolarizing.') +
|
||||
S.drugRow('Succinylcholine', S.dStr(wt, (wt < 10 ? 2 : 1.5), 150, ' mg'), 'IV', '<10kg: 2 mg/kg; ≥10kg: 1.5 mg/kg. Avoid in burns/crush/hyperK/MH/NM dz. IM option: 4 mg/kg.') +
|
||||
S.drugRow('Vecuronium', S.dStr(wt, 0.1, 10, ' mg'), 'IV', '2-3 min onset; longer duration than roc')
|
||||
);
|
||||
|
||||
// Post-intubation sedation
|
||||
html += '<h5 style="font-size:13px;font-weight:700;color:var(--g700);margin:14px 0 4px;">Maintenance sedation / analgesia</h5>';
|
||||
html += S.drugTable(
|
||||
S.drugRow('Midazolam gtt', S.dStr(wt, 0.05, 2, ' mg') + '-' + S.dStr(wt, 0.2, 10, ' mg') + '/hr', 'IV infusion', '0.05-0.2 mg/kg/hr') +
|
||||
S.drugRow('Fentanyl gtt', S.dStr(wt, 1, 100, ' mcg') + '-' + S.dStr(wt, 4, 200, ' mcg') + '/hr', 'IV infusion', '1-4 mcg/kg/hr') +
|
||||
S.drugRow('Ketamine gtt', S.dStr(wt, 0.5, 30, ' mg') + '-' + S.dStr(wt, 2, 100, ' mg') + '/hr', 'IV infusion', '0.5-2 mg/kg/hr. Good for asthma') +
|
||||
S.drugRow('Dexmedetomidine gtt', '0.2-1.4 mcg/kg/hr', 'IV infusion', 'No resp depression; causes bradycardia + hypotension') +
|
||||
S.drugRow('Rocuronium gtt', '10-12 mcg/kg/min', 'IV infusion', 'If continued paralysis needed — only with adequate sedation')
|
||||
);
|
||||
|
||||
// Equipment
|
||||
html += '<h5 style="font-size:13px;font-weight:700;color:var(--g700);margin:14px 0 4px;">Equipment sizing</h5>';
|
||||
html += '<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:8px;font-size:12px;">';
|
||||
if (age != null) {
|
||||
html += '<div style="padding:10px;background:var(--g50);border-radius:6px;"><strong>ETT (uncuffed):</strong> ' + ettUncuff + ' mm<br><strong>ETT (cuffed):</strong> ' + ettCuff + ' mm<br><strong>Depth at lip:</strong> ~' + ettDepth + ' cm</div>';
|
||||
html += '<div style="padding:10px;background:var(--g50);border-radius:6px;"><strong>Laryngoscope blade:</strong><br>' + bladeSize + '</div>';
|
||||
} else {
|
||||
html += '<div style="padding:10px;background:var(--g50);border-radius:6px;"><em>Enter age for ETT/blade sizing.</em><br>ETT (uncuffed) = (age/4) + 4<br>ETT (cuffed) = (age/4) + 3.5<br>Depth = ETT size × 3</div>';
|
||||
}
|
||||
html += '<div style="padding:10px;background:var(--g50);border-radius:6px;"><strong>LMA size:</strong> ' + lmaSize + '<br>OPA = corner mouth → angle mandible<br>NPA = tip nose → tragus</div>';
|
||||
html += '<div style="padding:10px;background:var(--g50);border-radius:6px;"><strong>Suction catheter (Fr):</strong> ETT size × 2<br><strong>NG tube:</strong> ETT × 2<br><strong>Chest tube:</strong> 4 × ETT</div>';
|
||||
html += '</div>';
|
||||
|
||||
// Ventilator starting settings
|
||||
html += '<h5 style="font-size:13px;font-weight:700;color:var(--g700);margin:14px 0 4px;">Ventilator — starting settings</h5>';
|
||||
html += '<div style="padding:10px;background:var(--blue-light);border-radius:6px;font-size:12px;color:var(--g700);">';
|
||||
html += '<strong>Mode:</strong> Volume or pressure control<br>';
|
||||
html += '<strong>Tidal volume:</strong> 6-8 mL/kg (= ' + S.d(wt, 6) + '-' + S.d(wt, 8) + ' mL). Use lower (4-6 mL/kg) for ARDS.<br>';
|
||||
html += '<strong>Rate:</strong> Infant 25-30, Child 15-25, Adolescent 12-16<br>';
|
||||
html += '<strong>PEEP:</strong> 5 cmH2O (↑ for oxygenation problems)<br>';
|
||||
html += '<strong>FiO2:</strong> Start 100%, wean to SpO2 92-97%<br>';
|
||||
html += '<strong>I:E ratio:</strong> 1:2 (1:3-4 for obstructive disease)';
|
||||
html += '</div>';
|
||||
|
||||
html += S.ref('Harriet Lane Handbook 23rd ed / PALS 2020. Always confirm doses against institutional protocols.');
|
||||
el.innerHTML = html;
|
||||
}
|
||||
|
||||
export function init() {
|
||||
document.addEventListener('click', function(e) {
|
||||
if (e.target.id === 'btn-airway-calc' || e.target.closest('#btn-airway-calc')) calcAirway();
|
||||
});
|
||||
}
|
||||
82
public/js/bedside/anaphylaxis.js
Normal file
82
public/js/bedside/anaphylaxis.js
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
// ============================================================
|
||||
// bedside/anaphylaxis.js
|
||||
// ANAPHYLAXIS MANAGEMENT.
|
||||
// ============================================================
|
||||
|
||||
import { S } from './shared.js';
|
||||
|
||||
function d(wt, mgPerKg, max) { var v = Math.round(wt * mgPerKg * 100) / 100; return max ? Math.min(v, max) : v; }
|
||||
|
||||
// Lookup a drug entry (by name) from window._DRUGS.sections.anaphylaxis,
|
||||
// falling back to a hardcoded record so rendering stays identical if the
|
||||
// JSON fetch hasn't resolved (or failed). Returns an object exposing the
|
||||
// per-kg + max fields needed by S.dStr.
|
||||
var ANAPH_FALLBACK = {
|
||||
'Epinephrine IM': { dose_mg_per_kg: 0.01, max_mg: 0.5, unit: 'mg' },
|
||||
'Normal saline bolus': { dose_mg_per_kg: 20, max_mg: null, unit: 'mL' },
|
||||
'Diphenhydramine': { dose_mg_per_kg: 1.25, max_mg: 50, unit: 'mg' },
|
||||
'Ranitidine': { dose_mg_per_kg: 1, max_mg: 50, unit: 'mg' },
|
||||
'Dexamethasone': { dose_mg_per_kg: 0.6, max_mg: 16, unit: 'mg' },
|
||||
'Methylprednisolone': { dose_mg_per_kg: 2, max_mg: 125, unit: 'mg' },
|
||||
'Glucagon': { dose_mg_per_kg: 0.02, max_mg: 1, unit: 'mg' }
|
||||
};
|
||||
|
||||
function anaphDrug(name) {
|
||||
var s = window._DRUGS && window._DRUGS.sections && window._DRUGS.sections.anaphylaxis;
|
||||
if (s && s.drugs) {
|
||||
var hit = s.drugs.filter(function(x) { return x.name === name; })[0];
|
||||
if (hit) return hit;
|
||||
}
|
||||
return ANAPH_FALLBACK[name] || {};
|
||||
}
|
||||
|
||||
function calcAnaphylaxis() {
|
||||
var wt = parseFloat(document.getElementById('anaph-weight').value);
|
||||
var age = document.getElementById('anaph-age') ? document.getElementById('anaph-age').value : null; // kept for parity; not currently branched on
|
||||
void age;
|
||||
var resultDiv = document.getElementById('anaph-result');
|
||||
if (!wt) { resultDiv.innerHTML = '<p style="color:var(--red);font-size:13px;">Enter weight (kg).</p>'; return; }
|
||||
|
||||
var epi = anaphDrug('Epinephrine IM');
|
||||
var fluids = anaphDrug('Normal saline bolus');
|
||||
var dph = anaphDrug('Diphenhydramine');
|
||||
var rnt = anaphDrug('Ranitidine');
|
||||
var dex = anaphDrug('Dexamethasone');
|
||||
var mpn = anaphDrug('Methylprednisolone');
|
||||
var glc = anaphDrug('Glucagon');
|
||||
|
||||
var epiDose = d(wt, epi.dose_mg_per_kg, epi.max_mg);
|
||||
var epiVol = Math.round(epiDose * 10) / 10; // 1:1000 = 1 mg/mL
|
||||
var autoInjector = wt < 10 ? 'Draw up manually' : wt <= 25 ? 'EpiPen Jr / Auvi-Q 0.15 mg' : 'EpiPen / Auvi-Q 0.3 mg';
|
||||
|
||||
var html = '<div style="padding:14px;border-radius:10px;background:#fee2e2;border:2px solid #ef4444;margin-bottom:16px;">';
|
||||
html += '<div style="font-size:18px;font-weight:700;color:#dc2626;margin-bottom:6px;"><i class="fas fa-triangle-exclamation"></i> STEP 1: Epinephrine IM — GIVE IMMEDIATELY</div>';
|
||||
html += '<div style="font-size:15px;font-weight:600;color:var(--g800);margin-bottom:4px;">Epinephrine 1:1000 (1 mg/mL): <span style="color:#dc2626;">' + epiDose + ' mg (' + epiVol + ' mL)</span> IM to lateral thigh</div>';
|
||||
html += '<div style="font-size:13px;color:var(--g600);">' + epi.dose_mg_per_kg + ' mg/kg (max ' + epi.max_mg + ' mg) | Auto-injector: ' + autoInjector + '</div>';
|
||||
html += '<div style="font-size:12px;color:#991b1b;margin-top:6px;">May repeat every 5-15 minutes if symptoms persist. No contraindications in anaphylaxis.</div>';
|
||||
html += '</div>';
|
||||
|
||||
html += '<div style="overflow-x:auto;-webkit-overflow-scrolling:touch;margin-bottom:12px;"><table style="width:100%;min-width:560px;border-collapse:collapse;font-size:13px;"><thead><tr style="background:var(--g100);"><th style="text-align:left;padding:6px 8px;">Step</th><th style="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>';
|
||||
|
||||
html += '<tr><td style="font-weight:600;color:var(--blue);">STEP 2</td><td style="font-weight:600;">Position</td><td>Supine + legs elevated</td><td>—</td><td style="font-size:12px;color:var(--g500);">If dyspneic: sitting position. If vomiting: recovery position</td></tr>';
|
||||
html += '<tr><td style="font-weight:600;color:var(--blue);">STEP 3</td><td style="font-weight:600;">O2</td><td>High flow 10-15 L/min</td><td>Face mask</td><td style="font-size:12px;color:var(--g500);">100% O2. Prepare for airway management</td></tr>';
|
||||
html += '<tr><td style="font-weight:600;color:var(--blue);">STEP 4</td><td style="font-weight:600;">IV fluids</td><td>NS ' + S.dStr(wt, fluids.dose_mg_per_kg, fluids.max_mg, ' mL') + ' bolus</td><td>IV/IO</td><td style="font-size:12px;color:var(--g500);">Repeat up to 60 mL/kg for hypotension</td></tr>';
|
||||
html += '<tr><td style="font-weight:600;color:var(--blue);">STEP 5</td><td style="font-weight:600;">Diphenhydramine</td><td>' + S.dStr(wt, dph.dose_mg_per_kg, dph.max_mg) + '</td><td>IV/IM/PO</td><td style="font-size:12px;color:var(--g500);">H1 blocker. NOT first-line — adjunct only</td></tr>';
|
||||
html += '<tr><td></td><td style="font-weight:600;">Ranitidine</td><td>' + S.dStr(wt, rnt.dose_mg_per_kg, rnt.max_mg) + '</td><td>IV over 5 min</td><td style="font-size:12px;color:var(--g500);">H2 blocker. Optional adjunct</td></tr>';
|
||||
html += '<tr><td style="font-weight:600;color:var(--blue);">STEP 6</td><td style="font-weight:600;">Dexamethasone</td><td>' + S.dStr(wt, dex.dose_mg_per_kg, dex.max_mg) + '</td><td>IV/IM/PO</td><td style="font-size:12px;color:var(--g500);">Prevents biphasic reaction (4-6 hrs later)</td></tr>';
|
||||
html += '<tr><td></td><td style="font-weight:600;">Methylprednisolone</td><td>' + S.dStr(wt, mpn.dose_mg_per_kg, mpn.max_mg) + '</td><td>IV</td><td style="font-size:12px;color:var(--g500);">Alternative steroid</td></tr>';
|
||||
|
||||
html += '<tr style="background:#fef3c7;"><td style="font-weight:600;color:#dc2626;">IF REFRACTORY</td><td style="font-weight:600;">Epinephrine gtt</td><td>0.1-1 mcg/kg/min</td><td>IV infusion</td><td style="font-size:12px;color:var(--g500);">For persistent hypotension despite fluids + IM epi</td></tr>';
|
||||
html += '<tr style="background:#fef3c7;"><td></td><td style="font-weight:600;">Glucagon</td><td>' + S.dStr(wt, glc.dose_mg_per_kg, glc.max_mg) + '</td><td>IV/IM</td><td style="font-size:12px;color:var(--g500);">For patients on beta-blockers not responding to epi</td></tr>';
|
||||
|
||||
html += '</tbody></table></div>';
|
||||
html += '<div style="padding:10px 12px;background:var(--amber-light);border-radius:6px;font-size:12px;color:#92400e;"><strong>Observe minimum 4-6 hours</strong> after last dose of epinephrine (biphasic reactions occur in 5-20% of cases). Discharge with EpiPen prescription and anaphylaxis action plan. Refer to allergist.</div>';
|
||||
html += '<p style="font-size:11px;color:var(--g400);margin:8px 0 0;">ASCIA Anaphylaxis Guidelines 2021. WAO Anaphylaxis Guidance 2020. AAP/ACAAI Practice Parameters.</p>';
|
||||
resultDiv.innerHTML = html;
|
||||
}
|
||||
|
||||
export function init() {
|
||||
document.addEventListener('click', function(e) {
|
||||
if (e.target.id === 'btn-anaph-calc' || e.target.closest('#btn-anaph-calc')) calcAnaphylaxis();
|
||||
});
|
||||
}
|
||||
59
public/js/bedside/antiemetics.js
Normal file
59
public/js/bedside/antiemetics.js
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
// ============================================================
|
||||
// bedside/antiemetics.js
|
||||
// ANTIEMETICS — drugs.json-backed.
|
||||
// ============================================================
|
||||
|
||||
import { S } from './shared.js';
|
||||
|
||||
// Fallback inline data — used if /data/drugs.json hasn't loaded yet.
|
||||
// Must match drugs.json shape for the antiemetics section.
|
||||
var EMET_FALLBACK = [
|
||||
{ name: 'Ondansetron', dose_mg_per_kg: 0.15, max_mg: 8, unit: 'mg', route: 'PO / ODT / IV', notes: 'First-line. Max single 8 mg. Repeat q8h. QT prolongation — avoid with other QT drugs. <6 mo: limited data.', weight_band_dose: { under_15kg: '2 mg', '15_to_30kg': '4 mg', '30kg_or_more': '8 mg' } },
|
||||
{ name: 'Metoclopramide', dose_mg_per_kg: 0.15, max_mg: 10, unit: 'mg', route: 'IV / IM / PO', notes: '0.1-0.15 mg/kg (max 10 mg). Give with diphenhydramine to prevent EPS / dystonia.' },
|
||||
{ name: 'Dimenhydrinate', dose_mg_per_kg: 1.25, max_mg: 50, unit: 'mg', route: 'PO / IV / IM / PR', notes: '1.25 mg/kg (max 50 mg) q6h. ≥2 yr.' },
|
||||
{ name: 'Diphenhydramine', dose_mg_per_kg: 1, max_mg: 50, unit: 'mg', route: 'PO / IV / IM', notes: '1 mg/kg (max 50 mg) q6h. Adjunct, sedating.' },
|
||||
{ name: 'Promethazine', dose_mg_per_kg: 0.25, max_mg: 25, unit: 'mg', route: 'PO / IV / IM', notes: '0.25-1 mg/kg. <strong>CONTRAINDICATED <2 yr</strong> (resp depression). Tissue injury if IV extrav.' },
|
||||
{ name: 'Dexamethasone', dose_mg_per_kg: 0.15, max_mg: 10, unit: 'mg', route: 'IV / PO', notes: 'Adjunct, esp. chemo-induced. 0.15 mg/kg (max 10 mg).' },
|
||||
{ name: 'Scopolamine patch', dose_mg_per_kg: null, max_mg: null, unit: 'mg', dose_display: '1.5 mg patch', route: 'Transdermal', notes: '≥12 yr. Motion sickness. Apply 4h before exposure.' }
|
||||
];
|
||||
|
||||
// Prefer JSON-backed data if loaded; fall back to the inline list above.
|
||||
function emetDrugs() {
|
||||
var s = window._DRUGS && window._DRUGS.sections && window._DRUGS.sections.antiemetics;
|
||||
return (s && s.drugs && s.drugs.length) ? s.drugs : EMET_FALLBACK;
|
||||
}
|
||||
|
||||
// Iterate the drug list to produce S.drugRow(...) output, handling the
|
||||
// special cases (Ondansetron weight-band prefix, Scopolamine fixed dose).
|
||||
function emetRows(wt, drugs) {
|
||||
return drugs.map(function(dg) {
|
||||
var dose;
|
||||
if (dg.name === 'Ondansetron' && dg.weight_band_dose) {
|
||||
var bd = dg.weight_band_dose;
|
||||
var band = wt < 15 ? bd.under_15kg : wt < 30 ? bd['15_to_30kg'] : bd['30kg_or_more'];
|
||||
dose = band + ' (weight band) OR ' + S.dStr(wt, dg.dose_mg_per_kg, dg.max_mg, ' ' + dg.unit) + ' (' + dg.dose_mg_per_kg + ' ' + dg.unit + '/kg)';
|
||||
} else if (dg.dose_display) {
|
||||
dose = dg.dose_display;
|
||||
} else {
|
||||
dose = S.dStr(wt, dg.dose_mg_per_kg, dg.max_mg, ' ' + dg.unit);
|
||||
}
|
||||
return S.drugRow(dg.name, dose, dg.route, dg.notes);
|
||||
}).join('');
|
||||
}
|
||||
|
||||
function calcEmet() {
|
||||
var wt = parseFloat(document.getElementById('emet-weight').value);
|
||||
var el = document.getElementById('emet-result');
|
||||
if (!wt) { el.innerHTML = '<p style="color:var(--red);font-size:13px;">Enter weight (kg).</p>'; return; }
|
||||
var html = '<div style="padding:10px 14px;border-radius:8px;background:var(--blue-light);border:1.5px solid var(--blue);margin-bottom:10px;"><strong style="color:var(--blue-dark);">Antiemetic doses — ' + wt + ' kg</strong></div>';
|
||||
html += S.drugTable(emetRows(wt, emetDrugs()));
|
||||
html += '<div style="padding:10px;background:#fef3c7;border-radius:6px;font-size:12px;color:#92400e;margin-top:10px;"><strong>Pearls:</strong> Ondansetron is first-line in ED for acute gastroenteritis vomiting — single PO/ODT dose increases oral rehydration success. Avoid anticholinergics + opioid combinations. Check QTc if stacking ondansetron + other QT drugs.</div>';
|
||||
html += S.ref('AAP gastroenteritis guidance; WHO essential medicines; Lexicomp.');
|
||||
el.innerHTML = html;
|
||||
}
|
||||
|
||||
export function init() {
|
||||
document.addEventListener('click', function(e) {
|
||||
if (e.target.id === 'btn-emet-calc' || e.target.closest('#btn-emet-calc')) calcEmet();
|
||||
});
|
||||
}
|
||||
65
public/js/bedside/antimicrobials.js
Normal file
65
public/js/bedside/antimicrobials.js
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
// ============================================================
|
||||
// bedside/antimicrobials.js
|
||||
// ANTIMICROBIALS (empiric, by age + infection type).
|
||||
// ============================================================
|
||||
|
||||
import { S } from './shared.js';
|
||||
|
||||
var REG = {
|
||||
neonate: {
|
||||
sepsis: { first: 'Ampicillin + gentamicin', alt: 'Add cefotaxime if meningitis suspected or gram-neg concern', duration: '7-10 days; 21 days if confirmed meningitis', notes: 'Cover GBS, E. coli, Listeria. Add acyclovir if HSV risk (maternal lesions, vesicles, seizures).' },
|
||||
meningitis: { first: 'Ampicillin + cefotaxime (or gentamicin) + acyclovir', alt: 'Add vancomycin if gram-positive cocci', duration: '14-21 days depending on organism', notes: 'HSV coverage essential. LP + HSV PCR.' },
|
||||
pna: { first: 'Ampicillin + gentamicin', alt: 'Add cefotaxime for gram-neg', duration: '7-10 days', notes: 'Same as sepsis coverage.' },
|
||||
uti: { first: 'Ampicillin + gentamicin', alt: 'Cefotaxime if sensitivities known', duration: '10-14 days', notes: 'Evaluate for sepsis. Renal US and VCUG workup.' },
|
||||
skin: { first: 'Ampicillin + gentamicin', alt: 'Add vancomycin if MRSA risk', duration: '7-10 days', notes: 'Omphalitis, mastitis — broad coverage.' },
|
||||
ent: { first: 'Discuss with infectious diseases', alt: '—', duration: '—', notes: 'Rare in neonates. Any ear/throat infection warrants sepsis eval.' },
|
||||
neutropenic: { first: 'Cefepime or piperacillin-tazobactam', alt: 'Add vancomycin if indwelling line, severe mucositis, or MRSA', duration: 'Until ANC recovery + afebrile ≥48h', notes: 'Oncology/ID consult.' },
|
||||
ic: { first: 'Ampicillin + gentamicin + metronidazole', alt: 'Or piperacillin-tazobactam', duration: '7-14 days', notes: 'NEC: add metronidazole/clindamycin for anaerobes.' },
|
||||
bone: { first: 'Ampicillin + gentamicin', alt: 'Vancomycin if MRSA risk', duration: '3-6 weeks (IV then PO)', notes: 'Usually hematogenous. S. aureus, GBS, E. coli.' }
|
||||
},
|
||||
infant: {
|
||||
sepsis: { first: 'Ampicillin + ceftriaxone (or gentamicin)', alt: '+ vancomycin if MRSA / severe', duration: '7-10 days', notes: 'Add acyclovir if HSV suspected (<6 wks). Fever in <90 days requires workup.' },
|
||||
meningitis: { first: 'Ceftriaxone + vancomycin (± ampicillin if <6 wks for Listeria)', alt: '+ acyclovir if HSV', duration: '10-14 days (longer for Listeria/gram-neg)', notes: 'Dexamethasone if H. influenzae suspected.' },
|
||||
pna: { first: 'Ampicillin (or ceftriaxone)', alt: 'Add azithromycin if atypical', duration: '7-10 days', notes: 'S. pneumoniae most common. RSV/viral often primary.' },
|
||||
uti: { first: 'Ceftriaxone or cefotaxime', alt: 'Ampicillin + gentamicin', duration: '10-14 days', notes: 'US kidney/bladder if first febrile UTI.' },
|
||||
skin: { first: 'Cefazolin or clindamycin', alt: 'Vancomycin if MRSA risk', duration: '7-10 days', notes: 'Consider MRSA if purulent or local resistance >10%.' },
|
||||
ent: { first: 'Amoxicillin (90 mg/kg/day) for AOM', alt: 'Amox-clav if recent abx/treatment failure', duration: 'AOM 10 days <2 yr; 7 days ≥2 yr', notes: 'Observe mild AOM if >6 mo and no severe features.' },
|
||||
neutropenic: { first: 'Cefepime or piperacillin-tazobactam', alt: '+ vancomycin for severe/mucositis/line', duration: 'Until ANC recovery + afebrile', notes: 'Oncology/ID consult.' },
|
||||
ic: { first: 'Ceftriaxone + metronidazole', alt: 'Piperacillin-tazobactam', duration: '5-7 days (uncomplicated), longer for complicated', notes: 'Appendicitis — surgical consult.' },
|
||||
bone: { first: 'Cefazolin ± vancomycin', alt: 'Clindamycin', duration: '3-4 weeks (IV then PO transition)', notes: 'S. aureus most common. Kingella in <4 yr.' }
|
||||
},
|
||||
child: {
|
||||
sepsis: { first: 'Ceftriaxone + vancomycin', alt: 'Piperacillin-tazobactam if intra-abdominal', duration: '7-14 days', notes: 'Expand if specific source. Add antifungal if prolonged neutropenia.' },
|
||||
meningitis: { first: 'Ceftriaxone + vancomycin', alt: '+ dexamethasone (Hib coverage)', duration: '10-14 days', notes: 'Lumbar puncture. Dexamethasone 0.15 mg/kg q6h x 4 days.' },
|
||||
pna: { first: 'Ampicillin OR amoxicillin (high dose)', alt: 'Ceftriaxone if hospitalized; add azithromycin for atypical', duration: '5-7 days (uncomplicated)', notes: 'Mycoplasma if >5 yr. Consider viral causes.' },
|
||||
uti: { first: 'Cephalexin or TMP-SMX (PO)', alt: 'Ceftriaxone if ill', duration: '7-10 days (10-14 if pyelo)', notes: 'Culture-guided de-escalation.' },
|
||||
skin: { first: 'Cephalexin or clindamycin', alt: 'TMP-SMX or doxycycline if MRSA', duration: '5-10 days', notes: 'Abscess — incision & drainage primary tx.' },
|
||||
ent: { first: 'Amoxicillin 90 mg/kg/day (AOM); Penicillin V for strep pharyngitis', alt: 'Amox-clav; cephalexin if non-anaphylactic PCN allergy', duration: 'AOM 5-10 days; strep 10 days', notes: 'Strep pharyngitis dose: PCN V 250 mg BID-TID (<27 kg) or 500 mg BID (≥27 kg).' },
|
||||
neutropenic: { first: 'Cefepime OR piperacillin-tazobactam', alt: '+ vancomycin', duration: 'Until ANC recovery + afebrile', notes: 'Add empiric antifungal if fever >4-7 days.' },
|
||||
ic: { first: 'Ceftriaxone + metronidazole', alt: 'Piperacillin-tazobactam', duration: '5-7 days (uncomplicated appendicitis)', notes: 'Surgical consult.' },
|
||||
bone: { first: 'Cefazolin ± vancomycin', alt: 'Clindamycin', duration: '3-4 weeks total', notes: 'S. aureus, group A strep. Consider MRSA if severe.' }
|
||||
}
|
||||
};
|
||||
|
||||
function lookupAbx() {
|
||||
var age = document.getElementById('abx-age').value;
|
||||
var inf = document.getElementById('abx-infection').value;
|
||||
var el = document.getElementById('abx-result');
|
||||
var r = REG[age] && REG[age][inf];
|
||||
if (!r) { el.innerHTML = '<p style="color:var(--red);font-size:13px;">No regimen found.</p>'; return; }
|
||||
var ageLbl = { neonate: 'Neonate (0-28 d)', infant: 'Infant (1-3 mo)', child: 'Child (>3 mo)' }[age];
|
||||
var infLbl = document.querySelector('#abx-infection option[value="' + inf + '"]').textContent;
|
||||
var html = '<div style="padding:10px 14px;border-radius:8px;background:var(--blue-light);border:1.5px solid var(--blue);margin-bottom:10px;"><strong style="color:var(--blue-dark);">' + ageLbl + ' — ' + infLbl + '</strong></div>';
|
||||
html += '<div style="padding:12px;background:#d1fae5;border-radius:6px;margin-bottom:10px;"><strong style="color:#065f46;">First-line:</strong> ' + r.first + '</div>';
|
||||
html += '<div style="padding:12px;background:var(--g50);border-radius:6px;margin-bottom:10px;"><strong>Alternative / add:</strong> ' + r.alt + '</div>';
|
||||
html += '<div style="padding:12px;background:var(--g50);border-radius:6px;margin-bottom:10px;"><strong>Duration:</strong> ' + r.duration + '</div>';
|
||||
html += '<div style="padding:10px;background:#fef3c7;border-radius:6px;font-size:12px;color:#92400e;"><strong>Notes:</strong> ' + r.notes + '</div>';
|
||||
html += S.ref('AAP Red Book, IDSA guidelines, Harriet Lane. Always tailor to local resistance patterns and culture results.');
|
||||
el.innerHTML = html;
|
||||
}
|
||||
|
||||
export function init() {
|
||||
document.addEventListener('click', function(e) {
|
||||
if (e.target.id === 'btn-abx-lookup' || e.target.closest('#btn-abx-lookup')) lookupAbx();
|
||||
});
|
||||
}
|
||||
155
public/js/bedside/burns.js
Normal file
155
public/js/bedside/burns.js
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
// ============================================================
|
||||
// bedside/burns.js
|
||||
// BURNS — Lund-Browder body parts + Parkland formula.
|
||||
// ============================================================
|
||||
|
||||
import { S } from './shared.js';
|
||||
|
||||
// Lund-Browder age-adjusted region percentages. Regions marked "*" change with age.
|
||||
// Column order: infant, young (1-5), child (5-10), adol (10-15), adult (>15).
|
||||
var LB = {
|
||||
head: { label: 'Head', vals: [18, 13, 11, 9, 7], ageSensitive: true },
|
||||
neck: { label: 'Neck', vals: [2, 2, 2, 2, 2] },
|
||||
ant_trunk: { label: 'Anterior trunk', vals: [13, 13, 13, 13, 13] },
|
||||
post_trunk:{ label: 'Posterior trunk', vals: [13, 13, 13, 13, 13] },
|
||||
r_buttock: { label: 'Right buttock', vals: [2.5, 2.5, 2.5, 2.5, 2.5] },
|
||||
l_buttock: { label: 'Left buttock', vals: [2.5, 2.5, 2.5, 2.5, 2.5] },
|
||||
genital: { label: 'Genitalia', vals: [1, 1, 1, 1, 1] },
|
||||
r_uparm: { label: 'R upper arm', vals: [4, 4, 4, 4, 4] },
|
||||
l_uparm: { label: 'L upper arm', vals: [4, 4, 4, 4, 4] },
|
||||
r_forearm: { label: 'R forearm', vals: [3, 3, 3, 3, 3] },
|
||||
l_forearm: { label: 'L forearm', vals: [3, 3, 3, 3, 3] },
|
||||
r_hand: { label: 'R hand', vals: [2.5, 2.5, 2.5, 2.5, 2.5] },
|
||||
l_hand: { label: 'L hand', vals: [2.5, 2.5, 2.5, 2.5, 2.5] },
|
||||
r_thigh: { label: 'R thigh', vals: [5.5, 8, 8.5, 9, 9.5], ageSensitive: true },
|
||||
l_thigh: { label: 'L thigh', vals: [5.5, 8, 8.5, 9, 9.5], ageSensitive: true },
|
||||
r_leg: { label: 'R lower leg', vals: [5, 5.5, 6, 6.5, 7], ageSensitive: true },
|
||||
l_leg: { label: 'L lower leg', vals: [5, 5.5, 6, 6.5, 7], ageSensitive: true },
|
||||
r_foot: { label: 'R foot', vals: [3.5, 3.5, 3.5, 3.5, 3.5] },
|
||||
l_foot: { label: 'L foot', vals: [3.5, 3.5, 3.5, 3.5, 3.5] }
|
||||
};
|
||||
var AGE_KEYS = ['infant','young','child','adol','adult'];
|
||||
|
||||
function ageIdx() { return Math.max(0, AGE_KEYS.indexOf(document.getElementById('burn-age').value)); }
|
||||
|
||||
function renderBodyParts() {
|
||||
var wrap = document.getElementById('burn-bodyparts-wrapper');
|
||||
if (!wrap) return;
|
||||
var idx = ageIdx();
|
||||
var html = '<div style="background:var(--g50);border-radius:8px;padding:10px 12px;">';
|
||||
html += '<div style="font-size:12px;color:var(--g600);margin-bottom:8px;line-height:1.4;">For each involved body region, enter <strong>% of that region burned (2° or deeper)</strong>. Leave 0 if uninvolved. TBSA = sum of (region size × % involvement).</div>';
|
||||
html += '<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:6px;">';
|
||||
Object.keys(LB).forEach(function(key) {
|
||||
var r = LB[key];
|
||||
var max = r.vals[idx];
|
||||
var isAgeDep = r.ageSensitive;
|
||||
html += '<div style="display:flex;align-items:center;gap:8px;padding:4px 8px;background:white;border-radius:6px;border:1px solid var(--g200);">' +
|
||||
'<label style="flex:1;font-size:12px;color:var(--g700);">' + r.label + ' <span style="color:var(--g400);">(' + max + '%' + (isAgeDep ? '<span title="age-adjusted">*</span>' : '') + ')</span></label>' +
|
||||
'<input type="number" min="0" max="100" step="5" value="0" data-burn-region="' + key + '" data-burn-max="' + max + '" style="width:68px;padding:3px 6px;border:1px solid var(--g300);border-radius:4px;font-size:12px;text-align:right;"> %' +
|
||||
'</div>';
|
||||
});
|
||||
html += '</div></div>';
|
||||
wrap.innerHTML = html;
|
||||
updateLive();
|
||||
}
|
||||
|
||||
function resetParts() {
|
||||
var inputs = document.querySelectorAll('[data-burn-region]');
|
||||
inputs.forEach(function(i) { i.value = 0; });
|
||||
document.getElementById('burn-tbsa').value = '';
|
||||
updateLive();
|
||||
}
|
||||
|
||||
function computeTbsa() {
|
||||
var idx = ageIdx();
|
||||
var total = 0;
|
||||
Object.keys(LB).forEach(function(key) {
|
||||
var el = document.querySelector('[data-burn-region="' + key + '"]');
|
||||
if (!el) return;
|
||||
var pct = parseFloat(el.value) || 0;
|
||||
if (pct < 0) pct = 0; if (pct > 100) pct = 100;
|
||||
total += LB[key].vals[idx] * (pct / 100);
|
||||
});
|
||||
return Math.round(total * 10) / 10;
|
||||
}
|
||||
|
||||
function updateLive() {
|
||||
var t = computeTbsa();
|
||||
var live = document.getElementById('burn-tbsa-live');
|
||||
if (live) live.textContent = t > 0 ? 'Computed TBSA: ' + t + '%' : '';
|
||||
}
|
||||
|
||||
function calcBurn() {
|
||||
var wt = parseFloat(document.getElementById('burn-weight').value);
|
||||
var override = document.getElementById('burn-tbsa').value;
|
||||
var tbsa = override !== '' ? parseFloat(override) : computeTbsa();
|
||||
var el = document.getElementById('burn-result');
|
||||
if (!wt) { el.innerHTML = '<p style="color:var(--red);font-size:13px;">Enter weight (kg).</p>'; return; }
|
||||
if (!tbsa || tbsa <= 0) { el.innerHTML = '<p style="color:var(--red);font-size:13px;">Enter % involvement per region, or override TBSA.</p>'; return; }
|
||||
var total = Math.round(4 * wt * tbsa);
|
||||
var first8 = Math.round(total / 2);
|
||||
var rateFirst = Math.round(first8 / 8);
|
||||
var next16 = total - first8;
|
||||
var rateNext = Math.round(next16 / 16);
|
||||
var maint = wt <= 10 ? wt * 4 : wt <= 20 ? 40 + (wt - 10) * 2 : 60 + (wt - 20);
|
||||
maint = Math.round(maint);
|
||||
|
||||
var html = '<div style="padding:10px 14px;border-radius:8px;background:#fee2e2;border:1.5px solid #ef4444;margin-bottom:10px;"><strong style="color:#dc2626;">Burn fluid resuscitation — ' + wt + ' kg, ' + tbsa + '% TBSA (2° or deeper)</strong></div>';
|
||||
html += '<div style="padding:12px;background:#fef2f2;border-radius:6px;margin-bottom:10px;"><strong>Parkland formula:</strong> 4 mL × kg × %TBSA = <strong>' + total + ' mL LR over 24 hours</strong>' +
|
||||
'<br> <strong>First 8 h</strong> (from time of burn): ' + first8 + ' mL (~<strong>' + rateFirst + ' mL/hr</strong>)' +
|
||||
'<br> <strong>Next 16 h</strong>: ' + next16 + ' mL (~<strong>' + rateNext + ' mL/hr</strong>)</div>';
|
||||
html += '<div style="padding:12px;background:var(--g50);border-radius:6px;margin-bottom:10px;"><strong>Plus maintenance fluids (4-2-1):</strong> ' + maint + ' mL/hr (D5 1/2NS ± 20 mEq KCl/L once UOP established). Consider dextrose in children <30 kg.</div>';
|
||||
html += '<div style="padding:12px;background:var(--g50);border-radius:6px;margin-bottom:10px;"><strong>Titrate to UOP:</strong> target 1-2 mL/kg/hr (infants / children), 0.5-1 mL/kg/hr (adolescents). <strong>Clinical response trumps formula.</strong></div>';
|
||||
|
||||
// Breakdown of what regions contributed
|
||||
var idx = ageIdx();
|
||||
var breakdown = '';
|
||||
Object.keys(LB).forEach(function(key) {
|
||||
var el2 = document.querySelector('[data-burn-region="' + key + '"]');
|
||||
if (!el2) return;
|
||||
var pct = parseFloat(el2.value) || 0;
|
||||
if (pct > 0) {
|
||||
var contrib = Math.round(LB[key].vals[idx] * pct / 100 * 10) / 10;
|
||||
breakdown += '<div>' + LB[key].label + ': ' + pct + '% of ' + LB[key].vals[idx] + '% = ' + contrib + '%</div>';
|
||||
}
|
||||
});
|
||||
if (breakdown) {
|
||||
html += '<details style="margin-bottom:10px;"><summary style="cursor:pointer;font-size:13px;font-weight:600;color:var(--g700);">Region breakdown</summary><div style="padding:10px;background:var(--g50);border-radius:6px;font-size:12px;margin-top:6px;">' + breakdown + '</div></details>';
|
||||
}
|
||||
|
||||
html += '<h5 style="font-size:13px;font-weight:700;color:var(--g700);margin:14px 0 6px;">Other pearls</h5>';
|
||||
html += '<div style="padding:10px;background:var(--g50);border-radius:6px;font-size:12px;">';
|
||||
html += '<strong>Rule of palm:</strong> Patient\'s palm + fingers ≈ 1% TBSA — good for scattered burns.<br>';
|
||||
html += '<strong>First-degree burns DO NOT count</strong> toward TBSA or Parkland.<br>';
|
||||
html += '<strong>Analgesia:</strong> Morphine 0.05-0.1 mg/kg IV q2h, or fentanyl 1-2 mcg/kg IV q30-60 min.<br>';
|
||||
html += '<strong>Tetanus</strong> prophylaxis if indicated. Tdap/Td ± tetanus immunoglobulin.';
|
||||
html += '</div>';
|
||||
|
||||
html += '<h5 style="font-size:13px;font-weight:700;color:var(--g700);margin:14px 0 6px;">Burn center referral (ABA)</h5>';
|
||||
html += '<div style="padding:10px;background:#fef3c7;border-radius:6px;font-size:12px;color:#92400e;">Partial-thickness >10% TBSA; any full-thickness; face/hands/feet/genital/perineum/major joints; electrical/chemical/inhalation; associated trauma; significant comorbidities; pediatric burns in non-pediatric center.</div>';
|
||||
html += S.ref('ABA Advanced Burn Life Support (ABLS) 2018. Parkland formula: Baxter 1968. Lund-Browder 1944 chart (age-adjusted regions).');
|
||||
el.innerHTML = html;
|
||||
}
|
||||
|
||||
export function init() {
|
||||
document.addEventListener('click', function(e) {
|
||||
if (e.target.id === 'btn-burn-calc' || e.target.closest('#btn-burn-calc')) calcBurn();
|
||||
if (e.target.id === 'btn-burn-reset' || e.target.closest('#btn-burn-reset')) resetParts();
|
||||
// Render body-parts table the first (or any) time the Burns sub-pill is activated
|
||||
var bp = e.target.closest && e.target.closest('[data-em="burns"]');
|
||||
if (bp) setTimeout(renderBodyParts, 0);
|
||||
// Also render if the top-level Bedside tab is clicked (ensures table ready before user navigates)
|
||||
var nav = e.target.closest && e.target.closest('[data-calc="bedside"]');
|
||||
if (nav) setTimeout(renderBodyParts, 50);
|
||||
});
|
||||
document.addEventListener('change', function(e) {
|
||||
if (e.target && e.target.id === 'burn-age') renderBodyParts();
|
||||
});
|
||||
document.addEventListener('input', function(e) {
|
||||
if (e.target && e.target.dataset && e.target.dataset.burnRegion) updateLive();
|
||||
});
|
||||
// Also attempt render on these events in case component mounts later
|
||||
document.addEventListener('DOMContentLoaded', renderBodyParts);
|
||||
document.addEventListener('tabChanged', function() { setTimeout(renderBodyParts, 100); });
|
||||
if (document.readyState !== 'loading') setTimeout(renderBodyParts, 0);
|
||||
}
|
||||
103
public/js/bedside/cardiac.js
Normal file
103
public/js/bedside/cardiac.js
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
// ============================================================
|
||||
// bedside/cardiac.js
|
||||
// CARDIAC ARREST / PALS.
|
||||
// ============================================================
|
||||
|
||||
import { S } from './shared.js';
|
||||
|
||||
function getWt() {
|
||||
return parseFloat(document.getElementById('cardiac-weight').value) || 0;
|
||||
}
|
||||
|
||||
function title(k) {
|
||||
return { general: 'PALS General Doses', asystole: 'Asystole / PEA', brady: 'Bradycardia', svt: 'SVT', vfib: 'VF / Pulseless VT', vt: 'Stable VT' }[k] || k;
|
||||
}
|
||||
|
||||
function cardiacShow(kind) {
|
||||
var wt = getWt();
|
||||
var el = document.getElementById('cardiac-result');
|
||||
if (!wt) { el.innerHTML = '<p style="color:var(--red);font-size:13px;">Enter weight (kg).</p>'; return; }
|
||||
var html = '<div style="padding:10px 14px;border-radius:8px;background:var(--red)10;border:1.5px solid var(--red);margin-bottom:10px;"><strong style="color:var(--red);">' + title(kind) + ' — ' + wt + ' kg</strong></div>';
|
||||
|
||||
if (kind === 'general') {
|
||||
html += S.drugTable(
|
||||
S.drugRow('Epinephrine 1:10,000', S.dStr(wt, 0.01, 1, ' mg') + ' (' + S.dStr(wt, 0.1, 10, ' mL') + ')', 'IV / IO', '0.01 mg/kg = 0.1 mL/kg of 1:10,000. Max single dose 1 mg. Repeat q3-5 min.') +
|
||||
S.drugRow('Epinephrine ET', S.dStr(wt, 0.1, 2.5, ' mg'), 'ETT', '0.1 mg/kg (1 mL/kg of 1:10,000) if no IV/IO') +
|
||||
S.drugRow('Amiodarone', S.dStr(wt, 5, 300, ' mg'), 'IV / IO bolus', '5 mg/kg for arrest. Max 300 mg. Repeat up to 2 times (max 15 mg/kg/day).') +
|
||||
S.drugRow('Lidocaine', S.dStr(wt, 1, 100, ' mg'), 'IV / IO bolus', '1 mg/kg. Alternative to amiodarone for VF/VT.') +
|
||||
S.drugRow('Atropine', S.dStr(wt, 0.02, 0.5, ' mg') + ' (min 0.1 mg)', 'IV / IO', '0.02 mg/kg. Bradycardia from ↑vagal tone or AV block.') +
|
||||
S.drugRow('Adenosine (1st)', S.dStr(wt, 0.1, 6, ' mg'), 'IV push + flush', '0.1 mg/kg (max 6 mg). For SVT. Rapid push, double flush.') +
|
||||
S.drugRow('Adenosine (2nd)', S.dStr(wt, 0.2, 12, ' mg'), 'IV push + flush', '0.2 mg/kg (max 12 mg). Second dose if first unsuccessful.') +
|
||||
S.drugRow('Sodium bicarb 8.4%', S.dStr(wt, 1, 50, ' mEq'), 'IV / IO', '1 mEq/kg. ONLY if severe metabolic acidosis, hyperK, or TCA OD. Not routine.') +
|
||||
S.drugRow('Calcium chloride 10%', S.dStr(wt, 20, 1000, ' mg') + ' (' + S.dStr(wt, 0.2, 10, ' mL') + ')', 'IV / IO (central preferred)', 'For hyperK, hypoCa, Mg OD, CCB OD. 20 mg/kg = 0.2 mL/kg.') +
|
||||
S.drugRow('Calcium gluconate 10%', S.dStr(wt, 60, 3000, ' mg') + ' (' + S.dStr(wt, 0.6, 30, ' mL') + ')', 'IV / IO (peripheral OK)', '60 mg/kg = 0.6 mL/kg. Preferred peripherally.') +
|
||||
S.drugRow('Magnesium sulfate', S.dStr(wt, 50, 2000, ' mg'), 'IV over 10-20 min', '25-50 mg/kg. Torsades, severe asthma. Max 2 g.') +
|
||||
S.drugRow('Dextrose 10%', S.dStr(wt, 2, null, ' mL') + ' (0.2 g/kg)', 'IV push', 'For documented hypoglycemia') +
|
||||
S.drugRow('Defibrillation', '2 J/kg → 4 J/kg → 10 J/kg (max 10 J/kg or adult dose)', 'Pad', 'For VF/pulseless VT. Resume CPR immediately after shock.') +
|
||||
S.drugRow('Cardioversion (sync)', '0.5-1 J/kg → 2 J/kg', 'Pad', 'For unstable SVT/VT. Sedate if possible.')
|
||||
);
|
||||
html += '<div style="padding:10px;background:#fef3c7;border-radius:6px;font-size:12px;color:#92400e;margin-top:10px;"><strong>Concentrations:</strong> Epi 1:10,000 (0.1 mg/mL) for IV arrest. 1:1000 (1 mg/mL) for IM anaphylaxis. Atropine <0.1 mg can cause paradoxical bradycardia.</div>';
|
||||
} else if (kind === 'asystole') {
|
||||
html += S.drugTable(
|
||||
S.drugRow('CPR', '100-120/min, depth 1/3 AP', '—', '15:2 (2 rescuer) or 30:2 (single). Rotate every 2 min.') +
|
||||
S.drugRow('Epinephrine', S.dStr(wt, 0.01, 1, ' mg') + ' IV/IO', 'IV / IO', '0.01 mg/kg 1:10,000 = 0.1 mL/kg. Q3-5 min. Start early.') +
|
||||
S.drugRow('Epinephrine ETT', S.dStr(wt, 0.1, 2.5, ' mg'), 'ETT', '0.1 mg/kg if no IV access')
|
||||
);
|
||||
html += '<h5 style="font-size:13px;font-weight:700;color:var(--g700);margin:14px 0 6px;">Reversible causes (H\'s & T\'s)</h5>';
|
||||
html += '<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:6px;font-size:12px;">';
|
||||
html += '<div style="padding:8px;background:var(--g50);border-radius:6px;"><strong>H\'s</strong><br>Hypoxia, Hypovolemia, H+ (acidosis), Hypo/hyperK, Hypoglycemia, Hypothermia</div>';
|
||||
html += '<div style="padding:8px;background:var(--g50);border-radius:6px;"><strong>T\'s</strong><br>Tension PTX, Tamponade, Toxins, Thrombosis (MI/PE), Trauma</div>';
|
||||
html += '</div>';
|
||||
} else if (kind === 'brady') {
|
||||
html += S.stepBox('#f59e0b', 'Bradycardia with poor perfusion (HR < 60)', 'Start CPR if HR < 60 with poor perfusion despite oxygenation and ventilation.');
|
||||
html += S.arrow;
|
||||
html += S.drugTable(
|
||||
S.drugRow('Epinephrine', S.dStr(wt, 0.01, 1, ' mg'), 'IV / IO', 'First-line. Q3-5 min.') +
|
||||
S.drugRow('Atropine', S.dStr(wt, 0.02, 0.5, ' mg') + ' (min 0.1 mg)', 'IV / IO', 'If ↑ vagal tone or AV block. Max 1 mg child / 0.5 mg infant.') +
|
||||
S.drugRow('Transcutaneous pacing', '—', 'Pad', 'For refractory bradycardia not responsive to drugs.') +
|
||||
S.drugRow('Consider', '—', '—', 'Hypoxia, tension pneumothorax, ↑ICP, toxic ingestion (organo, CCB, BB), heart block')
|
||||
);
|
||||
} else if (kind === 'svt') {
|
||||
html += S.stepBox('#3b82f6', 'SVT — narrow complex, rate usually >220 infant / >180 child', 'Differentiate from sinus tach: abrupt onset/offset, no P waves or abnormal P axis, HR minimally variable.');
|
||||
html += S.arrow;
|
||||
html += '<h5 style="font-size:13px;font-weight:700;color:var(--g700);margin:6px 0;">If STABLE</h5>';
|
||||
html += S.drugTable(
|
||||
S.drugRow('Vagal maneuvers', 'Ice to face, blow into syringe, Valsalva', '—', 'Try first in stable patient') +
|
||||
S.drugRow('Adenosine (1st)', S.dStr(wt, 0.1, 6, ' mg'), 'IV push (proximal) + flush', 'Rapid push, 3-way stopcock with NS flush') +
|
||||
S.drugRow('Adenosine (2nd)', S.dStr(wt, 0.2, 12, ' mg'), 'IV push + flush', 'If first dose unsuccessful') +
|
||||
S.drugRow('Procainamide', '15 mg/kg over 30-60 min', 'IV', 'Expert consult. Avoid with amiodarone.') +
|
||||
S.drugRow('Amiodarone', '5 mg/kg over 20-60 min', 'IV', 'Expert consult.')
|
||||
);
|
||||
html += '<h5 style="font-size:13px;font-weight:700;color:var(--g700);margin:10px 0 4px;">If UNSTABLE</h5>';
|
||||
html += S.drugTable(S.drugRow('Synchronized cardioversion', '0.5-1 J/kg → 2 J/kg', 'Pad', 'Sedate if possible. Pre-treat with adenosine if IV available.'));
|
||||
} else if (kind === 'vfib') {
|
||||
html += S.stepBox('#dc2626', 'VF / Pulseless VT', 'High-quality CPR + defibrillation are key. Minimize interruptions.');
|
||||
html += S.arrow;
|
||||
html += S.drugTable(
|
||||
S.drugRow('Defibrillation (1st)', '2 J/kg', 'Pad', 'Resume CPR immediately after shock.') +
|
||||
S.drugRow('Defibrillation (2nd)', '4 J/kg', 'Pad', 'After 2 min CPR.') +
|
||||
S.drugRow('Defibrillation (3rd+)', '≥4 J/kg (max 10 J/kg or adult dose)', 'Pad', '') +
|
||||
S.drugRow('Epinephrine', S.dStr(wt, 0.01, 1, ' mg'), 'IV / IO', 'After 2nd shock. Q3-5 min.') +
|
||||
S.drugRow('Amiodarone', S.dStr(wt, 5, 300, ' mg'), 'IV / IO bolus', 'Refractory VF/VT. Max 15 mg/kg/day.') +
|
||||
S.drugRow('Lidocaine', S.dStr(wt, 1, 100, ' mg'), 'IV / IO', 'Alternative to amiodarone. Then 20-50 mcg/kg/min gtt.') +
|
||||
S.drugRow('Mg sulfate', S.dStr(wt, 50, 2000, ' mg'), 'IV over 1-2 min', 'For torsades or hypoMg.')
|
||||
);
|
||||
} else if (kind === 'vt') {
|
||||
html += S.stepBox('#f59e0b', 'Stable monomorphic VT', 'Expert consult. Avoid combining QT-prolonging agents.');
|
||||
html += S.arrow;
|
||||
html += S.drugTable(
|
||||
S.drugRow('Amiodarone', '5 mg/kg over 20-60 min', 'IV', '= ' + S.d(wt, 5, 300) + ' mg over 20-60 min') +
|
||||
S.drugRow('Procainamide', '15 mg/kg over 30-60 min', 'IV', 'Do not combine with amiodarone') +
|
||||
S.drugRow('Sync cardioversion', '0.5-1 → 2 J/kg', 'Pad', 'If becomes unstable. Sedate.')
|
||||
);
|
||||
}
|
||||
html += S.ref('AHA PALS Guidelines 2020. Always verify against institutional protocols.');
|
||||
el.innerHTML = html;
|
||||
}
|
||||
|
||||
export function init() {
|
||||
document.addEventListener('click', function(e) {
|
||||
var btn = e.target.closest('[data-cardiac]');
|
||||
if (btn) cardiacShow(btn.dataset.cardiac);
|
||||
});
|
||||
}
|
||||
40
public/js/bedside/image-lightbox.js
Normal file
40
public/js/bedside/image-lightbox.js
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
// ============================================================
|
||||
// bedside/image-lightbox.js
|
||||
// IMAGE LIGHTBOX — any element with data-img-src opens fullscreen.
|
||||
// ============================================================
|
||||
|
||||
export function init() {
|
||||
document.addEventListener('click', function(e) {
|
||||
var opener = e.target.closest && e.target.closest('[data-img-src]');
|
||||
if (opener) {
|
||||
e.preventDefault();
|
||||
var lb = document.getElementById('img-lightbox');
|
||||
if (!lb) return;
|
||||
var src = opener.dataset.imgSrc;
|
||||
var title = opener.dataset.imgTitle || '';
|
||||
document.getElementById('img-lightbox-img').src = src;
|
||||
document.getElementById('img-lightbox-img').alt = title;
|
||||
document.getElementById('img-lightbox-title').textContent = title;
|
||||
document.getElementById('img-lightbox-open').href = src;
|
||||
lb.style.display = 'flex';
|
||||
return;
|
||||
}
|
||||
// Close on close button, on backdrop click (not on image), and on Escape (below)
|
||||
var closeBtn = e.target.closest && e.target.closest('#img-lightbox-close');
|
||||
var lb2 = document.getElementById('img-lightbox');
|
||||
if (!lb2 || lb2.style.display !== 'flex') return;
|
||||
if (closeBtn || e.target === lb2) {
|
||||
lb2.style.display = 'none';
|
||||
document.getElementById('img-lightbox-img').src = '';
|
||||
}
|
||||
});
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Escape') {
|
||||
var lb = document.getElementById('img-lightbox');
|
||||
if (lb && lb.style.display === 'flex') {
|
||||
lb.style.display = 'none';
|
||||
document.getElementById('img-lightbox-img').src = '';
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
46
public/js/bedside/index.js
Normal file
46
public/js/bedside/index.js
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
// ============================================================
|
||||
// bedside/index.js
|
||||
// Imports + registers all Bedside section modules on load.
|
||||
// Loaded as <script type="module"> from index.html and e2e-harness.html.
|
||||
// ============================================================
|
||||
|
||||
import './shared.js'; // side-effect: sets window._EM for back-compat
|
||||
import * as ageWeight from './age-weight.js';
|
||||
import * as subNav from './sub-nav.js';
|
||||
import * as lightbox from './image-lightbox.js';
|
||||
import * as neonatal from './neonatal.js';
|
||||
import * as airway from './airway.js';
|
||||
import * as cardiac from './cardiac.js';
|
||||
import * as respiratory from './respiratory.js';
|
||||
import * as ventilation from './ventilation.js';
|
||||
import * as seizure from './seizure.js';
|
||||
import * as sepsis from './sepsis.js';
|
||||
import * as anaphylaxis from './anaphylaxis.js';
|
||||
import * as sedation from './sedation.js';
|
||||
import * as agitation from './agitation.js';
|
||||
import * as antiemetics from './antiemetics.js';
|
||||
import * as antimicrobials from './antimicrobials.js';
|
||||
import * as burns from './burns.js';
|
||||
import * as toxicology from './toxicology.js';
|
||||
import * as trauma from './trauma.js';
|
||||
|
||||
[
|
||||
ageWeight,
|
||||
subNav,
|
||||
lightbox,
|
||||
neonatal,
|
||||
airway,
|
||||
cardiac,
|
||||
respiratory,
|
||||
ventilation,
|
||||
seizure,
|
||||
sepsis,
|
||||
anaphylaxis,
|
||||
sedation,
|
||||
agitation,
|
||||
antiemetics,
|
||||
antimicrobials,
|
||||
burns,
|
||||
toxicology,
|
||||
trauma,
|
||||
].forEach(function(m) { if (m && typeof m.init === 'function') m.init(); });
|
||||
247
public/js/bedside/neonatal.js
Normal file
247
public/js/bedside/neonatal.js
Normal file
|
|
@ -0,0 +1,247 @@
|
|||
// ============================================================
|
||||
// bedside/neonatal.js
|
||||
// NEONATAL ASSESSMENT (Fenton growth) + NRP pathway + Apgar.
|
||||
// ============================================================
|
||||
|
||||
import { S } from './shared.js';
|
||||
|
||||
// Fenton 2013 LMS data (weight in grams, GA in weeks)
|
||||
var fentonLMS = {
|
||||
male: {
|
||||
22:{L:0.21,M:496,S:0.17},23:{L:0.21,M:575,S:0.17},24:{L:0.21,M:660,S:0.17},25:{L:0.21,M:762,S:0.16},
|
||||
26:{L:0.21,M:870,S:0.16},27:{L:0.20,M:993,S:0.15},28:{L:0.20,M:1124,S:0.15},29:{L:0.19,M:1272,S:0.14},
|
||||
30:{L:0.18,M:1430,S:0.14},31:{L:0.17,M:1607,S:0.14},32:{L:0.15,M:1795,S:0.14},33:{L:0.13,M:2008,S:0.13},
|
||||
34:{L:0.12,M:2230,S:0.13},35:{L:0.10,M:2467,S:0.13},36:{L:0.08,M:2710,S:0.13},37:{L:0.06,M:2948,S:0.12},
|
||||
38:{L:0.04,M:3195,S:0.12},39:{L:0.02,M:3380,S:0.12},40:{L:0.01,M:3530,S:0.12},41:{L:0.00,M:3660,S:0.12},
|
||||
42:{L:-0.02,M:3820,S:0.12}
|
||||
},
|
||||
female: {
|
||||
22:{L:0.23,M:474,S:0.17},23:{L:0.22,M:538,S:0.17},24:{L:0.22,M:610,S:0.17},25:{L:0.22,M:705,S:0.16},
|
||||
26:{L:0.22,M:810,S:0.16},27:{L:0.21,M:920,S:0.15},28:{L:0.21,M:1040,S:0.15},29:{L:0.20,M:1178,S:0.14},
|
||||
30:{L:0.19,M:1330,S:0.14},31:{L:0.18,M:1500,S:0.14},32:{L:0.16,M:1680,S:0.14},33:{L:0.14,M:1880,S:0.13},
|
||||
34:{L:0.12,M:2090,S:0.13},35:{L:0.10,M:2310,S:0.13},36:{L:0.08,M:2540,S:0.13},37:{L:0.06,M:2766,S:0.12},
|
||||
38:{L:0.04,M:3000,S:0.12},39:{L:0.02,M:3180,S:0.12},40:{L:0.01,M:3340,S:0.12},41:{L:0.00,M:3480,S:0.12},
|
||||
42:{L:-0.02,M:3630,S:0.12}
|
||||
}
|
||||
};
|
||||
|
||||
function interpolateLMS(table, val) {
|
||||
var keys = Object.keys(table).map(Number).sort(function(a,b){return a-b;});
|
||||
if (val <= keys[0]) return table[keys[0]];
|
||||
if (val >= keys[keys.length-1]) return table[keys[keys.length-1]];
|
||||
for (var i = 0; i < keys.length - 1; i++) {
|
||||
if (val >= keys[i] && val <= keys[i+1]) {
|
||||
var t = (val - keys[i]) / (keys[i+1] - keys[i]);
|
||||
var a = table[keys[i]], b = table[keys[i+1]];
|
||||
return { L: a.L + t*(b.L-a.L), M: a.M + t*(b.M-a.M), S: a.S + t*(b.S-a.S) };
|
||||
}
|
||||
}
|
||||
return table[keys[0]];
|
||||
}
|
||||
|
||||
function calcZ(value, L, M, S) {
|
||||
if (Math.abs(L) < 0.001) return Math.log(value / M) / S;
|
||||
return (Math.pow(value / M, L) - 1) / (L * S);
|
||||
}
|
||||
|
||||
function zToPercentile(z) {
|
||||
// Approximation of the standard normal CDF
|
||||
var a1=0.254829592, a2=-0.284496736, a3=1.421413741, a4=-1.453152027, a5=1.061405429, p=0.3275911;
|
||||
var sign = z < 0 ? -1 : 1;
|
||||
var x = Math.abs(z) / Math.sqrt(2);
|
||||
var t = 1 / (1 + p * x);
|
||||
var y = 1 - (((((a5*t + a4)*t) + a3)*t + a2)*t + a1)*t * Math.exp(-x*x);
|
||||
return Math.round(((1 + sign * y) / 2) * 1000) / 10;
|
||||
}
|
||||
|
||||
function classifyGA(weeks, days) {
|
||||
var total = weeks + (days || 0) / 7;
|
||||
if (total < 28) return { label: 'Extremely Preterm', color: '#dc2626', icon: 'fa-triangle-exclamation' };
|
||||
if (total < 32) return { label: 'Very Preterm', color: '#ea580c', icon: 'fa-triangle-exclamation' };
|
||||
if (total < 34) return { label: 'Moderate Preterm', color: '#d97706', icon: 'fa-circle-exclamation' };
|
||||
if (total < 37) return { label: 'Late Preterm', color: '#ca8a04', icon: 'fa-circle-info' };
|
||||
if (total < 39) return { label: 'Early Term', color: '#2563eb', icon: 'fa-circle-info' };
|
||||
if (total < 41) return { label: 'Full Term', color: '#16a34a', icon: 'fa-circle-check' };
|
||||
if (total < 42) return { label: 'Late Term', color: '#d97706', icon: 'fa-circle-info' };
|
||||
return { label: 'Post Term', color: '#dc2626', icon: 'fa-triangle-exclamation' };
|
||||
}
|
||||
|
||||
function classifyWeight(percentile) {
|
||||
if (percentile < 3) return { label: 'Severely SGA', color: '#dc2626', detail: '<3rd percentile' };
|
||||
if (percentile < 10) return { label: 'SGA', color: '#ea580c', detail: '<10th percentile' };
|
||||
if (percentile > 97) return { label: 'Severely LGA', color: '#dc2626', detail: '>97th percentile' };
|
||||
if (percentile > 90) return { label: 'LGA', color: '#ea580c', detail: '>90th percentile' };
|
||||
return { label: 'AGA', color: '#16a34a', detail: '10th-90th percentile' };
|
||||
}
|
||||
|
||||
function classifyBirthWeight(grams) {
|
||||
if (grams < 1000) return { label: 'Extremely Low Birth Weight (ELBW)', color: '#dc2626' };
|
||||
if (grams < 1500) return { label: 'Very Low Birth Weight (VLBW)', color: '#ea580c' };
|
||||
if (grams < 2500) return { label: 'Low Birth Weight (LBW)', color: '#d97706' };
|
||||
if (grams <= 4000) return { label: 'Normal Birth Weight', color: '#16a34a' };
|
||||
return { label: 'Macrosomia (>4000g)', color: '#ea580c' };
|
||||
}
|
||||
|
||||
function neoAssess() {
|
||||
var weeks = parseInt(document.getElementById('neo-ga-weeks').value);
|
||||
var days = parseInt(document.getElementById('neo-ga-days').value) || 0;
|
||||
var weight = parseFloat(document.getElementById('neo-weight').value);
|
||||
var sex = document.getElementById('neo-sex').value;
|
||||
var resultDiv = document.getElementById('neo-result');
|
||||
|
||||
if (!weeks || !weight) {
|
||||
resultDiv.innerHTML = '<p style="color:var(--red);font-size:13px;">Enter GA and birth weight.</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
var gaDecimal = weeks + days / 7;
|
||||
var gaClass = classifyGA(weeks, days);
|
||||
var bwClass = classifyBirthWeight(weight);
|
||||
|
||||
// Calculate percentile using Fenton LMS
|
||||
var lms = interpolateLMS(fentonLMS[sex], gaDecimal);
|
||||
var z = calcZ(weight, lms.L, lms.M, lms.S);
|
||||
var percentile = zToPercentile(z);
|
||||
var wtClass = classifyWeight(percentile);
|
||||
var expectedWeight = Math.round(lms.M);
|
||||
|
||||
var html = '';
|
||||
|
||||
// GA Classification
|
||||
html += '<div style="display:flex;gap:12px;flex-wrap:wrap;margin-bottom:12px;">';
|
||||
html += '<div style="flex:1;min-width:200px;padding:14px;border-radius:10px;background:' + gaClass.color + '10;border:1.5px solid ' + gaClass.color + '30;">';
|
||||
html += '<div style="font-size:12px;color:var(--g500);margin-bottom:4px;">Gestational Age Classification</div>';
|
||||
html += '<div style="font-size:18px;font-weight:700;color:' + gaClass.color + ';"><i class="fas ' + gaClass.icon + '"></i> ' + gaClass.label + '</div>';
|
||||
html += '<div style="font-size:13px;color:var(--g600);margin-top:4px;">' + weeks + ' weeks ' + days + ' days (' + gaDecimal.toFixed(1) + ' weeks)</div>';
|
||||
html += '</div>';
|
||||
|
||||
// Weight Classification (AGA/SGA/LGA)
|
||||
html += '<div style="flex:1;min-width:200px;padding:14px;border-radius:10px;background:' + wtClass.color + '10;border:1.5px solid ' + wtClass.color + '30;">';
|
||||
html += '<div style="font-size:12px;color:var(--g500);margin-bottom:4px;">Weight for Gestational Age</div>';
|
||||
html += '<div style="font-size:18px;font-weight:700;color:' + wtClass.color + ';">' + wtClass.label + '</div>';
|
||||
html += '<div style="font-size:13px;color:var(--g600);margin-top:4px;">' + percentile.toFixed(1) + 'th percentile (' + wtClass.detail + ')</div>';
|
||||
html += '</div>';
|
||||
html += '</div>';
|
||||
|
||||
// Birth Weight Category
|
||||
html += '<div style="display:flex;gap:12px;flex-wrap:wrap;margin-bottom:12px;">';
|
||||
html += '<div style="flex:1;min-width:200px;padding:14px;border-radius:10px;background:' + bwClass.color + '10;border:1.5px solid ' + bwClass.color + '30;">';
|
||||
html += '<div style="font-size:12px;color:var(--g500);margin-bottom:4px;">Birth Weight Category</div>';
|
||||
html += '<div style="font-size:18px;font-weight:700;color:' + bwClass.color + ';">' + bwClass.label + '</div>';
|
||||
html += '<div style="font-size:13px;color:var(--g600);margin-top:4px;">' + weight + 'g (' + (weight/1000).toFixed(2) + ' kg)</div>';
|
||||
html += '</div>';
|
||||
|
||||
// Stats
|
||||
html += '<div style="flex:1;min-width:200px;padding:14px;border-radius:10px;background:var(--g50);border:1.5px solid var(--g200);">';
|
||||
html += '<div style="font-size:12px;color:var(--g500);margin-bottom:4px;">Fenton Growth Data (' + (sex === 'male' ? 'Male' : 'Female') + ')</div>';
|
||||
html += '<div style="font-size:13px;color:var(--g700);line-height:1.8;">';
|
||||
html += '<strong>Expected weight (50th %ile):</strong> ' + expectedWeight + 'g<br>';
|
||||
html += '<strong>Z-score:</strong> ' + z.toFixed(2) + '<br>';
|
||||
html += '<strong>Percentile:</strong> ' + percentile.toFixed(1) + '%';
|
||||
html += '</div></div>';
|
||||
html += '</div>';
|
||||
|
||||
// Reference
|
||||
html += '<p style="font-size:11px;color:var(--g400);margin:8px 0 0;">Fenton TR, Kim JH. A systematic review and meta-analysis to revise the Fenton growth chart for preterm infants. BMC Pediatrics 2013;13:59. GA classification per ACOG/AAP definitions.</p>';
|
||||
|
||||
resultDiv.innerHTML = html;
|
||||
}
|
||||
|
||||
// ---- NRP pathway + drug calc -----------------------------------------
|
||||
|
||||
function renderPathway() {
|
||||
var el = document.getElementById('nrp-pathway-static');
|
||||
if (!el || el.dataset.rendered) return;
|
||||
var html = '';
|
||||
html += S.stepBox('#3b82f6', 'BIRTH — ASSESS (first 30 sec)', 'Term? Tone? Breathing/crying? If <strong>all yes</strong> → routine care with mother. If <strong>any no</strong> → warm, dry, stimulate, position airway, clear airway PRN, evaluate HR & respirations.');
|
||||
html += S.arrow;
|
||||
html += S.stepBox('#8b5cf6', 'HR < 100 OR apneic/gasping (60 sec)', '<strong>Start PPV</strong> 40-60 breaths/min, room air for term / 21-30% for preterm. Attach SpO2 (right hand) ± ECG. MR SOPA if ineffective: <em>M</em>ask adjust, <em>R</em>eposition, <em>S</em>uction, <em>O</em>pen mouth, <em>P</em>ressure increase, <em>A</em>lternative airway.');
|
||||
html += S.arrow;
|
||||
html += S.stepBox('#f59e0b', 'HR < 100 after 30 sec effective PPV', 'Reassess ventilation — ensure chest rise. Consider increasing FiO2, intubation, or LMA. Continue PPV.');
|
||||
html += S.arrow;
|
||||
html += S.stepBox('#ef4444', 'HR < 60 after 30 sec of effective PPV', '<strong>Intubate + Chest compressions</strong> — 3:1 ratio (90 compressions + 30 breaths per minute), FiO2 100%, lower 1/3 sternum, depth 1/3 AP chest.');
|
||||
html += S.arrow;
|
||||
html += S.stepBox('#dc2626', 'HR < 60 despite compressions + PPV x 60 sec', '<strong>Epinephrine</strong> 1:10,000 (0.1 mg/mL): <br>• <strong>IV/IO:</strong> 0.01-0.03 mg/kg (0.1-0.3 mL/kg) — preferred<br>• <strong>ETT:</strong> 0.05-0.1 mg/kg (0.5-1 mL/kg) — while IV being placed<br>Repeat q3-5 min. If hypovolemia: <strong>NS 10 mL/kg IV/IO</strong> over 5-10 min.');
|
||||
html += '<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:8px;margin-top:14px;">';
|
||||
html += '<div style="padding:10px;background:var(--g50);border-radius:6px;font-size:12px;"><strong>Target SpO2 (preductal):</strong><br>1 min: 60-65%<br>2 min: 65-70%<br>3 min: 70-75%<br>4 min: 75-80%<br>5 min: 80-85%<br>10 min: 85-95%</div>';
|
||||
html += '<div style="padding:10px;background:var(--g50);border-radius:6px;font-size:12px;"><strong>Initial ETT size:</strong><br><1 kg / <28 wk: <strong>2.5</strong><br>1-2 kg / 28-34 wk: <strong>3.0</strong><br>2-3 kg / 34-38 wk: <strong>3.5</strong><br>>3 kg / >38 wk: <strong>3.5-4.0</strong></div>';
|
||||
html += '<div style="padding:10px;background:var(--g50);border-radius:6px;font-size:12px;"><strong>ETT depth (lip):</strong><br>~ 6 + weight(kg) cm</div>';
|
||||
html += '</div>';
|
||||
el.innerHTML = html;
|
||||
el.dataset.rendered = '1';
|
||||
}
|
||||
|
||||
function calcNRP() {
|
||||
var wt = parseFloat(document.getElementById('nrp-weight').value);
|
||||
var el = document.getElementById('nrp-result');
|
||||
if (!wt) { el.innerHTML = '<p style="color:var(--red);font-size:13px;">Enter weight (kg).</p>'; return; }
|
||||
var epiIvLow = Math.round(wt * 0.01 * 100) / 100;
|
||||
var epiIvHigh = Math.round(wt * 0.03 * 100) / 100;
|
||||
var epiEtLow = Math.round(wt * 0.05 * 100) / 100;
|
||||
var epiEtHigh = Math.round(wt * 0.1 * 100) / 100;
|
||||
var ns = Math.round(wt * 10);
|
||||
var d10 = Math.round(wt * 2 * 10) / 10;
|
||||
var html = '<div style="padding:10px 14px;border-radius:8px;background:var(--red)10;border:1.5px solid var(--red);margin-bottom:10px;"><strong style="color:var(--red);">NRP doses — ' + wt + ' kg</strong></div>';
|
||||
var pk = function(txt) { return ' <span style="color:var(--g500);font-size:11px;">(' + txt + ')</span>'; };
|
||||
html += S.drugTable(
|
||||
S.drugRow('Epinephrine 1:10,000', epiIvLow + '-' + epiIvHigh + ' mg, ' + (Math.round(epiIvLow*10)/10) + '-' + (Math.round(epiIvHigh*10)/10) + ' mL' + pk('0.01-0.03 mg/kg = 0.1-0.3 mL/kg'), 'IV / IO', 'Preferred route. Repeat q3-5 min.') +
|
||||
S.drugRow('Epinephrine 1:10,000', epiEtLow + '-' + epiEtHigh + ' mg, ' + (Math.round(epiEtLow*10)/10) + '-' + (Math.round(epiEtHigh*10)/10) + ' mL' + pk('0.05-0.1 mg/kg = 0.5-1 mL/kg'), 'ETT', 'While IV being placed.') +
|
||||
S.drugRow('Normal saline', ns + ' mL' + pk('10 mL/kg'), 'IV / IO', 'Over 5-10 min for volume. Repeat PRN.') +
|
||||
S.drugRow('Dextrose 10%', d10 + ' mL' + pk('2 mL/kg = 0.2 g/kg'), 'IV slow push', 'For documented hypoglycemia. Then D10 infusion 4-6 mg/kg/min.')
|
||||
);
|
||||
html += '<div style="padding:10px;background:#fef3c7;border-radius:6px;font-size:12px;color:#92400e;"><strong>Concentration note:</strong> NRP uses epinephrine <strong>1:10,000</strong> (0.1 mg/mL). NOT 1:1000 (1 mg/mL) — that is IM for anaphylaxis / older patients.</div>';
|
||||
html += S.ref('AHA/AAP Neonatal Resuscitation Program (NRP) 8th edition, 2020.');
|
||||
el.innerHTML = html;
|
||||
}
|
||||
|
||||
// ---- Apgar -----------------------------------------------------------
|
||||
|
||||
function calcApgar() {
|
||||
var keys = ['appearance','pulse','grimace','activity','respiration'];
|
||||
var total = keys.reduce(function(s, k) {
|
||||
return s + (parseInt(document.getElementById('apgar-' + k).value) || 0);
|
||||
}, 0);
|
||||
var el = document.getElementById('apgar-result');
|
||||
var color, severity, guidance;
|
||||
if (total >= 7) {
|
||||
color = '#10b981'; severity = 'Reassuring';
|
||||
guidance = 'Routine newborn care. Continue reassessment. Repeat at 5 min.';
|
||||
} else if (total >= 4) {
|
||||
color = '#f59e0b'; severity = 'Moderately depressed';
|
||||
guidance = 'Stimulate, clear airway, warm. Give O2 if cyanotic. Ventilate with PPV if HR <100 or apneic/gasping. Reassess q30 sec.';
|
||||
} else {
|
||||
color = '#ef4444'; severity = 'Severely depressed';
|
||||
guidance = 'Full NRP pathway — PPV immediately. Intubate if PPV ineffective. Chest compressions if HR <60. Epinephrine and volume per NRP.';
|
||||
}
|
||||
var html = '<div style="padding:10px 14px;border-radius:8px;background:' + color + '10;border:1.5px solid ' + color + ';">';
|
||||
html += '<strong style="color:' + color + ';font-size:16px;">Apgar: ' + total + '/10 — ' + severity + '</strong>';
|
||||
html += '<div style="font-size:12px;color:var(--g700);margin-top:6px;line-height:1.5;">' + guidance + '</div></div>';
|
||||
html += '<p style="font-size:11px;color:var(--g400);margin-top:8px;font-style:italic;">Apgar is a description of status — <strong>never</strong> delay resuscitation while scoring. Follow NRP algorithm based on HR and breathing. Apgar <7 at 5 min: repeat q5 min up to 20 min.</p>';
|
||||
el.innerHTML = html;
|
||||
}
|
||||
|
||||
export function init() {
|
||||
// Neonatal assessment
|
||||
document.addEventListener('click', function(e) {
|
||||
if (e.target.id === 'btn-neo-assess' || e.target.closest('#btn-neo-assess')) neoAssess();
|
||||
});
|
||||
|
||||
// NRP pathway render + calc
|
||||
document.addEventListener('DOMContentLoaded', renderPathway);
|
||||
document.addEventListener('tabChanged', function() { setTimeout(renderPathway, 100); });
|
||||
if (document.readyState !== 'loading') setTimeout(renderPathway, 0);
|
||||
|
||||
document.addEventListener('click', function(e) {
|
||||
if (e.target.id === 'btn-nrp-calc' || e.target.closest('#btn-nrp-calc')) calcNRP();
|
||||
// Render NRP pathway when Neonatal sub-pill is clicked (in case component loaded late)
|
||||
var np = e.target.closest && e.target.closest('[data-em="neonatal"]');
|
||||
if (np) setTimeout(renderPathway, 0);
|
||||
var nav = e.target.closest && e.target.closest('[data-calc="bedside"]');
|
||||
if (nav) setTimeout(renderPathway, 50);
|
||||
});
|
||||
|
||||
// Apgar
|
||||
document.addEventListener('click', function(e) {
|
||||
if (e.target.id === 'btn-apgar-calc' || e.target.closest('#btn-apgar-calc')) calcApgar();
|
||||
});
|
||||
}
|
||||
174
public/js/bedside/respiratory.js
Normal file
174
public/js/bedside/respiratory.js
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
// ============================================================
|
||||
// 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();
|
||||
});
|
||||
}
|
||||
112
public/js/bedside/sedation.js
Normal file
112
public/js/bedside/sedation.js
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
// ============================================================
|
||||
// bedside/sedation.js
|
||||
// PROCEDURAL SEDATION — agents + reversal, drugs.json-backed.
|
||||
// ============================================================
|
||||
|
||||
// Pulls in shared side-effect (window._EM) in case anything else needs it.
|
||||
import './shared.js';
|
||||
|
||||
function d(wt, mgPerKg, max) { var v = Math.round(wt * mgPerKg * 100) / 100; return max ? Math.min(v, max) : v; }
|
||||
|
||||
// Fallback inline data — mirrors drugs.json sedation section. The first
|
||||
// table's rows group by drug (Ketamine has both IV + IM lines), hence the
|
||||
// display vs name split. `reverses` marks the final 2 rows as reversal
|
||||
// agents shown in a separate table.
|
||||
var SED_FALLBACK = [
|
||||
{ name: 'Ketamine IV', display: 'Ketamine', dose_mg_per_kg_low: 1.5, dose_mg_per_kg_high: 2, max_mg_low: 100, max_mg_high: 150, unit: 'mg', route: 'IV', onset: '1 min', duration: '15-30 min', notes: 'Dissociative. Preserves airway reflexes.' },
|
||||
{ name: 'Ketamine IM', display: '', dose_mg_per_kg_low: 4, dose_mg_per_kg_high: 5, max_mg_low: 300, max_mg_high: 400, unit: 'mg', route: 'IM', onset: '3-5 min', duration: '30-60 min', notes: 'Give atropine 0.01 mg/kg to reduce secretions.' },
|
||||
{ name: 'Midazolam IV', display: 'Midazolam', dose_mg_per_kg_low: 0.05, dose_mg_per_kg_high: 0.1, max_mg_low: 2, max_mg_high: 5, unit: 'mg', route: 'IV', onset: '2-3 min', duration: '30-60 min', notes: 'Anxiolysis. Titrate q3-5 min.' },
|
||||
{ name: 'Midazolam IN/PO', display: '', dose_mg_per_kg: 0.5, max_mg: 20, unit: 'mg', route: 'IN/PO', onset: '10-15 min', duration: '30-60 min', notes: 'IN (max 10 mg / naris) or PO (max 20 mg).' },
|
||||
{ name: 'Propofol', display: 'Propofol', dose_mg_per_kg: 1, max_mg: 40, unit: 'mg', route: 'IV', onset: '30 sec', duration: '5-10 min', notes: 'Short procedures. Causes apnea — manage airway.' },
|
||||
{ name: 'Fentanyl IV', display: 'Fentanyl', dose_mg_per_kg: 1, max_mg: 100, unit: 'mcg', route: 'IV', onset: '2-3 min', duration: '30-60 min', notes: 'Analgesic. Often combined with midazolam.' },
|
||||
{ name: 'Fentanyl IN', display: '', dose_mg_per_kg: 2, max_mg: 100, unit: 'mcg', route: 'IN', onset: '5-10 min', duration: '30-60 min', notes: 'Intranasal.' },
|
||||
{ name: 'Nitrous oxide', display: 'Nitrous oxide', dose_display: '50:50 or 70:30 mix', unit: 'mix', route: 'Inhaled',onset: '2-5 min', duration: '5 min off', notes: 'Self-administered via demand valve. Anxiolysis + mild analgesia.' },
|
||||
{ name: 'Dexmedetomidine IN', display: 'Dexmedetomidine', dose_mg_per_kg_low: 2, dose_mg_per_kg_high: 3, max_mg_low: 100, max_mg_high: null, unit: 'mcg', route: 'IN', onset: '15-30 min', duration: '60-90 min', notes: 'Intranasal. No respiratory depression. Good for imaging.' },
|
||||
{ name: 'Naloxone', display: 'Naloxone', dose_mg_per_kg: 0.1, max_mg: 2, unit: 'mg', route: 'IV/IM/IN', reverses: 'Opioids (fentanyl, morphine)', notes: 'Max 2 mg. Repeat q2-3 min. Duration shorter than opioids — monitor for re-sedation.' },
|
||||
{ name: 'Flumazenil', display: 'Flumazenil', dose_mg_per_kg: 0.01, max_mg: 0.2, unit: 'mg', route: 'IV', reverses: 'Benzodiazepines (midazolam)', notes: 'Max 0.2 mg single dose. Repeat q1 min to max 1 mg total. Risk of seizures — use cautiously.' }
|
||||
];
|
||||
|
||||
function sedDrugs() {
|
||||
var s = window._DRUGS && window._DRUGS.sections && window._DRUGS.sections.sedation;
|
||||
if (s && s.drugs && s.drugs.length) {
|
||||
return s.drugs.map(function(dg) {
|
||||
var fb = SED_FALLBACK.filter(function(x) { return x.name === dg.name; })[0] || {};
|
||||
return Object.assign({}, fb, dg);
|
||||
});
|
||||
}
|
||||
return SED_FALLBACK;
|
||||
}
|
||||
|
||||
// perKg footer that matches the original inline format exactly.
|
||||
function sedPerKg(lo, hi, unit) {
|
||||
unit = unit || 'mg';
|
||||
return ' <span style="color:var(--g500);font-size:11px;">(' + lo + (hi != null ? '-' + hi : '') + ' ' + unit + '/kg)</span>';
|
||||
}
|
||||
|
||||
// Build the Dose cell text for a sedation drug (supports low/high range
|
||||
// and single-dose entries; honors custom dose_display for fixed doses).
|
||||
function sedDoseCell(wt, dg) {
|
||||
if (dg.dose_display) return dg.dose_display;
|
||||
var unitLabel = dg.unit === 'mcg' ? ' mcg' : (dg.unit === 'mix' ? '' : ' mg');
|
||||
var perKgUnit = dg.unit === 'mcg' ? 'mcg' : 'mg';
|
||||
if (dg.dose_mg_per_kg_low != null) {
|
||||
var lo = d(wt, dg.dose_mg_per_kg_low, dg.max_mg_low);
|
||||
if (dg.dose_mg_per_kg_high != null) {
|
||||
var hi = d(wt, dg.dose_mg_per_kg_high, dg.max_mg_high);
|
||||
return lo + '-' + hi + unitLabel + sedPerKg(dg.dose_mg_per_kg_low, dg.dose_mg_per_kg_high, perKgUnit);
|
||||
}
|
||||
return lo + unitLabel + sedPerKg(dg.dose_mg_per_kg_low, null, perKgUnit);
|
||||
}
|
||||
if (dg.dose_mg_per_kg != null) {
|
||||
var v = d(wt, dg.dose_mg_per_kg, dg.max_mg);
|
||||
return v + unitLabel + sedPerKg(dg.dose_mg_per_kg, null, perKgUnit);
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
function sedRow(wt, dg, showReverses) {
|
||||
var displayName = dg.display == null ? dg.name : dg.display;
|
||||
var nameCell = displayName
|
||||
? '<td style="font-weight:600;">' + displayName + '</td>'
|
||||
: '<td></td>';
|
||||
var dose = sedDoseCell(wt, dg);
|
||||
if (showReverses) {
|
||||
return '<tr>' + nameCell + '<td>' + dose + '</td><td>' + dg.route + '</td><td>' + (dg.reverses || '') + '</td><td style="font-size:12px;color:var(--g500);">' + dg.notes + '</td></tr>';
|
||||
}
|
||||
return '<tr>' + nameCell + '<td>' + dose + '</td><td>' + dg.route + '</td><td>' + (dg.onset || '') + '</td><td>' + (dg.duration || '') + '</td><td style="font-size:12px;color:var(--g500);">' + dg.notes + '</td></tr>';
|
||||
}
|
||||
|
||||
function calcSedation() {
|
||||
var wt = parseFloat(document.getElementById('sed-weight').value);
|
||||
var resultDiv = document.getElementById('sed-result');
|
||||
if (!wt) { resultDiv.innerHTML = '<p style="color:var(--red);font-size:13px;">Enter weight (kg).</p>'; return; }
|
||||
|
||||
var all = sedDrugs();
|
||||
var agents = all.filter(function(dg) { return !dg.reverses; });
|
||||
var reversals = all.filter(function(dg) { return !!dg.reverses; });
|
||||
|
||||
var html = '<div style="padding:10px 14px;border-radius:8px;background:var(--purple-light);border:1.5px solid var(--purple);margin-bottom:12px;"><span style="font-size:14px;font-weight:700;color:var(--purple);">Procedural Sedation — ' + wt + ' kg patient</span></div>';
|
||||
|
||||
// Sedation agents
|
||||
html += '<h4 style="font-size:14px;font-weight:700;color:var(--g800);margin:0 0 8px;">Sedation Agents</h4>';
|
||||
html += '<div style="overflow-x:auto;-webkit-overflow-scrolling:touch;margin-bottom:16px;"><table style="width:100%;min-width:640px;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;">Onset</th><th style="padding:6px 8px;">Duration</th><th style="padding:6px 8px;">Notes</th></tr></thead><tbody>';
|
||||
html += agents.map(function(dg) { return sedRow(wt, dg, false); }).join('');
|
||||
html += '</tbody></table></div>';
|
||||
|
||||
// Reversal agents
|
||||
html += '<h4 style="font-size:14px;font-weight:700;color:var(--red);margin:0 0 8px;"><i class="fas fa-rotate-left"></i> Reversal Agents</h4>';
|
||||
html += '<div style="overflow-x:auto;-webkit-overflow-scrolling:touch;"><table style="width:100%;min-width:560px;border-collapse:collapse;font-size:13px;"><thead><tr style="background:#fee2e2;"><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;">Reverses</th><th style="padding:6px 8px;">Notes</th></tr></thead><tbody>';
|
||||
html += reversals.map(function(dg) { return sedRow(wt, dg, true); }).join('');
|
||||
html += '</tbody></table></div>';
|
||||
|
||||
html += '<div style="margin-top:12px;padding:10px 12px;background:var(--amber-light);border-radius:6px;font-size:12px;color:#92400e;"><strong>Pre-sedation checklist:</strong> NPO status (2h clear liquids, 6h solids), consent, monitoring equipment (pulse ox, capnography, BP), resuscitation equipment at bedside, suction ready, IV access. Minimum monitoring: continuous SpO2, HR, capnography. Provider capable of managing airway must be present.</div>';
|
||||
html += '<p style="font-size:11px;color:var(--g400);margin:8px 0 0;">AAP Guidelines for Monitoring and Management of Pediatric Patients Before, During, and After Sedation. ASA Practice Guidelines for Sedation and Analgesia by Non-Anesthesiologists.</p>';
|
||||
resultDiv.innerHTML = html;
|
||||
}
|
||||
|
||||
export function init() {
|
||||
document.addEventListener('click', function(e) {
|
||||
if (e.target.id === 'btn-sed-calc' || e.target.closest('#btn-sed-calc')) calcSedation();
|
||||
});
|
||||
}
|
||||
|
||||
130
public/js/bedside/seizure.js
Normal file
130
public/js/bedside/seizure.js
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
// ============================================================
|
||||
// bedside/seizure.js
|
||||
// SEIZURE MANAGEMENT (status epilepticus pathway).
|
||||
// Keeps its own local `d` + `row` helpers used by the refractory
|
||||
// infusion rendering.
|
||||
// ============================================================
|
||||
|
||||
import { S } from './shared.js';
|
||||
|
||||
function d(wt, mgPerKg, max) { var v = Math.round(wt * mgPerKg * 100) / 100; return max ? Math.min(v, max) : v; }
|
||||
function row(phase, drug, dose, route, notes) {
|
||||
return '<tr><td style="font-weight:600;color:var(--blue);">' + phase + '</td><td style="font-weight:600;">' + drug + '</td><td>' + dose + '</td><td>' + route + '</td><td style="font-size:12px;color:var(--g500);">' + notes + '</td></tr>';
|
||||
}
|
||||
|
||||
// Visual pathway row — time badge + step card linked by vertical line
|
||||
function stepRow(time, color, title, bodyHtml, isLast) {
|
||||
return '<div style="display:flex;gap:12px;align-items:stretch;">' +
|
||||
'<div style="flex:0 0 auto;width:70px;display:flex;flex-direction:column;align-items:center;">' +
|
||||
'<div style="background:' + color + ';color:white;padding:6px 0;border-radius:20px;font-size:12px;font-weight:700;width:60px;text-align:center;box-shadow:0 1px 3px rgba(0,0,0,0.15);">' + time + '</div>' +
|
||||
(isLast ? '' : '<div style="flex:1;width:3px;background:' + color + '40;margin:2px 0;"></div>') +
|
||||
'</div>' +
|
||||
'<div style="flex:1;padding:10px 14px;border-left:4px solid ' + color + ';background:' + color + '10;border-radius:0 8px 8px 0;margin-bottom:' + (isLast ? '4px' : '12px') + ';">' +
|
||||
'<div style="font-weight:700;color:' + color + ';font-size:13px;margin-bottom:6px;text-transform:uppercase;letter-spacing:0.3px;">' + title + '</div>' +
|
||||
'<div style="font-size:13px;color:var(--g700);line-height:1.55;">' + bodyHtml + '</div>' +
|
||||
'</div>' +
|
||||
'</div>';
|
||||
}
|
||||
|
||||
// Fallback inline data — mirrors the non-refractory seizure entries in
|
||||
// drugs.json. Refractory infusion drips are kept inline below because
|
||||
// their dose strings (bolus → gtt range) don't map cleanly to the
|
||||
// single-row JSON schema.
|
||||
var SEIZURE_FALLBACK = [
|
||||
{ name: 'Lorazepam', phase: '1st benzo', dose_mg_per_kg: 0.1, max_mg: 4, unit: 'mg', route: 'IV / IO', label_suffix: ' <span style="color:#065f46;font-weight:600;">(IV preferred)</span>', notes: 'Slow push over 1-2 min.' },
|
||||
{ name: 'Midazolam', phase: '1st benzo', dose_mg_per_kg: 0.2, max_mg: 10, unit: 'mg', route: 'IM / IN / buccal', notes: 'Use 5 mg/mL concentrate for IN; split between nares.' },
|
||||
{ name: 'Diazepam', phase: '1st benzo', dose_mg_per_kg: 0.5, max_mg: 20, unit: 'mg', route: 'PR', notes: 'Only if no IV/IM/IN access.' },
|
||||
{ name: 'Levetiracetam', phase: '2nd-line', dose_mg_per_kg: 60, max_mg: 4500, unit: 'mg', route: 'IV over 5-15 min', label_suffix: ' <span style="color:#065f46;font-weight:600;">(preferred)</span>', notes: 'Best tolerability. No ECG monitoring needed.' },
|
||||
{ name: 'Fosphenytoin', phase: '2nd-line', dose_mg_per_kg: 20, max_mg: 1500, unit: 'mg PE', route: 'IV over 10 min', notes: 'Max rate 3 mg PE/kg/min. Monitor ECG, BP. Avoid if cardiac dysrhythmia.' },
|
||||
{ name: 'Valproic acid', phase: '2nd-line', dose_mg_per_kg: 40, max_mg: 3000, unit: 'mg', route: 'IV over 10 min', notes: '<strong>Avoid <2 yr, hepatic / mitochondrial disease, pregnancy.</strong>' },
|
||||
{ name: 'Phenobarbital', phase: '2nd-line', dose_mg_per_kg: 20, max_mg: 1000, unit: 'mg', route: 'IV over 20 min', notes: 'Last-choice 2nd line. High risk of resp depression + hypotension.' }
|
||||
];
|
||||
|
||||
function seizureDrugs() {
|
||||
var s = window._DRUGS && window._DRUGS.sections && window._DRUGS.sections.seizure;
|
||||
if (s && s.drugs && s.drugs.length) {
|
||||
return s.drugs.map(function(dg) {
|
||||
var fb = SEIZURE_FALLBACK.filter(function(x) { return x.name === dg.name; })[0] || {};
|
||||
return Object.assign({}, fb, dg);
|
||||
});
|
||||
}
|
||||
return SEIZURE_FALLBACK;
|
||||
}
|
||||
|
||||
// Build the joined S.drugRow output for one phase ("1st benzo" / "2nd-line").
|
||||
function seizureRowsByPhase(wt, phase) {
|
||||
return seizureDrugs().filter(function(dg) { return dg.phase === phase; }).map(function(dg) {
|
||||
var label = dg.name + (dg.label_suffix || '');
|
||||
var unitArg = dg.unit && dg.unit !== 'mg' ? ' ' + dg.unit : undefined;
|
||||
var dose = unitArg
|
||||
? S.dStr(wt, dg.dose_mg_per_kg, dg.max_mg, unitArg)
|
||||
: S.dStr(wt, dg.dose_mg_per_kg, dg.max_mg);
|
||||
return S.drugRow(label, dose, dg.route, dg.notes);
|
||||
}).join('');
|
||||
}
|
||||
|
||||
function calcSeizure() {
|
||||
var wt = parseFloat(document.getElementById('seizure-weight').value);
|
||||
var resultDiv = document.getElementById('seizure-result');
|
||||
if (!wt) { resultDiv.innerHTML = '<p style="color:var(--red);font-size:13px;">Enter weight (kg).</p>'; return; }
|
||||
var d10Low = Math.round(wt * 2 * 10) / 10;
|
||||
var d10High = Math.round(wt * 5 * 10) / 10;
|
||||
|
||||
var html = '<div style="padding:10px 14px;border-radius:8px;background:#fee2e2;border:1.5px solid #ef4444;margin-bottom:14px;"><strong style="color:#dc2626;font-size:14px;"><i class="fas fa-bolt"></i> Status Epilepticus Pathway — ' + wt + ' kg</strong><div style="font-size:12px;color:var(--g600);margin-top:3px;">Time = 0 at seizure onset. Do not pause between steps for benzo to take effect — act on the clock.</div></div>';
|
||||
|
||||
// 0 min — STABILIZE
|
||||
html += stepRow('0 min', '#3b82f6', 'Stabilize',
|
||||
'<strong>ABCs</strong> — position, 100% O2 via NC / NRB, suction. <strong>IV or IO</strong> × 2. Continuous SpO2, ECG, BP. ' +
|
||||
'<strong>POC glucose</strong> — if <60 mg/dL: <strong>D10W ' + d10Low + '-' + d10High + ' mL IV</strong> (2-5 mL/kg). ' +
|
||||
'<strong>Labs</strong>: CBC, CMP, Mg, Ca, Phos, VBG, lactate, AED levels, toxicology if unclear etiology. ' +
|
||||
'<strong>Consider pyridoxine</strong> 100 mg IV in infants <18 mo or INH ingestion. Temperature: treat hyperthermia aggressively.'
|
||||
);
|
||||
|
||||
// 5 min — 1ST BENZO
|
||||
html += stepRow('5 min', '#8b5cf6', '1st benzodiazepine (give once, pick by access)',
|
||||
S.drugTable(seizureRowsByPhase(wt, '1st benzo'))
|
||||
);
|
||||
|
||||
// 10 min — 2ND BENZO
|
||||
html += stepRow('10 min', '#a855f7', '2nd benzodiazepine — if still seizing',
|
||||
'Repeat same agent + dose <strong>once</strong>. Prepare 2nd-line now (don\'t wait to see if benzo works). If apnea/airway compromise: BVM, consider advanced airway.'
|
||||
);
|
||||
|
||||
// 20 min — 2ND-LINE
|
||||
html += stepRow('20 min', '#f59e0b', '2nd-line anti-epileptic — pick one (ESETT: equivalent efficacy)',
|
||||
S.drugTable(seizureRowsByPhase(wt, '2nd-line'))
|
||||
);
|
||||
|
||||
// 30 min — 2nd of 2nd-line (optional)
|
||||
html += stepRow('30 min', '#ef4444', 'Still seizing? Consider 2nd agent from 2nd-line OR proceed to refractory',
|
||||
'If the first 2nd-line drug failed, give a different 2nd-line agent <strong>OR</strong> move directly to refractory therapy. Activate ICU, prepare for intubation. Confirm etiology not reversible (electrolytes, glucose, fever, toxin).'
|
||||
);
|
||||
|
||||
// 40 min — REFRACTORY
|
||||
html += stepRow('40 min', '#dc2626', 'Refractory status — intubate + continuous infusion + continuous EEG',
|
||||
S.drugTable(
|
||||
S.drugRow('Midazolam <span style="color:#065f46;font-weight:600;">(1st-line infusion)</span>', 'Bolus ' + d(wt, 0.2, 10) + ' mg → gtt ' + d(wt, 0.05, 2) + '-' + d(wt, 0.5, 18) + ' mg/hr <span style="color:var(--g500);font-size:11px;">(bolus 0.2 mg/kg, gtt 0.05-0.5 mg/kg/hr)</span>', 'IV', 'Titrate to seizure control / burst suppression.') +
|
||||
S.drugRow('Pentobarbital', 'Bolus ' + d(wt, 5, 200) + ' mg → gtt ' + d(wt, 1, null) + '-' + d(wt, 5, null) + ' mg/hr <span style="color:var(--g500);font-size:11px;">(bolus 5 mg/kg, gtt 1-5 mg/kg/hr)</span>', 'IV', 'Causes hypotension — often need pressors.') +
|
||||
S.drugRow('Propofol', 'Bolus ' + d(wt, 2, 100) + ' mg → gtt ' + d(wt, 2, null) + '-' + d(wt, 5, null) + ' mg/hr <span style="color:var(--g500);font-size:11px;">(bolus 2 mg/kg, gtt 2-5 mg/kg/hr)</span>', 'IV', '<strong>PRIS risk in children</strong> — limit to <4 mg/kg/hr and duration <48 h.') +
|
||||
S.drugRow('Ketamine', 'Bolus ' + d(wt, 2, 100) + ' mg → gtt ' + d(wt, 1, null) + '-' + d(wt, 5, null) + ' mg/hr <span style="color:var(--g500);font-size:11px;">(bolus 2 mg/kg, gtt 1-5 mg/kg/hr)</span>', 'IV', 'NMDA antagonist — rescue. Good BP profile; useful if refractory to GABAergics.')
|
||||
) +
|
||||
'<div style="margin-top:8px;font-size:12px;color:var(--g600);"><strong>Continuous EEG within 1 h</strong> — target electrographic seizure suppression × 24-48 h, then wean. Reassess etiology: CNS imaging, LP, expanded workup.</div>'
|
||||
, true);
|
||||
|
||||
// Key points
|
||||
html += '<div style="margin-top:16px;padding:10px 12px;background:#fef3c7;border-radius:6px;font-size:12px;color:#92400e;line-height:1.55;"><strong>Key points</strong><br>' +
|
||||
'• Do <strong>NOT</strong> wait for benzo response before starting the 2nd-line — parallel preparation.<br>' +
|
||||
'• Maximum <strong>2 benzo doses</strong> total (1 pre-hospital + 1 in ED, or 2 in ED).<br>' +
|
||||
'• Respiratory depression is common — have BVM, airway equipment, naloxone, flumazenil accessible (but avoid flumazenil here).<br>' +
|
||||
'• ESETT trial: levetiracetam = fosphenytoin = valproate for efficacy. Pick by tolerability / contraindications.<br>' +
|
||||
'• Always reassess: ongoing seizure? Non-convulsive status? Pseudo-seizure? → continuous EEG if any doubt.</div>';
|
||||
|
||||
html += '<p style="font-size:11px;color:var(--g400);margin:8px 0 0;font-style:italic;">Based on AES Guidelines 2016 and ESETT (Kapur et al., NEJM 2019). Verify all doses against institutional protocols.</p>';
|
||||
resultDiv.innerHTML = html;
|
||||
}
|
||||
|
||||
export function init() {
|
||||
document.addEventListener('click', function(e) {
|
||||
if (e.target.id === 'btn-seizure-calc' || e.target.closest('#btn-seizure-calc')) calcSeizure();
|
||||
});
|
||||
}
|
||||
137
public/js/bedside/sepsis.js
Normal file
137
public/js/bedside/sepsis.js
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
// ============================================================
|
||||
// bedside/sepsis.js
|
||||
// SEPSIS — Phoenix criteria, febrile-infant rules, first-hour bundle.
|
||||
// ============================================================
|
||||
|
||||
import { S } from './shared.js';
|
||||
|
||||
function showSepsis() {
|
||||
var wt = parseFloat(document.getElementById('sepsis-weight').value);
|
||||
var age = document.getElementById('sepsis-age').value;
|
||||
var el = document.getElementById('sepsis-result');
|
||||
var html = '';
|
||||
|
||||
var ageLbl = { neonate: 'Neonate (0-28 d)', infant: 'Young infant (29 d - 3 mo)', child: 'Older child / adolescent' }[age];
|
||||
html += '<div style="padding:10px 14px;border-radius:8px;background:#fee2e2;border:1.5px solid #ef4444;margin-bottom:12px;"><strong style="color:#dc2626;">Sepsis approach — ' + ageLbl + (wt ? ', ' + wt + ' kg' : '') + '</strong></div>';
|
||||
|
||||
// Definition (Phoenix 2024)
|
||||
html += '<h5 style="font-size:13px;font-weight:700;color:var(--g700);margin:0 0 6px;">Definition — Phoenix Sepsis Criteria (JAMA 2024)</h5>';
|
||||
html += '<div style="padding:10px;background:var(--g50);border-radius:6px;font-size:12px;line-height:1.55;margin-bottom:12px;">';
|
||||
html += '<strong>Sepsis</strong> = suspected or confirmed infection + Phoenix Score ≥2 (organ dysfunction across respiratory, cardiovascular, coagulation, neurological).<br>';
|
||||
html += '<strong>Septic shock</strong> = sepsis + cardiovascular dysfunction (vasoactive support, or ↑lactate ≥5, or ↓MAP for age).<br>';
|
||||
html += '<em style="color:var(--g500);">Previous SIRS-based criteria (Goldstein 2005) are now superseded. Still useful for quick clinical screening.</em>';
|
||||
html += '</div>';
|
||||
|
||||
// Quick red flags
|
||||
html += '<h5 style="font-size:13px;font-weight:700;color:var(--g700);margin:10px 0 6px;">Red flags (recognize early)</h5>';
|
||||
html += '<div style="padding:10px;background:#fef2f2;border-radius:6px;font-size:12px;color:#991b1b;">Abnormal behavior / mentation • Fever + ill-appearance • Tachycardia out of proportion to fever • Prolonged cap refill (>3 s) • Cold/mottled extremities • Weak pulses or wide pulse pressure ("warm shock") • Hypotension is a <strong>LATE</strong> sign in children • Any immune compromise / indwelling line.</div>';
|
||||
|
||||
// Age-specific approach
|
||||
html += '<h5 style="font-size:13px;font-weight:700;color:var(--g700);margin:14px 0 6px;">Evaluation & empiric therapy — ' + ageLbl + '</h5>';
|
||||
if (age === 'neonate') {
|
||||
html += '<div style="padding:10px;background:var(--g50);border-radius:6px;font-size:12px;margin-bottom:10px;">';
|
||||
html += '<strong>Workup (full sepsis eval):</strong> CBC + diff, CRP, blood culture, UA + urine culture (cath), <strong>lumbar puncture</strong> (CSF + HSV PCR), CXR if respiratory sx. Consider procalcitonin. Rapid viral panel if available.<br>';
|
||||
html += '<strong>Early-onset (<72 h):</strong> GBS, E. coli, Listeria. <strong>Late-onset (>72 h):</strong> coag-neg staph, S. aureus, gram-negs, Candida.</div>';
|
||||
html += S.drugTable(
|
||||
S.drugRow('Ampicillin', wt ? S.dStr(wt, 100, 2000) : '50-100 mg/kg/dose', 'IV', 'q8-12h depending on age/weight. Covers GBS, Listeria, Enterococcus.') +
|
||||
S.drugRow('Gentamicin', wt ? S.dStr(wt, 4, 120) : '4-5 mg/kg/dose', 'IV', 'q24-48h. Monitor levels. Peak 5-12, trough <1.') +
|
||||
S.drugRow('Cefotaxime (add)', wt ? S.dStr(wt, 50, 2000) : '50 mg/kg/dose', 'IV', 'If meningitis suspected or gram-neg concern. Ceftriaxone avoided in hyperbilirubinemia.') +
|
||||
S.drugRow('Acyclovir', wt ? S.dStr(wt, 20, 1200) : '20 mg/kg/dose', 'IV q8h', 'If HSV risk: maternal genital lesions, vesicles, seizures, CSF pleocytosis, hypothermia, hepatitis.')
|
||||
);
|
||||
} else if (age === 'infant') {
|
||||
html += '<div style="padding:10px;background:var(--g50);border-radius:6px;font-size:12px;margin-bottom:10px;">';
|
||||
html += '<strong>Workup:</strong> Use validated rules — PECARN, Aronson, Rochester, Step-by-Step (see below). Most warrant CBC + ANC, procalcitonin and/or CRP, blood culture, UA + urine culture. Many warrant LP + admission + empiric abx.<br>';
|
||||
html += '<strong>Coverage:</strong> GBS, E. coli, Listeria (up to ~6 wk), S. pneumoniae, N. meningitidis, H. influenzae, Salmonella.</div>';
|
||||
|
||||
// Febrile-infant rules — collapsible details
|
||||
html += '<details style="margin-bottom:10px;border:1px solid var(--g200);border-radius:8px;padding:8px 10px;background:white;"><summary style="cursor:pointer;font-weight:600;font-size:13px;color:var(--blue-dark);"><i class="fas fa-list-check"></i> Validated febrile-infant rules (click to expand)</summary>';
|
||||
html += '<div style="margin-top:10px;font-size:12px;line-height:1.55;">';
|
||||
|
||||
// PECARN 2019
|
||||
html += '<div style="padding:10px 12px;background:#eff6ff;border-radius:6px;margin-bottom:8px;border-left:4px solid #3b82f6;">';
|
||||
html += '<div style="font-weight:700;color:#1e40af;margin-bottom:4px;">PECARN febrile infant rule (Kuppermann et al., JAMA Pediatrics 2019)</div>';
|
||||
html += '<strong>Age:</strong> 29-60 days, well-appearing, febrile. <strong>Low-risk if ALL:</strong><br>';
|
||||
html += ' • Normal UA (no LE, no nitrite, <5 WBC/hpf)<br>';
|
||||
html += ' • ANC ≤4,090 /μL<br>';
|
||||
html += ' • Procalcitonin ≤0.5 ng/mL (if procal unavailable: use CRP ≤20 mg/L as proxy)<br>';
|
||||
html += '<strong>Low-risk</strong> → may defer LP/abx, observe, close follow-up. <strong>Not low-risk</strong> → full workup + empiric abx.';
|
||||
html += '</div>';
|
||||
|
||||
// Aronson 2019
|
||||
html += '<div style="padding:10px 12px;background:#f0fdf4;border-radius:6px;margin-bottom:8px;border-left:4px solid #16a34a;">';
|
||||
html += '<div style="font-weight:700;color:#166534;margin-bottom:4px;">Aronson rule (Aronson et al., PEDIATRICS 2019)</div>';
|
||||
html += '<strong>Age:</strong> 8-60 days, febrile ≥38°C. Point-based score for invasive bacterial infection (IBI):<br>';
|
||||
html += ' • Age 22-28 days: <strong>+1</strong><br>';
|
||||
html += ' • Max temperature ≥38.5°C: <strong>+2</strong><br>';
|
||||
html += ' • ANC ≥5,185 /μL: <strong>+2</strong><br>';
|
||||
html += ' • Abnormal UA: <strong>+3</strong><br>';
|
||||
html += '<strong>Score 0-1</strong> → low risk. <strong>≥2</strong> → consider full workup + abx. Higher scores → higher IBI risk.';
|
||||
html += '</div>';
|
||||
|
||||
// Rochester
|
||||
html += '<div style="padding:10px 12px;background:#fffbeb;border-radius:6px;margin-bottom:8px;border-left:4px solid #f59e0b;">';
|
||||
html += '<div style="font-weight:700;color:#92400e;margin-bottom:4px;">Rochester criteria (Jaskiewicz et al., Pediatrics 1994)</div>';
|
||||
html += '<strong>Age:</strong> 0-60 days, febrile. <strong>Low-risk if ALL:</strong><br>';
|
||||
html += ' • Previously healthy (term, no perinatal complications, no prior abx/hospitalization)<br>';
|
||||
html += ' • Well-appearing, no focal infection (not skin/bone/joint/soft tissue)<br>';
|
||||
html += ' • WBC 5,000-15,000 /μL and absolute band count ≤1,500 /μL<br>';
|
||||
html += ' • UA ≤10 WBC/hpf<br>';
|
||||
html += ' • If diarrhea: stool ≤5 WBC/hpf<br>';
|
||||
html += '<strong>Low-risk</strong> → observation without abx possible (NPV >98%). Does not include procal.';
|
||||
html += '</div>';
|
||||
|
||||
// Step-by-Step
|
||||
html += '<div style="padding:10px 12px;background:#fdf4ff;border-radius:6px;margin-bottom:4px;border-left:4px solid #a855f7;">';
|
||||
html += '<div style="font-weight:700;color:#6b21a8;margin-bottom:4px;">Step-by-Step (Gomez et al., Pediatrics 2016 — European)</div>';
|
||||
html += '<strong>Age:</strong> ≤90 days, febrile. Sequential triage; stop at first positive:<br>';
|
||||
html += ' <strong>1.</strong> Ill-appearing? → <em>high risk</em><br>';
|
||||
html += ' <strong>2.</strong> Age ≤21 days? → <em>high risk</em><br>';
|
||||
html += ' <strong>3.</strong> Leukocyturia (UA abnormal)? → <em>high risk</em><br>';
|
||||
html += ' <strong>4.</strong> Procalcitonin ≥0.5 ng/mL? → <em>high risk</em><br>';
|
||||
html += ' <strong>5.</strong> CRP >20 mg/L AND/OR ANC >10,000 /μL? → <em>intermediate</em><br>';
|
||||
html += ' <strong>6.</strong> Otherwise → <em>low risk</em>';
|
||||
html += '<div style="margin-top:4px;color:var(--g500);">Highest sensitivity for IBI among these rules when procal available.</div>';
|
||||
html += '</div>';
|
||||
|
||||
html += '<div style="margin-top:8px;padding:8px 10px;background:var(--g50);border-radius:6px;font-size:11px;color:var(--g600);"><strong>Practical note:</strong> AAP 2021 Clinical Practice Guideline on the well-appearing febrile infant 8-60 days synthesizes these — ages 8-21 d full workup regardless, 22-28 d inflammatory markers guide LP, 29-60 d UA + markers guide LP/abx. Defer to your local institutional pathway if available.</div>';
|
||||
html += '</div></details>';
|
||||
|
||||
html += S.drugTable(
|
||||
S.drugRow('Ceftriaxone', wt ? S.dStr(wt, 75, 2000) : '50-75 mg/kg/dose', 'IV / IM', 'q24h (or 100 mg/kg/day divided q12h for meningitis). <strong>Avoid <28 days</strong> if hyperbilirubinemia.') +
|
||||
S.drugRow('Ampicillin', wt ? S.dStr(wt, 100, 2000) : '50-100 mg/kg/dose', 'IV', 'If <6 wks: add for Listeria coverage.') +
|
||||
S.drugRow('Vancomycin', wt ? S.dStr(wt, 15, 1000) : '15 mg/kg/dose', 'IV', 'If severe / MRSA risk / meningitis. Target trough 15-20.') +
|
||||
S.drugRow('Acyclovir', wt ? S.dStr(wt, 20, 1200) : '20 mg/kg/dose', 'IV q8h', '<6 weeks with suspicion of HSV.')
|
||||
);
|
||||
} else {
|
||||
html += '<div style="padding:10px;background:var(--g50);border-radius:6px;font-size:12px;margin-bottom:10px;">';
|
||||
html += '<strong>Recognition:</strong> Phoenix score or clinical concern + suspected infection. <strong>Workup:</strong> CBC, CRP, procalcitonin, blood cx (+ site-specific cx), lactate, blood gas, glucose, electrolytes, coags, LP if CNS concern. Source-directed imaging.<br>';
|
||||
html += '<strong>Common sources:</strong> respiratory, UTI, CNS, skin/soft tissue, intra-abdominal, bone/joint, indwelling device.</div>';
|
||||
html += S.drugTable(
|
||||
S.drugRow('Ceftriaxone', wt ? S.dStr(wt, 50, 2000) : '50 mg/kg/dose', 'IV', 'q24h (or 100 mg/kg/day divided for meningitis).') +
|
||||
S.drugRow('Vancomycin', wt ? S.dStr(wt, 15, 1000) : '15 mg/kg/dose', 'IV', 'q6h. If severe, indwelling line, or MRSA prevalence >10%.') +
|
||||
S.drugRow('Piperacillin-tazobactam', wt ? S.dStr(wt, 100, 4500) : '80-100 mg/kg/dose', 'IV', 'If intra-abdominal / neutropenic.') +
|
||||
S.drugRow('Clindamycin', wt ? S.dStr(wt, 10, 900) : '10-13 mg/kg/dose', 'IV', 'Adjunct for toxic shock syndrome (toxin suppression).') +
|
||||
S.drugRow('Acyclovir', wt ? S.dStr(wt, 20, 1200) : '10-20 mg/kg/dose', 'IV q8h', 'If HSV CNS concern.')
|
||||
);
|
||||
}
|
||||
|
||||
// First-hour bundle
|
||||
html += '<h5 style="font-size:13px;font-weight:700;color:var(--g700);margin:14px 0 6px;">First-hour bundle (SSC Peds 2020)</h5>';
|
||||
html += S.stepBox('#3b82f6', '0-5 min — Recognize', 'Screen for sepsis; sepsis huddle / activation. Assess ABCs. O2 to SpO2 >94%. Warm patient.');
|
||||
html += S.stepBox('#8b5cf6', '5-15 min — Access & labs', 'Two IVs or IO. Draw: blood cx (ideally before abx), lactate, CBC, CMP, coags, blood gas, glucose. UA + culture. Source-specific cultures.');
|
||||
html += S.stepBox('#f59e0b', '15-30 min — Fluids', (wt ? 'NS or LR <strong>' + S.d(wt, 20) + ' mL</strong> bolus over 5-10 min (20 mL/kg). ' : 'NS/LR 10-20 mL/kg bolus over 5-10 min. ') + 'Reassess after each bolus: HR, perfusion, lung sounds, liver size. Repeat up to 40-60 mL/kg; stop if crackles or hepatomegaly.');
|
||||
html += S.stepBox('#10b981', '30-60 min — Antibiotics + reassess', 'Broad-spectrum empiric abx within 1 hour (ideally 3 hr in sepsis, ≤1 hr in septic shock). Recheck lactate, perfusion.');
|
||||
html += S.stepBox('#ef4444', '>60 min — Fluid-refractory shock', 'Start vasoactive (<strong>epinephrine 0.05-0.3 mcg/kg/min</strong> for cold shock or <strong>norepinephrine 0.05-0.3 mcg/kg/min</strong> for warm shock). Consider central/IO access. Stress-dose hydrocortisone ' + (wt ? '<strong>' + S.d(wt, 2, 100) + ' mg</strong> IV <span style="color:var(--g500);font-size:11px;">(2 mg/kg, max 100 mg)</span>' : '2 mg/kg IV (max 100 mg)') + ' if catecholamine-resistant. Consider ICU transfer.');
|
||||
|
||||
// Key targets
|
||||
html += '<h5 style="font-size:13px;font-weight:700;color:var(--g700);margin:14px 0 6px;">Resuscitation targets</h5>';
|
||||
html += '<div style="padding:10px;background:var(--g50);border-radius:6px;font-size:12px;">Normal mentation • Cap refill ≤2 s • Warm extremities • Strong peripheral pulses • UOP ≥1 mL/kg/hr • MAP ≥5th %ile for age (>65 mmHg adolescent) • SpO2 ≥94% • Lactate trending down.</div>';
|
||||
html += S.ref('Phoenix Sepsis Criteria (Schlapbach et al., JAMA 2024). Surviving Sepsis Campaign Pediatric 2020. AAP pediatric sepsis guidance.');
|
||||
el.innerHTML = html;
|
||||
}
|
||||
|
||||
export function init() {
|
||||
document.addEventListener('click', function(e) {
|
||||
if (e.target.id === 'btn-sepsis-show' || e.target.closest('#btn-sepsis-show')) showSepsis();
|
||||
});
|
||||
}
|
||||
44
public/js/bedside/shared.js
Normal file
44
public/js/bedside/shared.js
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
// ============================================================
|
||||
// bedside/shared.js
|
||||
// Shared string-building helpers used by every Bedside section.
|
||||
// Pure functions with no side effects. Exports `S`; also sets
|
||||
// window._EM = S for back-compat with any stray lookups.
|
||||
// ============================================================
|
||||
|
||||
export const S = {
|
||||
d: function(wt, perKg, max) { var v = Math.round(wt * perKg * 100) / 100; return max ? Math.min(v, max) : v; },
|
||||
// Dose string — always shows the per-kg used (and max when capped), so the
|
||||
// prescriber can verify the math without opening a drug ref.
|
||||
// S.dStr(30, 0.1, 4) → "3 mg (0.1 mg/kg, max 4 mg)"
|
||||
// S.dStr(60, 0.1, 4) → "4 mg (0.1 mg/kg, max 4 mg · capped)"
|
||||
// S.dStr(20, 2, 150, ' mcg') → "40 mcg (2 mcg/kg, max 150 mcg)"
|
||||
dStr: function(wt, perKg, max, unit) {
|
||||
unit = unit || ' mg';
|
||||
var uTrim = unit.trim() || 'mg';
|
||||
var raw = wt * perKg;
|
||||
var v = Math.round(raw * 100) / 100;
|
||||
var capped = max != null && v > max;
|
||||
var val = capped ? max : v;
|
||||
var perKgStr = '(' + perKg + ' ' + uTrim + '/kg';
|
||||
if (max != null) perKgStr += ', max ' + max + ' ' + uTrim;
|
||||
if (capped) perKgStr += ' · capped';
|
||||
perKgStr += ')';
|
||||
return val + unit + ' <span style="color:var(--g500);font-size:11px;font-weight:400;">' + perKgStr + '</span>';
|
||||
},
|
||||
drugTable: function(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="text-align:left;padding:6px 8px;">Dose</th><th style="text-align:left;padding:6px 8px;">Route</th><th style="text-align:left;padding:6px 8px;">Notes</th></tr></thead><tbody>' + rows + '</tbody></table></div>';
|
||||
},
|
||||
drugRow: function(name, dose, route, notes) {
|
||||
return '<tr><td style="font-weight:600;padding:5px 8px;border-bottom:1px solid var(--g100);">' + name + '</td><td style="padding:5px 8px;border-bottom:1px solid var(--g100);">' + dose + '</td><td style="padding:5px 8px;border-bottom:1px solid var(--g100);">' + route + '</td><td style="padding:5px 8px;border-bottom:1px solid var(--g100);font-size:12px;color:var(--g500);">' + (notes || '') + '</td></tr>';
|
||||
},
|
||||
stepBox: function(color, title, body) {
|
||||
return '<div style="border-left:4px solid ' + color + ';background:' + color + '10;padding:10px 14px;border-radius:6px;margin-bottom:10px;">' +
|
||||
'<div style="font-weight:700;color:' + color + ';font-size:13px;margin-bottom:4px;">' + title + '</div>' +
|
||||
'<div style="font-size:13px;color:var(--g700);line-height:1.5;">' + body + '</div></div>';
|
||||
},
|
||||
arrow: '<div style="text-align:center;color:var(--g400);font-size:16px;margin:-4px 0 6px;">↓</div>',
|
||||
badge: function(label, color) { return '<span style="display:inline-block;padding:3px 10px;border-radius:6px;background:' + color + '20;color:' + color + ';font-weight:700;font-size:12px;">' + label + '</span>'; },
|
||||
ref: function(text) { return '<p style="font-size:11px;color:var(--g400);margin:8px 0 0;font-style:italic;">' + text + '</p>'; }
|
||||
};
|
||||
|
||||
if (typeof window !== 'undefined') window._EM = S;
|
||||
18
public/js/bedside/sub-nav.js
Normal file
18
public/js/bedside/sub-nav.js
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
// ============================================================
|
||||
// bedside/sub-nav.js
|
||||
// EMERGENCIES SUB-NAV — data-em pill switching.
|
||||
// ============================================================
|
||||
|
||||
export function init() {
|
||||
document.addEventListener('click', function(e) {
|
||||
var pill = e.target.closest('[data-em]');
|
||||
if (!pill) return;
|
||||
document.querySelectorAll('[data-em]').forEach(function(p) { p.classList.remove('active'); });
|
||||
pill.classList.add('active');
|
||||
var sections = ['neonatal','airway','cardiac','respiratory','ventilation','seizure','sepsis','anaphylaxis','sedation','agitation','antiemetics','antimicrobials','burns','toxicology','trauma'];
|
||||
sections.forEach(function(s) {
|
||||
var el = document.getElementById('em-' + s);
|
||||
if (el) el.style.display = pill.dataset.em === s ? '' : 'none';
|
||||
});
|
||||
});
|
||||
}
|
||||
120
public/js/bedside/toxicology.js
Normal file
120
public/js/bedside/toxicology.js
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
// ============================================================
|
||||
// bedside/toxicology.js
|
||||
// TOXICOLOGY.
|
||||
// ============================================================
|
||||
|
||||
import { S } from './shared.js';
|
||||
|
||||
function lookupTox() {
|
||||
var wt = parseFloat(document.getElementById('tox-weight').value);
|
||||
var topic = document.getElementById('tox-topic').value;
|
||||
var el = document.getElementById('tox-result');
|
||||
var html = '';
|
||||
|
||||
if (topic === 'approach') {
|
||||
html += S.stepBox('#3b82f6', '1. Stabilize (ABCDE)', 'Airway, breathing, circulation, disability (glucose, pupils, GCS), exposure. Naloxone if depressed LOC + resp depression. Dextrose if hypoglycemic. Thiamine in select cases.');
|
||||
html += S.stepBox('#8b5cf6', '2. Identify toxidrome', '<strong>Sympathomimetic:</strong> HTN, tachy, mydriasis, diaphoresis (cocaine, meth). <strong>Anticholinergic:</strong> hot/dry, mydriasis, tachy, delirium (antihistamines, TCAs). <strong>Cholinergic:</strong> SLUDGE-M, miosis (organophosphates). <strong>Opioid:</strong> miosis, resp depression, ↓LOC. <strong>Sedative-hypnotic:</strong> ↓LOC, normal/low vitals.');
|
||||
html += S.stepBox('#f59e0b', '3. Decontamination', '<strong>Activated charcoal</strong> 1 g/kg PO (max 50 g): within 1h of ingestion, intact airway, ingestion adsorbed (not Li, metals, alcohols). Avoid caustics/hydrocarbons. <strong>Whole bowel irrigation:</strong> PEG for metals/iron/lithium/sustained release. <strong>Gastric lavage:</strong> rarely indicated. <strong>Ipecac:</strong> no longer recommended.');
|
||||
html += S.stepBox('#10b981', '4. Enhance elimination', '<strong>Urinary alkalinization</strong> (salicylates, phenobarbital). <strong>Hemodialysis</strong> (see ISTUMBLE). <strong>Lipid emulsion</strong> for lipid-soluble drug toxicity (LA, CCB, TCA).');
|
||||
html += S.stepBox('#ef4444', '5. Antidotes', 'See topic list. <strong>Contact Poison Center</strong> (US: 1-800-222-1222) early for any significant exposure.');
|
||||
} else if (topic === 'acetaminophen') {
|
||||
html += '<div style="padding:10px 14px;border-radius:8px;background:var(--blue-light);border:1.5px solid var(--blue);margin-bottom:10px;"><strong>Acetaminophen overdose</strong></div>';
|
||||
html += '<p style="font-size:13px;">Acute toxic dose: >150 mg/kg (or 7.5 g total) single ingestion. Hepatotoxic after 24h; AST/ALT peak day 3-4.</p>';
|
||||
html += '<p style="font-size:13px;"><strong>Diagnosis:</strong> Draw level at 4h post-ingestion (or on arrival if >4h). Plot on <strong>Rumack-Matthew nomogram</strong> (treatment line at 150 mcg/mL at 4h).</p>';
|
||||
html += S.drugTable(
|
||||
S.drugRow('N-acetylcysteine (NAC) — IV', '150 mg/kg over 1h → 50 mg/kg over 4h → 100 mg/kg over 16h (21h total)', 'IV', 'Total 300 mg/kg. Most effective if started <8h post-ingestion.') +
|
||||
S.drugRow('N-acetylcysteine — PO', '140 mg/kg load, then 70 mg/kg q4h × 17 doses', 'PO', 'Alternative to IV. Unpleasant taste — often with juice.') +
|
||||
S.drugRow('Activated charcoal', '1 g/kg (max 50 g)', 'PO', 'If within 1-2h of ingestion and airway protected.')
|
||||
);
|
||||
if (wt) html += '<div style="padding:10px;background:var(--g50);border-radius:6px;font-size:12px;"><strong>For ' + wt + ' kg:</strong> IV loading = ' + S.d(wt, 150) + ' mg over 1h <span style="color:var(--g500);font-size:11px;">(150 mg/kg)</span>; 2nd bag = ' + S.d(wt, 50) + ' mg over 4h <span style="color:var(--g500);font-size:11px;">(50 mg/kg)</span>; 3rd bag = ' + S.d(wt, 100) + ' mg over 16h <span style="color:var(--g500);font-size:11px;">(100 mg/kg)</span>. <em>Total 300 mg/kg.</em></div>';
|
||||
} else if (topic === 'opioids') {
|
||||
html += '<div style="padding:10px 14px;border-radius:8px;background:var(--blue-light);border:1.5px solid var(--blue);margin-bottom:10px;"><strong>Opioid overdose</strong></div>';
|
||||
html += '<p style="font-size:13px;">Triad: <strong>miosis + respiratory depression + ↓ LOC</strong>. Fentanyl / synthetics may require repeated/higher doses.</p>';
|
||||
html += S.drugTable(
|
||||
S.drugRow('Naloxone (initial)', '0.01-0.1 mg/kg' + (wt ? ' = ' + S.d(wt, 0.01) + '-' + S.d(wt, 0.1) + ' mg' : ''), 'IV / IM / IN / IO', 'Start low if chronic opioid use (avoid withdrawal). Titrate to respiratory effort, not consciousness.') +
|
||||
S.drugRow('Naloxone (full reversal)', '2 mg IV/IM/IN', 'IV / IM / IN', 'If no chronic use and severe resp depression. Repeat q2-3 min.') +
|
||||
S.drugRow('Naloxone infusion', '2/3 of effective bolus per hour', 'IV', 'For long-acting opioids (methadone, fentanyl patch, sustained release)')
|
||||
);
|
||||
html += '<div style="padding:10px;background:#fef3c7;border-radius:6px;font-size:12px;color:#92400e;margin-top:10px;">Observe ≥4h after last dose; longer for long-acting agents. Duration of naloxone (30-90 min) is shorter than most opioids — <strong>re-sedation is common</strong>.</div>';
|
||||
} else if (topic === 'iron') {
|
||||
html += '<div style="padding:10px 14px;border-radius:8px;background:var(--blue-light);border:1.5px solid var(--blue);margin-bottom:10px;"><strong>Iron overdose</strong></div>';
|
||||
html += '<p style="font-size:13px;">Toxic dose: elemental Fe ≥20 mg/kg. Severe ≥60 mg/kg. <strong>Charcoal does NOT bind iron.</strong></p>';
|
||||
html += '<p style="font-size:13px;"><strong>Stages:</strong> 1) GI (0-6h: vomiting, bloody diarrhea); 2) Latent (6-24h: deceptive improvement); 3) Shock/metabolic acidosis (12-24h); 4) Hepatotoxicity (2-5d); 5) Gastric scarring (2-8 wk).</p>';
|
||||
html += S.drugTable(
|
||||
S.drugRow('Whole bowel irrigation', 'PEG-ES 25-40 mL/kg/hr', 'NG', 'For radiopaque pills on KUB or ingested sustained-release iron.') +
|
||||
S.drugRow('Deferoxamine', '15 mg/kg/hr IV infusion', 'IV', 'Max 6-8 g/day. Indications: shock, metabolic acidosis, Fe level >500 mcg/dL, or severe symptoms. Urine turns "vin rosé" color.')
|
||||
);
|
||||
html += '<div style="padding:10px;background:#fef3c7;border-radius:6px;font-size:12px;color:#92400e;margin-top:10px;">Iron level at 4-6h. <350 usually asymptomatic; 350-500 mild; >500 severe. Consider KUB for radiopaque pills.</div>';
|
||||
} else if (topic === 'tca') {
|
||||
html += '<div style="padding:10px 14px;border-radius:8px;background:var(--blue-light);border:1.5px solid var(--blue);margin-bottom:10px;"><strong>TCA overdose</strong></div>';
|
||||
html += '<p style="font-size:13px;">Anticholinergic + Na-channel blockade. <strong>Risks:</strong> seizure, VT/VF, hypotension, coma. ECG: QRS >100 ms or R in aVR >3 mm predicts toxicity.</p>';
|
||||
html += S.drugTable(
|
||||
S.drugRow('Sodium bicarbonate 8.4%', '1-2 mEq/kg IV bolus → infusion', 'IV', 'Goal pH 7.45-7.55 and narrowing QRS. Repeat bolus for QRS widening, hypotension, or arrhythmia.') +
|
||||
S.drugRow('Mg sulfate', S.dStr(wt || 30, 50, 2000, ' mg'), 'IV over 10 min', 'For torsades.') +
|
||||
S.drugRow('IV lipid emulsion 20%', '1.5 mL/kg bolus → 0.25 mL/kg/min × 30-60 min', 'IV', 'If refractory shock/arrest. Consult toxicology.')
|
||||
);
|
||||
html += '<div style="padding:10px;background:#fef3c7;border-radius:6px;font-size:12px;color:#92400e;margin-top:10px;"><strong>Avoid</strong> Class IA/IC antiarrhythmics, beta-blockers, physostigmine (risk of asystole with TCAs).</div>';
|
||||
} else if (topic === 'bbccb') {
|
||||
html += '<div style="padding:10px 14px;border-radius:8px;background:var(--blue-light);border:1.5px solid var(--blue);margin-bottom:10px;"><strong>β-blocker / Calcium-channel blocker overdose</strong></div>';
|
||||
html += '<p style="font-size:13px;">Bradycardia + hypotension. CCBs (esp. verapamil, diltiazem, amlodipine) can be lethal in small pediatric doses.</p>';
|
||||
html += S.drugTable(
|
||||
S.drugRow('IV fluids', '20 mL/kg NS bolus', 'IV', 'Cautious — avoid volume overload') +
|
||||
S.drugRow('Calcium chloride 10% or gluconate', 'CaCl 20 mg/kg / Ca-glu 60 mg/kg', 'IV', 'First-line for CCB. Repeat q15-20 min.') +
|
||||
S.drugRow('Glucagon', '50 mcg/kg IV bolus → 50-150 mcg/kg/hr', 'IV', 'β-blocker antidote. GI side effects common.') +
|
||||
S.drugRow('High-dose insulin (HIE)', 'Regular insulin 1 U/kg bolus → 0.5-2 U/kg/hr + D25% infusion', 'IV', 'Hyperinsulinemic euglycemia therapy. Monitor K+ and glucose closely.') +
|
||||
S.drugRow('Vasopressors', 'Epi / norepi infusion', 'IV', 'Titrate to MAP.') +
|
||||
S.drugRow('Lipid emulsion 20%', '1.5 mL/kg → 0.25 mL/kg/min', 'IV', 'For lipid-soluble CCBs (verapamil) if refractory.') +
|
||||
S.drugRow('Methylene blue / ECMO', '—', '—', 'Rescue therapy — toxicology/ICU consult.')
|
||||
);
|
||||
} else if (topic === 'benzo') {
|
||||
html += '<div style="padding:10px 14px;border-radius:8px;background:var(--blue-light);border:1.5px solid var(--blue);margin-bottom:10px;"><strong>Benzodiazepine overdose</strong></div>';
|
||||
html += '<p style="font-size:13px;">Sedation + resp depression. Usually supportive — intubation rarely needed alone. <strong>Flumazenil caution.</strong></p>';
|
||||
html += S.drugTable(
|
||||
S.drugRow('Flumazenil', '0.01 mg/kg (max 0.2 mg) IV, may repeat q1 min to max 1 mg', 'IV', 'Only for iatrogenic reversal in a benzo-naive patient with no TCAs, seizure disorder, or chronic benzo use. <strong>Can precipitate seizures.</strong>')
|
||||
);
|
||||
html += '<div style="padding:10px;background:#fef3c7;border-radius:6px;font-size:12px;color:#92400e;margin-top:10px;">In real-world ingestion, <strong>supportive care (airway, monitoring)</strong> is usually safer than flumazenil.</div>';
|
||||
} else if (topic === 'organo') {
|
||||
html += '<div style="padding:10px 14px;border-radius:8px;background:var(--blue-light);border:1.5px solid var(--blue);margin-bottom:10px;"><strong>Organophosphate / carbamate poisoning</strong></div>';
|
||||
html += '<p style="font-size:13px;">Cholinergic toxidrome: <strong>SLUDGE-M</strong> (salivation, lacrimation, urination, defecation, GI distress, emesis, miosis) + muscle fasciculation, bradycardia, bronchorrhea. Decontamination critical (PPE for providers).</p>';
|
||||
html += S.drugTable(
|
||||
S.drugRow('Atropine', '0.05 mg/kg IV, double q3-5 min until bronchial secretions dry', 'IV', 'Endpoint = dry lungs, not dry mouth or heart rate. Can require huge doses.') +
|
||||
S.drugRow('Pralidoxime (2-PAM)', '25-50 mg/kg IV over 30 min (max 2 g) → 10-20 mg/kg/hr', 'IV', 'Only for organophosphates (not carbamates). Regenerates acetylcholinesterase.') +
|
||||
S.drugRow('Diazepam / midazolam', 'Standard seizure doses', 'IV', 'For seizures or severe fasciculations.')
|
||||
);
|
||||
} else if (topic === 'salicylate') {
|
||||
html += '<div style="padding:10px 14px;border-radius:8px;background:var(--blue-light);border:1.5px solid var(--blue);margin-bottom:10px;"><strong>Salicylate overdose</strong></div>';
|
||||
html += '<p style="font-size:13px;">Mixed respiratory alkalosis + anion gap metabolic acidosis. Tinnitus, tachypnea, diaphoresis, hyperthermia. Severe: CNS depression, seizures, pulmonary edema, cerebral edema.</p>';
|
||||
html += S.drugTable(
|
||||
S.drugRow('Fluids', 'Aggressive resuscitation', 'IV', 'Dehydration common. Avoid fluid overload (risk of pulmonary edema).') +
|
||||
S.drugRow('Sodium bicarb', '1-2 mEq/kg IV bolus → drip', 'IV', 'Urinary alkalinization (goal urine pH >7.5). Prevents CNS entry.') +
|
||||
S.drugRow('Glucose', 'Maintain euglycemia even if BG normal', 'IV', 'CNS hypoglycemia despite normal serum glucose.') +
|
||||
S.drugRow('Hemodialysis', '—', '—', 'For severe: altered MS, pulmonary edema, renal failure, refractory acidosis, or level >100 mg/dL acute / >60 chronic.')
|
||||
);
|
||||
} else if (topic === 'etoh') {
|
||||
html += '<div style="padding:10px 14px;border-radius:8px;background:var(--blue-light);border:1.5px solid var(--blue);margin-bottom:10px;"><strong>Toxic alcohols (methanol / ethylene glycol)</strong></div>';
|
||||
html += '<p style="font-size:13px;">Anion gap metabolic acidosis + osmolar gap. Methanol → visual changes, blindness. Ethylene glycol → calcium oxalate crystals in urine, renal failure.</p>';
|
||||
html += S.drugTable(
|
||||
S.drugRow('Fomepizole', '15 mg/kg IV load → 10 mg/kg q12h × 4 → 15 mg/kg q12h', 'IV', 'First-line. Inhibits alcohol dehydrogenase.') +
|
||||
S.drugRow('Ethanol (alternative)', 'Load 600 mg/kg → maintain level 100-150 mg/dL', 'IV / PO', 'If fomepizole unavailable. Monitor closely.') +
|
||||
S.drugRow('Hemodialysis', '—', '—', 'For severe acidosis, end-organ damage, or high levels.') +
|
||||
S.drugRow('Folate / thiamine / pyridoxine', 'Standard doses', 'IV', 'Methanol: folate. EG: thiamine + pyridoxine (divert to non-toxic metabolites).')
|
||||
);
|
||||
} else if (topic === 'dialyzable') {
|
||||
html += '<div style="padding:10px 14px;border-radius:8px;background:var(--blue-light);border:1.5px solid var(--blue);margin-bottom:10px;"><strong>Dialyzable drugs (ISTUMBLE)</strong></div>';
|
||||
html += '<p style="font-size:13px;">Commonly dialyzable in overdose:</p>';
|
||||
html += '<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:6px;font-size:13px;">';
|
||||
[['I','Isopropyl alcohol, INH'],['S','Salicylates'],['T','Theophylline, Toxic alcohols'],['U','Uremia-related drugs'],['M','Methanol, Metformin, Methotrexate'],['B','Barbiturates (long-acting)'],['L','Lithium'],['E','Ethylene glycol']].forEach(function(x) {
|
||||
html += '<div style="padding:8px 10px;background:var(--g50);border-radius:6px;"><strong>' + x[0] + ':</strong> ' + x[1] + '</div>';
|
||||
});
|
||||
html += '</div>';
|
||||
html += '<div style="padding:10px;background:#fef3c7;border-radius:6px;font-size:12px;color:#92400e;margin-top:10px;"><strong>Poor candidates for HD:</strong> Large Vd (TCAs, digoxin, BBs), highly protein-bound (benzos, CCBs), lipid-soluble (opioids, phenothiazines). EXTRIP recommendations are evidence-based — consult toxicology.</div>';
|
||||
}
|
||||
html += S.ref('Call Poison Control early: 1-800-222-1222 (US). Refs: Goldfrank\'s Toxicologic Emergencies, EXTRIP workgroup.');
|
||||
el.innerHTML = html;
|
||||
}
|
||||
|
||||
export function init() {
|
||||
document.addEventListener('click', function(e) {
|
||||
if (e.target.id === 'btn-tox-lookup' || e.target.closest('#btn-tox-lookup')) lookupTox();
|
||||
});
|
||||
}
|
||||
44
public/js/bedside/trauma.js
Normal file
44
public/js/bedside/trauma.js
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
// ============================================================
|
||||
// bedside/trauma.js
|
||||
// TRAUMA — primary survey, MTP, c-spine, shock signs.
|
||||
// ============================================================
|
||||
|
||||
import { S } from './shared.js';
|
||||
|
||||
function showTrauma() {
|
||||
var wt = parseFloat(document.getElementById('trauma-weight').value);
|
||||
var el = document.getElementById('trauma-result');
|
||||
var html = '';
|
||||
|
||||
html += '<h5 style="font-size:13px;font-weight:700;color:var(--g700);margin:0 0 6px;">Primary Survey — ABCDE</h5>';
|
||||
html += S.stepBox('#ef4444', 'A — Airway + c-spine', 'Maintain airway (jaw thrust, suction). <strong>Manual inline stabilization</strong>; apply collar. Intubate if GCS ≤8, inadequate airway, or impending compromise. Avoid succinylcholine if burn/crush/prolonged immobilization.');
|
||||
html += S.stepBox('#f59e0b', 'B — Breathing', 'SpO2, bilateral breath sounds, chest wall integrity. Identify tension PTX (needle decompression), open PTX (3-sided dressing), flail chest, massive hemothorax.');
|
||||
html += S.stepBox('#10b981', 'C — Circulation', 'Two large-bore IVs / IO. Control hemorrhage (direct pressure, tourniquet, pelvic binder). ' + (wt ? 'NS or LR 20 mL/kg = ' + S.d(wt, 20) + ' mL bolus.' : 'NS/LR 20 mL/kg bolus.') + ' Consider blood after 40-60 mL/kg crystalloid or in Class III shock.');
|
||||
html += S.stepBox('#3b82f6', 'D — Disability', 'GCS, pupils, glucose, gross motor. Consider ↑ICP (head up 30°, mannitol 0.5-1 g/kg or hypertonic saline 3% 3-5 mL/kg).');
|
||||
html += S.stepBox('#8b5cf6', 'E — Exposure + environment', 'Fully expose; prevent hypothermia (warm blankets, fluids).');
|
||||
|
||||
html += '<h5 style="font-size:13px;font-weight:700;color:var(--g700);margin:16px 0 6px;">Massive Transfusion Protocol (MTP)</h5>';
|
||||
html += S.drugTable(
|
||||
S.drugRow('Ratio', '1:1:1 (pRBC : FFP : platelets)', '—', 'Activate early if ≥40 mL/kg transfused or ongoing hemorrhage. Avoid excessive crystalloid.') +
|
||||
S.drugRow('Tranexamic acid (TXA)', '15 mg/kg IV (max 1 g) over 10 min → 2 mg/kg/hr × 8h', 'IV', 'Within 3 hr of injury. CRASH-2 / MATIC.') +
|
||||
S.drugRow('Calcium', '20 mg/kg CaCl or 60 mg/kg Ca-gluconate IV per unit citrated blood', 'IV', 'Citrated blood chelates calcium.')
|
||||
);
|
||||
|
||||
html += '<h5 style="font-size:13px;font-weight:700;color:var(--g700);margin:16px 0 6px;">C-spine clearance (NEXUS / CCR)</h5>';
|
||||
html += '<div style="padding:10px;background:var(--g50);border-radius:6px;font-size:12px;">Pediatric c-spine decision tools are imperfect. <strong>Imaging if any:</strong> focal neurologic deficit, altered mental status, neck pain / tenderness, torticollis, substantial torso injury, high-risk mechanism (diving, MVC >55 mph, fall >10 ft). Plain films + CT if positive or equivocal.</div>';
|
||||
|
||||
html += '<h5 style="font-size:13px;font-weight:700;color:var(--g700);margin:16px 0 6px;">Pediatric shock — signs</h5>';
|
||||
html += '<div style="padding:10px;background:#fee2e2;border-radius:6px;font-size:12px;color:#991b1b;">Children compensate well — <strong>hypotension is a late finding</strong>. Early signs: tachycardia, cool extremities, weak peripheral pulses, prolonged cap refill (>3 sec), narrowed pulse pressure, altered mentation. Minimum SBP = 70 + (2 × age in years) for ages 1-10.</div>';
|
||||
|
||||
html += '<h5 style="font-size:13px;font-weight:700;color:var(--g700);margin:16px 0 6px;">Secondary survey — AMPLE + head-to-toe</h5>';
|
||||
html += '<div style="padding:10px;background:var(--g50);border-radius:6px;font-size:12px;"><strong>AMPLE:</strong> Allergies, Medications, Past history, Last meal, Events of injury. Head-to-toe exam; log-roll for back; digital rectal; neurovascular checks of all extremities.</div>';
|
||||
|
||||
html += S.ref('ATLS 10th ed; PALS 2020; PECARN c-spine rule; CRASH-2 trial (TXA).');
|
||||
el.innerHTML = html;
|
||||
}
|
||||
|
||||
export function init() {
|
||||
document.addEventListener('click', function(e) {
|
||||
if (e.target.id === 'btn-trauma-show' || e.target.closest('#btn-trauma-show')) showTrauma();
|
||||
});
|
||||
}
|
||||
147
public/js/bedside/ventilation.js
Normal file
147
public/js/bedside/ventilation.js
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
// ============================================================
|
||||
// bedside/ventilation.js
|
||||
// O2 & VENTILATION (teaching reference).
|
||||
// ============================================================
|
||||
|
||||
import { S } from './shared.js';
|
||||
|
||||
function showVent() {
|
||||
var wt = parseFloat(document.getElementById('vent-weight').value);
|
||||
var age = parseFloat(document.getElementById('vent-age').value);
|
||||
var el = document.getElementById('vent-result');
|
||||
var html = '';
|
||||
|
||||
// ── Target SpO2 quick reference ──
|
||||
html += '<h5 style="font-size:13px;font-weight:700;color:var(--g700);margin:0 0 6px;">Target SpO2</h5>';
|
||||
html += '<div style="overflow-x:auto;-webkit-overflow-scrolling:touch;margin-bottom:14px;"><table style="width:100%;min-width:460px;border-collapse:collapse;font-size:12px;">';
|
||||
html += '<thead><tr style="background:var(--g100);"><th style="text-align:left;padding:5px 8px;">Patient</th><th style="text-align:left;padding:5px 8px;">Target</th><th style="text-align:left;padding:5px 8px;">Notes</th></tr></thead><tbody>';
|
||||
html += '<tr><td style="padding:5px 8px;border-bottom:1px solid var(--g100);">Most children</td><td style="padding:5px 8px;border-bottom:1px solid var(--g100);"><strong>94-98%</strong></td><td style="padding:5px 8px;border-bottom:1px solid var(--g100);">Normal</td></tr>';
|
||||
html += '<tr><td style="padding:5px 8px;border-bottom:1px solid var(--g100);">Bronchiolitis (AAP 2014/2023)</td><td style="padding:5px 8px;border-bottom:1px solid var(--g100);"><strong>≥90%</strong></td><td style="padding:5px 8px;border-bottom:1px solid var(--g100);">Don\'t chase higher saturations</td></tr>';
|
||||
html += '<tr><td style="padding:5px 8px;border-bottom:1px solid var(--g100);">Chronic lung disease / CF</td><td style="padding:5px 8px;border-bottom:1px solid var(--g100);"><strong>90-94%</strong></td><td style="padding:5px 8px;border-bottom:1px solid var(--g100);">Avoid hyperoxia in CO2 retainers</td></tr>';
|
||||
html += '<tr><td style="padding:5px 8px;border-bottom:1px solid var(--g100);">Preterm neonate</td><td style="padding:5px 8px;border-bottom:1px solid var(--g100);"><strong>90-95%</strong></td><td style="padding:5px 8px;border-bottom:1px solid var(--g100);">Minimize ROP risk</td></tr>';
|
||||
html += '<tr><td style="padding:5px 8px;border-bottom:1px solid var(--g100);">Term neonate (min of life)</td><td style="padding:5px 8px;border-bottom:1px solid var(--g100);"><strong>Per NRP ladder</strong></td><td style="padding:5px 8px;border-bottom:1px solid var(--g100);">1 min 60-65%, 10 min 85-95%</td></tr>';
|
||||
html += '</tbody></table></div>';
|
||||
|
||||
// ── Escalation ladder ──
|
||||
html += '<h5 style="font-size:13px;font-weight:700;color:var(--g700);margin:14px 0 6px;">Escalation ladder (step up when target not reached / work of breathing)</h5>';
|
||||
html += S.stepBox('#10b981',
|
||||
'1. Nasal cannula (low-flow)',
|
||||
'<strong>0.5-6 L/min</strong> · FiO2 ~24-40% (rough: room air 21% + 4% per L/min) · comfortable, no humidification needed. Good for mild hypoxia or baseline support.');
|
||||
html += S.stepBox('#3b82f6',
|
||||
'2. Simple face mask',
|
||||
'<strong>6-10 L/min</strong> · FiO2 35-60%. Must keep flow >6 L/min to flush CO2. Tolerated less well than NC.');
|
||||
html += S.stepBox('#8b5cf6',
|
||||
'3. Non-rebreather mask',
|
||||
'<strong>10-15 L/min</strong> · FiO2 60-90%. Reservoir bag must stay inflated. For severe hypoxia with intact breathing.');
|
||||
html += S.stepBox('#f59e0b',
|
||||
'4. High-flow nasal cannula (HFNC)',
|
||||
'<strong>' + (wt ? '1-2 L/kg/min = ' + S.d(wt, 1) + '-' + S.d(wt, 2) + ' L/min' : '1-2 L/kg/min') + '</strong> (max ~50-60 L/min adults) · heated + humidified · FiO2 30-100% titratable · generates ~2-5 cmH2O PEEP · works for bronchiolitis, early resp failure. Reassess at 1-2 h — no improvement → step up.');
|
||||
html += S.stepBox('#ef4444',
|
||||
'5. Non-invasive ventilation (CPAP / BiPAP)',
|
||||
'<strong>CPAP</strong> 5-10 cmH2O (pure PEEP) · <strong>BiPAP</strong> IPAP 10-14 / EPAP 5 (adds pressure support) · FiO2 as needed. Needs cooperative patient, intact airway reflexes, no copious secretions. Contraindicated: coma, apnea, unstable hemodynamics, facial trauma.');
|
||||
html += S.stepBox('#dc2626',
|
||||
'6. Intubate + mechanical ventilation',
|
||||
'When NIV fails, airway compromised, apnea, or GCS ≤8. See RSI under the Airway tab for drugs.');
|
||||
|
||||
// ── BVM how-to ──
|
||||
html += '<h5 style="font-size:13px;font-weight:700;color:var(--g700);margin:14px 0 6px;">Bag-Valve-Mask (BVM)</h5>';
|
||||
html += '<div style="padding:10px 12px;background:var(--blue-light);border-radius:6px;font-size:12px;line-height:1.6;">';
|
||||
html += '<strong>When:</strong> apnea, bradycardia (HR <60 in neonate; inadequate breathing at any age), during resuscitation.<br>';
|
||||
html += '<strong>Rate:</strong> Newborn 40-60 /min · Infant-child 20-30 /min · Adolescent 10-12 /min (1 breath q5-6 sec).<br>';
|
||||
html += '<strong>Tidal volume:</strong> 6-8 mL/kg — only enough to see <strong>gentle chest rise</strong>. Avoid over-ventilation (gastric distension, ↓venous return, lung injury).<br>';
|
||||
html += '<strong>Technique:</strong> head tilt / jaw thrust, proper mask seal (E-C grip or 2-thumb), squeeze 1 sec, release fully before next breath. Attach O2 at 10-15 L/min + reservoir.<br>';
|
||||
html += '<strong>If not ventilating:</strong> MR SOPA — <u>M</u>ask reseal, <u>R</u>eposition airway, <u>S</u>uction mouth+nose, <u>O</u>pen mouth, <u>P</u>ressure ↑, <u>A</u>lternative airway (LMA or ETT).';
|
||||
html += '</div>';
|
||||
|
||||
// ── Mechanical vent settings ──
|
||||
html += '<h5 style="font-size:13px;font-weight:700;color:var(--g700);margin:14px 0 6px;">Mechanical ventilator — starting settings</h5>';
|
||||
var rate = age == null ? '' : (age < 0.1 ? '30-40' : age < 1 ? '25-35' : age < 5 ? '20-25' : age < 12 ? '16-20' : '12-16');
|
||||
html += '<div style="padding:10px 12px;background:var(--g50);border-radius:6px;font-size:12px;line-height:1.65;">';
|
||||
html += '<strong>Mode:</strong> Volume-control (set TV, get pressure) OR Pressure-control (set pressure, get TV). PRVC / SIMV-PS are hybrids. Pick what your unit uses.<br>';
|
||||
html += '<strong>Tidal volume:</strong> <strong>' + (wt ? S.d(wt, 6) + '-' + S.d(wt, 8) + ' mL' : '6-8 mL/kg') + '</strong> <span style="color:var(--g500);font-size:11px;">(6-8 mL/kg)</span>. Use lower (4-6 mL/kg) for ARDS.<br>';
|
||||
html += '<strong>Rate:</strong> ' + (rate ? rate + ' /min (for age ' + age + ' yr)' : 'Newborn 30-40 · Infant 25-35 · Child 16-20 · Adolescent 12-16') + '.<br>';
|
||||
html += '<strong>PEEP:</strong> start <strong>5 cmH2O</strong> (routine). Increase to 8-12+ for refractory hypoxia (ARDS).<br>';
|
||||
html += '<strong>FiO2:</strong> start 100%, wean rapidly to lowest that maintains target SpO2 (usually <60%).<br>';
|
||||
html += '<strong>I:E ratio:</strong> 1:2 normally. 1:3-4 for obstructive disease (asthma, bronchiolitis) to allow exhalation.<br>';
|
||||
html += '<strong>Inspiratory time:</strong> 0.5 s infant · 0.7-1 s child · 1-1.2 s adolescent.<br>';
|
||||
html += '<strong>Pressure limits:</strong> keep plateau <30 cmH2O (ideally <28) to avoid barotrauma.';
|
||||
html += '</div>';
|
||||
|
||||
// ── Pressure-time waveform ──
|
||||
html += '<h5 style="font-size:13px;font-weight:700;color:var(--g700);margin:14px 0 6px;">Pressure-time waveform (what the monitor shows)</h5>';
|
||||
html += '<div style="padding:10px;background:white;border:1px solid var(--g200);border-radius:8px;margin-bottom:14px;">';
|
||||
html += '<svg viewBox="0 0 540 260" xmlns="http://www.w3.org/2000/svg" style="width:100%;max-width:640px;display:block;margin:0 auto;">' +
|
||||
// grid lines
|
||||
'<line x1="60" y1="60" x2="500" y2="60" stroke="#f3f4f6"/>' +
|
||||
'<line x1="60" y1="100" x2="500" y2="100" stroke="#f3f4f6"/>' +
|
||||
'<line x1="60" y1="140" x2="500" y2="140" stroke="#f3f4f6"/>' +
|
||||
'<line x1="60" y1="180" x2="500" y2="180" stroke="#f3f4f6"/>' +
|
||||
// axes
|
||||
'<line x1="60" y1="220" x2="500" y2="220" stroke="#374151" stroke-width="1.5"/>' +
|
||||
'<line x1="60" y1="20" x2="60" y2="220" stroke="#374151" stroke-width="1.5"/>' +
|
||||
// y-axis tick labels (cmH2O)
|
||||
'<text x="55" y="64" font-size="10" text-anchor="end" fill="#6b7280">30</text>' +
|
||||
'<text x="55" y="104" font-size="10" text-anchor="end" fill="#6b7280">20</text>' +
|
||||
'<text x="55" y="144" font-size="10" text-anchor="end" fill="#6b7280">10</text>' +
|
||||
'<text x="55" y="184" font-size="10" text-anchor="end" fill="#6b7280">5</text>' +
|
||||
'<text x="55" y="224" font-size="10" text-anchor="end" fill="#6b7280">0</text>' +
|
||||
// axis titles
|
||||
'<text x="275" y="250" font-size="11" text-anchor="middle" fill="#374151">Time (seconds)</text>' +
|
||||
'<text x="18" y="120" font-size="11" text-anchor="middle" fill="#374151" transform="rotate(-90 18 120)">Pressure (cmH₂O)</text>' +
|
||||
// x-axis time markers
|
||||
'<text x="60" y="238" font-size="10" text-anchor="middle" fill="#6b7280">0</text>' +
|
||||
'<text x="200" y="238" font-size="10" text-anchor="middle" fill="#6b7280">2</text>' +
|
||||
'<text x="340" y="238" font-size="10" text-anchor="middle" fill="#6b7280">4</text>' +
|
||||
'<text x="480" y="238" font-size="10" text-anchor="middle" fill="#6b7280">6</text>' +
|
||||
// PEEP reference dashed line (y=180 = 5 cmH2O)
|
||||
'<line x1="60" y1="180" x2="500" y2="180" stroke="#10b981" stroke-width="1" stroke-dasharray="4,3" opacity="0.5"/>' +
|
||||
// Pressure curve — 3 breaths, each 2s (Ti=0.8s, Te=1.2s); 70px/sec; PEEP=5→y=180, PIP=25→y=80, Plateau=22→y=88
|
||||
'<path d="M 60 180 L 80 180 L 90 80 L 145 88 L 155 180 L 200 180 L 210 80 L 265 88 L 275 180 L 340 180 L 350 80 L 405 88 L 415 180 L 480 180" fill="none" stroke="#3b82f6" stroke-width="2.5" stroke-linejoin="round"/>' +
|
||||
// PIP label
|
||||
'<line x1="120" y1="50" x2="92" y2="78" stroke="#ef4444"/>' +
|
||||
'<text x="125" y="48" font-size="11" font-weight="bold" fill="#ef4444">PIP</text>' +
|
||||
'<text x="125" y="60" font-size="9" fill="#6b7280">peak inspiratory</text>' +
|
||||
// Plateau label
|
||||
'<line x1="230" y1="50" x2="145" y2="88" stroke="#a855f7"/>' +
|
||||
'<text x="235" y="48" font-size="11" font-weight="bold" fill="#a855f7">Plateau</text>' +
|
||||
'<text x="235" y="60" font-size="9" fill="#6b7280">reflects alveolar pressure</text>' +
|
||||
// PEEP label
|
||||
'<line x1="380" y1="208" x2="345" y2="182" stroke="#10b981"/>' +
|
||||
'<text x="385" y="208" font-size="11" font-weight="bold" fill="#10b981">PEEP</text>' +
|
||||
// Ti bracket (x=90 to 155)
|
||||
'<line x1="90" y1="32" x2="155" y2="32" stroke="#f59e0b" stroke-width="1"/>' +
|
||||
'<line x1="90" y1="28" x2="90" y2="36" stroke="#f59e0b"/>' +
|
||||
'<line x1="155" y1="28" x2="155" y2="36" stroke="#f59e0b"/>' +
|
||||
'<text x="122" y="25" font-size="10" font-weight="bold" text-anchor="middle" fill="#f59e0b">Ti</text>' +
|
||||
// Te bracket (x=155 to 200)
|
||||
'<line x1="155" y1="202" x2="200" y2="202" stroke="#f59e0b" stroke-width="1"/>' +
|
||||
'<line x1="155" y1="198" x2="155" y2="206" stroke="#f59e0b"/>' +
|
||||
'<line x1="200" y1="198" x2="200" y2="206" stroke="#f59e0b"/>' +
|
||||
'<text x="177" y="215" font-size="10" font-weight="bold" text-anchor="middle" fill="#f59e0b">Te</text>' +
|
||||
'</svg>';
|
||||
html += '<div style="font-size:11px;color:var(--g500);margin-top:8px;line-height:1.5;"><strong style="color:#ef4444;">PIP</strong> = peak pressure during inspiration (depends on TV, airway resistance, lung compliance). <strong style="color:#a855f7;">Plateau</strong> = static pressure at end-inspiration with brief breath-hold (reflects alveolar pressure — keep <30). <strong style="color:#10b981;">PEEP</strong> = baseline end-expiratory pressure keeping alveoli open. <strong style="color:#f59e0b;">Ti / Te</strong> = inspiratory / expiratory time. Rate × (Ti+Te) = 60 s.</div>';
|
||||
html += '</div>';
|
||||
|
||||
// ── Troubleshooting: "if this, change that" ──
|
||||
html += '<h5 style="font-size:13px;font-weight:700;color:var(--g700);margin:14px 0 6px;">Adjusting for gas exchange</h5>';
|
||||
html += '<div style="overflow-x:auto;-webkit-overflow-scrolling:touch;"><table style="width:100%;min-width:480px;border-collapse:collapse;font-size:12px;">';
|
||||
html += '<thead><tr style="background:var(--g100);"><th style="text-align:left;padding:5px 8px;">Problem</th><th style="text-align:left;padding:5px 8px;">First lever</th><th style="text-align:left;padding:5px 8px;">Second lever</th></tr></thead><tbody>';
|
||||
html += '<tr><td style="padding:5px 8px;border-bottom:1px solid var(--g100);"><strong>Low SpO2</strong> (oxygenation)</td><td style="padding:5px 8px;border-bottom:1px solid var(--g100);">↑ FiO2</td><td style="padding:5px 8px;border-bottom:1px solid var(--g100);">↑ PEEP (recruits collapsed alveoli)</td></tr>';
|
||||
html += '<tr><td style="padding:5px 8px;border-bottom:1px solid var(--g100);"><strong>↑ PCO2</strong> (ventilation)</td><td style="padding:5px 8px;border-bottom:1px solid var(--g100);">↑ Rate</td><td style="padding:5px 8px;border-bottom:1px solid var(--g100);">↑ Tidal volume</td></tr>';
|
||||
html += '<tr><td style="padding:5px 8px;border-bottom:1px solid var(--g100);"><strong>↓ PCO2</strong> (over-ventilating)</td><td style="padding:5px 8px;border-bottom:1px solid var(--g100);">↓ Rate</td><td style="padding:5px 8px;border-bottom:1px solid var(--g100);">↓ Tidal volume</td></tr>';
|
||||
html += '<tr><td style="padding:5px 8px;border-bottom:1px solid var(--g100);"><strong>High peak pressure</strong></td><td style="padding:5px 8px;border-bottom:1px solid var(--g100);">Check tube (kink, mucus plug), compliance, bronchospasm</td><td style="padding:5px 8px;border-bottom:1px solid var(--g100);">Suction, bronchodilator, lower TV</td></tr>';
|
||||
html += '<tr><td style="padding:5px 8px;border-bottom:1px solid var(--g100);"><strong>Auto-PEEP</strong> (asthma, bronchiolitis)</td><td style="padding:5px 8px;border-bottom:1px solid var(--g100);">↓ Rate, ↑ expiratory time</td><td style="padding:5px 8px;border-bottom:1px solid var(--g100);">Disconnect + bag briefly if critical</td></tr>';
|
||||
html += '</tbody></table></div>';
|
||||
|
||||
// ── Mental model summary ──
|
||||
html += '<div style="margin-top:14px;padding:10px 12px;background:#d1fae5;border-radius:6px;font-size:12px;color:#065f46;line-height:1.55;"><strong>Mental model:</strong> Oxygenation is mostly <strong>FiO2 + PEEP</strong>. Ventilation (CO2) is mostly <strong>rate + tidal volume</strong>. Match mode to disease: obstructive (asthma, bronchiolitis) → long expiratory time, permissive hypercapnia. Restrictive (ARDS) → low TV, high PEEP, permissive hypercapnia + hypoxia.</div>';
|
||||
|
||||
html += S.ref('Based on AAP / PALS / AARC guidance. Tailor to individual patient and institutional protocols.');
|
||||
el.innerHTML = html;
|
||||
}
|
||||
|
||||
export function init() {
|
||||
document.addEventListener('click', function(e) {
|
||||
if (e.target.id === 'btn-vent-show' || e.target.closest('#btn-vent-show')) showVent();
|
||||
});
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue