extract calculator vitals data
This commit is contained in:
parent
0891894cc0
commit
a4b03e8195
4 changed files with 231 additions and 149 deletions
141
public/data/calculators/vitals.json
Normal file
141
public/data/calculators/vitals.json
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
{
|
||||
"version": "1.0",
|
||||
"source": "Harriet Lane Handbook 23rd Edition reference values",
|
||||
"ageGroups": {
|
||||
"premie": {
|
||||
"label": "Premie",
|
||||
"hr": { "awake": "120-170", "sleeping": "100-150" },
|
||||
"rr": "40-70",
|
||||
"sbp": "55-75",
|
||||
"dbp": "35-45",
|
||||
"temp": "36.5-37.5",
|
||||
"weight": "0.5-2.5 kg",
|
||||
"spo2": "88-95% (target)",
|
||||
"notes": [
|
||||
"HR and RR are highly variable and depend on gestational age",
|
||||
"BP increases with gestational age and postnatal age",
|
||||
"Target SpO2 88-95% to reduce retinopathy of prematurity risk",
|
||||
"Temperature instability is common - use servo-controlled warmers",
|
||||
"Bradycardia (<100 bpm) and apnea are common in premature infants"
|
||||
]
|
||||
},
|
||||
"0-3mo": {
|
||||
"label": "0-3 Months",
|
||||
"hr": { "awake": "100-150", "sleeping": "85-135" },
|
||||
"rr": "35-55",
|
||||
"sbp": "65-85",
|
||||
"dbp": "45-55",
|
||||
"temp": "36.5-37.5",
|
||||
"weight": "2.5-6 kg",
|
||||
"spo2": ">95%",
|
||||
"notes": [
|
||||
"HR normally increases with crying (up to 180-190 bpm) - this is physiologic",
|
||||
"Periodic breathing (pauses <10 sec) is normal in neonates",
|
||||
"Acrocyanosis (blue hands/feet) is normal; central cyanosis is not",
|
||||
"BP is best measured in the right arm (pre-ductal) in neonates",
|
||||
"Normal weight loss of 5-7% in first 3-5 days; regain by 10-14 days"
|
||||
]
|
||||
},
|
||||
"3-6mo": {
|
||||
"label": "3-6 Months",
|
||||
"hr": { "awake": "90-120", "sleeping": "75-110" },
|
||||
"rr": "30-45",
|
||||
"sbp": "70-90",
|
||||
"dbp": "50-65",
|
||||
"temp": "36.5-37.5",
|
||||
"weight": "5-8 kg",
|
||||
"spo2": ">95%",
|
||||
"notes": [
|
||||
"Expected weight gain: 20-30 g/day (150-200 g/week)",
|
||||
"HR gradually decreases as vagal tone matures",
|
||||
"RR >60 at rest may indicate lower respiratory tract disease",
|
||||
"BP should be measured with appropriate cuff size (width 40% of arm circumference)"
|
||||
]
|
||||
},
|
||||
"6-12mo": {
|
||||
"label": "6-12 Months",
|
||||
"hr": { "awake": "80-120", "sleeping": "70-110" },
|
||||
"rr": "25-40",
|
||||
"sbp": "80-100",
|
||||
"dbp": "55-65",
|
||||
"temp": "36.0-37.5",
|
||||
"weight": "8-10 kg",
|
||||
"spo2": ">95%",
|
||||
"notes": [
|
||||
"Expected weight: triple birth weight by 12 months (~10 kg average)",
|
||||
"Weight gain slows to ~10-15 g/day",
|
||||
"Sinus arrhythmia (HR varies with breathing) is normal",
|
||||
"Febrile tachycardia: HR increases ~10 bpm per 1 degree C above 37"
|
||||
]
|
||||
},
|
||||
"1-3yr": {
|
||||
"label": "1-3 Years",
|
||||
"hr": { "awake": "70-110", "sleeping": "60-100" },
|
||||
"rr": "20-30",
|
||||
"sbp": "90-105",
|
||||
"dbp": "55-70",
|
||||
"temp": "36.0-37.5",
|
||||
"weight": "10-15 kg",
|
||||
"spo2": ">95%",
|
||||
"notes": [
|
||||
"Expected weight gain: ~200-250 g/month (2-2.5 kg/year)",
|
||||
"Tachycardia: HR >110 at rest warrants evaluation",
|
||||
"Tachypnea: RR >30 at rest may indicate respiratory distress",
|
||||
"BP screening begins at age 3 per AAP 2017 guidelines",
|
||||
"Estimated weight: 2 x (age in years) + 8"
|
||||
]
|
||||
},
|
||||
"3-6yr": {
|
||||
"label": "3-6 Years",
|
||||
"hr": { "awake": "65-110", "sleeping": "55-100" },
|
||||
"rr": "20-25",
|
||||
"sbp": "95-110",
|
||||
"dbp": "60-75",
|
||||
"temp": "36.0-37.5",
|
||||
"weight": "14-20 kg",
|
||||
"spo2": ">95%",
|
||||
"notes": [
|
||||
"Annual BP screening recommended from age 3",
|
||||
"Normal BP <90th percentile for age, sex, and height",
|
||||
"Elevated BP: 90th to <95th percentile (or 120/80 if lower)",
|
||||
"Estimated weight: 2 x (age in years) + 8",
|
||||
"ETT size (uncuffed): (age/4) + 4"
|
||||
]
|
||||
},
|
||||
"6-12yr": {
|
||||
"label": "6-12 Years",
|
||||
"hr": { "awake": "60-95", "sleeping": "50-85" },
|
||||
"rr": "14-22",
|
||||
"sbp": "100-120",
|
||||
"dbp": "60-75",
|
||||
"temp": "36.0-37.5",
|
||||
"weight": "20-40 kg",
|
||||
"spo2": ">95%",
|
||||
"notes": [
|
||||
"Resting HR >95 or <60 warrants evaluation",
|
||||
"BP should be measured at every clinical encounter",
|
||||
"Stage 1 HTN: >=95th percentile on 3 separate occasions",
|
||||
"Estimated weight: 3 x (age in years) + 7",
|
||||
"ETT size (cuffed): (age/4) + 3.5"
|
||||
]
|
||||
},
|
||||
">12yr": {
|
||||
"label": ">12 Years",
|
||||
"hr": { "awake": "55-85", "sleeping": "45-75" },
|
||||
"rr": "12-18",
|
||||
"sbp": "110-135",
|
||||
"dbp": "65-85",
|
||||
"temp": "36.0-37.5",
|
||||
"weight": "40-80 kg",
|
||||
"spo2": ">95%",
|
||||
"notes": [
|
||||
"Vital signs approach adult values",
|
||||
"From age 13: use adult BP thresholds (AAP 2017)",
|
||||
"Normal: <120/<80 mmHg; Elevated: 120-129/<80 mmHg",
|
||||
"Stage 1 HTN: 130-139/80-89 mmHg; Stage 2 HTN: >=140/>=90 mmHg",
|
||||
"Orthostatic vitals: measure lying, sitting, standing if dizzy",
|
||||
"Athletic bradycardia (HR 45-60) may be normal in trained adolescents"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
import { parseAgeMonths, formatParsedAge } from './calculators/age.js';
|
||||
import { initImageLightbox } from './calculators/lightbox.js';
|
||||
import { loadVitalsData, renderVitalsResult } from './calculators/vitals.js';
|
||||
|
||||
// ============================================================
|
||||
// CALCULATORS.JS — Pediatric clinical calculators
|
||||
|
|
@ -1662,135 +1663,10 @@ initImageLightbox();
|
|||
// Harriet Lane Handbook 23rd Edition reference values
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
var VITALS_DATA = {
|
||||
premie: {
|
||||
label: 'Premie',
|
||||
hr: { awake: '120-170', sleeping: '100-150' },
|
||||
rr: '40-70',
|
||||
sbp: '55-75', dbp: '35-45',
|
||||
temp: '36.5-37.5',
|
||||
weight: '0.5-2.5 kg',
|
||||
spo2: '88-95% (target)',
|
||||
notes: [
|
||||
'HR and RR are highly variable and depend on gestational age',
|
||||
'BP increases with gestational age and postnatal age',
|
||||
'Target SpO2 88-95% to reduce retinopathy of prematurity risk',
|
||||
'Temperature instability is common — use servo-controlled warmers',
|
||||
'Bradycardia (<100 bpm) and apnea are common in premature infants'
|
||||
]
|
||||
},
|
||||
'0-3mo': {
|
||||
label: '0-3 Months',
|
||||
hr: { awake: '100-150', sleeping: '85-135' },
|
||||
rr: '35-55',
|
||||
sbp: '65-85', dbp: '45-55',
|
||||
temp: '36.5-37.5',
|
||||
weight: '2.5-6 kg',
|
||||
spo2: '>95%',
|
||||
notes: [
|
||||
'HR normally increases with crying (up to 180-190 bpm) — this is physiologic',
|
||||
'Periodic breathing (pauses <10 sec) is normal in neonates',
|
||||
'Acrocyanosis (blue hands/feet) is normal; central cyanosis is not',
|
||||
'BP is best measured in the right arm (pre-ductal) in neonates',
|
||||
'Normal weight loss of 5-7% in first 3-5 days; regain by 10-14 days'
|
||||
]
|
||||
},
|
||||
'3-6mo': {
|
||||
label: '3-6 Months',
|
||||
hr: { awake: '90-120', sleeping: '75-110' },
|
||||
rr: '30-45',
|
||||
sbp: '70-90', dbp: '50-65',
|
||||
temp: '36.5-37.5',
|
||||
weight: '5-8 kg',
|
||||
spo2: '>95%',
|
||||
notes: [
|
||||
'Expected weight gain: 20-30 g/day (150-200 g/week)',
|
||||
'HR gradually decreases as vagal tone matures',
|
||||
'RR >60 at rest may indicate lower respiratory tract disease',
|
||||
'BP should be measured with appropriate cuff size (width 40% of arm circumference)'
|
||||
]
|
||||
},
|
||||
'6-12mo': {
|
||||
label: '6-12 Months',
|
||||
hr: { awake: '80-120', sleeping: '70-110' },
|
||||
rr: '25-40',
|
||||
sbp: '80-100', dbp: '55-65',
|
||||
temp: '36.0-37.5',
|
||||
weight: '8-10 kg',
|
||||
spo2: '>95%',
|
||||
notes: [
|
||||
'Expected weight: triple birth weight by 12 months (~10 kg average)',
|
||||
'Weight gain slows to ~10-15 g/day',
|
||||
'Sinus arrhythmia (HR varies with breathing) is normal',
|
||||
'Febrile tachycardia: HR increases ~10 bpm per 1 degree C above 37'
|
||||
]
|
||||
},
|
||||
'1-3yr': {
|
||||
label: '1-3 Years',
|
||||
hr: { awake: '70-110', sleeping: '60-100' },
|
||||
rr: '20-30',
|
||||
sbp: '90-105', dbp: '55-70',
|
||||
temp: '36.0-37.5',
|
||||
weight: '10-15 kg',
|
||||
spo2: '>95%',
|
||||
notes: [
|
||||
'Expected weight gain: ~200-250 g/month (2-2.5 kg/year)',
|
||||
'Tachycardia: HR >110 at rest warrants evaluation',
|
||||
'Tachypnea: RR >30 at rest may indicate respiratory distress',
|
||||
'BP screening begins at age 3 per AAP 2017 guidelines',
|
||||
'Estimated weight: 2 x (age in years) + 8'
|
||||
]
|
||||
},
|
||||
'3-6yr': {
|
||||
label: '3-6 Years',
|
||||
hr: { awake: '65-110', sleeping: '55-100' },
|
||||
rr: '20-25',
|
||||
sbp: '95-110', dbp: '60-75',
|
||||
temp: '36.0-37.5',
|
||||
weight: '14-20 kg',
|
||||
spo2: '>95%',
|
||||
notes: [
|
||||
'Annual BP screening recommended from age 3',
|
||||
'Normal BP <90th percentile for age, sex, and height',
|
||||
'Elevated BP: 90th to <95th percentile (or 120/80 if lower)',
|
||||
'Estimated weight: 2 x (age in years) + 8',
|
||||
'ETT size (uncuffed): (age/4) + 4'
|
||||
]
|
||||
},
|
||||
'6-12yr': {
|
||||
label: '6-12 Years',
|
||||
hr: { awake: '60-95', sleeping: '50-85' },
|
||||
rr: '14-22',
|
||||
sbp: '100-120', dbp: '60-75',
|
||||
temp: '36.0-37.5',
|
||||
weight: '20-40 kg',
|
||||
spo2: '>95%',
|
||||
notes: [
|
||||
'Resting HR >95 or <60 warrants evaluation',
|
||||
'BP should be measured at every clinical encounter',
|
||||
'Stage 1 HTN: >=95th percentile on 3 separate occasions',
|
||||
'Estimated weight: 3 x (age in years) + 7',
|
||||
'ETT size (cuffed): (age/4) + 3.5'
|
||||
]
|
||||
},
|
||||
'>12yr': {
|
||||
label: '>12 Years',
|
||||
hr: { awake: '55-85', sleeping: '45-75' },
|
||||
rr: '12-18',
|
||||
sbp: '110-135', dbp: '65-85',
|
||||
temp: '36.0-37.5',
|
||||
weight: '40-80 kg',
|
||||
spo2: '>95%',
|
||||
notes: [
|
||||
'Vital signs approach adult values',
|
||||
'From age 13: use adult BP thresholds (AAP 2017)',
|
||||
'Normal: <120/<80 mmHg; Elevated: 120-129/<80 mmHg',
|
||||
'Stage 1 HTN: 130-139/80-89 mmHg; Stage 2 HTN: >=140/>=90 mmHg',
|
||||
'Orthostatic vitals: measure lying, sitting, standing if dizzy',
|
||||
'Athletic bradycardia (HR 45-60) may be normal in trained adolescents'
|
||||
]
|
||||
}
|
||||
};
|
||||
var VITALS_DATA = {};
|
||||
loadVitalsData()
|
||||
.then(function(data) { VITALS_DATA = data; })
|
||||
.catch(function(err) { console.warn('[calculators] Vitals data unavailable:', err && err.message); });
|
||||
|
||||
// Use event delegation on the vitals panel so the listener works even
|
||||
// when the panel is hidden at init time (hidden elements are still in the DOM,
|
||||
|
|
@ -1805,26 +1681,7 @@ initImageLightbox();
|
|||
|
||||
var v = VITALS_DATA[key];
|
||||
resultDiv.classList.remove('hidden');
|
||||
resultDiv.innerHTML =
|
||||
'<div class="calc-result-header" style="background:#eff6ff;border-color:#2563eb;">' +
|
||||
'<div style="font-size:18px;font-weight:700;color:#1d4ed8;">' + v.label + '</div>' +
|
||||
'</div>' +
|
||||
'<div class="calc-result-grid" style="margin-top:12px;">' +
|
||||
'<div class="calc-result-item"><div class="calc-result-label">Heart Rate (awake)</div><div class="calc-result-value" style="font-size:18px;color:#ef4444;">' + v.hr.awake + '</div><div class="calc-result-sub">bpm</div></div>' +
|
||||
'<div class="calc-result-item"><div class="calc-result-label">Heart Rate (sleep)</div><div class="calc-result-value" style="font-size:18px;color:#8b5cf6;">' + v.hr.sleeping + '</div><div class="calc-result-sub">bpm</div></div>' +
|
||||
'<div class="calc-result-item"><div class="calc-result-label">Respiratory Rate</div><div class="calc-result-value" style="font-size:18px;color:#0ea5e9;">' + v.rr + '</div><div class="calc-result-sub">breaths/min</div></div>' +
|
||||
'<div class="calc-result-item"><div class="calc-result-label">Systolic BP</div><div class="calc-result-value" style="font-size:18px;color:#f97316;">' + v.sbp + '</div><div class="calc-result-sub">mmHg</div></div>' +
|
||||
'<div class="calc-result-item"><div class="calc-result-label">Diastolic BP</div><div class="calc-result-value" style="font-size:18px;color:#f59e0b;">' + v.dbp + '</div><div class="calc-result-sub">mmHg</div></div>' +
|
||||
'<div class="calc-result-item"><div class="calc-result-label">Temperature</div><div class="calc-result-value" style="font-size:18px;color:#10b981;">' + v.temp + '</div><div class="calc-result-sub">°C</div></div>' +
|
||||
'<div class="calc-result-item"><div class="calc-result-label">SpO2</div><div class="calc-result-value" style="font-size:18px;color:#06b6d4;">' + v.spo2 + '</div><div class="calc-result-sub"></div></div>' +
|
||||
'<div class="calc-result-item"><div class="calc-result-label">Weight Range</div><div class="calc-result-value" style="font-size:18px;color:#6366f1;">' + v.weight + '</div><div class="calc-result-sub"></div></div>' +
|
||||
'</div>' +
|
||||
'<div style="margin-top:14px;padding:12px;background:var(--g50);border-radius:8px;">' +
|
||||
'<div style="font-size:12px;font-weight:600;color:var(--g600);margin-bottom:6px;">Clinical Notes</div>' +
|
||||
'<ul style="margin:0;padding-left:16px;font-size:12px;color:var(--g700);line-height:1.7;">' +
|
||||
v.notes.map(function(n) { return '<li>' + n + '</li>'; }).join('') +
|
||||
'</ul>' +
|
||||
'</div>';
|
||||
resultDiv.innerHTML = renderVitalsResult(v);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
37
public/js/calculators/vitals.js
Normal file
37
public/js/calculators/vitals.js
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
var vitalsCache = null;
|
||||
|
||||
export function loadVitalsData() {
|
||||
if (vitalsCache) return Promise.resolve(vitalsCache);
|
||||
return fetch('/data/calculators/vitals.json', { credentials: 'same-origin' })
|
||||
.then(function(response) {
|
||||
if (!response.ok) throw new Error('Failed to load vitals data');
|
||||
return response.json();
|
||||
})
|
||||
.then(function(data) {
|
||||
vitalsCache = data.ageGroups || {};
|
||||
return vitalsCache;
|
||||
});
|
||||
}
|
||||
|
||||
export function renderVitalsResult(v) {
|
||||
return ''
|
||||
+ '<div class="calc-result-header" style="background:#eff6ff;border-color:#2563eb;">'
|
||||
+ '<div style="font-size:18px;font-weight:700;color:#1d4ed8;">' + v.label + '</div>'
|
||||
+ '</div>'
|
||||
+ '<div class="calc-result-grid" style="margin-top:12px;">'
|
||||
+ '<div class="calc-result-item"><div class="calc-result-label">Heart Rate (awake)</div><div class="calc-result-value" style="font-size:18px;color:#ef4444;">' + v.hr.awake + '</div><div class="calc-result-sub">bpm</div></div>'
|
||||
+ '<div class="calc-result-item"><div class="calc-result-label">Heart Rate (sleep)</div><div class="calc-result-value" style="font-size:18px;color:#8b5cf6;">' + v.hr.sleeping + '</div><div class="calc-result-sub">bpm</div></div>'
|
||||
+ '<div class="calc-result-item"><div class="calc-result-label">Respiratory Rate</div><div class="calc-result-value" style="font-size:18px;color:#0ea5e9;">' + v.rr + '</div><div class="calc-result-sub">breaths/min</div></div>'
|
||||
+ '<div class="calc-result-item"><div class="calc-result-label">Systolic BP</div><div class="calc-result-value" style="font-size:18px;color:#f97316;">' + v.sbp + '</div><div class="calc-result-sub">mmHg</div></div>'
|
||||
+ '<div class="calc-result-item"><div class="calc-result-label">Diastolic BP</div><div class="calc-result-value" style="font-size:18px;color:#f59e0b;">' + v.dbp + '</div><div class="calc-result-sub">mmHg</div></div>'
|
||||
+ '<div class="calc-result-item"><div class="calc-result-label">Temperature</div><div class="calc-result-value" style="font-size:18px;color:#10b981;">' + v.temp + '</div><div class="calc-result-sub">°C</div></div>'
|
||||
+ '<div class="calc-result-item"><div class="calc-result-label">SpO2</div><div class="calc-result-value" style="font-size:18px;color:#06b6d4;">' + v.spo2 + '</div><div class="calc-result-sub"></div></div>'
|
||||
+ '<div class="calc-result-item"><div class="calc-result-label">Weight Range</div><div class="calc-result-value" style="font-size:18px;color:#6366f1;">' + v.weight + '</div><div class="calc-result-sub"></div></div>'
|
||||
+ '</div>'
|
||||
+ '<div style="margin-top:14px;padding:12px;background:var(--g50);border-radius:8px;">'
|
||||
+ '<div style="font-size:12px;font-weight:600;color:var(--g600);margin-bottom:6px;">Clinical Notes</div>'
|
||||
+ '<ul style="margin:0;padding-left:16px;font-size:12px;color:var(--g700);line-height:1.7;">'
|
||||
+ v.notes.map(function(n) { return '<li>' + n + '</li>'; }).join('')
|
||||
+ '</ul>'
|
||||
+ '</div>';
|
||||
}
|
||||
47
test/calculators-data.test.js
Normal file
47
test/calculators-data.test.js
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
const { test } = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
|
||||
function loadVitals() {
|
||||
const raw = fs.readFileSync(path.join(__dirname, '..', 'public', 'data', 'calculators', 'vitals.json'), 'utf8');
|
||||
return JSON.parse(raw);
|
||||
}
|
||||
|
||||
test('vitals.json exposes all calculator age groups', () => {
|
||||
const data = loadVitals();
|
||||
assert.equal(data.version, '1.0');
|
||||
assert.deepEqual(Object.keys(data.ageGroups), [
|
||||
'premie',
|
||||
'0-3mo',
|
||||
'3-6mo',
|
||||
'6-12mo',
|
||||
'1-3yr',
|
||||
'3-6yr',
|
||||
'6-12yr',
|
||||
'>12yr'
|
||||
]);
|
||||
});
|
||||
|
||||
test('vitals.json preserves key Harriet Lane ranges', () => {
|
||||
const groups = loadVitals().ageGroups;
|
||||
assert.equal(groups.premie.hr.awake, '120-170');
|
||||
assert.equal(groups.premie.spo2, '88-95% (target)');
|
||||
assert.equal(groups['0-3mo'].sbp, '65-85');
|
||||
assert.equal(groups['3-6mo'].rr, '30-45');
|
||||
assert.equal(groups['1-3yr'].weight, '10-15 kg');
|
||||
assert.equal(groups['6-12yr'].hr.sleeping, '50-85');
|
||||
assert.equal(groups['>12yr'].dbp, '65-85');
|
||||
});
|
||||
|
||||
test('vitals.json entries have complete renderable fields', () => {
|
||||
const groups = loadVitals().ageGroups;
|
||||
Object.entries(groups).forEach(([key, group]) => {
|
||||
assert.ok(group.label, `${key}.label`);
|
||||
assert.ok(group.hr && group.hr.awake && group.hr.sleeping, `${key}.hr`);
|
||||
['rr', 'sbp', 'dbp', 'temp', 'weight', 'spo2'].forEach((field) => {
|
||||
assert.ok(group[field], `${key}.${field}`);
|
||||
});
|
||||
assert.ok(Array.isArray(group.notes) && group.notes.length > 0, `${key}.notes`);
|
||||
});
|
||||
});
|
||||
Loading…
Reference in a new issue