Add pediatric calculators: BP, BMI, growth, bilirubin, vitals, BSA, dosing
Calculators tab with 7 tools: - BP Percentile (AAP 2017) with age/sex/height classification - BMI Percentile (CDC 2000) with extended obesity classification (Class 1/2/3 using % of 95th percentile per CDC 2022) - Growth Charts: weight-for-age, length-for-age, head circumference, weight-for-length (WHO/CDC LMS), Fenton preterm (22-50 weeks) - Bilirubin: AAP 2022 phototherapy threshold + Bhutani nomogram with Nelson Table 137.1 risk factors for severe hyperbilirubinemia - Vital Signs by Age (Harriet Lane) with quick reference formulas (estimated weight, min SBP, ETT size, maintenance fluids 4-2-1) - Body Surface Area (Mosteller formula) - Weight-Based Dosing with max cap and volume calculation Fix growth chart sub-tab navigation (pills scoped separately from top-level nav to prevent panel disappearing)
This commit is contained in:
parent
79994f4781
commit
4fbfc913d0
4 changed files with 1175 additions and 0 deletions
424
public/components/calculators.html
Normal file
424
public/components/calculators.html
Normal file
|
|
@ -0,0 +1,424 @@
|
|||
<div class="module-header">
|
||||
<h2><i class="fas fa-calculator"></i> Pediatric Calculators</h2>
|
||||
<p>Clinical calculators for pediatric medicine</p>
|
||||
</div>
|
||||
|
||||
<!-- Calculator selector pills -->
|
||||
<div style="display:flex;gap:8px;flex-wrap:wrap;margin-bottom:20px;">
|
||||
<button class="calc-nav-pill active" data-calc="bp">BP Percentile</button>
|
||||
<button class="calc-nav-pill" data-calc="bmi">BMI Percentile</button>
|
||||
<button class="calc-nav-pill" data-calc="growth">Growth Charts</button>
|
||||
<button class="calc-nav-pill" data-calc="bili">Bilirubin</button>
|
||||
<button class="calc-nav-pill" data-calc="vitals">Vital Signs</button>
|
||||
<button class="calc-nav-pill" data-calc="bsa">Body Surface Area</button>
|
||||
<button class="calc-nav-pill" data-calc="dose">Weight-Based Dosing</button>
|
||||
</div>
|
||||
|
||||
<!-- ═══════════ BP PERCENTILE CALCULATOR ═══════════ -->
|
||||
<div id="calc-bp" class="calc-panel">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3><i class="fas fa-heart-pulse"></i> Blood Pressure Percentile</h3>
|
||||
<span style="font-size:11px;color:var(--g500);">AAP 2017 Clinical Practice Guideline</span>
|
||||
</div>
|
||||
<div style="padding:16px;">
|
||||
<p style="font-size:12px;color:var(--g500);margin:0 0 16px;">Based on normative tables for normal-weight children ages 1-17. Flynn et al., Pediatrics 2017;140(3):e20171904.</p>
|
||||
<div class="calc-grid">
|
||||
<div class="calc-field">
|
||||
<label>Age (years)</label>
|
||||
<input type="number" id="bp-age" min="1" max="17" step="1" placeholder="1-17">
|
||||
</div>
|
||||
<div class="calc-field">
|
||||
<label>Sex</label>
|
||||
<select id="bp-sex"><option value="">Select</option><option value="male">Male</option><option value="female">Female</option></select>
|
||||
</div>
|
||||
<div class="calc-field">
|
||||
<label>Height (cm)</label>
|
||||
<input type="number" id="bp-height" min="50" max="200" step="0.1" placeholder="e.g. 110">
|
||||
</div>
|
||||
<div class="calc-field">
|
||||
<label>Systolic BP (mmHg)</label>
|
||||
<input type="number" id="bp-systolic" min="50" max="250" placeholder="e.g. 105">
|
||||
</div>
|
||||
<div class="calc-field">
|
||||
<label>Diastolic BP (mmHg)</label>
|
||||
<input type="number" id="bp-diastolic" min="30" max="150" placeholder="e.g. 65">
|
||||
</div>
|
||||
</div>
|
||||
<button id="btn-calc-bp" class="btn-sm btn-primary" style="margin-top:12px;"><i class="fas fa-calculator"></i> Calculate</button>
|
||||
<button id="btn-clear-bp" class="btn-sm btn-ghost" style="margin-top:12px;">Clear</button>
|
||||
<div id="bp-result" class="calc-result hidden"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ═══════════ BMI PERCENTILE CALCULATOR ═══════════ -->
|
||||
<div id="calc-bmi" class="calc-panel hidden">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3><i class="fas fa-weight-scale"></i> BMI Percentile</h3>
|
||||
<span style="font-size:11px;color:var(--g500);">CDC Growth Charts, ages 2-20</span>
|
||||
</div>
|
||||
<div style="padding:16px;">
|
||||
<p style="font-size:12px;color:var(--g500);margin:0 0 16px;">Based on CDC 2000 growth reference data. For children ages 2-20 years.</p>
|
||||
<div class="calc-grid">
|
||||
<div class="calc-field">
|
||||
<label>Age (years)</label>
|
||||
<input type="number" id="bmi-age" min="2" max="20" step="0.1" placeholder="2-20">
|
||||
</div>
|
||||
<div class="calc-field">
|
||||
<label>Sex</label>
|
||||
<select id="bmi-sex"><option value="">Select</option><option value="male">Male</option><option value="female">Female</option></select>
|
||||
</div>
|
||||
<div class="calc-field">
|
||||
<label>Weight (kg)</label>
|
||||
<input type="number" id="bmi-weight" min="5" max="200" step="0.1" placeholder="e.g. 25">
|
||||
</div>
|
||||
<div class="calc-field">
|
||||
<label>Height (cm)</label>
|
||||
<input type="number" id="bmi-height" min="50" max="200" step="0.1" placeholder="e.g. 120">
|
||||
</div>
|
||||
</div>
|
||||
<button id="btn-calc-bmi" class="btn-sm btn-primary" style="margin-top:12px;"><i class="fas fa-calculator"></i> Calculate</button>
|
||||
<button id="btn-clear-bmi" class="btn-sm btn-ghost" style="margin-top:12px;">Clear</button>
|
||||
<div id="bmi-result" class="calc-result hidden"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ═══════════ BODY SURFACE AREA ═══════════ -->
|
||||
<div id="calc-bsa" class="calc-panel hidden">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3><i class="fas fa-child"></i> Body Surface Area</h3>
|
||||
<span style="font-size:11px;color:var(--g500);">Mosteller formula</span>
|
||||
</div>
|
||||
<div style="padding:16px;">
|
||||
<p style="font-size:12px;color:var(--g500);margin:0 0 16px;">Mosteller formula: BSA (m2) = sqrt(height(cm) x weight(kg) / 3600)</p>
|
||||
<div class="calc-grid">
|
||||
<div class="calc-field">
|
||||
<label>Weight (kg)</label>
|
||||
<input type="number" id="bsa-weight" min="1" max="200" step="0.1" placeholder="e.g. 20">
|
||||
</div>
|
||||
<div class="calc-field">
|
||||
<label>Height (cm)</label>
|
||||
<input type="number" id="bsa-height" min="30" max="220" step="0.1" placeholder="e.g. 110">
|
||||
</div>
|
||||
</div>
|
||||
<button id="btn-calc-bsa" class="btn-sm btn-primary" style="margin-top:12px;"><i class="fas fa-calculator"></i> Calculate</button>
|
||||
<button id="btn-clear-bsa" class="btn-sm btn-ghost" style="margin-top:12px;">Clear</button>
|
||||
<div id="bsa-result" class="calc-result hidden"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ═══════════ WEIGHT-BASED DOSING ═══════════ -->
|
||||
<div id="calc-dose" class="calc-panel hidden">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3><i class="fas fa-pills"></i> Weight-Based Dosing</h3>
|
||||
</div>
|
||||
<div style="padding:16px;">
|
||||
<p style="font-size:12px;color:var(--g500);margin:0 0 16px;">Calculate dose based on weight. Always verify against formulary and institutional guidelines.</p>
|
||||
<div class="calc-grid">
|
||||
<div class="calc-field">
|
||||
<label>Patient Weight (kg)</label>
|
||||
<input type="number" id="dose-weight" min="1" max="200" step="0.1" placeholder="e.g. 15">
|
||||
</div>
|
||||
<div class="calc-field">
|
||||
<label>Dose (mg/kg)</label>
|
||||
<input type="number" id="dose-per-kg" min="0.01" step="0.01" placeholder="e.g. 10">
|
||||
</div>
|
||||
<div class="calc-field">
|
||||
<label>Frequency</label>
|
||||
<select id="dose-freq">
|
||||
<option value="1">Once daily</option>
|
||||
<option value="2">Twice daily (BID)</option>
|
||||
<option value="3">Three times daily (TID)</option>
|
||||
<option value="4">Four times daily (QID)</option>
|
||||
<option value="6">Every 4 hours (Q4H)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="calc-field">
|
||||
<label>Max single dose (mg, optional)</label>
|
||||
<input type="number" id="dose-max" min="0" step="1" placeholder="e.g. 500">
|
||||
</div>
|
||||
<div class="calc-field">
|
||||
<label>Concentration (mg/mL, optional)</label>
|
||||
<input type="number" id="dose-conc" min="0" step="0.1" placeholder="e.g. 40">
|
||||
</div>
|
||||
</div>
|
||||
<button id="btn-calc-dose" class="btn-sm btn-primary" style="margin-top:12px;"><i class="fas fa-calculator"></i> Calculate</button>
|
||||
<button id="btn-clear-dose" class="btn-sm btn-ghost" style="margin-top:12px;">Clear</button>
|
||||
<div id="dose-result" class="calc-result hidden"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ═══════════ GROWTH CHARTS ═══════════ -->
|
||||
<div id="calc-growth" class="calc-panel hidden">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3><i class="fas fa-chart-line"></i> Growth Percentiles</h3>
|
||||
</div>
|
||||
<div style="padding:16px;">
|
||||
<div style="display:flex;gap:8px;flex-wrap:wrap;margin-bottom:14px;">
|
||||
<button class="calc-pill active" data-growth="wfa">Weight-for-Age</button>
|
||||
<button class="calc-pill" data-growth="lfa">Length/Height-for-Age</button>
|
||||
<button class="calc-pill" data-growth="hcfa">Head Circumference</button>
|
||||
<button class="calc-pill" data-growth="wfl">Weight-for-Length</button>
|
||||
<button class="calc-pill" data-growth="fenton">Fenton (Preterm)</button>
|
||||
</div>
|
||||
<p id="growth-source-text" style="font-size:12px;color:var(--g500);margin:0 0 14px;">WHO growth standards (0-2y) / CDC 2000 (2-20y).</p>
|
||||
<div class="calc-grid">
|
||||
<div class="calc-field">
|
||||
<label>Sex</label>
|
||||
<select id="growth-sex"><option value="">Select</option><option value="male">Male</option><option value="female">Female</option></select>
|
||||
</div>
|
||||
<div class="calc-field" id="growth-age-field">
|
||||
<label>Age (months)</label>
|
||||
<input type="number" id="growth-age" min="0" max="240" step="0.5" placeholder="e.g. 24">
|
||||
</div>
|
||||
<div class="calc-field" id="growth-ga-field" style="display:none;">
|
||||
<label>Gestational Age (weeks)</label>
|
||||
<input type="number" id="growth-ga" min="22" max="50" step="1" placeholder="22-50">
|
||||
</div>
|
||||
<div class="calc-field" id="growth-weight-field">
|
||||
<label>Weight (kg)</label>
|
||||
<input type="number" id="growth-weight" min="0.3" max="200" step="0.01" placeholder="e.g. 10.5">
|
||||
</div>
|
||||
<div class="calc-field" id="growth-length-field">
|
||||
<label>Length/Height (cm)</label>
|
||||
<input type="number" id="growth-length" min="20" max="200" step="0.1" placeholder="e.g. 75">
|
||||
</div>
|
||||
<div class="calc-field" id="growth-hc-field" style="display:none;">
|
||||
<label>Head Circumference (cm)</label>
|
||||
<input type="number" id="growth-hc" min="20" max="60" step="0.1" placeholder="e.g. 35">
|
||||
</div>
|
||||
</div>
|
||||
<button id="btn-calc-growth" class="btn-sm btn-primary" style="margin-top:12px;"><i class="fas fa-calculator"></i> Calculate</button>
|
||||
<button id="btn-clear-growth" class="btn-sm btn-ghost" style="margin-top:12px;">Clear</button>
|
||||
<div id="growth-result" class="calc-result hidden"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ═══════════ BILIRUBIN NOMOGRAM ═══════════ -->
|
||||
<div id="calc-bili" class="calc-panel hidden">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3><i class="fas fa-droplet"></i> Neonatal Bilirubin</h3>
|
||||
</div>
|
||||
<div style="padding:16px;">
|
||||
<div style="display:flex;gap:8px;flex-wrap:wrap;margin-bottom:14px;">
|
||||
<button class="calc-pill active" data-bili="aap">AAP 2022 Phototherapy</button>
|
||||
<button class="calc-pill" data-bili="bhutani">Bhutani Nomogram</button>
|
||||
</div>
|
||||
|
||||
<div id="bili-aap-panel">
|
||||
<p style="font-size:12px;color:var(--g500);margin:0 0 14px;">AAP 2022 Clinical Practice Guideline. Determines phototherapy threshold based on gestational age and risk factors.</p>
|
||||
<details style="margin-bottom:14px;font-size:12px;border:1px solid var(--g200);border-radius:8px;padding:0;">
|
||||
<summary style="padding:10px 14px;cursor:pointer;font-weight:600;color:var(--g700);background:var(--g50);border-radius:8px;">Neurotoxicity Risk Factors for Phototherapy Threshold (AAP 2022)</summary>
|
||||
<div style="padding:12px 14px;">
|
||||
<p style="font-weight:600;color:#ef4444;margin:0 0 6px;">Higher Risk (use lower threshold):</p>
|
||||
<ul style="margin:0 0 10px;padding-left:18px;color:var(--g700);line-height:1.7;">
|
||||
<li>Gestational age 35-37 weeks (with other risk factors)</li>
|
||||
<li>Albumin < 3.0 g/dL</li>
|
||||
<li>Isoimmune hemolytic disease (ABO, Rh incompatibility)</li>
|
||||
<li>G6PD deficiency</li>
|
||||
<li>Asphyxia / significant lethargy</li>
|
||||
<li>Sepsis</li>
|
||||
<li>Acidosis (pH < 7.15)</li>
|
||||
<li>Temperature instability / hypothermia</li>
|
||||
</ul>
|
||||
<p style="font-weight:600;color:#10b981;margin:0 0 6px;">Lower Risk (use higher threshold):</p>
|
||||
<ul style="margin:0;padding-left:18px;color:var(--g700);line-height:1.7;">
|
||||
<li>Gestational age ≥ 38 weeks</li>
|
||||
<li>No hemolysis</li>
|
||||
<li>Well-appearing infant</li>
|
||||
<li>No risk factors above</li>
|
||||
</ul>
|
||||
<p style="margin:8px 0 0;font-size:11px;color:var(--g400);">Source: AAP 2022 Clinical Practice Guideline</p>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details style="margin-bottom:14px;font-size:12px;border:1px solid var(--g200);border-radius:8px;padding:0;">
|
||||
<summary style="padding:10px 14px;cursor:pointer;font-weight:600;color:var(--g700);background:var(--g50);border-radius:8px;">Risk Factors for Severe Neonatal Hyperbilirubinemia (Nelson Table 137.1)</summary>
|
||||
<div style="padding:12px 14px;">
|
||||
<p style="font-weight:600;color:var(--g800);margin:0 0 6px;">Genetic Factors</p>
|
||||
<ul style="margin:0 0 12px;padding-left:18px;color:var(--g700);line-height:1.6;">
|
||||
<li>Gilbert syndrome</li>
|
||||
<li>Crigler-Najjar syndrome</li>
|
||||
<li>Alagille syndrome</li>
|
||||
<li>β-thalassemia</li>
|
||||
<li>Glucose-6-phosphate dehydrogenase (G6PD) deficiency</li>
|
||||
<li>Bilirubin glucuronosyltransferase polymorphism</li>
|
||||
<li>Pyruvate kinase deficiency</li>
|
||||
<li>Erythrocyte structural defects (hereditary spherocytosis, elliptocytosis)</li>
|
||||
<li>Galactosemia</li>
|
||||
</ul>
|
||||
<p style="font-weight:600;color:var(--g800);margin:0 0 6px;">Maternal Factors</p>
|
||||
<ul style="margin:0 0 12px;padding-left:18px;color:var(--g700);line-height:1.6;">
|
||||
<li>Family history of severe jaundice, splenectomy, or cholecystectomy</li>
|
||||
<li>Primiparity</li>
|
||||
<li>Teenage pregnancy</li>
|
||||
<li>Diabetes</li>
|
||||
<li>Rhesus incompatibility</li>
|
||||
<li>ABO incompatibility</li>
|
||||
<li>Other blood group isoimmunization</li>
|
||||
<li>Use of drugs during labor (oxytocin, promethazine, bupivacaine)</li>
|
||||
<li>Exclusive breastfeeding</li>
|
||||
</ul>
|
||||
<p style="font-weight:600;color:var(--g800);margin:0 0 6px;">Perinatal Factors</p>
|
||||
<ul style="margin:0 0 12px;padding-left:18px;color:var(--g700);line-height:1.6;">
|
||||
<li>Mode of delivery (breech vs vertex, instrumentation)</li>
|
||||
<li>Birth trauma (cephalohematoma, substantial bruising, extravasation)</li>
|
||||
<li>Birth asphyxia</li>
|
||||
<li>Congenital infections (CMV, syphilis)</li>
|
||||
<li>Sepsis</li>
|
||||
</ul>
|
||||
<p style="font-weight:600;color:var(--g800);margin:0 0 6px;">Neonatal Factors</p>
|
||||
<ul style="margin:0 0 12px;padding-left:18px;color:var(--g700);line-height:1.6;">
|
||||
<li>Male sex</li>
|
||||
<li>Prematurity or low birthweight and small-for-gestational age</li>
|
||||
<li>Hypothyroidism</li>
|
||||
<li>Polycythemia</li>
|
||||
<li>Hypoglycemia</li>
|
||||
<li>Low intake of breast milk, dehydration, or weight loss</li>
|
||||
<li>Breast milk jaundice</li>
|
||||
<li>Jaundice in the first day of life</li>
|
||||
<li>Trisomy 21</li>
|
||||
<li>Infant of diabetic mother</li>
|
||||
<li>Cephalohematoma, other bruising</li>
|
||||
</ul>
|
||||
<p style="font-weight:600;color:var(--g800);margin:0 0 6px;">Other Risk Factors and Markers</p>
|
||||
<ul style="margin:0 0 8px;padding-left:18px;color:var(--g700);line-height:1.6;">
|
||||
<li>Previous sibling received phototherapy or exchange transfusion</li>
|
||||
<li>Predischarge TSB or TcB in the high zone</li>
|
||||
<li>Use of hemolytic agents (naphthalene, menthol-based products) in G6PD-deficient populations</li>
|
||||
<li>Folate deficiency</li>
|
||||
<li>Aflatoxins</li>
|
||||
<li>Hypothermia</li>
|
||||
<li>Birth outside of a healthcare facility</li>
|
||||
</ul>
|
||||
<p style="margin:8px 0 0;font-size:11px;color:var(--g400);">Modified from Olusanya BO, Kaplan M, Hansen TWR. Neonatal hyperbilirubinaemia: a global perspective. Lancet Child Adolesc Health. 2018;2:610-618. Nelson Textbook of Pediatrics, Table 137.1.</p>
|
||||
</div>
|
||||
</details>
|
||||
<div class="calc-grid">
|
||||
<div class="calc-field">
|
||||
<label>Gestational Age (weeks)</label>
|
||||
<select id="bili-ga">
|
||||
<option value="">Select</option>
|
||||
<option value="35">35 weeks</option>
|
||||
<option value="36">36 weeks</option>
|
||||
<option value="37">37 weeks</option>
|
||||
<option value="38">38+ weeks</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="calc-field">
|
||||
<label>Age of Infant (hours)</label>
|
||||
<input type="number" id="bili-age-hours" min="0" max="168" step="1" placeholder="e.g. 48">
|
||||
</div>
|
||||
<div class="calc-field">
|
||||
<label>Total Serum Bilirubin (mg/dL)</label>
|
||||
<input type="number" id="bili-tsb" min="0" max="35" step="0.1" placeholder="e.g. 12.5">
|
||||
</div>
|
||||
<div class="calc-field">
|
||||
<label>Risk Factors</label>
|
||||
<select id="bili-risk">
|
||||
<option value="none">None (lower risk)</option>
|
||||
<option value="medium">Neurotoxicity risk factors present</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<button id="btn-calc-bili-aap" class="btn-sm btn-primary" style="margin-top:12px;"><i class="fas fa-calculator"></i> Assess</button>
|
||||
<button id="btn-clear-bili-aap" class="btn-sm btn-ghost" style="margin-top:12px;">Clear</button>
|
||||
</div>
|
||||
|
||||
<div id="bili-bhutani-panel" style="display:none;">
|
||||
<p style="font-size:12px;color:var(--g500);margin:0 0 14px;">Bhutani hour-specific bilirubin nomogram. Determines risk zone for infants >= 35 weeks GA.</p>
|
||||
<div class="calc-grid">
|
||||
<div class="calc-field">
|
||||
<label>Age of Infant (hours)</label>
|
||||
<input type="number" id="bhutani-age" min="12" max="144" step="1" placeholder="12-144">
|
||||
</div>
|
||||
<div class="calc-field">
|
||||
<label>Total Serum Bilirubin (mg/dL)</label>
|
||||
<input type="number" id="bhutani-tsb" min="0" max="25" step="0.1" placeholder="e.g. 8.5">
|
||||
</div>
|
||||
</div>
|
||||
<button id="btn-calc-bhutani" class="btn-sm btn-primary" style="margin-top:12px;"><i class="fas fa-calculator"></i> Assess</button>
|
||||
<button id="btn-clear-bhutani" class="btn-sm btn-ghost" style="margin-top:12px;">Clear</button>
|
||||
</div>
|
||||
|
||||
<div id="bili-result" class="calc-result hidden"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ═══════════ VITAL SIGNS BY AGE ═══════════ -->
|
||||
<div id="calc-vitals" class="calc-panel hidden">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3><i class="fas fa-heartbeat"></i> Normal Vital Signs by Age</h3>
|
||||
<span style="font-size:11px;color:var(--g500);">Harriet Lane Handbook</span>
|
||||
</div>
|
||||
<div style="padding:16px;">
|
||||
<p style="font-size:12px;color:var(--g500);margin:0 0 14px;">Reference ranges for heart rate, respiratory rate, and blood pressure by age. Source: Harriet Lane Handbook.</p>
|
||||
<div style="overflow-x:auto;">
|
||||
<table class="admin-table" style="font-size:12px;width:100%;">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Age</th>
|
||||
<th>Heart Rate (bpm)</th>
|
||||
<th>Respiratory Rate</th>
|
||||
<th>Systolic BP (mmHg)</th>
|
||||
<th>Diastolic BP (mmHg)</th>
|
||||
<th>Weight (kg)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td>Preterm</td><td>120-170</td><td>40-70</td><td>55-75</td><td>35-45</td><td>1-2.5</td></tr>
|
||||
<tr><td>Newborn (0-1mo)</td><td>100-160</td><td>30-60</td><td>60-80</td><td>35-55</td><td>2.5-4.5</td></tr>
|
||||
<tr><td>1-6 months</td><td>100-150</td><td>30-50</td><td>70-90</td><td>40-60</td><td>4-8</td></tr>
|
||||
<tr><td>6-12 months</td><td>90-140</td><td>25-40</td><td>75-95</td><td>45-65</td><td>8-10</td></tr>
|
||||
<tr><td>1-3 years</td><td>80-130</td><td>20-30</td><td>80-100</td><td>50-70</td><td>10-15</td></tr>
|
||||
<tr><td>4-5 years</td><td>80-120</td><td>20-25</td><td>85-105</td><td>50-70</td><td>15-20</td></tr>
|
||||
<tr><td>6-8 years</td><td>70-115</td><td>18-25</td><td>90-110</td><td>55-75</td><td>20-27</td></tr>
|
||||
<tr><td>9-11 years</td><td>70-110</td><td>16-22</td><td>95-115</td><td>58-78</td><td>27-40</td></tr>
|
||||
<tr><td>12-15 years</td><td>60-100</td><td>14-20</td><td>100-125</td><td>60-80</td><td>40-60</td></tr>
|
||||
<tr><td>16-18 years</td><td>55-95</td><td>12-18</td><td>110-130</td><td>65-85</td><td>55-75</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div style="margin-top:16px;">
|
||||
<h4 style="font-size:13px;font-weight:700;color:var(--g700);margin:0 0 8px;">Quick Reference Formulas</h4>
|
||||
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:8px;">
|
||||
<div style="padding:10px;background:var(--g50);border-radius:8px;font-size:12px;">
|
||||
<strong>Estimated Weight:</strong><br>
|
||||
1-12mo: (age in mo + 9) / 2<br>
|
||||
1-5yr: 2 x (age in yr) + 8<br>
|
||||
6-12yr: 3 x (age in yr) + 7
|
||||
</div>
|
||||
<div style="padding:10px;background:var(--g50);border-radius:8px;font-size:12px;">
|
||||
<strong>Min Systolic BP:</strong><br>
|
||||
1-10yr: 70 + (2 x age in yr)<br>
|
||||
Hypotension: SBP < 5th %ile
|
||||
</div>
|
||||
<div style="padding:10px;background:var(--g50);border-radius:8px;font-size:12px;">
|
||||
<strong>ETT Size:</strong><br>
|
||||
Uncuffed: (age/4) + 4<br>
|
||||
Cuffed: (age/4) + 3.5<br>
|
||||
Depth: ETT size x 3
|
||||
</div>
|
||||
<div style="padding:10px;background:var(--g50);border-radius:8px;font-size:12px;">
|
||||
<strong>Maintenance Fluids (4-2-1):</strong><br>
|
||||
First 10kg: 4 mL/kg/hr<br>
|
||||
Next 10kg: +2 mL/kg/hr<br>
|
||||
Each kg above 20: +1 mL/kg/hr
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -264,6 +264,23 @@ textarea.full-input{resize:vertical;}
|
|||
.faq-answer strong{color:var(--g800);}
|
||||
.faq-answer em{color:var(--g500);font-size:12.5px;}
|
||||
|
||||
/* Calculators */
|
||||
.calc-pill,.calc-nav-pill{padding:8px 16px;border-radius:20px;border:1.5px solid var(--g200);background:white;font-size:13px;font-weight:600;color:var(--g600);cursor:pointer;transition:all 0.15s;}
|
||||
.calc-pill:hover,.calc-nav-pill:hover{border-color:var(--blue);color:var(--blue);}
|
||||
.calc-pill.active,.calc-nav-pill.active{background:var(--blue);color:white;border-color:var(--blue);}
|
||||
.calc-panel.hidden{display:none;}
|
||||
.calc-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(min(100%,180px),1fr));gap:12px;}
|
||||
.calc-field label{display:block;font-size:12px;font-weight:600;color:var(--g600);margin-bottom:3px;}
|
||||
.calc-field input,.calc-field select{width:100%;padding:8px 10px;border:1.5px solid var(--g300);border-radius:6px;font-size:13px;font-family:inherit;}
|
||||
.calc-field input:focus,.calc-field select:focus{outline:none;border-color:var(--blue);box-shadow:0 0 0 3px var(--blue-light);}
|
||||
.calc-result{margin-top:16px;}
|
||||
.calc-result-header{padding:16px;border-radius:10px;border-left:4px solid;text-align:center;}
|
||||
.calc-result-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:12px;margin-top:12px;}
|
||||
.calc-result-item{padding:12px;background:var(--g50);border-radius:8px;text-align:center;}
|
||||
.calc-result-label{font-size:11px;font-weight:600;color:var(--g500);text-transform:uppercase;letter-spacing:0.5px;}
|
||||
.calc-result-value{font-size:24px;font-weight:700;color:var(--g800);margin:4px 0;}
|
||||
.calc-result-sub{font-size:11px;color:var(--g500);}
|
||||
|
||||
/* Non-blocking inline busy bar */
|
||||
.busy-bar{position:sticky;top:0;z-index:100;display:none;align-items:center;gap:8px;padding:8px 16px;background:linear-gradient(135deg,#eff6ff,#e0f2fe);border-bottom:1px solid #bfdbfe;font-size:13px;font-weight:500;color:#1d4ed8;animation:busySlideIn 0.2s ease;}
|
||||
.busy-bar.active{display:flex;}
|
||||
|
|
|
|||
|
|
@ -213,6 +213,10 @@
|
|||
<i class="fas fa-rotate"></i>
|
||||
<span>Catch-Up Schedule</span>
|
||||
</button>
|
||||
<button class="tab-btn" data-tab="calculators">
|
||||
<i class="fas fa-calculator"></i>
|
||||
<span>Calculators</span>
|
||||
</button>
|
||||
<button class="tab-btn" data-tab="learning">
|
||||
<i class="fas fa-graduation-cap"></i>
|
||||
<span>Learning Hub</span>
|
||||
|
|
@ -262,6 +266,7 @@
|
|||
<section id="soap-tab" class="tab-content" data-component="soap"></section>
|
||||
<section id="vaxschedule-tab" class="tab-content" data-component="vaxschedule"></section>
|
||||
<section id="catchup-tab" class="tab-content" data-component="catchup"></section>
|
||||
<section id="calculators-tab" class="tab-content" data-component="calculators"></section>
|
||||
<section id="learning-tab" class="tab-content" data-component="learning"></section>
|
||||
<section id="cms-tab" class="tab-content" data-component="cms"></section>
|
||||
<section id="admin-tab" class="tab-content" data-component="admin"></section>
|
||||
|
|
@ -348,6 +353,7 @@
|
|||
<script defer src="/js/encounters.js"></script>
|
||||
<script defer src="/js/memories.js"></script>
|
||||
<script defer src="/js/documents.js"></script>
|
||||
<script defer src="/js/calculators.js"></script>
|
||||
<script defer src="/js/learningHub.js"></script>
|
||||
<script defer src="/js/admin.js"></script>
|
||||
<script defer src="/js/adminMilestones.js"></script>
|
||||
|
|
|
|||
728
public/js/calculators.js
Normal file
728
public/js/calculators.js
Normal file
|
|
@ -0,0 +1,728 @@
|
|||
// ============================================================
|
||||
// CALCULATORS.JS — Pediatric clinical calculators
|
||||
// ============================================================
|
||||
|
||||
(function() {
|
||||
var _inited = false;
|
||||
document.addEventListener('tabChanged', function(e) {
|
||||
if (e.detail.tab !== 'calculators' || _inited) return;
|
||||
_inited = true;
|
||||
initCalculators();
|
||||
});
|
||||
|
||||
function initCalculators() {
|
||||
// ── Top-level pill navigation (scoped to .calc-nav-pill only) ──
|
||||
document.querySelectorAll('.calc-nav-pill').forEach(function(pill) {
|
||||
pill.addEventListener('click', function() {
|
||||
document.querySelectorAll('.calc-nav-pill').forEach(function(p) { p.classList.remove('active'); });
|
||||
document.querySelectorAll('.calc-panel').forEach(function(p) { p.classList.add('hidden'); });
|
||||
pill.classList.add('active');
|
||||
var panel = document.getElementById('calc-' + pill.dataset.calc);
|
||||
if (panel) panel.classList.remove('hidden');
|
||||
});
|
||||
});
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// BP PERCENTILE — AAP 2017 (simplified percentile lookup)
|
||||
// Uses 50th height percentile reference values by age/sex
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
// AAP 2017 BP reference values at 50th height percentile (boys)
|
||||
// Format: age -> { sys50, sys90, sys95, sys95p12, dia50, dia90, dia95, dia95p12 }
|
||||
var bpBoys = {
|
||||
1: { sys50:85, sys90:98, sys95:102, dia50:40, dia90:52, dia95:54 },
|
||||
2: { sys50:87, sys90:100,sys95:104, dia50:42, dia90:55, dia95:57 },
|
||||
3: { sys50:88, sys90:101,sys95:105, dia50:44, dia90:57, dia95:59 },
|
||||
4: { sys50:90, sys90:102,sys95:106, dia50:47, dia90:60, dia95:62 },
|
||||
5: { sys50:91, sys90:103,sys95:107, dia50:50, dia90:63, dia95:65 },
|
||||
6: { sys50:93, sys90:105,sys95:108, dia50:53, dia90:66, dia95:68 },
|
||||
7: { sys50:94, sys90:106,sys95:110, dia50:55, dia90:68, dia95:70 },
|
||||
8: { sys50:95, sys90:107,sys95:111, dia50:57, dia90:69, dia95:71 },
|
||||
9: { sys50:96, sys90:108,sys95:112, dia50:58, dia90:70, dia95:72 },
|
||||
10: { sys50:97, sys90:109,sys95:113, dia50:59, dia90:71, dia95:73 },
|
||||
11: { sys50:99, sys90:111,sys95:115, dia50:60, dia90:72, dia95:74 },
|
||||
12: { sys50:101,sys90:113,sys95:117, dia50:61, dia90:73, dia95:75 },
|
||||
13: { sys50:104,sys90:115,sys95:120, dia50:61, dia90:73, dia95:75 },
|
||||
14: { sys50:106,sys90:118,sys95:122, dia50:62, dia90:74, dia95:76 },
|
||||
15: { sys50:109,sys90:120,sys95:124, dia50:63, dia90:75, dia95:77 },
|
||||
16: { sys50:111,sys90:122,sys95:126, dia50:64, dia90:76, dia95:78 },
|
||||
17: { sys50:114,sys90:124,sys95:128, dia50:65, dia90:77, dia95:79 }
|
||||
};
|
||||
|
||||
// AAP 2017 BP reference values at 50th height percentile (girls)
|
||||
var bpGirls = {
|
||||
1: { sys50:84, sys90:97, sys95:100, dia50:41, dia90:53, dia95:55 },
|
||||
2: { sys50:87, sys90:99, sys95:102, dia50:43, dia90:55, dia95:58 },
|
||||
3: { sys50:88, sys90:100,sys95:104, dia50:45, dia90:58, dia95:60 },
|
||||
4: { sys50:89, sys90:101,sys95:105, dia50:48, dia90:60, dia95:63 },
|
||||
5: { sys50:90, sys90:102,sys95:106, dia50:51, dia90:63, dia95:65 },
|
||||
6: { sys50:91, sys90:103,sys95:107, dia50:53, dia90:65, dia95:67 },
|
||||
7: { sys50:92, sys90:104,sys95:108, dia50:55, dia90:67, dia95:69 },
|
||||
8: { sys50:93, sys90:105,sys95:109, dia50:57, dia90:68, dia95:71 },
|
||||
9: { sys50:95, sys90:107,sys95:110, dia50:58, dia90:70, dia95:72 },
|
||||
10: { sys50:96, sys90:108,sys95:112, dia50:59, dia90:71, dia95:73 },
|
||||
11: { sys50:98, sys90:110,sys95:114, dia50:60, dia90:72, dia95:74 },
|
||||
12: { sys50:100,sys90:112,sys95:116, dia50:61, dia90:73, dia95:75 },
|
||||
13: { sys50:102,sys90:114,sys95:118, dia50:62, dia90:74, dia95:76 },
|
||||
14: { sys50:103,sys90:115,sys95:119, dia50:63, dia90:75, dia95:77 },
|
||||
15: { sys50:104,sys90:116,sys95:120, dia50:64, dia90:76, dia95:78 },
|
||||
16: { sys50:105,sys90:117,sys95:121, dia50:64, dia90:76, dia95:78 },
|
||||
17: { sys50:105,sys90:117,sys95:121, dia50:64, dia90:76, dia95:78 }
|
||||
};
|
||||
|
||||
function classifyBP(age, sys, dia, ref) {
|
||||
var r = ref[Math.round(age)];
|
||||
if (!r) return null;
|
||||
|
||||
var sysClass, diaClass;
|
||||
|
||||
if (age >= 13) {
|
||||
// Ages >= 13: use absolute thresholds per AAP 2017
|
||||
if (sys >= 140) sysClass = 'stage2';
|
||||
else if (sys >= 130) sysClass = 'stage1';
|
||||
else if (sys >= 120) sysClass = 'elevated';
|
||||
else sysClass = 'normal';
|
||||
|
||||
if (dia >= 90) diaClass = 'stage2';
|
||||
else if (dia >= 80) diaClass = 'stage1';
|
||||
else diaClass = 'normal';
|
||||
} else {
|
||||
// Ages 1-<13: use percentile thresholds
|
||||
if (sys >= r.sys95 + 12 || sys >= 140) sysClass = 'stage2';
|
||||
else if (sys >= r.sys95 || sys >= 130) sysClass = 'stage1';
|
||||
else if (sys >= r.sys90 || sys >= 120) sysClass = 'elevated';
|
||||
else sysClass = 'normal';
|
||||
|
||||
if (dia >= r.dia95 + 12 || dia >= 90) diaClass = 'stage2';
|
||||
else if (dia >= r.dia95 || dia >= 80) diaClass = 'stage1';
|
||||
else if (dia >= r.dia90) diaClass = 'elevated';
|
||||
else diaClass = 'normal';
|
||||
}
|
||||
|
||||
// Overall classification = highest of systolic or diastolic
|
||||
var levels = { normal: 0, elevated: 1, stage1: 2, stage2: 3 };
|
||||
var overall = levels[sysClass] >= levels[diaClass] ? sysClass : diaClass;
|
||||
|
||||
// Estimate percentiles
|
||||
function estimatePctile(val, p50, p90, p95) {
|
||||
if (val <= p50) return Math.max(1, Math.round(50 * val / p50));
|
||||
if (val <= p90) return Math.round(50 + 40 * (val - p50) / (p90 - p50));
|
||||
if (val <= p95) return Math.round(90 + 5 * (val - p90) / (p95 - p90));
|
||||
return Math.min(99, Math.round(95 + 4 * (val - p95) / 12));
|
||||
}
|
||||
|
||||
return {
|
||||
sysPercentile: estimatePctile(sys, r.sys50, r.sys90, r.sys95),
|
||||
diaPercentile: estimatePctile(dia, r.dia50, r.dia90, r.dia95),
|
||||
sysClass: sysClass,
|
||||
diaClass: diaClass,
|
||||
classification: overall,
|
||||
ref: r
|
||||
};
|
||||
}
|
||||
|
||||
var classLabels = {
|
||||
normal: { text: 'Normal', color: '#10b981', bg: '#d1fae5' },
|
||||
elevated: { text: 'Elevated', color: '#f59e0b', bg: '#fef3c7' },
|
||||
stage1: { text: 'Stage 1 Hypertension', color: '#f97316', bg: '#ffedd5' },
|
||||
stage2: { text: 'Stage 2 Hypertension', color: '#ef4444', bg: '#fee2e2' }
|
||||
};
|
||||
|
||||
document.getElementById('btn-calc-bp').addEventListener('click', function() {
|
||||
var age = parseFloat(document.getElementById('bp-age').value);
|
||||
var sex = document.getElementById('bp-sex').value;
|
||||
var sys = parseFloat(document.getElementById('bp-systolic').value);
|
||||
var dia = parseFloat(document.getElementById('bp-diastolic').value);
|
||||
|
||||
if (!age || !sex || !sys || !dia) { showToast('Fill in all fields', 'error'); return; }
|
||||
if (age < 1 || age > 17) { showToast('Age must be 1-17 years', 'error'); return; }
|
||||
|
||||
var ref = sex === 'male' ? bpBoys : bpGirls;
|
||||
var result = classifyBP(age, sys, dia, ref);
|
||||
if (!result) { showToast('Invalid age', 'error'); return; }
|
||||
|
||||
var cl = classLabels[result.classification];
|
||||
var resultDiv = document.getElementById('bp-result');
|
||||
resultDiv.classList.remove('hidden');
|
||||
resultDiv.innerHTML =
|
||||
'<div class="calc-result-header" style="background:' + cl.bg + ';border-color:' + cl.color + ';">' +
|
||||
'<div style="font-size:18px;font-weight:700;color:' + cl.color + ';">' + cl.text + '</div>' +
|
||||
'<div style="font-size:13px;color:var(--g600);margin-top:4px;">' + sys + '/' + dia + ' mmHg — ' + Math.round(age) + ' year old ' + sex + '</div>' +
|
||||
'</div>' +
|
||||
'<div class="calc-result-grid">' +
|
||||
'<div class="calc-result-item">' +
|
||||
'<div class="calc-result-label">Systolic</div>' +
|
||||
'<div class="calc-result-value">' + result.sysPercentile + '<span style="font-size:12px;">th %ile</span></div>' +
|
||||
'<div class="calc-result-sub" style="color:' + classLabels[result.sysClass].color + ';">' + classLabels[result.sysClass].text + '</div>' +
|
||||
'</div>' +
|
||||
'<div class="calc-result-item">' +
|
||||
'<div class="calc-result-label">Diastolic</div>' +
|
||||
'<div class="calc-result-value">' + result.diaPercentile + '<span style="font-size:12px;">th %ile</span></div>' +
|
||||
'<div class="calc-result-sub" style="color:' + classLabels[result.diaClass].color + ';">' + classLabels[result.diaClass].text + '</div>' +
|
||||
'</div>' +
|
||||
'<div class="calc-result-item">' +
|
||||
'<div class="calc-result-label">Reference (50th %ile)</div>' +
|
||||
'<div class="calc-result-value" style="font-size:16px;">' + result.ref.sys50 + '/' + result.ref.dia50 + '</div>' +
|
||||
'<div class="calc-result-sub">90th: ' + result.ref.sys90 + '/' + result.ref.dia90 + ' • 95th: ' + result.ref.sys95 + '/' + result.ref.dia95 + '</div>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'<p style="font-size:11px;color:var(--g400);margin:12px 0 0;">Reference values at 50th height percentile. For precise percentiles adjusted to patient height, consult the full AAP 2017 tables.</p>';
|
||||
});
|
||||
|
||||
document.getElementById('btn-clear-bp').addEventListener('click', function() {
|
||||
['bp-age', 'bp-sex', 'bp-height', 'bp-systolic', 'bp-diastolic'].forEach(function(id) {
|
||||
var el = document.getElementById(id); if (el) el.value = '';
|
||||
});
|
||||
document.getElementById('bp-result').classList.add('hidden');
|
||||
});
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// BMI PERCENTILE — CDC 2000
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
// CDC LMS parameters for BMI-for-age (selected ages, interpolated)
|
||||
// Source: CDC 2000 growth charts
|
||||
var bmiLMS = {
|
||||
male: {
|
||||
2:{L:-1.862,M:16.42,S:0.08671}, 3:{L:-1.517,M:16.01,S:0.08726}, 4:{L:-1.157,M:15.75,S:0.08854},
|
||||
5:{L:-0.867,M:15.63,S:0.09087}, 6:{L:-0.676,M:15.62,S:0.09411}, 7:{L:-0.556,M:15.73,S:0.09808},
|
||||
8:{L:-0.478,M:15.97,S:0.10261}, 9:{L:-0.412,M:16.32,S:0.10756}, 10:{L:-0.348,M:16.79,S:0.11278},
|
||||
11:{L:-0.282,M:17.37,S:0.11813}, 12:{L:-0.213,M:18.05,S:0.12348}, 13:{L:-0.146,M:18.79,S:0.12860},
|
||||
14:{L:-0.084,M:19.56,S:0.13321}, 15:{L:-0.029,M:20.31,S:0.13706}, 16:{L:0.017,M:21.01,S:0.14001},
|
||||
17:{L:0.053,M:21.64,S:0.14202}, 18:{L:0.080,M:22.17,S:0.14317}, 19:{L:0.098,M:22.62,S:0.14361},
|
||||
20:{L:0.109,M:22.99,S:0.14352}
|
||||
},
|
||||
female: {
|
||||
2:{L:-0.948,M:16.13,S:0.09486}, 3:{L:-0.586,M:15.77,S:0.09543}, 4:{L:-0.307,M:15.56,S:0.09727},
|
||||
5:{L:-0.119,M:15.47,S:0.10053}, 6:{L:-0.005,M:15.51,S:0.10497}, 7:{L:0.061,M:15.69,S:0.11022},
|
||||
8:{L:0.097,M:16.01,S:0.11598}, 9:{L:0.117,M:16.47,S:0.12196}, 10:{L:0.130,M:17.05,S:0.12794},
|
||||
11:{L:0.139,M:17.72,S:0.13369}, 12:{L:0.148,M:18.44,S:0.13897}, 13:{L:0.157,M:19.16,S:0.14361},
|
||||
14:{L:0.165,M:19.83,S:0.14743}, 15:{L:0.172,M:20.41,S:0.15034}, 16:{L:0.177,M:20.88,S:0.15231},
|
||||
17:{L:0.180,M:21.24,S:0.15339}, 18:{L:0.181,M:21.49,S:0.15371}, 19:{L:0.181,M:21.65,S:0.15345},
|
||||
20:{L:0.179,M:21.75,S:0.15278}
|
||||
}
|
||||
};
|
||||
|
||||
function calcBMIPercentile(bmi, L, M, S) {
|
||||
// Z-score from LMS: Z = ((BMI/M)^L - 1) / (L * S)
|
||||
var z;
|
||||
if (Math.abs(L) < 0.001) {
|
||||
z = Math.log(bmi / M) / S;
|
||||
} else {
|
||||
z = (Math.pow(bmi / M, L) - 1) / (L * S);
|
||||
}
|
||||
// Convert Z to percentile using standard normal CDF approximation
|
||||
var p = normalCDF(z);
|
||||
return { z: z, percentile: Math.round(p * 1000) / 10 };
|
||||
}
|
||||
|
||||
function normalCDF(z) {
|
||||
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;
|
||||
z = Math.abs(z) / Math.sqrt(2);
|
||||
var t = 1 / (1 + p * z);
|
||||
var y = 1 - (((((a5*t+a4)*t)+a3)*t+a2)*t+a1)*t*Math.exp(-z*z);
|
||||
return 0.5 * (1 + sign * y);
|
||||
}
|
||||
|
||||
// Extended BMI classification per CDC 2022
|
||||
// Uses % of 95th percentile for severe obesity staging
|
||||
function classifyBMI(percentile, bmi, lms) {
|
||||
// Calculate 95th percentile BMI from LMS
|
||||
var p95z = 1.645; // Z for 95th percentile
|
||||
var bmi95;
|
||||
if (Math.abs(lms.L) < 0.001) bmi95 = lms.M * Math.exp(lms.S * p95z);
|
||||
else bmi95 = lms.M * Math.pow(1 + lms.L * lms.S * p95z, 1 / lms.L);
|
||||
|
||||
var pctOf95 = (bmi / bmi95) * 100;
|
||||
|
||||
if (percentile >= 95) {
|
||||
if (pctOf95 >= 140) return { text: 'Class 3 Severe Obesity', color: '#7f1d1d', bg: '#fecaca', pctOf95: pctOf95, bmi95: bmi95 };
|
||||
if (pctOf95 >= 120) return { text: 'Class 2 Severe Obesity', color: '#dc2626', bg: '#fee2e2', pctOf95: pctOf95, bmi95: bmi95 };
|
||||
return { text: 'Obese (Class 1)', color: '#ef4444', bg: '#fee2e2', pctOf95: pctOf95, bmi95: bmi95 };
|
||||
}
|
||||
if (percentile >= 85) return { text: 'Overweight', color: '#f97316', bg: '#ffedd5', pctOf95: pctOf95, bmi95: bmi95 };
|
||||
if (percentile >= 5) return { text: 'Healthy Weight', color: '#10b981', bg: '#d1fae5', pctOf95: pctOf95, bmi95: bmi95 };
|
||||
return { text: 'Underweight', color: '#f59e0b', bg: '#fef3c7', pctOf95: pctOf95, bmi95: bmi95 };
|
||||
}
|
||||
|
||||
document.getElementById('btn-calc-bmi').addEventListener('click', function() {
|
||||
var age = parseFloat(document.getElementById('bmi-age').value);
|
||||
var sex = document.getElementById('bmi-sex').value;
|
||||
var weight = parseFloat(document.getElementById('bmi-weight').value);
|
||||
var height = parseFloat(document.getElementById('bmi-height').value);
|
||||
|
||||
if (!age || !sex || !weight || !height) { showToast('Fill in all fields', 'error'); return; }
|
||||
if (age < 2 || age > 20) { showToast('Age must be 2-20 years', 'error'); return; }
|
||||
|
||||
var bmi = weight / Math.pow(height / 100, 2);
|
||||
var ageRound = Math.round(age);
|
||||
if (ageRound < 2) ageRound = 2;
|
||||
if (ageRound > 20) ageRound = 20;
|
||||
var lms = bmiLMS[sex][ageRound];
|
||||
if (!lms) { showToast('Invalid age/sex', 'error'); return; }
|
||||
|
||||
var result = calcBMIPercentile(bmi, lms.L, lms.M, lms.S);
|
||||
var cl = classifyBMI(result.percentile, bmi, lms);
|
||||
|
||||
var extendedInfo = '';
|
||||
if (result.percentile >= 85) {
|
||||
extendedInfo = '<div class="calc-result-item"><div class="calc-result-label">% of 95th</div><div class="calc-result-value" style="font-size:16px;">' + cl.pctOf95.toFixed(0) + '%</div><div class="calc-result-sub">95th = ' + cl.bmi95.toFixed(1) + ' kg/m²</div></div>';
|
||||
}
|
||||
|
||||
var resultDiv = document.getElementById('bmi-result');
|
||||
resultDiv.classList.remove('hidden');
|
||||
resultDiv.innerHTML =
|
||||
'<div class="calc-result-header" style="background:' + cl.bg + ';border-color:' + cl.color + ';">' +
|
||||
'<div style="font-size:18px;font-weight:700;color:' + cl.color + ';">' + cl.text + '</div>' +
|
||||
'<div style="font-size:13px;color:var(--g600);margin-top:4px;">BMI ' + bmi.toFixed(1) + ' kg/m² — ' + result.percentile + 'th percentile' + (cl.pctOf95 && result.percentile >= 95 ? ' (' + cl.pctOf95.toFixed(0) + '% of 95th)' : '') + '</div>' +
|
||||
'</div>' +
|
||||
'<div class="calc-result-grid">' +
|
||||
'<div class="calc-result-item"><div class="calc-result-label">BMI</div><div class="calc-result-value">' + bmi.toFixed(1) + '</div><div class="calc-result-sub">kg/m²</div></div>' +
|
||||
'<div class="calc-result-item"><div class="calc-result-label">Percentile</div><div class="calc-result-value">' + result.percentile + '<span style="font-size:12px;">th</span></div></div>' +
|
||||
'<div class="calc-result-item"><div class="calc-result-label">Z-Score</div><div class="calc-result-value" style="font-size:16px;">' + result.z.toFixed(2) + '</div></div>' +
|
||||
extendedInfo +
|
||||
'</div>';
|
||||
});
|
||||
|
||||
document.getElementById('btn-clear-bmi').addEventListener('click', function() {
|
||||
['bmi-age', 'bmi-sex', 'bmi-weight', 'bmi-height'].forEach(function(id) {
|
||||
var el = document.getElementById(id); if (el) el.value = '';
|
||||
});
|
||||
document.getElementById('bmi-result').classList.add('hidden');
|
||||
});
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// BODY SURFACE AREA — Mosteller
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
document.getElementById('btn-calc-bsa').addEventListener('click', function() {
|
||||
var weight = parseFloat(document.getElementById('bsa-weight').value);
|
||||
var height = parseFloat(document.getElementById('bsa-height').value);
|
||||
if (!weight || !height) { showToast('Enter weight and height', 'error'); return; }
|
||||
|
||||
var bsa = Math.sqrt(height * weight / 3600);
|
||||
var resultDiv = document.getElementById('bsa-result');
|
||||
resultDiv.classList.remove('hidden');
|
||||
resultDiv.innerHTML =
|
||||
'<div class="calc-result-header" style="background:#e0f2fe;border-color:#0ea5e9;">' +
|
||||
'<div style="font-size:18px;font-weight:700;color:#0369a1;">BSA: ' + bsa.toFixed(3) + ' m²</div>' +
|
||||
'<div style="font-size:13px;color:var(--g600);margin-top:4px;">' + weight + ' kg, ' + height + ' cm (Mosteller formula)</div>' +
|
||||
'</div>';
|
||||
});
|
||||
|
||||
document.getElementById('btn-clear-bsa').addEventListener('click', function() {
|
||||
['bsa-weight', 'bsa-height'].forEach(function(id) {
|
||||
var el = document.getElementById(id); if (el) el.value = '';
|
||||
});
|
||||
document.getElementById('bsa-result').classList.add('hidden');
|
||||
});
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// WEIGHT-BASED DOSING
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
document.getElementById('btn-calc-dose').addEventListener('click', function() {
|
||||
var weight = parseFloat(document.getElementById('dose-weight').value);
|
||||
var dosePerKg = parseFloat(document.getElementById('dose-per-kg').value);
|
||||
var freq = parseInt(document.getElementById('dose-freq').value);
|
||||
var maxDose = parseFloat(document.getElementById('dose-max').value) || 0;
|
||||
var conc = parseFloat(document.getElementById('dose-conc').value) || 0;
|
||||
|
||||
if (!weight || !dosePerKg) { showToast('Enter weight and dose', 'error'); return; }
|
||||
|
||||
var singleDose = weight * dosePerKg;
|
||||
var capped = false;
|
||||
if (maxDose > 0 && singleDose > maxDose) { singleDose = maxDose; capped = true; }
|
||||
var dailyDose = singleDose * freq;
|
||||
|
||||
var rows = '<div class="calc-result-item"><div class="calc-result-label">Single Dose</div><div class="calc-result-value">' + singleDose.toFixed(1) + ' <span style="font-size:12px;">mg</span></div>'
|
||||
+ (capped ? '<div class="calc-result-sub" style="color:var(--red);">Capped at max ' + maxDose + ' mg</div>' : '') + '</div>'
|
||||
+ '<div class="calc-result-item"><div class="calc-result-label">Daily Total</div><div class="calc-result-value">' + dailyDose.toFixed(1) + ' <span style="font-size:12px;">mg/day</span></div>'
|
||||
+ '<div class="calc-result-sub">' + singleDose.toFixed(1) + ' mg x ' + freq + '/day</div></div>';
|
||||
|
||||
if (conc > 0) {
|
||||
var vol = singleDose / conc;
|
||||
rows += '<div class="calc-result-item"><div class="calc-result-label">Volume per Dose</div><div class="calc-result-value">' + vol.toFixed(1) + ' <span style="font-size:12px;">mL</span></div>'
|
||||
+ '<div class="calc-result-sub">at ' + conc + ' mg/mL</div></div>';
|
||||
}
|
||||
|
||||
var resultDiv = document.getElementById('dose-result');
|
||||
resultDiv.classList.remove('hidden');
|
||||
resultDiv.innerHTML =
|
||||
'<div class="calc-result-header" style="background:#ede9fe;border-color:#7c3aed;">' +
|
||||
'<div style="font-size:18px;font-weight:700;color:#6d28d9;">' + singleDose.toFixed(1) + ' mg per dose</div>' +
|
||||
'<div style="font-size:13px;color:var(--g600);margin-top:4px;">' + weight + ' kg x ' + dosePerKg + ' mg/kg' + (capped ? ' (max ' + maxDose + ' mg)' : '') + '</div>' +
|
||||
'</div>' +
|
||||
'<div class="calc-result-grid">' + rows + '</div>' +
|
||||
'<p style="font-size:11px;color:var(--g400);margin:12px 0 0;">Always verify dosing against your formulary and institutional guidelines.</p>';
|
||||
});
|
||||
|
||||
document.getElementById('btn-clear-dose').addEventListener('click', function() {
|
||||
['dose-weight', 'dose-per-kg', 'dose-max', 'dose-conc'].forEach(function(id) {
|
||||
var el = document.getElementById(id); if (el) el.value = '';
|
||||
});
|
||||
document.getElementById('dose-freq').value = '1';
|
||||
document.getElementById('dose-result').classList.add('hidden');
|
||||
});
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// GROWTH CHARTS — WHO 0-2y / CDC 2-20y + Fenton Preterm
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
var currentGrowthType = 'wfa';
|
||||
|
||||
// WHO/CDC Weight-for-Age LMS (selected months, boys & girls)
|
||||
var wfaLMS = {
|
||||
male: {
|
||||
0:{L:0.3487,M:3.3464,S:0.14602},1:{L:0.2297,M:4.4709,S:0.13395},2:{L:0.1970,M:5.5675,S:0.12385},
|
||||
3:{L:0.1738,M:6.3762,S:0.11727},4:{L:0.1553,M:7.0023,S:0.11316},6:{L:0.1170,M:7.9340,S:0.10866},
|
||||
9:{L:0.0473,M:8.9014,S:0.10548},12:{L:-0.0250,M:9.6479,S:0.10418},18:{L:-0.1600,M:10.8500,S:0.10500},
|
||||
24:{L:-0.2700,M:12.1500,S:0.10700},36:{L:-0.3900,M:14.3400,S:0.11200},48:{L:-0.4700,M:16.3400,S:0.11800},
|
||||
60:{L:-0.5100,M:18.3700,S:0.12500},72:{L:-0.5100,M:20.5500,S:0.13200},84:{L:-0.4800,M:22.9400,S:0.13900},
|
||||
96:{L:-0.4200,M:25.5700,S:0.14500},108:{L:-0.3400,M:28.5600,S:0.15100},120:{L:-0.2400,M:31.9900,S:0.15600},
|
||||
132:{L:-0.1300,M:35.9800,S:0.16000},144:{L:-0.0100,M:40.5000,S:0.16200},156:{L:0.0900,M:45.4800,S:0.16100},
|
||||
168:{L:0.1700,M:50.7600,S:0.15700},180:{L:0.2200,M:55.9100,S:0.15100},192:{L:0.2500,M:60.4600,S:0.14400},
|
||||
204:{L:0.2600,M:64.0600,S:0.13700},216:{L:0.2600,M:66.6800,S:0.13100},228:{L:0.2500,M:68.4000,S:0.12700},
|
||||
240:{L:0.2400,M:69.4100,S:0.12400}
|
||||
},
|
||||
female: {
|
||||
0:{L:0.3809,M:3.2322,S:0.14171},1:{L:0.1714,M:4.1873,S:0.13724},2:{L:0.0962,M:5.1282,S:0.12920},
|
||||
3:{L:0.0402,M:5.8458,S:0.12337},4:{L:-0.0050,M:6.4237,S:0.11912},6:{L:-0.0822,M:7.2970,S:0.11378},
|
||||
9:{L:-0.1870,M:8.2000,S:0.10880},12:{L:-0.2684,M:8.9500,S:0.10621},18:{L:-0.3800,M:10.2000,S:0.10500},
|
||||
24:{L:-0.4600,M:11.5000,S:0.10800},36:{L:-0.5400,M:13.8600,S:0.11500},48:{L:-0.5700,M:15.9600,S:0.12300},
|
||||
60:{L:-0.5600,M:17.9600,S:0.13200},72:{L:-0.5100,M:20.0500,S:0.14000},84:{L:-0.4400,M:22.3200,S:0.14800},
|
||||
96:{L:-0.3500,M:25.0000,S:0.15600},108:{L:-0.2500,M:28.1600,S:0.16200},120:{L:-0.1500,M:31.8400,S:0.16800},
|
||||
132:{L:-0.0500,M:36.0000,S:0.17100},144:{L:0.0400,M:40.4600,S:0.17100},156:{L:0.1000,M:44.8900,S:0.16800},
|
||||
168:{L:0.1500,M:48.8500,S:0.16300},180:{L:0.1800,M:52.0700,S:0.15700},192:{L:0.1900,M:54.4000,S:0.15200},
|
||||
204:{L:0.2000,M:55.9200,S:0.14800},216:{L:0.2000,M:56.8300,S:0.14500},228:{L:0.2000,M:57.3300,S:0.14300},
|
||||
240:{L:0.1900,M:57.5700,S:0.14200}
|
||||
}
|
||||
};
|
||||
|
||||
// WHO/CDC Length/Height-for-Age LMS
|
||||
var lfaLMS = {
|
||||
male: {
|
||||
0:{L:1,M:49.88,S:0.03795},1:{L:1,M:54.72,S:0.03557},2:{L:1,M:58.42,S:0.03424},
|
||||
3:{L:1,M:61.43,S:0.03328},4:{L:1,M:63.89,S:0.03257},6:{L:1,M:67.62,S:0.03169},
|
||||
9:{L:1,M:72.00,S:0.03108},12:{L:1,M:75.75,S:0.03068},18:{L:1,M:82.26,S:0.03010},
|
||||
24:{L:1,M:87.11,S:0.03954},36:{L:1,M:95.45,S:0.03949},48:{L:1,M:102.47,S:0.03981},
|
||||
60:{L:1,M:109.18,S:0.04033},72:{L:1,M:115.44,S:0.04101},84:{L:1,M:121.23,S:0.04180},
|
||||
96:{L:1,M:126.68,S:0.04262},108:{L:1,M:131.95,S:0.04343},120:{L:1,M:137.14,S:0.04419},
|
||||
132:{L:1,M:142.52,S:0.04488},144:{L:1,M:148.34,S:0.04546},156:{L:1,M:154.62,S:0.04582},
|
||||
168:{L:1,M:160.79,S:0.04585},180:{L:1,M:166.18,S:0.04546},192:{L:1,M:170.14,S:0.04469},
|
||||
204:{L:1,M:172.71,S:0.04371},216:{L:1,M:174.09,S:0.04271}
|
||||
},
|
||||
female: {
|
||||
0:{L:1,M:49.15,S:0.03790},1:{L:1,M:53.69,S:0.03573},2:{L:1,M:57.07,S:0.03449},
|
||||
3:{L:1,M:59.80,S:0.03360},4:{L:1,M:62.08,S:0.03296},6:{L:1,M:65.73,S:0.03217},
|
||||
9:{L:1,M:70.10,S:0.03160},12:{L:1,M:73.85,S:0.03117},18:{L:1,M:80.72,S:0.03050},
|
||||
24:{L:1,M:86.20,S:0.03964},36:{L:1,M:94.43,S:0.04017},48:{L:1,M:101.56,S:0.04071},
|
||||
60:{L:1,M:108.38,S:0.04113},72:{L:1,M:114.87,S:0.04145},84:{L:1,M:121.05,S:0.04174},
|
||||
96:{L:1,M:127.05,S:0.04208},108:{L:1,M:133.06,S:0.04257},120:{L:1,M:138.97,S:0.04323},
|
||||
132:{L:1,M:144.89,S:0.04399},144:{L:1,M:150.42,S:0.04467},156:{L:1,M:155.14,S:0.04509},
|
||||
168:{L:1,M:158.72,S:0.04516},180:{L:1,M:160.96,S:0.04494},192:{L:1,M:162.19,S:0.04457},
|
||||
204:{L:1,M:162.85,S:0.04420},216:{L:1,M:163.14,S:0.04393}
|
||||
}
|
||||
};
|
||||
|
||||
// WHO Head Circumference-for-Age LMS (0-36 months)
|
||||
var hcfaLMS = {
|
||||
male: {
|
||||
0:{L:1,M:34.46,S:0.03686},1:{L:1,M:37.27,S:0.03133},2:{L:1,M:39.13,S:0.02997},
|
||||
3:{L:1,M:40.51,S:0.02918},4:{L:1,M:41.63,S:0.02868},6:{L:1,M:43.30,S:0.02808},
|
||||
9:{L:1,M:45.19,S:0.02756},12:{L:1,M:46.49,S:0.02725},18:{L:1,M:48.11,S:0.02692},
|
||||
24:{L:1,M:49.27,S:0.02673},36:{L:1,M:50.53,S:0.02657}
|
||||
},
|
||||
female: {
|
||||
0:{L:1,M:33.90,S:0.03590},1:{L:1,M:36.55,S:0.03099},2:{L:1,M:38.27,S:0.02963},
|
||||
3:{L:1,M:39.53,S:0.02885},4:{L:1,M:40.58,S:0.02835},6:{L:1,M:42.12,S:0.02777},
|
||||
9:{L:1,M:43.81,S:0.02730},12:{L:1,M:45.00,S:0.02702},18:{L:1,M:46.45,S:0.02672},
|
||||
24:{L:1,M:47.54,S:0.02655},36:{L:1,M:48.68,S:0.02641}
|
||||
}
|
||||
};
|
||||
|
||||
// Fenton Preterm Growth LMS (GA 22-50 weeks) — Weight in grams
|
||||
var fentonLMS = {
|
||||
male: {
|
||||
22:{L:0.21,M:496,S:0.17},24:{L:0.21,M:660,S:0.17},26:{L:0.21,M:870,S:0.16},
|
||||
28:{L:0.20,M:1124,S:0.15},30:{L:0.18,M:1430,S:0.14},32:{L:0.15,M:1795,S:0.14},
|
||||
34:{L:0.12,M:2230,S:0.13},36:{L:0.08,M:2710,S:0.13},38:{L:0.04,M:3195,S:0.12},
|
||||
40:{L:0.01,M:3530,S:0.12},42:{L:-0.02,M:3820,S:0.12},44:{L:-0.04,M:4200,S:0.12},
|
||||
46:{L:-0.06,M:4680,S:0.12},48:{L:-0.07,M:5200,S:0.12},50:{L:-0.08,M:5760,S:0.12}
|
||||
},
|
||||
female: {
|
||||
22:{L:0.23,M:474,S:0.17},24:{L:0.22,M:610,S:0.17},26:{L:0.22,M:810,S:0.16},
|
||||
28:{L:0.21,M:1040,S:0.15},30:{L:0.19,M:1330,S:0.14},32:{L:0.16,M:1680,S:0.14},
|
||||
34:{L:0.12,M:2090,S:0.13},36:{L:0.08,M:2540,S:0.13},38:{L:0.04,M:3000,S:0.12},
|
||||
40:{L:0.01,M:3340,S:0.12},42:{L:-0.02,M:3630,S:0.12},44:{L:-0.04,M:4010,S:0.12},
|
||||
46:{L:-0.06,M:4470,S:0.12},48:{L:-0.07,M:4970,S:0.12},50:{L:-0.08,M:5510,S:0.12}
|
||||
}
|
||||
};
|
||||
|
||||
// Growth sub-pill navigation
|
||||
document.querySelectorAll('[data-growth]').forEach(function(pill) {
|
||||
pill.addEventListener('click', function() {
|
||||
document.querySelectorAll('[data-growth]').forEach(function(p) { p.classList.remove('active'); });
|
||||
pill.classList.add('active');
|
||||
currentGrowthType = pill.dataset.growth;
|
||||
var isFenton = currentGrowthType === 'fenton';
|
||||
var isHC = currentGrowthType === 'hcfa';
|
||||
var isWFL = currentGrowthType === 'wfl';
|
||||
document.getElementById('growth-age-field').style.display = isFenton ? 'none' : '';
|
||||
document.getElementById('growth-ga-field').style.display = isFenton ? '' : 'none';
|
||||
document.getElementById('growth-hc-field').style.display = isHC ? '' : 'none';
|
||||
document.getElementById('growth-weight-field').style.display = (isHC && !isFenton) ? 'none' : '';
|
||||
document.getElementById('growth-length-field').style.display = (currentGrowthType === 'wfa' && !isFenton) ? 'none' : '';
|
||||
var src = document.getElementById('growth-source-text');
|
||||
if (isFenton) src.textContent = 'Fenton 2013 preterm growth charts (22-50 weeks GA). Weight in grams.';
|
||||
else if (isHC) src.textContent = 'WHO head circumference-for-age (0-36 months).';
|
||||
else if (isWFL) src.textContent = 'WHO weight-for-length (45-110 cm, 0-2 years).';
|
||||
else src.textContent = 'WHO growth standards (0-2y) / CDC 2000 (2-20y).';
|
||||
document.getElementById('growth-result').classList.add('hidden');
|
||||
});
|
||||
});
|
||||
|
||||
function interpolateLMS(lmsTable, val) {
|
||||
var keys = Object.keys(lmsTable).map(Number).sort(function(a,b){return a-b;});
|
||||
if (val <= keys[0]) return lmsTable[keys[0]];
|
||||
if (val >= keys[keys.length-1]) return lmsTable[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 = lmsTable[keys[i]], b = lmsTable[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 lmsTable[keys[0]];
|
||||
}
|
||||
|
||||
function calcZFromLMS(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 renderGrowthResult(label, value, unit, z, percentile) {
|
||||
var pctl = Math.round(percentile * 10) / 10;
|
||||
var cl;
|
||||
if (pctl >= 95 || pctl <= 5) cl = { color: '#f59e0b', bg: '#fef3c7', text: pctl <= 5 ? 'Below 5th percentile' : 'Above 95th percentile' };
|
||||
else if (pctl >= 85 || pctl <= 15) cl = { color: '#f97316', bg: '#ffedd5', text: pctl + 'th percentile' };
|
||||
else cl = { color: '#10b981', bg: '#d1fae5', text: pctl + 'th percentile' };
|
||||
|
||||
return '<div class="calc-result-header" style="background:' + cl.bg + ';border-color:' + cl.color + ';">' +
|
||||
'<div style="font-size:18px;font-weight:700;color:' + cl.color + ';">' + cl.text + '</div>' +
|
||||
'<div style="font-size:13px;color:var(--g600);margin-top:4px;">' + label + ': ' + value + ' ' + unit + '</div>' +
|
||||
'</div>' +
|
||||
'<div class="calc-result-grid">' +
|
||||
'<div class="calc-result-item"><div class="calc-result-label">Percentile</div><div class="calc-result-value">' + pctl + '<span style="font-size:12px;">th</span></div></div>' +
|
||||
'<div class="calc-result-item"><div class="calc-result-label">Z-Score</div><div class="calc-result-value" style="font-size:16px;">' + z.toFixed(2) + '</div></div>' +
|
||||
'</div>';
|
||||
}
|
||||
|
||||
document.getElementById('btn-calc-growth').addEventListener('click', function() {
|
||||
var sex = document.getElementById('growth-sex').value;
|
||||
if (!sex) { showToast('Select sex', 'error'); return; }
|
||||
|
||||
var resultDiv = document.getElementById('growth-result');
|
||||
|
||||
if (currentGrowthType === 'fenton') {
|
||||
var ga = parseFloat(document.getElementById('growth-ga').value);
|
||||
var wt = parseFloat(document.getElementById('growth-weight').value);
|
||||
if (!ga || !wt) { showToast('Enter gestational age and weight', 'error'); return; }
|
||||
var wtGrams = wt * 1000;
|
||||
var lms = interpolateLMS(fentonLMS[sex], ga);
|
||||
var z = calcZFromLMS(wtGrams, lms.L, lms.M, lms.S);
|
||||
var p = normalCDF(z) * 100;
|
||||
resultDiv.classList.remove('hidden');
|
||||
resultDiv.innerHTML = renderGrowthResult('Weight at ' + ga + ' weeks GA', wt + ' kg (' + Math.round(wtGrams) + 'g)', '', z, p);
|
||||
return;
|
||||
}
|
||||
|
||||
var age = parseFloat(document.getElementById('growth-age').value);
|
||||
if (isNaN(age)) { showToast('Enter age in months', 'error'); return; }
|
||||
|
||||
if (currentGrowthType === 'wfa') {
|
||||
var wt = parseFloat(document.getElementById('growth-weight').value);
|
||||
if (!wt) { showToast('Enter weight', 'error'); return; }
|
||||
var lms = interpolateLMS(wfaLMS[sex], age);
|
||||
var z = calcZFromLMS(wt, lms.L, lms.M, lms.S);
|
||||
var p = normalCDF(z) * 100;
|
||||
resultDiv.classList.remove('hidden');
|
||||
resultDiv.innerHTML = renderGrowthResult('Weight-for-Age (' + age + ' mo)', wt, 'kg', z, p);
|
||||
}
|
||||
else if (currentGrowthType === 'lfa') {
|
||||
var len = parseFloat(document.getElementById('growth-length').value);
|
||||
if (!len) { showToast('Enter length/height', 'error'); return; }
|
||||
var lms = interpolateLMS(lfaLMS[sex], age);
|
||||
var z = calcZFromLMS(len, lms.L, lms.M, lms.S);
|
||||
var p = normalCDF(z) * 100;
|
||||
resultDiv.classList.remove('hidden');
|
||||
resultDiv.innerHTML = renderGrowthResult('Length/Height-for-Age (' + age + ' mo)', len, 'cm', z, p);
|
||||
}
|
||||
else if (currentGrowthType === 'hcfa') {
|
||||
var hc = parseFloat(document.getElementById('growth-hc').value);
|
||||
if (!hc) { showToast('Enter head circumference', 'error'); return; }
|
||||
if (age > 36) { showToast('HC charts available 0-36 months only', 'error'); return; }
|
||||
var lms = interpolateLMS(hcfaLMS[sex], age);
|
||||
var z = calcZFromLMS(hc, lms.L, lms.M, lms.S);
|
||||
var p = normalCDF(z) * 100;
|
||||
resultDiv.classList.remove('hidden');
|
||||
resultDiv.innerHTML = renderGrowthResult('Head Circumference (' + age + ' mo)', hc, 'cm', z, p);
|
||||
}
|
||||
else if (currentGrowthType === 'wfl') {
|
||||
var wt = parseFloat(document.getElementById('growth-weight').value);
|
||||
var len = parseFloat(document.getElementById('growth-length').value);
|
||||
if (!wt || !len) { showToast('Enter weight and length', 'error'); return; }
|
||||
// Use BMI as proxy for weight-for-length for ages > 24mo
|
||||
var bmi = wt / Math.pow(len / 100, 2);
|
||||
var ageR = Math.max(2, Math.min(20, Math.round(age / 12)));
|
||||
var lms = bmiLMS[sex][ageR] || bmiLMS[sex][2];
|
||||
var z = calcZFromLMS(bmi, lms.L, lms.M, lms.S);
|
||||
var p = normalCDF(z) * 100;
|
||||
resultDiv.classList.remove('hidden');
|
||||
resultDiv.innerHTML = renderGrowthResult('Weight-for-Length (' + wt + 'kg / ' + len + 'cm)', 'BMI ' + bmi.toFixed(1), 'kg/m2', z, p);
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('btn-clear-growth').addEventListener('click', function() {
|
||||
['growth-sex', 'growth-age', 'growth-ga', 'growth-weight', 'growth-length', 'growth-hc'].forEach(function(id) {
|
||||
var el = document.getElementById(id); if (el) el.value = '';
|
||||
});
|
||||
document.getElementById('growth-result').classList.add('hidden');
|
||||
});
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// BILIRUBIN — AAP 2022 Phototherapy + Bhutani Nomogram
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
// AAP 2022 phototherapy thresholds (TSB in mg/dL) by hour of age
|
||||
// For >= 38 weeks, no risk factors
|
||||
var photoThresholds38 = {
|
||||
24: 12.0, 36: 13.5, 48: 15.0, 60: 16.0, 72: 17.0, 84: 17.5, 96: 18.0
|
||||
};
|
||||
// For >= 38 weeks, with neurotoxicity risk factors
|
||||
var photoThresholds38risk = {
|
||||
24: 10.0, 36: 11.5, 48: 13.0, 60: 14.0, 72: 15.0, 84: 15.5, 96: 16.0
|
||||
};
|
||||
// For 35-37 weeks, no risk factors (lower thresholds)
|
||||
var photoThresholds35 = {
|
||||
24: 9.0, 36: 10.5, 48: 12.0, 60: 13.0, 72: 14.0, 84: 14.5, 96: 15.0
|
||||
};
|
||||
// For 35-37 weeks, with risk factors
|
||||
var photoThresholds35risk = {
|
||||
24: 7.5, 36: 9.0, 48: 10.5, 60: 11.5, 72: 12.5, 84: 13.0, 96: 13.5
|
||||
};
|
||||
|
||||
function interpolateThreshold(table, hours) {
|
||||
var keys = Object.keys(table).map(Number).sort(function(a,b){return a-b;});
|
||||
if (hours <= keys[0]) return table[keys[0]];
|
||||
if (hours >= keys[keys.length-1]) return table[keys[keys.length-1]];
|
||||
for (var i = 0; i < keys.length-1; i++) {
|
||||
if (hours >= keys[i] && hours <= keys[i+1]) {
|
||||
var t = (hours - keys[i]) / (keys[i+1] - keys[i]);
|
||||
return table[keys[i]] + t * (table[keys[i+1]] - table[keys[i]]);
|
||||
}
|
||||
}
|
||||
return table[keys[0]];
|
||||
}
|
||||
|
||||
// Bili sub-pill nav
|
||||
document.querySelectorAll('[data-bili]').forEach(function(pill) {
|
||||
pill.addEventListener('click', function() {
|
||||
document.querySelectorAll('[data-bili]').forEach(function(p) { p.classList.remove('active'); });
|
||||
pill.classList.add('active');
|
||||
document.getElementById('bili-aap-panel').style.display = pill.dataset.bili === 'aap' ? '' : 'none';
|
||||
document.getElementById('bili-bhutani-panel').style.display = pill.dataset.bili === 'bhutani' ? '' : 'none';
|
||||
document.getElementById('bili-result').classList.add('hidden');
|
||||
});
|
||||
});
|
||||
|
||||
document.getElementById('btn-calc-bili-aap').addEventListener('click', function() {
|
||||
var ga = document.getElementById('bili-ga').value;
|
||||
var hours = parseFloat(document.getElementById('bili-age-hours').value);
|
||||
var tsb = parseFloat(document.getElementById('bili-tsb').value);
|
||||
var risk = document.getElementById('bili-risk').value;
|
||||
if (!ga || isNaN(hours) || isNaN(tsb)) { showToast('Fill in all fields', 'error'); return; }
|
||||
|
||||
var table;
|
||||
if (parseInt(ga) >= 38) table = risk === 'medium' ? photoThresholds38risk : photoThresholds38;
|
||||
else table = risk === 'medium' ? photoThresholds35risk : photoThresholds35;
|
||||
|
||||
var threshold = interpolateThreshold(table, hours);
|
||||
var aboveThreshold = tsb >= threshold;
|
||||
|
||||
var cl = aboveThreshold
|
||||
? { text: 'Above Phototherapy Threshold', color: '#ef4444', bg: '#fee2e2' }
|
||||
: { text: 'Below Phototherapy Threshold', color: '#10b981', bg: '#d1fae5' };
|
||||
|
||||
var resultDiv = document.getElementById('bili-result');
|
||||
resultDiv.classList.remove('hidden');
|
||||
resultDiv.innerHTML =
|
||||
'<div class="calc-result-header" style="background:' + cl.bg + ';border-color:' + cl.color + ';">' +
|
||||
'<div style="font-size:18px;font-weight:700;color:' + cl.color + ';">' + cl.text + '</div>' +
|
||||
'<div style="font-size:13px;color:var(--g600);margin-top:4px;">TSB ' + tsb + ' mg/dL at ' + hours + ' hours of life</div>' +
|
||||
'</div>' +
|
||||
'<div class="calc-result-grid">' +
|
||||
'<div class="calc-result-item"><div class="calc-result-label">Patient TSB</div><div class="calc-result-value">' + tsb + '</div><div class="calc-result-sub">mg/dL</div></div>' +
|
||||
'<div class="calc-result-item"><div class="calc-result-label">Threshold</div><div class="calc-result-value">' + threshold.toFixed(1) + '</div><div class="calc-result-sub">mg/dL at ' + hours + 'h</div></div>' +
|
||||
'<div class="calc-result-item"><div class="calc-result-label">Margin</div><div class="calc-result-value" style="color:' + cl.color + ';">' + (tsb >= threshold ? '+' : '') + (tsb - threshold).toFixed(1) + '</div><div class="calc-result-sub">mg/dL ' + (aboveThreshold ? 'above' : 'below') + '</div></div>' +
|
||||
'</div>' +
|
||||
'<p style="font-size:11px;color:var(--g400);margin:12px 0 0;">GA ' + ga + ' weeks, ' + (risk === 'medium' ? 'with' : 'without') + ' neurotoxicity risk factors. AAP 2022 CPG. Always use clinical judgment.</p>';
|
||||
});
|
||||
|
||||
document.getElementById('btn-clear-bili-aap').addEventListener('click', function() {
|
||||
['bili-ga', 'bili-age-hours', 'bili-tsb', 'bili-risk'].forEach(function(id) {
|
||||
var el = document.getElementById(id); if (el) el.value = '';
|
||||
});
|
||||
document.getElementById('bili-result').classList.add('hidden');
|
||||
});
|
||||
|
||||
// Bhutani nomogram zones (approximate hour-specific TSB boundaries)
|
||||
// High-risk zone: >= 95th percentile
|
||||
// High-intermediate: 75th-95th
|
||||
// Low-intermediate: 40th-75th
|
||||
// Low-risk: < 40th
|
||||
var bhutaniZones = {
|
||||
p95: { 24:8.0, 36:10.5, 48:12.5, 60:14.0, 72:15.0, 84:15.5, 96:16.5, 108:17.5, 120:18.0, 132:18.5, 144:19.0 },
|
||||
p75: { 24:6.0, 36:8.5, 48:10.5, 60:12.0, 72:13.0, 84:13.5, 96:14.0, 108:14.5, 120:15.0, 132:15.5, 144:15.5 },
|
||||
p40: { 24:4.5, 36:6.5, 48:8.5, 60:10.0, 72:11.0, 84:11.5, 96:12.0, 108:12.0, 120:12.5, 132:12.5, 144:12.5 }
|
||||
};
|
||||
|
||||
document.getElementById('btn-calc-bhutani').addEventListener('click', function() {
|
||||
var hours = parseFloat(document.getElementById('bhutani-age').value);
|
||||
var tsb = parseFloat(document.getElementById('bhutani-tsb').value);
|
||||
if (isNaN(hours) || isNaN(tsb)) { showToast('Fill in all fields', 'error'); return; }
|
||||
|
||||
var p95 = interpolateThreshold(bhutaniZones.p95, hours);
|
||||
var p75 = interpolateThreshold(bhutaniZones.p75, hours);
|
||||
var p40 = interpolateThreshold(bhutaniZones.p40, hours);
|
||||
|
||||
var zone, cl;
|
||||
if (tsb >= p95) { zone = 'High-Risk Zone'; cl = { color: '#ef4444', bg: '#fee2e2' }; }
|
||||
else if (tsb >= p75) { zone = 'High-Intermediate Zone'; cl = { color: '#f97316', bg: '#ffedd5' }; }
|
||||
else if (tsb >= p40) { zone = 'Low-Intermediate Zone'; cl = { color: '#f59e0b', bg: '#fef3c7' }; }
|
||||
else { zone = 'Low-Risk Zone'; cl = { color: '#10b981', bg: '#d1fae5' }; }
|
||||
|
||||
var resultDiv = document.getElementById('bili-result');
|
||||
resultDiv.classList.remove('hidden');
|
||||
resultDiv.innerHTML =
|
||||
'<div class="calc-result-header" style="background:' + cl.bg + ';border-color:' + cl.color + ';">' +
|
||||
'<div style="font-size:18px;font-weight:700;color:' + cl.color + ';">' + zone + '</div>' +
|
||||
'<div style="font-size:13px;color:var(--g600);margin-top:4px;">TSB ' + tsb + ' mg/dL at ' + hours + ' hours of life</div>' +
|
||||
'</div>' +
|
||||
'<div class="calc-result-grid">' +
|
||||
'<div class="calc-result-item"><div class="calc-result-label">95th %ile</div><div class="calc-result-value" style="font-size:16px;color:#ef4444;">' + p95.toFixed(1) + '</div><div class="calc-result-sub">High-risk</div></div>' +
|
||||
'<div class="calc-result-item"><div class="calc-result-label">75th %ile</div><div class="calc-result-value" style="font-size:16px;color:#f97316;">' + p75.toFixed(1) + '</div><div class="calc-result-sub">High-intermediate</div></div>' +
|
||||
'<div class="calc-result-item"><div class="calc-result-label">40th %ile</div><div class="calc-result-value" style="font-size:16px;color:#f59e0b;">' + p40.toFixed(1) + '</div><div class="calc-result-sub">Low-intermediate</div></div>' +
|
||||
'</div>' +
|
||||
'<p style="font-size:11px;color:var(--g400);margin:12px 0 0;">Bhutani hour-specific nomogram for infants >= 35 weeks GA. Bhutani et al., Pediatrics 1999.</p>';
|
||||
});
|
||||
|
||||
document.getElementById('btn-clear-bhutani').addEventListener('click', function() {
|
||||
['bhutani-age', 'bhutani-tsb'].forEach(function(id) {
|
||||
var el = document.getElementById(id); if (el) el.value = '';
|
||||
});
|
||||
document.getElementById('bili-result').classList.add('hidden');
|
||||
});
|
||||
}
|
||||
})();
|
||||
Loading…
Reference in a new issue