feat: B — extract drug data to public/data/drugs.json (schema v1.0)
Moved 43 weight-based drug entries out of calculators.js string literals
into a structured JSON reference. Renderers now iterate JSON → S.drugRow.
Sections extracted (43 drugs):
- anaphylaxis (7), sedation (11), agitation (11), antiemetics (7), seizure (7)
Out of scope: seizure refractory drips (compound strings), NRP, antimicrobial
empiric regimens (already structured), airway/cardiac/tox/trauma/respiratory
/sepsis/burns sections (fine as-is or no drugs).
New:
- public/data/drugs.json — { version, last_reviewed, sections.<key>.drugs[] }
- public/js/drugs-loader.js — fetches JSON on boot, exposes window._DRUGS +
window._DRUGS_READY Promise. Non-fatal: each calc function has a matching
*_FALLBACK constant so a 404 on drugs.json doesn't break anything.
Schema:
- dose_mg_per_kg | dose_mg_per_kg_low/high (for ranges) | max_mg | unit |
route | notes | source | optional special-cases (weight bands, age-dep text)
Added one unit test asserting drugs.json loads + has all 5 sections with
non-empty drugs arrays. 37/37 unit tests + 26/26 Playwright e2e tests pass.
This commit is contained in:
parent
cd131e0b02
commit
54285865d5
7 changed files with 802 additions and 63 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -3,6 +3,7 @@ node_modules/
|
|||
.env.local
|
||||
.env.production
|
||||
data/
|
||||
!public/data/
|
||||
*.db
|
||||
*.db-journal
|
||||
*.db-wal
|
||||
|
|
|
|||
498
public/data/drugs.json
Normal file
498
public/data/drugs.json
Normal file
|
|
@ -0,0 +1,498 @@
|
|||
{
|
||||
"version": "1.0",
|
||||
"last_reviewed": "2026-04-20",
|
||||
"sections": {
|
||||
"anaphylaxis": {
|
||||
"drugs": [
|
||||
{
|
||||
"name": "Epinephrine IM",
|
||||
"dose_mg_per_kg": 0.01,
|
||||
"max_mg": 0.5,
|
||||
"unit": "mg",
|
||||
"route": "IM",
|
||||
"notes": "1:1000 (1 mg/mL), lateral thigh. May repeat q5-15 min.",
|
||||
"concentration": "1:1000",
|
||||
"source": "ASCIA 2021"
|
||||
},
|
||||
{
|
||||
"name": "Normal saline bolus",
|
||||
"dose_mg_per_kg": 20,
|
||||
"max_mg": null,
|
||||
"unit": "mL",
|
||||
"route": "IV/IO",
|
||||
"notes": "Repeat up to 60 mL/kg for hypotension",
|
||||
"source": "ASCIA 2021"
|
||||
},
|
||||
{
|
||||
"name": "Diphenhydramine",
|
||||
"dose_mg_per_kg": 1.25,
|
||||
"max_mg": 50,
|
||||
"unit": "mg",
|
||||
"route": "IV/IM/PO",
|
||||
"notes": "H1 blocker. NOT first-line — adjunct only",
|
||||
"source": "AAP/ACAAI"
|
||||
},
|
||||
{
|
||||
"name": "Ranitidine",
|
||||
"dose_mg_per_kg": 1,
|
||||
"max_mg": 50,
|
||||
"unit": "mg",
|
||||
"route": "IV over 5 min",
|
||||
"notes": "H2 blocker. Optional adjunct",
|
||||
"source": "AAP/ACAAI"
|
||||
},
|
||||
{
|
||||
"name": "Dexamethasone",
|
||||
"dose_mg_per_kg": 0.6,
|
||||
"max_mg": 16,
|
||||
"unit": "mg",
|
||||
"route": "IV/IM/PO",
|
||||
"notes": "Prevents biphasic reaction (4-6 hrs later)",
|
||||
"source": "AAP/ACAAI"
|
||||
},
|
||||
{
|
||||
"name": "Methylprednisolone",
|
||||
"dose_mg_per_kg": 2,
|
||||
"max_mg": 125,
|
||||
"unit": "mg",
|
||||
"route": "IV",
|
||||
"notes": "Alternative steroid",
|
||||
"source": "AAP/ACAAI"
|
||||
},
|
||||
{
|
||||
"name": "Glucagon",
|
||||
"dose_mg_per_kg": 0.02,
|
||||
"max_mg": 1,
|
||||
"unit": "mg",
|
||||
"route": "IV/IM",
|
||||
"notes": "For patients on beta-blockers not responding to epi",
|
||||
"source": "ASCIA 2021"
|
||||
}
|
||||
]
|
||||
},
|
||||
"sedation": {
|
||||
"drugs": [
|
||||
{
|
||||
"name": "Ketamine IV",
|
||||
"display": "Ketamine",
|
||||
"dose_mg_per_kg_low": 1.5,
|
||||
"dose_mg_per_kg_high": 2,
|
||||
"max_mg_low": 100,
|
||||
"max_mg_high": 150,
|
||||
"unit": "mg",
|
||||
"route": "IV",
|
||||
"onset": "1 min",
|
||||
"duration": "15-30 min",
|
||||
"notes": "Dissociative. Preserves airway reflexes.",
|
||||
"source": "AAP sedation guideline"
|
||||
},
|
||||
{
|
||||
"name": "Ketamine IM",
|
||||
"display": "",
|
||||
"dose_mg_per_kg_low": 4,
|
||||
"dose_mg_per_kg_high": 5,
|
||||
"max_mg_low": 300,
|
||||
"max_mg_high": 400,
|
||||
"unit": "mg",
|
||||
"route": "IM",
|
||||
"onset": "3-5 min",
|
||||
"duration": "30-60 min",
|
||||
"notes": "Give atropine 0.01 mg/kg to reduce secretions.",
|
||||
"source": "AAP sedation guideline"
|
||||
},
|
||||
{
|
||||
"name": "Midazolam IV",
|
||||
"display": "Midazolam",
|
||||
"dose_mg_per_kg_low": 0.05,
|
||||
"dose_mg_per_kg_high": 0.1,
|
||||
"max_mg_low": 2,
|
||||
"max_mg_high": 5,
|
||||
"unit": "mg",
|
||||
"route": "IV",
|
||||
"onset": "2-3 min",
|
||||
"duration": "30-60 min",
|
||||
"notes": "Anxiolysis. Titrate q3-5 min.",
|
||||
"source": "AAP sedation guideline"
|
||||
},
|
||||
{
|
||||
"name": "Midazolam IN/PO",
|
||||
"display": "",
|
||||
"dose_mg_per_kg": 0.5,
|
||||
"max_mg": 20,
|
||||
"unit": "mg",
|
||||
"route": "IN/PO",
|
||||
"onset": "10-15 min",
|
||||
"duration": "30-60 min",
|
||||
"notes": "IN (max 10 mg / naris) or PO (max 20 mg).",
|
||||
"source": "AAP sedation guideline"
|
||||
},
|
||||
{
|
||||
"name": "Propofol",
|
||||
"display": "Propofol",
|
||||
"dose_mg_per_kg": 1,
|
||||
"max_mg": 40,
|
||||
"unit": "mg",
|
||||
"route": "IV",
|
||||
"onset": "30 sec",
|
||||
"duration": "5-10 min",
|
||||
"notes": "Short procedures. Causes apnea — manage airway.",
|
||||
"source": "AAP sedation guideline"
|
||||
},
|
||||
{
|
||||
"name": "Fentanyl IV",
|
||||
"display": "Fentanyl",
|
||||
"dose_mg_per_kg": 1,
|
||||
"max_mg": 100,
|
||||
"unit": "mcg",
|
||||
"route": "IV",
|
||||
"onset": "2-3 min",
|
||||
"duration": "30-60 min",
|
||||
"notes": "Analgesic. Often combined with midazolam.",
|
||||
"source": "AAP sedation guideline"
|
||||
},
|
||||
{
|
||||
"name": "Fentanyl IN",
|
||||
"display": "",
|
||||
"dose_mg_per_kg": 2,
|
||||
"max_mg": 100,
|
||||
"unit": "mcg",
|
||||
"route": "IN",
|
||||
"onset": "5-10 min",
|
||||
"duration": "30-60 min",
|
||||
"notes": "Intranasal.",
|
||||
"source": "AAP sedation guideline"
|
||||
},
|
||||
{
|
||||
"name": "Nitrous oxide",
|
||||
"display": "Nitrous oxide",
|
||||
"dose_mg_per_kg": null,
|
||||
"max_mg": null,
|
||||
"unit": "mix",
|
||||
"dose_display": "50:50 or 70:30 mix",
|
||||
"route": "Inhaled",
|
||||
"onset": "2-5 min",
|
||||
"duration": "5 min off",
|
||||
"notes": "Self-administered via demand valve. Anxiolysis + mild analgesia.",
|
||||
"source": "AAP sedation guideline"
|
||||
},
|
||||
{
|
||||
"name": "Dexmedetomidine IN",
|
||||
"display": "Dexmedetomidine",
|
||||
"dose_mg_per_kg_low": 2,
|
||||
"dose_mg_per_kg_high": 3,
|
||||
"max_mg_low": 100,
|
||||
"max_mg_high": null,
|
||||
"unit": "mcg",
|
||||
"route": "IN",
|
||||
"onset": "15-30 min",
|
||||
"duration": "60-90 min",
|
||||
"notes": "Intranasal. No respiratory depression. Good for imaging.",
|
||||
"source": "AAP sedation guideline"
|
||||
},
|
||||
{
|
||||
"name": "Naloxone",
|
||||
"display": "Naloxone",
|
||||
"dose_mg_per_kg": 0.1,
|
||||
"max_mg": 2,
|
||||
"unit": "mg",
|
||||
"route": "IV/IM/IN",
|
||||
"reverses": "Opioids (fentanyl, morphine)",
|
||||
"notes": "Max 2 mg. Repeat q2-3 min. Duration shorter than opioids — monitor for re-sedation.",
|
||||
"source": "AAP sedation guideline"
|
||||
},
|
||||
{
|
||||
"name": "Flumazenil",
|
||||
"display": "Flumazenil",
|
||||
"dose_mg_per_kg": 0.01,
|
||||
"max_mg": 0.2,
|
||||
"unit": "mg",
|
||||
"route": "IV",
|
||||
"reverses": "Benzodiazepines (midazolam)",
|
||||
"notes": "Max 0.2 mg single dose. Repeat q1 min to max 1 mg total. Risk of seizures — use cautiously.",
|
||||
"source": "AAP sedation guideline"
|
||||
}
|
||||
]
|
||||
},
|
||||
"agitation": {
|
||||
"drugs": [
|
||||
{
|
||||
"name": "Lorazepam PO",
|
||||
"display": "Lorazepam",
|
||||
"step": 2,
|
||||
"dose_mg_per_kg": 0.05,
|
||||
"max_mg": 2,
|
||||
"unit": "mg",
|
||||
"route": "PO",
|
||||
"notes": "0.05 mg/kg. May repeat in 30 min.",
|
||||
"source": "AAP/ACEP agitation"
|
||||
},
|
||||
{
|
||||
"name": "Midazolam PO",
|
||||
"display": "Midazolam",
|
||||
"step": 2,
|
||||
"dose_mg_per_kg": 0.5,
|
||||
"max_mg": 10,
|
||||
"unit": "mg",
|
||||
"route": "PO",
|
||||
"notes": "0.5 mg/kg (max 10 mg)",
|
||||
"source": "AAP/ACEP agitation"
|
||||
},
|
||||
{
|
||||
"name": "Midazolam IN",
|
||||
"display": "Midazolam",
|
||||
"step": 2,
|
||||
"dose_mg_per_kg": 0.3,
|
||||
"max_mg": 10,
|
||||
"unit": "mg",
|
||||
"route": "IN (5 mg/mL)",
|
||||
"notes": "0.3 mg/kg split between nares (max 10 mg)",
|
||||
"source": "AAP/ACEP agitation"
|
||||
},
|
||||
{
|
||||
"name": "Olanzapine (ODT)",
|
||||
"display": "Olanzapine (ODT)",
|
||||
"step": 2,
|
||||
"dose_mg_per_kg": null,
|
||||
"max_mg": null,
|
||||
"unit": "mg",
|
||||
"route": "PO / ODT",
|
||||
"dose_display_if_under_30kg": "2.5-5 mg",
|
||||
"dose_display_if_30kg_or_more": "5-10 mg",
|
||||
"notes": "Age ≥6 yr. Avoid IM + benzo combo (risk of resp depression).",
|
||||
"source": "AAP/ACEP agitation"
|
||||
},
|
||||
{
|
||||
"name": "Diphenhydramine PO",
|
||||
"display": "Diphenhydramine",
|
||||
"step": 2,
|
||||
"dose_mg_per_kg": 1,
|
||||
"max_mg": 50,
|
||||
"unit": "mg",
|
||||
"route": "PO",
|
||||
"notes": "1 mg/kg. Adjunct only; sedating.",
|
||||
"source": "AAP/ACEP agitation"
|
||||
},
|
||||
{
|
||||
"name": "Midazolam IV/IM",
|
||||
"display": "Midazolam",
|
||||
"step": 3,
|
||||
"dose_mg_per_kg": 0.1,
|
||||
"max_mg": 5,
|
||||
"unit": "mg",
|
||||
"route": "IV / IM",
|
||||
"notes": "0.05-0.1 mg/kg IV, 0.1-0.15 mg/kg IM (max 10 mg)",
|
||||
"source": "AAP/ACEP agitation"
|
||||
},
|
||||
{
|
||||
"name": "Lorazepam IV/IM",
|
||||
"display": "Lorazepam",
|
||||
"step": 3,
|
||||
"dose_mg_per_kg": 0.1,
|
||||
"max_mg": 4,
|
||||
"unit": "mg",
|
||||
"route": "IV / IM",
|
||||
"notes": "0.05-0.1 mg/kg (max 4 mg). May cause resp depression.",
|
||||
"source": "AAP/ACEP agitation"
|
||||
},
|
||||
{
|
||||
"name": "Haloperidol",
|
||||
"display": "Haloperidol",
|
||||
"step": 3,
|
||||
"dose_mg_per_kg": 0.05,
|
||||
"max_mg": null,
|
||||
"unit": "mg",
|
||||
"route": "IM / IV",
|
||||
"dose_display_if_under_40kg": "0.025-0.075 mg/kg",
|
||||
"dose_display_if_40kg_or_more": "2.5-5 mg",
|
||||
"notes": "Avoid <3 yr. Risk: QT, EPS, NMS. ECG if repeated.",
|
||||
"source": "AAP/ACEP agitation"
|
||||
},
|
||||
{
|
||||
"name": "Olanzapine IM",
|
||||
"display": "Olanzapine",
|
||||
"step": 3,
|
||||
"dose_mg_per_kg": null,
|
||||
"max_mg": null,
|
||||
"unit": "mg",
|
||||
"route": "IM",
|
||||
"dose_display_if_under_40kg": "2.5-5 mg",
|
||||
"dose_display_if_40kg_or_more": "5-10 mg",
|
||||
"notes": "Avoid benzo co-administration (resp depression, hypotension)",
|
||||
"source": "AAP/ACEP agitation"
|
||||
},
|
||||
{
|
||||
"name": "Ketamine IM",
|
||||
"display": "Ketamine",
|
||||
"step": 3,
|
||||
"dose_mg_per_kg": 4,
|
||||
"max_mg": 500,
|
||||
"unit": "mg",
|
||||
"route": "IM",
|
||||
"notes": "4-5 mg/kg IM (rescue for severe excited delirium). Monitor airway.",
|
||||
"source": "AAP/ACEP agitation"
|
||||
},
|
||||
{
|
||||
"name": "Droperidol",
|
||||
"display": "Droperidol",
|
||||
"step": 3,
|
||||
"dose_mg_per_kg": 0.05,
|
||||
"max_mg": null,
|
||||
"unit": "mg",
|
||||
"route": "IM / IV",
|
||||
"dose_display_prefix": "0.03-0.07 mg/kg",
|
||||
"notes": "Effective but QT concern — get ECG.",
|
||||
"source": "AAP/ACEP agitation"
|
||||
}
|
||||
]
|
||||
},
|
||||
"antiemetics": {
|
||||
"drugs": [
|
||||
{
|
||||
"name": "Ondansetron",
|
||||
"dose_mg_per_kg": 0.15,
|
||||
"max_mg": 8,
|
||||
"unit": "mg",
|
||||
"route": "PO / ODT / IV",
|
||||
"weight_band_dose": {
|
||||
"under_15kg": "2 mg",
|
||||
"15_to_30kg": "4 mg",
|
||||
"30kg_or_more": "8 mg"
|
||||
},
|
||||
"notes": "First-line. Max single 8 mg. Repeat q8h. QT prolongation — avoid with other QT drugs. <6 mo: limited data.",
|
||||
"source": "AAP gastroenteritis; Lexicomp"
|
||||
},
|
||||
{
|
||||
"name": "Metoclopramide",
|
||||
"dose_mg_per_kg": 0.15,
|
||||
"max_mg": 10,
|
||||
"unit": "mg",
|
||||
"route": "IV / IM / PO",
|
||||
"notes": "0.1-0.15 mg/kg (max 10 mg). Give with diphenhydramine to prevent EPS / dystonia.",
|
||||
"source": "Lexicomp"
|
||||
},
|
||||
{
|
||||
"name": "Dimenhydrinate",
|
||||
"dose_mg_per_kg": 1.25,
|
||||
"max_mg": 50,
|
||||
"unit": "mg",
|
||||
"route": "PO / IV / IM / PR",
|
||||
"notes": "1.25 mg/kg (max 50 mg) q6h. ≥2 yr.",
|
||||
"source": "Lexicomp"
|
||||
},
|
||||
{
|
||||
"name": "Diphenhydramine",
|
||||
"dose_mg_per_kg": 1,
|
||||
"max_mg": 50,
|
||||
"unit": "mg",
|
||||
"route": "PO / IV / IM",
|
||||
"notes": "1 mg/kg (max 50 mg) q6h. Adjunct, sedating.",
|
||||
"source": "Lexicomp"
|
||||
},
|
||||
{
|
||||
"name": "Promethazine",
|
||||
"dose_mg_per_kg": 0.25,
|
||||
"max_mg": 25,
|
||||
"unit": "mg",
|
||||
"route": "PO / IV / IM",
|
||||
"notes": "0.25-1 mg/kg. <strong>CONTRAINDICATED <2 yr</strong> (resp depression). Tissue injury if IV extrav.",
|
||||
"source": "Lexicomp"
|
||||
},
|
||||
{
|
||||
"name": "Dexamethasone",
|
||||
"dose_mg_per_kg": 0.15,
|
||||
"max_mg": 10,
|
||||
"unit": "mg",
|
||||
"route": "IV / PO",
|
||||
"notes": "Adjunct, esp. chemo-induced. 0.15 mg/kg (max 10 mg).",
|
||||
"source": "Lexicomp"
|
||||
},
|
||||
{
|
||||
"name": "Scopolamine patch",
|
||||
"dose_mg_per_kg": null,
|
||||
"max_mg": null,
|
||||
"unit": "mg",
|
||||
"dose_display": "1.5 mg patch",
|
||||
"route": "Transdermal",
|
||||
"notes": "≥12 yr. Motion sickness. Apply 4h before exposure.",
|
||||
"source": "Lexicomp"
|
||||
}
|
||||
]
|
||||
},
|
||||
"seizure": {
|
||||
"drugs": [
|
||||
{
|
||||
"name": "Lorazepam",
|
||||
"phase": "1st benzo",
|
||||
"dose_mg_per_kg": 0.1,
|
||||
"max_mg": 4,
|
||||
"unit": "mg",
|
||||
"route": "IV / IO",
|
||||
"label_suffix": " <span style=\"color:#065f46;font-weight:600;\">(IV preferred)</span>",
|
||||
"notes": "Slow push over 1-2 min.",
|
||||
"source": "AES 2016; ESETT 2019"
|
||||
},
|
||||
{
|
||||
"name": "Midazolam",
|
||||
"phase": "1st benzo",
|
||||
"dose_mg_per_kg": 0.2,
|
||||
"max_mg": 10,
|
||||
"unit": "mg",
|
||||
"route": "IM / IN / buccal",
|
||||
"notes": "Use 5 mg/mL concentrate for IN; split between nares.",
|
||||
"source": "AES 2016; ESETT 2019"
|
||||
},
|
||||
{
|
||||
"name": "Diazepam",
|
||||
"phase": "1st benzo",
|
||||
"dose_mg_per_kg": 0.5,
|
||||
"max_mg": 20,
|
||||
"unit": "mg",
|
||||
"route": "PR",
|
||||
"notes": "Only if no IV/IM/IN access.",
|
||||
"source": "AES 2016"
|
||||
},
|
||||
{
|
||||
"name": "Levetiracetam",
|
||||
"phase": "2nd-line",
|
||||
"dose_mg_per_kg": 60,
|
||||
"max_mg": 4500,
|
||||
"unit": "mg",
|
||||
"route": "IV over 5-15 min",
|
||||
"label_suffix": " <span style=\"color:#065f46;font-weight:600;\">(preferred)</span>",
|
||||
"notes": "Best tolerability. No ECG monitoring needed.",
|
||||
"source": "ESETT 2019"
|
||||
},
|
||||
{
|
||||
"name": "Fosphenytoin",
|
||||
"phase": "2nd-line",
|
||||
"dose_mg_per_kg": 20,
|
||||
"max_mg": 1500,
|
||||
"unit": "mg PE",
|
||||
"route": "IV over 10 min",
|
||||
"notes": "Max rate 3 mg PE/kg/min. Monitor ECG, BP. Avoid if cardiac dysrhythmia.",
|
||||
"source": "ESETT 2019"
|
||||
},
|
||||
{
|
||||
"name": "Valproic acid",
|
||||
"phase": "2nd-line",
|
||||
"dose_mg_per_kg": 40,
|
||||
"max_mg": 3000,
|
||||
"unit": "mg",
|
||||
"route": "IV over 10 min",
|
||||
"notes": "<strong>Avoid <2 yr, hepatic / mitochondrial disease, pregnancy.</strong>",
|
||||
"source": "ESETT 2019"
|
||||
},
|
||||
{
|
||||
"name": "Phenobarbital",
|
||||
"phase": "2nd-line",
|
||||
"dose_mg_per_kg": 20,
|
||||
"max_mg": 1000,
|
||||
"unit": "mg",
|
||||
"route": "IV over 20 min",
|
||||
"notes": "Last-choice 2nd line. High risk of resp depression + hypotension.",
|
||||
"source": "AES 2016"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -16,6 +16,7 @@
|
|||
<section id="calculators-tab" class="tab-content active" data-component="calculators"></section>
|
||||
|
||||
<script defer src="/js/calc-math.js"></script>
|
||||
<script defer src="/js/drugs-loader.js"></script>
|
||||
<script defer src="/js/calculators.js"></script>
|
||||
<script defer src="/js/e2e-bootstrap.js"></script>
|
||||
</body>
|
||||
|
|
|
|||
|
|
@ -445,6 +445,7 @@
|
|||
<script defer src="/js/memories.js"></script>
|
||||
<script defer src="/js/documents.js"></script>
|
||||
<script defer src="/js/calc-math.js"></script>
|
||||
<script defer src="/js/drugs-loader.js"></script>
|
||||
<script defer src="/js/calculators.js"></script>
|
||||
<script defer src="/js/learningHub.js"></script>
|
||||
<script defer src="/js/admin.js"></script>
|
||||
|
|
|
|||
|
|
@ -2565,6 +2565,44 @@
|
|||
'</div>';
|
||||
}
|
||||
|
||||
// Fallback inline data — mirrors the non-refractory seizure entries in
|
||||
// drugs.json. Refractory infusion drips are kept inline below because
|
||||
// their dose strings (bolus → gtt range) don't map cleanly to the
|
||||
// single-row JSON schema.
|
||||
var SEIZURE_FALLBACK = [
|
||||
{ name: 'Lorazepam', phase: '1st benzo', dose_mg_per_kg: 0.1, max_mg: 4, unit: 'mg', route: 'IV / IO', label_suffix: ' <span style="color:#065f46;font-weight:600;">(IV preferred)</span>', notes: 'Slow push over 1-2 min.' },
|
||||
{ name: 'Midazolam', phase: '1st benzo', dose_mg_per_kg: 0.2, max_mg: 10, unit: 'mg', route: 'IM / IN / buccal', notes: 'Use 5 mg/mL concentrate for IN; split between nares.' },
|
||||
{ name: 'Diazepam', phase: '1st benzo', dose_mg_per_kg: 0.5, max_mg: 20, unit: 'mg', route: 'PR', notes: 'Only if no IV/IM/IN access.' },
|
||||
{ name: 'Levetiracetam', phase: '2nd-line', dose_mg_per_kg: 60, max_mg: 4500, unit: 'mg', route: 'IV over 5-15 min', label_suffix: ' <span style="color:#065f46;font-weight:600;">(preferred)</span>', notes: 'Best tolerability. No ECG monitoring needed.' },
|
||||
{ name: 'Fosphenytoin', phase: '2nd-line', dose_mg_per_kg: 20, max_mg: 1500, unit: 'mg PE', route: 'IV over 10 min', notes: 'Max rate 3 mg PE/kg/min. Monitor ECG, BP. Avoid if cardiac dysrhythmia.' },
|
||||
{ name: 'Valproic acid', phase: '2nd-line', dose_mg_per_kg: 40, max_mg: 3000, unit: 'mg', route: 'IV over 10 min', notes: '<strong>Avoid <2 yr, hepatic / mitochondrial disease, pregnancy.</strong>' },
|
||||
{ name: 'Phenobarbital', phase: '2nd-line', dose_mg_per_kg: 20, max_mg: 1000, unit: 'mg', route: 'IV over 20 min', notes: 'Last-choice 2nd line. High risk of resp depression + hypotension.' }
|
||||
];
|
||||
|
||||
function seizureDrugs() {
|
||||
var s = window._DRUGS && window._DRUGS.sections && window._DRUGS.sections.seizure;
|
||||
if (s && s.drugs && s.drugs.length) {
|
||||
return s.drugs.map(function(d) {
|
||||
var fb = SEIZURE_FALLBACK.filter(function(x) { return x.name === d.name; })[0] || {};
|
||||
return Object.assign({}, fb, d);
|
||||
});
|
||||
}
|
||||
return SEIZURE_FALLBACK;
|
||||
}
|
||||
|
||||
// Build the joined S.drugRow output for one phase ("1st benzo" / "2nd-line").
|
||||
function seizureRowsByPhase(wt, phase) {
|
||||
var S = window._EM;
|
||||
return seizureDrugs().filter(function(dg) { return dg.phase === phase; }).map(function(dg) {
|
||||
var label = dg.name + (dg.label_suffix || '');
|
||||
var unitArg = dg.unit && dg.unit !== 'mg' ? ' ' + dg.unit : undefined;
|
||||
var dose = unitArg
|
||||
? S.dStr(wt, dg.dose_mg_per_kg, dg.max_mg, unitArg)
|
||||
: S.dStr(wt, dg.dose_mg_per_kg, dg.max_mg);
|
||||
return S.drugRow(label, dose, dg.route, dg.notes);
|
||||
}).join('');
|
||||
}
|
||||
|
||||
function calcSeizure() {
|
||||
var wt = parseFloat(document.getElementById('seizure-weight').value);
|
||||
var resultDiv = document.getElementById('seizure-result');
|
||||
|
|
@ -2585,11 +2623,7 @@
|
|||
|
||||
// 5 min — 1ST BENZO
|
||||
html += stepRow('5 min', '#8b5cf6', '1st benzodiazepine (give once, pick by access)',
|
||||
S.drugTable(
|
||||
S.drugRow('Lorazepam <span style="color:#065f46;font-weight:600;">(IV preferred)</span>', S.dStr(wt, 0.1, 4), 'IV / IO', 'Slow push over 1-2 min.') +
|
||||
S.drugRow('Midazolam', S.dStr(wt, 0.2, 10), 'IM / IN / buccal', 'Use 5 mg/mL concentrate for IN; split between nares.') +
|
||||
S.drugRow('Diazepam', S.dStr(wt, 0.5, 20), 'PR', 'Only if no IV/IM/IN access.')
|
||||
)
|
||||
S.drugTable(seizureRowsByPhase(wt, '1st benzo'))
|
||||
);
|
||||
|
||||
// 10 min — 2ND BENZO
|
||||
|
|
@ -2599,12 +2633,7 @@
|
|||
|
||||
// 20 min — 2ND-LINE
|
||||
html += stepRow('20 min', '#f59e0b', '2nd-line anti-epileptic — pick one (ESETT: equivalent efficacy)',
|
||||
S.drugTable(
|
||||
S.drugRow('Levetiracetam <span style="color:#065f46;font-weight:600;">(preferred)</span>', S.dStr(wt, 60, 4500), 'IV over 5-15 min', 'Best tolerability. No ECG monitoring needed.') +
|
||||
S.drugRow('Fosphenytoin', S.dStr(wt, 20, 1500, ' mg PE'), 'IV over 10 min', 'Max rate 3 mg PE/kg/min. Monitor ECG, BP. Avoid if cardiac dysrhythmia.') +
|
||||
S.drugRow('Valproic acid', S.dStr(wt, 40, 3000), 'IV over 10 min', '<strong>Avoid <2 yr, hepatic / mitochondrial disease, pregnancy.</strong>') +
|
||||
S.drugRow('Phenobarbital', S.dStr(wt, 20, 1000), 'IV over 20 min', 'Last-choice 2nd line. High risk of resp depression + hypotension.')
|
||||
)
|
||||
S.drugTable(seizureRowsByPhase(wt, '2nd-line'))
|
||||
);
|
||||
|
||||
// 30 min — 2nd of 2nd-line (optional)
|
||||
|
|
@ -2646,6 +2675,28 @@
|
|||
|
||||
function d(wt, mgPerKg, max) { var v = Math.round(wt * mgPerKg * 100) / 100; return max ? Math.min(v, max) : v; }
|
||||
|
||||
// Lookup a drug entry (by name) from window._DRUGS.sections.anaphylaxis,
|
||||
// falling back to a hardcoded record so rendering stays identical if the
|
||||
// JSON fetch hasn't resolved (or failed). Returns an object exposing the
|
||||
// per-kg + max fields needed by S.dStr.
|
||||
var ANAPH_FALLBACK = {
|
||||
'Epinephrine IM': { dose_mg_per_kg: 0.01, max_mg: 0.5, unit: 'mg' },
|
||||
'Normal saline bolus': { dose_mg_per_kg: 20, max_mg: null, unit: 'mL' },
|
||||
'Diphenhydramine': { dose_mg_per_kg: 1.25, max_mg: 50, unit: 'mg' },
|
||||
'Ranitidine': { dose_mg_per_kg: 1, max_mg: 50, unit: 'mg' },
|
||||
'Dexamethasone': { dose_mg_per_kg: 0.6, max_mg: 16, unit: 'mg' },
|
||||
'Methylprednisolone': { dose_mg_per_kg: 2, max_mg: 125, unit: 'mg' },
|
||||
'Glucagon': { dose_mg_per_kg: 0.02, max_mg: 1, unit: 'mg' }
|
||||
};
|
||||
function anaphDrug(name) {
|
||||
var s = window._DRUGS && window._DRUGS.sections && window._DRUGS.sections.anaphylaxis;
|
||||
if (s && s.drugs) {
|
||||
var hit = s.drugs.filter(function(x) { return x.name === name; })[0];
|
||||
if (hit) return hit;
|
||||
}
|
||||
return ANAPH_FALLBACK[name] || {};
|
||||
}
|
||||
|
||||
function calcAnaphylaxis() {
|
||||
var wt = parseFloat(document.getElementById('anaph-weight').value);
|
||||
var age = document.getElementById('anaph-age').value;
|
||||
|
|
@ -2653,14 +2704,22 @@
|
|||
if (!wt) { resultDiv.innerHTML = '<p style="color:var(--red);font-size:13px;">Enter weight (kg).</p>'; return; }
|
||||
var S = window._EM;
|
||||
|
||||
var epiDose = d(wt, 0.01, 0.5);
|
||||
var epi = anaphDrug('Epinephrine IM');
|
||||
var fluids = anaphDrug('Normal saline bolus');
|
||||
var dph = anaphDrug('Diphenhydramine');
|
||||
var rnt = anaphDrug('Ranitidine');
|
||||
var dex = anaphDrug('Dexamethasone');
|
||||
var mpn = anaphDrug('Methylprednisolone');
|
||||
var glc = anaphDrug('Glucagon');
|
||||
|
||||
var epiDose = d(wt, epi.dose_mg_per_kg, epi.max_mg);
|
||||
var epiVol = Math.round(epiDose * 10) / 10; // 1:1000 = 1 mg/mL
|
||||
var autoInjector = wt < 10 ? 'Draw up manually' : wt <= 25 ? 'EpiPen Jr / Auvi-Q 0.15 mg' : 'EpiPen / Auvi-Q 0.3 mg';
|
||||
|
||||
var html = '<div style="padding:14px;border-radius:10px;background:#fee2e2;border:2px solid #ef4444;margin-bottom:16px;">';
|
||||
html += '<div style="font-size:18px;font-weight:700;color:#dc2626;margin-bottom:6px;"><i class="fas fa-triangle-exclamation"></i> STEP 1: Epinephrine IM — GIVE IMMEDIATELY</div>';
|
||||
html += '<div style="font-size:15px;font-weight:600;color:var(--g800);margin-bottom:4px;">Epinephrine 1:1000 (1 mg/mL): <span style="color:#dc2626;">' + epiDose + ' mg (' + epiVol + ' mL)</span> IM to lateral thigh</div>';
|
||||
html += '<div style="font-size:13px;color:var(--g600);">0.01 mg/kg (max 0.5 mg) | Auto-injector: ' + autoInjector + '</div>';
|
||||
html += '<div style="font-size:13px;color:var(--g600);">' + epi.dose_mg_per_kg + ' mg/kg (max ' + epi.max_mg + ' mg) | Auto-injector: ' + autoInjector + '</div>';
|
||||
html += '<div style="font-size:12px;color:#991b1b;margin-top:6px;">May repeat every 5-15 minutes if symptoms persist. No contraindications in anaphylaxis.</div>';
|
||||
html += '</div>';
|
||||
|
||||
|
|
@ -2668,14 +2727,14 @@
|
|||
|
||||
html += '<tr><td style="font-weight:600;color:var(--blue);">STEP 2</td><td style="font-weight:600;">Position</td><td>Supine + legs elevated</td><td>—</td><td style="font-size:12px;color:var(--g500);">If dyspneic: sitting position. If vomiting: recovery position</td></tr>';
|
||||
html += '<tr><td style="font-weight:600;color:var(--blue);">STEP 3</td><td style="font-weight:600;">O2</td><td>High flow 10-15 L/min</td><td>Face mask</td><td style="font-size:12px;color:var(--g500);">100% O2. Prepare for airway management</td></tr>';
|
||||
html += '<tr><td style="font-weight:600;color:var(--blue);">STEP 4</td><td style="font-weight:600;">IV fluids</td><td>NS ' + S.dStr(wt, 20, null, ' mL') + ' bolus</td><td>IV/IO</td><td style="font-size:12px;color:var(--g500);">Repeat up to 60 mL/kg for hypotension</td></tr>';
|
||||
html += '<tr><td style="font-weight:600;color:var(--blue);">STEP 5</td><td style="font-weight:600;">Diphenhydramine</td><td>' + S.dStr(wt, 1.25, 50) + '</td><td>IV/IM/PO</td><td style="font-size:12px;color:var(--g500);">H1 blocker. NOT first-line — adjunct only</td></tr>';
|
||||
html += '<tr><td></td><td style="font-weight:600;">Ranitidine</td><td>' + S.dStr(wt, 1, 50) + '</td><td>IV over 5 min</td><td style="font-size:12px;color:var(--g500);">H2 blocker. Optional adjunct</td></tr>';
|
||||
html += '<tr><td style="font-weight:600;color:var(--blue);">STEP 6</td><td style="font-weight:600;">Dexamethasone</td><td>' + S.dStr(wt, 0.6, 16) + '</td><td>IV/IM/PO</td><td style="font-size:12px;color:var(--g500);">Prevents biphasic reaction (4-6 hrs later)</td></tr>';
|
||||
html += '<tr><td></td><td style="font-weight:600;">Methylprednisolone</td><td>' + S.dStr(wt, 2, 125) + '</td><td>IV</td><td style="font-size:12px;color:var(--g500);">Alternative steroid</td></tr>';
|
||||
html += '<tr><td style="font-weight:600;color:var(--blue);">STEP 4</td><td style="font-weight:600;">IV fluids</td><td>NS ' + S.dStr(wt, fluids.dose_mg_per_kg, fluids.max_mg, ' mL') + ' bolus</td><td>IV/IO</td><td style="font-size:12px;color:var(--g500);">Repeat up to 60 mL/kg for hypotension</td></tr>';
|
||||
html += '<tr><td style="font-weight:600;color:var(--blue);">STEP 5</td><td style="font-weight:600;">Diphenhydramine</td><td>' + S.dStr(wt, dph.dose_mg_per_kg, dph.max_mg) + '</td><td>IV/IM/PO</td><td style="font-size:12px;color:var(--g500);">H1 blocker. NOT first-line — adjunct only</td></tr>';
|
||||
html += '<tr><td></td><td style="font-weight:600;">Ranitidine</td><td>' + S.dStr(wt, rnt.dose_mg_per_kg, rnt.max_mg) + '</td><td>IV over 5 min</td><td style="font-size:12px;color:var(--g500);">H2 blocker. Optional adjunct</td></tr>';
|
||||
html += '<tr><td style="font-weight:600;color:var(--blue);">STEP 6</td><td style="font-weight:600;">Dexamethasone</td><td>' + S.dStr(wt, dex.dose_mg_per_kg, dex.max_mg) + '</td><td>IV/IM/PO</td><td style="font-size:12px;color:var(--g500);">Prevents biphasic reaction (4-6 hrs later)</td></tr>';
|
||||
html += '<tr><td></td><td style="font-weight:600;">Methylprednisolone</td><td>' + S.dStr(wt, mpn.dose_mg_per_kg, mpn.max_mg) + '</td><td>IV</td><td style="font-size:12px;color:var(--g500);">Alternative steroid</td></tr>';
|
||||
|
||||
html += '<tr style="background:#fef3c7;"><td style="font-weight:600;color:#dc2626;">IF REFRACTORY</td><td style="font-weight:600;">Epinephrine gtt</td><td>0.1-1 mcg/kg/min</td><td>IV infusion</td><td style="font-size:12px;color:var(--g500);">For persistent hypotension despite fluids + IM epi</td></tr>';
|
||||
html += '<tr style="background:#fef3c7;"><td></td><td style="font-weight:600;">Glucagon</td><td>' + S.dStr(wt, 0.02, 1) + '</td><td>IV/IM</td><td style="font-size:12px;color:var(--g500);">For patients on beta-blockers not responding to epi</td></tr>';
|
||||
html += '<tr style="background:#fef3c7;"><td></td><td style="font-weight:600;">Glucagon</td><td>' + S.dStr(wt, glc.dose_mg_per_kg, glc.max_mg) + '</td><td>IV/IM</td><td style="font-size:12px;color:var(--g500);">For patients on beta-blockers not responding to epi</td></tr>';
|
||||
|
||||
html += '</tbody></table></div>';
|
||||
html += '<div style="padding:10px 12px;background:var(--amber-light);border-radius:6px;font-size:12px;color:#92400e;"><strong>Observe minimum 4-6 hours</strong> after last dose of epinephrine (biphasic reactions occur in 5-20% of cases). Discharge with EpiPen prescription and anaphylaxis action plan. Refer to allergist.</div>';
|
||||
|
|
@ -2694,39 +2753,95 @@
|
|||
|
||||
function d(wt, mgPerKg, max) { var v = Math.round(wt * mgPerKg * 100) / 100; return max ? Math.min(v, max) : v; }
|
||||
|
||||
// Fallback inline data — mirrors drugs.json sedation section. The first
|
||||
// table's rows group by drug (Ketamine has both IV + IM lines), hence the
|
||||
// display vs name split. `reverses` marks the final 2 rows as reversal
|
||||
// agents shown in a separate table.
|
||||
var SED_FALLBACK = [
|
||||
{ name: 'Ketamine IV', display: 'Ketamine', dose_mg_per_kg_low: 1.5, dose_mg_per_kg_high: 2, max_mg_low: 100, max_mg_high: 150, unit: 'mg', route: 'IV', onset: '1 min', duration: '15-30 min', notes: 'Dissociative. Preserves airway reflexes.' },
|
||||
{ name: 'Ketamine IM', display: '', dose_mg_per_kg_low: 4, dose_mg_per_kg_high: 5, max_mg_low: 300, max_mg_high: 400, unit: 'mg', route: 'IM', onset: '3-5 min', duration: '30-60 min', notes: 'Give atropine 0.01 mg/kg to reduce secretions.' },
|
||||
{ name: 'Midazolam IV', display: 'Midazolam', dose_mg_per_kg_low: 0.05, dose_mg_per_kg_high: 0.1, max_mg_low: 2, max_mg_high: 5, unit: 'mg', route: 'IV', onset: '2-3 min', duration: '30-60 min', notes: 'Anxiolysis. Titrate q3-5 min.' },
|
||||
{ name: 'Midazolam IN/PO', display: '', dose_mg_per_kg: 0.5, max_mg: 20, unit: 'mg', route: 'IN/PO', onset: '10-15 min', duration: '30-60 min', notes: 'IN (max 10 mg / naris) or PO (max 20 mg).' },
|
||||
{ name: 'Propofol', display: 'Propofol', dose_mg_per_kg: 1, max_mg: 40, unit: 'mg', route: 'IV', onset: '30 sec', duration: '5-10 min', notes: 'Short procedures. Causes apnea — manage airway.' },
|
||||
{ name: 'Fentanyl IV', display: 'Fentanyl', dose_mg_per_kg: 1, max_mg: 100, unit: 'mcg', route: 'IV', onset: '2-3 min', duration: '30-60 min', notes: 'Analgesic. Often combined with midazolam.' },
|
||||
{ name: 'Fentanyl IN', display: '', dose_mg_per_kg: 2, max_mg: 100, unit: 'mcg', route: 'IN', onset: '5-10 min', duration: '30-60 min', notes: 'Intranasal.' },
|
||||
{ name: 'Nitrous oxide', display: 'Nitrous oxide', dose_display: '50:50 or 70:30 mix', unit: 'mix', route: 'Inhaled',onset: '2-5 min', duration: '5 min off', notes: 'Self-administered via demand valve. Anxiolysis + mild analgesia.' },
|
||||
{ name: 'Dexmedetomidine IN', display: 'Dexmedetomidine', dose_mg_per_kg_low: 2, dose_mg_per_kg_high: 3, max_mg_low: 100, max_mg_high: null, unit: 'mcg', route: 'IN', onset: '15-30 min', duration: '60-90 min', notes: 'Intranasal. No respiratory depression. Good for imaging.' },
|
||||
{ name: 'Naloxone', display: 'Naloxone', dose_mg_per_kg: 0.1, max_mg: 2, unit: 'mg', route: 'IV/IM/IN', reverses: 'Opioids (fentanyl, morphine)', notes: 'Max 2 mg. Repeat q2-3 min. Duration shorter than opioids — monitor for re-sedation.' },
|
||||
{ name: 'Flumazenil', display: 'Flumazenil', dose_mg_per_kg: 0.01, max_mg: 0.2, unit: 'mg', route: 'IV', reverses: 'Benzodiazepines (midazolam)', notes: 'Max 0.2 mg single dose. Repeat q1 min to max 1 mg total. Risk of seizures — use cautiously.' }
|
||||
];
|
||||
|
||||
function sedDrugs() {
|
||||
var s = window._DRUGS && window._DRUGS.sections && window._DRUGS.sections.sedation;
|
||||
if (s && s.drugs && s.drugs.length) {
|
||||
return s.drugs.map(function(d) {
|
||||
var fb = SED_FALLBACK.filter(function(x) { return x.name === d.name; })[0] || {};
|
||||
return Object.assign({}, fb, d);
|
||||
});
|
||||
}
|
||||
return SED_FALLBACK;
|
||||
}
|
||||
|
||||
// perKg footer that matches the original inline format exactly.
|
||||
function sedPerKg(lo, hi, unit) {
|
||||
unit = unit || 'mg';
|
||||
return ' <span style="color:var(--g500);font-size:11px;">(' + lo + (hi != null ? '-' + hi : '') + ' ' + unit + '/kg)</span>';
|
||||
}
|
||||
|
||||
// Build the Dose cell text for a sedation drug (supports low/high range
|
||||
// and single-dose entries; honors custom dose_display for fixed doses).
|
||||
function sedDoseCell(wt, dg) {
|
||||
if (dg.dose_display) return dg.dose_display;
|
||||
var unitLabel = dg.unit === 'mcg' ? ' mcg' : (dg.unit === 'mix' ? '' : ' mg');
|
||||
var perKgUnit = dg.unit === 'mcg' ? 'mcg' : 'mg';
|
||||
if (dg.dose_mg_per_kg_low != null) {
|
||||
var lo = d(wt, dg.dose_mg_per_kg_low, dg.max_mg_low);
|
||||
if (dg.dose_mg_per_kg_high != null) {
|
||||
var hi = d(wt, dg.dose_mg_per_kg_high, dg.max_mg_high);
|
||||
return lo + '-' + hi + unitLabel + sedPerKg(dg.dose_mg_per_kg_low, dg.dose_mg_per_kg_high, perKgUnit);
|
||||
}
|
||||
return lo + unitLabel + sedPerKg(dg.dose_mg_per_kg_low, null, perKgUnit);
|
||||
}
|
||||
if (dg.dose_mg_per_kg != null) {
|
||||
var v = d(wt, dg.dose_mg_per_kg, dg.max_mg);
|
||||
return v + unitLabel + sedPerKg(dg.dose_mg_per_kg, null, perKgUnit);
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
function sedRow(wt, dg, showReverses) {
|
||||
var displayName = dg.display == null ? dg.name : dg.display;
|
||||
var nameCell = displayName
|
||||
? '<td style="font-weight:600;">' + displayName + '</td>'
|
||||
: '<td></td>';
|
||||
var dose = sedDoseCell(wt, dg);
|
||||
if (showReverses) {
|
||||
return '<tr>' + nameCell + '<td>' + dose + '</td><td>' + dg.route + '</td><td>' + (dg.reverses || '') + '</td><td style="font-size:12px;color:var(--g500);">' + dg.notes + '</td></tr>';
|
||||
}
|
||||
return '<tr>' + nameCell + '<td>' + dose + '</td><td>' + dg.route + '</td><td>' + (dg.onset || '') + '</td><td>' + (dg.duration || '') + '</td><td style="font-size:12px;color:var(--g500);">' + dg.notes + '</td></tr>';
|
||||
}
|
||||
|
||||
function calcSedation() {
|
||||
var wt = parseFloat(document.getElementById('sed-weight').value);
|
||||
var resultDiv = document.getElementById('sed-result');
|
||||
if (!wt) { resultDiv.innerHTML = '<p style="color:var(--red);font-size:13px;">Enter weight (kg).</p>'; return; }
|
||||
|
||||
var all = sedDrugs();
|
||||
var agents = all.filter(function(dg) { return !dg.reverses; });
|
||||
var reversals = all.filter(function(dg) { return !!dg.reverses; });
|
||||
|
||||
var html = '<div style="padding:10px 14px;border-radius:8px;background:var(--purple-light);border:1.5px solid var(--purple);margin-bottom:12px;"><span style="font-size:14px;font-weight:700;color:var(--purple);">Procedural Sedation — ' + wt + ' kg patient</span></div>';
|
||||
|
||||
// Sedation agents
|
||||
html += '<h4 style="font-size:14px;font-weight:700;color:var(--g800);margin:0 0 8px;">Sedation Agents</h4>';
|
||||
html += '<div style="overflow-x:auto;-webkit-overflow-scrolling:touch;margin-bottom:16px;"><table style="width:100%;min-width:640px;border-collapse:collapse;font-size:13px;"><thead><tr style="background:var(--g100);"><th style="text-align:left;padding:6px 8px;">Drug</th><th style="padding:6px 8px;">Dose</th><th style="padding:6px 8px;">Route</th><th style="padding:6px 8px;">Onset</th><th style="padding:6px 8px;">Duration</th><th style="padding:6px 8px;">Notes</th></tr></thead><tbody>';
|
||||
|
||||
// Small helper: display dose ranges with per-kg footer consistently.
|
||||
var perKg = function(lo, hi, unit) {
|
||||
unit = unit || 'mg';
|
||||
return ' <span style="color:var(--g500);font-size:11px;">(' + lo + (hi != null ? '-' + hi : '') + ' ' + unit + '/kg)</span>';
|
||||
};
|
||||
html += '<tr><td style="font-weight:600;">Ketamine</td><td>' + d(wt, 1.5, 100) + '-' + d(wt, 2, 150) + ' mg' + perKg(1.5, 2) + '</td><td>IV</td><td>1 min</td><td>15-30 min</td><td style="font-size:12px;color:var(--g500);">Dissociative. Preserves airway reflexes.</td></tr>';
|
||||
html += '<tr><td></td><td>' + d(wt, 4, 300) + '-' + d(wt, 5, 400) + ' mg' + perKg(4, 5) + '</td><td>IM</td><td>3-5 min</td><td>30-60 min</td><td style="font-size:12px;color:var(--g500);">Give atropine 0.01 mg/kg to reduce secretions.</td></tr>';
|
||||
html += '<tr><td style="font-weight:600;">Midazolam</td><td>' + d(wt, 0.05, 2) + '-' + d(wt, 0.1, 5) + ' mg' + perKg(0.05, 0.1) + '</td><td>IV</td><td>2-3 min</td><td>30-60 min</td><td style="font-size:12px;color:var(--g500);">Anxiolysis. Titrate q3-5 min.</td></tr>';
|
||||
html += '<tr><td></td><td>' + d(wt, 0.5, 20) + ' mg' + perKg(0.5, null) + '</td><td>IN/PO</td><td>10-15 min</td><td>30-60 min</td><td style="font-size:12px;color:var(--g500);">IN (max 10 mg / naris) or PO (max 20 mg).</td></tr>';
|
||||
html += '<tr><td style="font-weight:600;">Propofol</td><td>' + d(wt, 1, 40) + ' mg' + perKg(1, null) + '</td><td>IV</td><td>30 sec</td><td>5-10 min</td><td style="font-size:12px;color:var(--g500);">Short procedures. Causes apnea — manage airway.</td></tr>';
|
||||
html += '<tr><td style="font-weight:600;">Fentanyl</td><td>' + d(wt, 1, 100) + ' mcg' + perKg(1, null, 'mcg') + '</td><td>IV</td><td>2-3 min</td><td>30-60 min</td><td style="font-size:12px;color:var(--g500);">Analgesic. Often combined with midazolam.</td></tr>';
|
||||
html += '<tr><td></td><td>' + d(wt, 2, 100) + ' mcg' + perKg(2, null, 'mcg') + '</td><td>IN</td><td>5-10 min</td><td>30-60 min</td><td style="font-size:12px;color:var(--g500);">Intranasal.</td></tr>';
|
||||
html += '<tr><td style="font-weight:600;">Nitrous oxide</td><td>50:50 or 70:30 mix</td><td>Inhaled</td><td>2-5 min</td><td>5 min off</td><td style="font-size:12px;color:var(--g500);">Self-administered via demand valve. Anxiolysis + mild analgesia.</td></tr>';
|
||||
html += '<tr><td style="font-weight:600;">Dexmedetomidine</td><td>' + d(wt, 2, 100) + ' mcg' + perKg(2, 3, 'mcg') + '</td><td>IN</td><td>15-30 min</td><td>60-90 min</td><td style="font-size:12px;color:var(--g500);">Intranasal. No respiratory depression. Good for imaging.</td></tr>';
|
||||
|
||||
html += agents.map(function(dg) { return sedRow(wt, dg, false); }).join('');
|
||||
html += '</tbody></table></div>';
|
||||
|
||||
// Reversal agents
|
||||
html += '<h4 style="font-size:14px;font-weight:700;color:var(--red);margin:0 0 8px;"><i class="fas fa-rotate-left"></i> Reversal Agents</h4>';
|
||||
html += '<div style="overflow-x:auto;-webkit-overflow-scrolling:touch;"><table style="width:100%;min-width:560px;border-collapse:collapse;font-size:13px;"><thead><tr style="background:#fee2e2;"><th style="text-align:left;padding:6px 8px;">Drug</th><th style="padding:6px 8px;">Dose</th><th style="padding:6px 8px;">Route</th><th style="padding:6px 8px;">Reverses</th><th style="padding:6px 8px;">Notes</th></tr></thead><tbody>';
|
||||
html += '<tr><td style="font-weight:600;">Naloxone</td><td>' + d(wt, 0.1, 2) + ' mg' + perKg(0.1, null) + '</td><td>IV/IM/IN</td><td>Opioids (fentanyl, morphine)</td><td style="font-size:12px;color:var(--g500);">Max 2 mg. Repeat q2-3 min. Duration shorter than opioids — monitor for re-sedation.</td></tr>';
|
||||
html += '<tr><td style="font-weight:600;">Flumazenil</td><td>' + d(wt, 0.01, 0.2) + ' mg' + perKg(0.01, null) + '</td><td>IV</td><td>Benzodiazepines (midazolam)</td><td style="font-size:12px;color:var(--g500);">Max 0.2 mg single dose. Repeat q1 min to max 1 mg total. Risk of seizures — use cautiously.</td></tr>';
|
||||
html += reversals.map(function(dg) { return sedRow(wt, dg, true); }).join('');
|
||||
html += '</tbody></table></div>';
|
||||
|
||||
html += '<div style="margin-top:12px;padding:10px 12px;background:var(--amber-light);border-radius:6px;font-size:12px;color:#92400e;"><strong>Pre-sedation checklist:</strong> NPO status (2h clear liquids, 6h solids), consent, monitoring equipment (pulse ox, capnography, BP), resuscitation equipment at bedside, suction ready, IV access. Minimum monitoring: continuous SpO2, HR, capnography. Provider capable of managing airway must be present.</div>';
|
||||
|
|
@ -3306,30 +3421,78 @@ window._EM = {
|
|||
if (e.target.id === 'btn-agit-calc' || e.target.closest('#btn-agit-calc')) calcAgit();
|
||||
});
|
||||
|
||||
// Fallback inline data — mirrors drugs.json agitation section, split into
|
||||
// step-2 (PO/IN) and step-3 (IV/IM) subgroups by the route field.
|
||||
// Display-name column intentionally drops the route suffix from the JSON
|
||||
// name so the table still shows "Lorazepam" / "Midazolam" / "Ketamine".
|
||||
var AGIT_FALLBACK = [
|
||||
// Step 2 — PO / IN
|
||||
{ name: 'Lorazepam PO', display: 'Lorazepam', step: 2, dose_mg_per_kg: 0.05, max_mg: 2, unit: 'mg', route: 'PO', notes: '0.05 mg/kg. May repeat in 30 min.' },
|
||||
{ name: 'Midazolam PO', display: 'Midazolam', step: 2, dose_mg_per_kg: 0.5, max_mg: 10, unit: 'mg', route: 'PO', notes: '0.5 mg/kg (max 10 mg)' },
|
||||
{ name: 'Midazolam IN', display: 'Midazolam', step: 2, dose_mg_per_kg: 0.3, max_mg: 10, unit: 'mg', route: 'IN (5 mg/mL)', notes: '0.3 mg/kg split between nares (max 10 mg)' },
|
||||
{ name: 'Olanzapine (ODT)', display: 'Olanzapine (ODT)', step: 2, dose_mg_per_kg: null, max_mg: null, unit: 'mg', route: 'PO / ODT', notes: 'Age ≥6 yr. Avoid IM + benzo combo (risk of resp depression).', dose_display_if_under_30kg: '2.5-5 mg', dose_display_if_30kg_or_more: '5-10 mg' },
|
||||
{ name: 'Diphenhydramine PO', display: 'Diphenhydramine', step: 2, dose_mg_per_kg: 1, max_mg: 50, unit: 'mg', route: 'PO', notes: '1 mg/kg. Adjunct only; sedating.' },
|
||||
// Step 3 — IV / IM
|
||||
{ name: 'Midazolam IV/IM', display: 'Midazolam', step: 3, dose_mg_per_kg: 0.1, max_mg: 5, unit: 'mg', route: 'IV / IM', notes: '0.05-0.1 mg/kg IV, 0.1-0.15 mg/kg IM (max 10 mg)' },
|
||||
{ name: 'Lorazepam IV/IM', display: 'Lorazepam', step: 3, dose_mg_per_kg: 0.1, max_mg: 4, unit: 'mg', route: 'IV / IM', notes: '0.05-0.1 mg/kg (max 4 mg). May cause resp depression.' },
|
||||
{ name: 'Haloperidol', display: 'Haloperidol', step: 3, dose_mg_per_kg: 0.05, max_mg: null, unit: 'mg', route: 'IM / IV', notes: 'Avoid <3 yr. Risk: QT, EPS, NMS. ECG if repeated.', dose_display_if_under_40kg: '0.025-0.075 mg/kg', dose_display_if_40kg_or_more: '2.5-5 mg' },
|
||||
{ name: 'Olanzapine IM', display: 'Olanzapine', step: 3, dose_mg_per_kg: null, max_mg: null, unit: 'mg', route: 'IM', notes: 'Avoid benzo co-administration (resp depression, hypotension)', dose_display_if_under_40kg: '2.5-5 mg', dose_display_if_40kg_or_more: '5-10 mg' },
|
||||
{ name: 'Ketamine IM', display: 'Ketamine', step: 3, dose_mg_per_kg: 4, max_mg: 500, unit: 'mg', route: 'IM', notes: '4-5 mg/kg IM (rescue for severe excited delirium). Monitor airway.' },
|
||||
{ name: 'Droperidol', display: 'Droperidol', step: 3, dose_mg_per_kg: 0.05, max_mg: null, unit: 'mg', route: 'IM / IV', notes: 'Effective but QT concern — get ECG.', dose_display_prefix: '0.03-0.07 mg/kg' }
|
||||
];
|
||||
|
||||
function agitDrugs() {
|
||||
var s = window._DRUGS && window._DRUGS.sections && window._DRUGS.sections.agitation;
|
||||
if (s && s.drugs && s.drugs.length) {
|
||||
// Merge per-drug hints from fallback (display name, step) when the
|
||||
// JSON doesn't carry them — keeps rendering identical.
|
||||
return s.drugs.map(function(d) {
|
||||
var fb = AGIT_FALLBACK.filter(function(x) { return x.name === d.name; })[0] || {};
|
||||
return Object.assign({}, fb, d);
|
||||
});
|
||||
}
|
||||
return AGIT_FALLBACK;
|
||||
}
|
||||
|
||||
// Render one drug row, handling the agitation-specific display quirks:
|
||||
// weight-conditional dose strings (Olanzapine, Haloperidol) and hand-
|
||||
// computed "prefix = value mg" strings (Droperidol, Haloperidol <40 kg).
|
||||
function agitRowFor(wt, d) {
|
||||
var S = window._EM;
|
||||
var dose;
|
||||
if (d.dose_display_if_under_30kg && d.dose_display_if_30kg_or_more) {
|
||||
dose = wt < 30 ? d.dose_display_if_under_30kg : d.dose_display_if_30kg_or_more;
|
||||
} else if (d.dose_display_if_under_40kg && d.dose_display_if_40kg_or_more) {
|
||||
dose = wt < 40 ? d.dose_display_if_under_40kg + ' = ' + S.d(wt, d.dose_mg_per_kg) + ' mg' : d.dose_display_if_40kg_or_more;
|
||||
} else if (d.dose_display_prefix) {
|
||||
dose = d.dose_display_prefix + ' = ' + S.d(wt, d.dose_mg_per_kg) + ' mg';
|
||||
} else if (d.dose_mg_per_kg != null) {
|
||||
dose = S.dStr(wt, d.dose_mg_per_kg, d.max_mg, ' ' + d.unit);
|
||||
} else {
|
||||
dose = d.dose_display || '';
|
||||
}
|
||||
return S.drugRow(d.display || d.name, dose, d.route, d.notes);
|
||||
}
|
||||
|
||||
// Haloperidol has a weight-specific quirk: under 40 kg the display line
|
||||
// needs the computed mg, but over 40 kg it's just "2.5-5 mg". Handled
|
||||
// uniformly by dose_display_if_under_40kg above.
|
||||
|
||||
function calcAgit() {
|
||||
var wt = parseFloat(document.getElementById('agit-weight').value);
|
||||
var el = document.getElementById('agit-result');
|
||||
if (!wt) { el.innerHTML = '<p style="color:var(--red);font-size:13px;">Enter weight (kg).</p>'; return; }
|
||||
var S = window._EM;
|
||||
var drugs = agitDrugs();
|
||||
var step2 = drugs.filter(function(d) { return d.step === 2; });
|
||||
var step3 = drugs.filter(function(d) { return d.step === 3; });
|
||||
|
||||
var html = '<div style="padding:10px 14px;border-radius:8px;background:var(--purple-light);border:1.5px solid var(--purple);margin-bottom:10px;"><strong style="color:var(--purple);">Agitation management — ' + wt + ' kg</strong></div>';
|
||||
html += S.stepBox('#10b981', 'Step 1 — Non-pharmacologic', 'Dim lights, quiet room, familiar caregiver present, reduce stimulation. Rule out hypoglycemia, hypoxia, pain, ↑ICP, toxic ingestion, infection.');
|
||||
html += S.stepBox('#3b82f6', 'Step 2 — Oral / intranasal (cooperative)', 'First-line for moderate agitation if the patient will accept PO/IN.');
|
||||
html += S.drugTable(
|
||||
S.drugRow('Lorazepam', S.dStr(wt, 0.05, 2, ' mg'), 'PO', '0.05 mg/kg. May repeat in 30 min.') +
|
||||
S.drugRow('Midazolam', S.dStr(wt, 0.5, 10, ' mg'), 'PO', '0.5 mg/kg (max 10 mg)') +
|
||||
S.drugRow('Midazolam', S.dStr(wt, 0.3, 10, ' mg'), 'IN (5 mg/mL)', '0.3 mg/kg split between nares (max 10 mg)') +
|
||||
S.drugRow('Olanzapine (ODT)', wt < 30 ? '2.5-5 mg' : '5-10 mg', 'PO / ODT', 'Age ≥6 yr. Avoid IM + benzo combo (risk of resp depression).') +
|
||||
S.drugRow('Diphenhydramine', S.dStr(wt, 1, 50, ' mg'), 'PO', '1 mg/kg. Adjunct only; sedating.')
|
||||
);
|
||||
html += S.drugTable(step2.map(function(d) { return agitRowFor(wt, d); }).join(''));
|
||||
html += S.stepBox('#f59e0b', 'Step 3 — IM / IV (severe, uncooperative, or safety risk)', 'Require continuous monitoring. Consider restraints only as last resort.');
|
||||
html += S.drugTable(
|
||||
S.drugRow('Midazolam', S.dStr(wt, 0.1, 5, ' mg'), 'IV / IM', '0.05-0.1 mg/kg IV, 0.1-0.15 mg/kg IM (max 10 mg)') +
|
||||
S.drugRow('Lorazepam', S.dStr(wt, 0.1, 4, ' mg'), 'IV / IM', '0.05-0.1 mg/kg (max 4 mg). May cause resp depression.') +
|
||||
S.drugRow('Haloperidol', wt < 40 ? '0.025-0.075 mg/kg = ' + S.d(wt, 0.05) + ' mg' : '2.5-5 mg', 'IM / IV', 'Avoid <3 yr. Risk: QT, EPS, NMS. ECG if repeated.') +
|
||||
S.drugRow('Olanzapine', wt < 40 ? '2.5-5 mg' : '5-10 mg', 'IM', 'Avoid benzo co-administration (resp depression, hypotension)') +
|
||||
S.drugRow('Ketamine', S.dStr(wt, 4, 500, ' mg'), 'IM', '4-5 mg/kg IM (rescue for severe excited delirium). Monitor airway.') +
|
||||
S.drugRow('Droperidol', '0.03-0.07 mg/kg = ' + S.d(wt, 0.05) + ' mg', 'IM / IV', 'Effective but QT concern — get ECG.')
|
||||
);
|
||||
html += S.drugTable(step3.map(function(d) { return agitRowFor(wt, d); }).join(''));
|
||||
html += '<div style="padding:10px;background:#fef3c7;border-radius:6px;font-size:12px;color:#92400e;margin-top:10px;"><strong>Monitoring:</strong> Continuous SpO2 + HR after parenteral sedation. Have airway equipment, flumazenil, naloxone, and IV access ready.</div>';
|
||||
html += S.ref('AAP Clinical Report on Pediatric Agitation. ACEP Guidelines for Acute Agitation.');
|
||||
el.innerHTML = html;
|
||||
|
|
@ -3344,23 +3507,50 @@ window._EM = {
|
|||
if (e.target.id === 'btn-emet-calc' || e.target.closest('#btn-emet-calc')) calcEmet();
|
||||
});
|
||||
|
||||
// Fallback inline data — used if /data/drugs.json hasn't loaded yet.
|
||||
// Must match drugs.json shape for the antiemetics section.
|
||||
var EMET_FALLBACK = [
|
||||
{ name: 'Ondansetron', dose_mg_per_kg: 0.15, max_mg: 8, unit: 'mg', route: 'PO / ODT / IV', notes: 'First-line. Max single 8 mg. Repeat q8h. QT prolongation — avoid with other QT drugs. <6 mo: limited data.', weight_band_dose: { under_15kg: '2 mg', '15_to_30kg': '4 mg', '30kg_or_more': '8 mg' } },
|
||||
{ name: 'Metoclopramide', dose_mg_per_kg: 0.15, max_mg: 10, unit: 'mg', route: 'IV / IM / PO', notes: '0.1-0.15 mg/kg (max 10 mg). Give with diphenhydramine to prevent EPS / dystonia.' },
|
||||
{ name: 'Dimenhydrinate', dose_mg_per_kg: 1.25, max_mg: 50, unit: 'mg', route: 'PO / IV / IM / PR', notes: '1.25 mg/kg (max 50 mg) q6h. ≥2 yr.' },
|
||||
{ name: 'Diphenhydramine', dose_mg_per_kg: 1, max_mg: 50, unit: 'mg', route: 'PO / IV / IM', notes: '1 mg/kg (max 50 mg) q6h. Adjunct, sedating.' },
|
||||
{ name: 'Promethazine', dose_mg_per_kg: 0.25, max_mg: 25, unit: 'mg', route: 'PO / IV / IM', notes: '0.25-1 mg/kg. <strong>CONTRAINDICATED <2 yr</strong> (resp depression). Tissue injury if IV extrav.' },
|
||||
{ name: 'Dexamethasone', dose_mg_per_kg: 0.15, max_mg: 10, unit: 'mg', route: 'IV / PO', notes: 'Adjunct, esp. chemo-induced. 0.15 mg/kg (max 10 mg).' },
|
||||
{ name: 'Scopolamine patch', dose_mg_per_kg: null, max_mg: null, unit: 'mg', dose_display: '1.5 mg patch', route: 'Transdermal', notes: '≥12 yr. Motion sickness. Apply 4h before exposure.' }
|
||||
];
|
||||
|
||||
// Prefer JSON-backed data if loaded; fall back to the inline list above.
|
||||
function emetDrugs() {
|
||||
var s = window._DRUGS && window._DRUGS.sections && window._DRUGS.sections.antiemetics;
|
||||
return (s && s.drugs && s.drugs.length) ? s.drugs : EMET_FALLBACK;
|
||||
}
|
||||
|
||||
// Iterate the drug list to produce S.drugRow(...) output, handling the
|
||||
// special cases (Ondansetron weight-band prefix, Scopolamine fixed dose).
|
||||
function emetRows(wt, drugs) {
|
||||
var S = window._EM;
|
||||
return drugs.map(function(d) {
|
||||
var dose;
|
||||
if (d.name === 'Ondansetron' && d.weight_band_dose) {
|
||||
var bd = d.weight_band_dose;
|
||||
var band = wt < 15 ? bd.under_15kg : wt < 30 ? bd['15_to_30kg'] : bd['30kg_or_more'];
|
||||
dose = band + ' (weight band) OR ' + S.dStr(wt, d.dose_mg_per_kg, d.max_mg, ' ' + d.unit) + ' (' + d.dose_mg_per_kg + ' ' + d.unit + '/kg)';
|
||||
} else if (d.dose_display) {
|
||||
dose = d.dose_display;
|
||||
} else {
|
||||
dose = S.dStr(wt, d.dose_mg_per_kg, d.max_mg, ' ' + d.unit);
|
||||
}
|
||||
return S.drugRow(d.name, dose, d.route, d.notes);
|
||||
}).join('');
|
||||
}
|
||||
|
||||
function calcEmet() {
|
||||
var wt = parseFloat(document.getElementById('emet-weight').value);
|
||||
var el = document.getElementById('emet-result');
|
||||
if (!wt) { el.innerHTML = '<p style="color:var(--red);font-size:13px;">Enter weight (kg).</p>'; return; }
|
||||
var S = window._EM;
|
||||
// Ondansetron weight bands
|
||||
var ondWt = wt < 15 ? '2 mg' : wt < 30 ? '4 mg' : '8 mg';
|
||||
var html = '<div style="padding:10px 14px;border-radius:8px;background:var(--blue-light);border:1.5px solid var(--blue);margin-bottom:10px;"><strong style="color:var(--blue-dark);">Antiemetic doses — ' + wt + ' kg</strong></div>';
|
||||
html += S.drugTable(
|
||||
S.drugRow('Ondansetron', ondWt + ' (weight band) OR ' + S.dStr(wt, 0.15, 8, ' mg') + ' (0.15 mg/kg)', 'PO / ODT / IV', 'First-line. Max single 8 mg. Repeat q8h. QT prolongation — avoid with other QT drugs. <6 mo: limited data.') +
|
||||
S.drugRow('Metoclopramide', S.dStr(wt, 0.15, 10, ' mg'), 'IV / IM / PO', '0.1-0.15 mg/kg (max 10 mg). Give with diphenhydramine to prevent EPS / dystonia.') +
|
||||
S.drugRow('Dimenhydrinate', S.dStr(wt, 1.25, 50, ' mg'), 'PO / IV / IM / PR', '1.25 mg/kg (max 50 mg) q6h. ≥2 yr.') +
|
||||
S.drugRow('Diphenhydramine', S.dStr(wt, 1, 50, ' mg'), 'PO / IV / IM', '1 mg/kg (max 50 mg) q6h. Adjunct, sedating.') +
|
||||
S.drugRow('Promethazine', S.dStr(wt, 0.25, 25, ' mg'), 'PO / IV / IM', '0.25-1 mg/kg. <strong>CONTRAINDICATED <2 yr</strong> (resp depression). Tissue injury if IV extrav.') +
|
||||
S.drugRow('Dexamethasone', S.dStr(wt, 0.15, 10, ' mg'), 'IV / PO', 'Adjunct, esp. chemo-induced. 0.15 mg/kg (max 10 mg).') +
|
||||
S.drugRow('Scopolamine patch', '1.5 mg patch', 'Transdermal', '≥12 yr. Motion sickness. Apply 4h before exposure.')
|
||||
);
|
||||
html += S.drugTable(emetRows(wt, emetDrugs()));
|
||||
html += '<div style="padding:10px;background:#fef3c7;border-radius:6px;font-size:12px;color:#92400e;margin-top:10px;"><strong>Pearls:</strong> Ondansetron is first-line in ED for acute gastroenteritis vomiting — single PO/ODT dose increases oral rehydration success. Avoid anticholinergics + opioid combinations. Check QTc if stacking ondansetron + other QT drugs.</div>';
|
||||
html += S.ref('AAP gastroenteritis guidance; WHO essential medicines; Lexicomp.');
|
||||
el.innerHTML = html;
|
||||
|
|
|
|||
31
public/js/drugs-loader.js
Normal file
31
public/js/drugs-loader.js
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
// ============================================================
|
||||
// drugs-loader.js
|
||||
// Fetches /data/drugs.json at page load and exposes:
|
||||
// window._DRUGS — the parsed JSON (after fetch resolves)
|
||||
// window._DRUGS_READY — a Promise that resolves to the JSON
|
||||
//
|
||||
// Calculator functions in calculators.js look up drugs via a small
|
||||
// helper (pickDrug) and fall back to hardcoded inline data if the
|
||||
// JSON isn't loaded yet. This means the app keeps working even if
|
||||
// the fetch fails — the JSON is an authoritative data source but
|
||||
// not a hard dependency at runtime.
|
||||
// ============================================================
|
||||
(function() {
|
||||
// Default to empty shape so lookups don't throw before fetch resolves.
|
||||
window._DRUGS = window._DRUGS || { version: null, sections: {} };
|
||||
|
||||
window._DRUGS_READY = fetch('/data/drugs.json', { credentials: 'same-origin' })
|
||||
.then(function(r) {
|
||||
if (!r.ok) throw new Error('drugs.json HTTP ' + r.status);
|
||||
return r.json();
|
||||
})
|
||||
.then(function(j) {
|
||||
window._DRUGS = j;
|
||||
return j;
|
||||
})
|
||||
.catch(function(err) {
|
||||
// Non-fatal — calc functions have inline fallbacks.
|
||||
console.warn('[drugs-loader] could not load /data/drugs.json:', err);
|
||||
return window._DRUGS;
|
||||
});
|
||||
})();
|
||||
|
|
@ -165,3 +165,20 @@ test('Lund-Browder: 50% of both thighs young child = 8%', () => {
|
|||
test('Lund-Browder: invalid bracket returns null', () => {
|
||||
assert.equal(M.lundBrowderTBSA('bogus', { head: 50 }), null);
|
||||
});
|
||||
|
||||
// ── drugs.json schema ───────────────────────────────────────────
|
||||
// Asserts the JSON is loadable and that each of the 5 extracted
|
||||
// bedside sections exists with a non-empty drugs array.
|
||||
test('drugs.json: schema loads with all 5 extracted sections + non-empty drugs', () => {
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const raw = fs.readFileSync(path.join(__dirname, '..', 'public', 'data', 'drugs.json'), 'utf8');
|
||||
const j = JSON.parse(raw);
|
||||
assert.equal(j.version, '1.0');
|
||||
assert.ok(j.sections, 'top-level sections object present');
|
||||
['anaphylaxis', 'sedation', 'agitation', 'antiemetics', 'seizure'].forEach((key) => {
|
||||
const sec = j.sections[key];
|
||||
assert.ok(sec, `section ${key} present`);
|
||||
assert.ok(Array.isArray(sec.drugs) && sec.drugs.length > 0, `${key}.drugs non-empty`);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue