// ============================================================ // 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 = '
Enter GA and birth weight.
'; 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 += '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.
'; 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 all yes → routine care with mother. If any no → 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)', 'Start PPV 40-60 breaths/min, room air for term / 21-30% for preterm. Attach SpO2 (right hand) ± ECG. MR SOPA if ineffective: Mask adjust, Reposition, Suction, Open mouth, Pressure increase, Alternative 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', 'Intubate + Chest compressions — 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', 'Epinephrine 1:10,000 (0.1 mg/mL):Enter weight (kg).
'; 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 = 'Apgar is a description of status — never delay resuscitation while scoring. Follow NRP algorithm based on HR and breathing. Apgar <7 at 5 min: repeat q5 min up to 20 min.
'; 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(); }); }