pediatric-ai-scribe-v3/client/src/data/pe-guide.ts
Daniel 9ad34a7f35 feat(client): port PE Guide reference libraries + rescue untracked FAQ data
Two things in one commit because they're coupled by a gitignore fix:
(1) PE Guide minimum-viable port, (2) a pre-existing bug where
client/src/data/faq.ts was silently gitignored and never committed.

.gitignore — narrow scope
  Changed `data/` to `/data/`. The old rule matched every nested
  `data/` directory in the tree, including client/src/data/, which
  meant faq.ts from the FAQ port (commit c0038da) never landed in
  git — the Faq page built locally only because the file existed
  on the dev machine. A fresh clone or CI build would fail the
  Vite build at '@/data/faq'. The narrowed rule still ignores the
  runtime DB directory at repo root while allowing project data
  modules to be tracked. This commit also commits the missing
  faq.ts so the FAQ page is actually buildable from git again.

client/src/data/pe-guide.ts
  Verbatim port of lines 23-311 of public/js/peGuide.js — the stable
  reference content:
    • SCALES (12 scales: MRC, DTR, plantar, Beighton, ATR, RR, SpO2,
      Silverman, Westley, Levine murmur, pulse amp, cap refill)
    • SYSTEM_SCALES (per-body-system scale mapping)
    • APTM_LEGEND (5 cardiac auscultation points)
    • INNOCENT_MURMURS (5 benign childhood murmurs)
    • RESP_SOUNDS (7 entries with /audio/respiratory/*.ogg paths)
    • CARDIAC_SOUNDS (6 entries with /audio/cardiac/* paths)
  Counts preserved exactly. Audio files stay under public/audio/ and
  are served unchanged.

What is NOT in this commit — on purpose
  PE_DATA (the ~1000-line age-group × system × component × step
  hierarchy) stays in the vanilla app. The migration checkpoint memory
  explicitly warns about the class of bug where an LLM silently drops
  entries from long clinical arrays. PE_DATA porting needs its own
  session with per-entry counts + visual diff against the vanilla
  source. An amber banner at the top of the React page links to
  /#peGuide (the legacy checklist viewer) so users still reach the
  full exam-step checklist + Generate-Exam-Report flow.

  Also skipped: the big inline APTM_SVG chest diagram. The letter
  legend (A/P/E/T/M) carries the clinical content; the pictorial
  SVG can land later without content risk.

client/src/pages/PeGuide.tsx — viewer
  Grid-of-cards layout: one card per scale, per APTM point, per
  innocent-murmur, and per sound entry. Sound cards use native
  <audio controls> so the browser does the usual play/pause/seek —
  no custom player. data-testid hooks throughout for the spec.

e2e/tests/peguide-react.spec.js — four smoke tests:
  all 12 scales render (this is the count that would fail loudly if
  someone trimmed SCALES later), APTM has all 5 letters, sound
  libraries have the exact respiratory + cardiac keys, and the
  legacy-viewer link is present.

Client tsc -b + vite build clean. Bundle 452.91 kB / 129.35 kB gz.
2026-04-23 23:57:16 +02:00

290 lines
15 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// ============================================================
// PE-GUIDE DATA — ported verbatim from public/js/peGuide.js
// (lines 23-311 of the vanilla file, as of commit before this one).
//
// This file ONLY contains the stable reference data:
// • SCALES — grading scales (MRC, DTR, Levine, Beighton, …)
// • SYSTEM_SCALES — which scales belong to which body system
// • APTM_LEGEND — the 5 cardiac auscultation points
// • INNOCENT_MURMURS — benign childhood murmurs
// • RESP_SOUNDS — respiratory sounds library (audio paths)
// • CARDIAC_SOUNDS — cardiac sounds library (audio paths)
//
// PE_DATA (the full age-group × system × component × step hierarchy,
// ~1000 lines) is intentionally NOT ported here. It holds clinically
// reviewed content and the migration checkpoint explicitly warns
// "An LLM will sometimes 'simplify' a long array — don't let that
// happen." PE_DATA port belongs in its own dedicated session with
// per-entry counts + visual diff verification against the vanilla
// source. Until that session, the React PE Guide surfaces the
// reference libraries below and links to the legacy viewer for
// exam-step checklists and narrative generation.
//
// Audio files stay in public/audio/respiratory/ and public/audio/cardiac/
// and are served unchanged from Express.
// ============================================================
export interface ScaleDef {
title: string;
icon: string;
rows: Array<[string, string]>;
}
export const SCALES: Record<string, ScaleDef> = {
mrc: {
title: 'MRC strength grade (05)',
icon: 'fa-hand-fist',
rows: [
['5', 'Normal power — holds against full resistance'],
['4', 'Reduced — moves against gravity + some resistance'],
['3', 'Moves against gravity only (no added resistance)'],
['2', 'Full range with gravity eliminated (horizontal plane)'],
['1', 'Flicker / trace contraction, no joint movement'],
['0', 'No contraction'],
],
},
dtr: {
title: 'Deep-tendon reflex grade (04+)',
icon: 'fa-circle-dot',
rows: [
['0', 'Absent'],
['1+', 'Hypoactive — trace, only with reinforcement'],
['2+', 'Normal'],
['3+', 'Brisk — may still be normal in anxious patients'],
['4+', 'Hyperactive with sustained clonus — always abnormal'],
],
},
plantar: {
title: 'Plantar response (Babinski)',
icon: 'fa-shoe-prints',
rows: [
['Down-going', 'Normal in anyone ≥ 2 years'],
['Up-going', 'Normal < 2 years; abnormal after — UMN lesion'],
['Asymmetric', 'Always abnormal at any age'],
],
},
beighton: {
title: 'Beighton hypermobility score (09)',
icon: 'fa-hands',
rows: [
['≤ 3', 'Normal flexibility'],
['4', 'Borderline — consider in context'],
['≥ 5', 'Hypermobility spectrum; screen for hEDS if other features present'],
],
},
atr: {
title: 'Scoliometer — angle of trunk rotation',
icon: 'fa-ruler',
rows: [
['< 5°', 'Normal, no follow-up'],
['56°', 'Borderline — re-check at each visit'],
['≥ 7°', 'Refer for PA/lateral spine x-ray + orthopedic evaluation'],
],
},
rr: {
title: 'Respiratory rate — upper limit by age (awake)',
icon: 'fa-lungs',
rows: [
['Newborn', '≤ 60 /min'],
['< 2 months', '≤ 60 /min (WHO tachypnea cutoff)'],
['212 months', '≤ 50 /min (WHO tachypnea cutoff)'],
['15 years', '≤ 40 /min (WHO tachypnea cutoff)'],
['611 years', '≤ 30 /min'],
['≥ 12 years', '≤ 20 /min (adult pattern)'],
],
},
spo2: {
title: 'Pulse oximetry (SpO₂) — at room air',
icon: 'fa-heart-pulse',
rows: [
['≥ 95%', 'Normal'],
['9294%', 'Mild hypoxemia — investigate cause'],
['< 92%', 'Moderate hypoxemia — supplemental O₂'],
['< 88%', 'Severe — urgent intervention; target ≥ 90% acutely'],
],
},
silverman: {
title: 'SilvermanAndersen retraction score (neonatal, 010)',
icon: 'fa-baby',
rows: [
['0', 'No respiratory distress'],
['13', 'Mild — close observation'],
['46', 'Moderate distress — consider CPAP / support'],
['710', 'Severe — imminent respiratory failure, intubate'],
],
},
westley: {
title: 'Westley croup severity score',
icon: 'fa-stethoscope',
rows: [
['≤ 2', 'Mild — home management, cool mist, oral dexamethasone'],
['35', 'Moderate — nebulised epinephrine + dexamethasone'],
['611', 'Severe — admit, continuous monitoring'],
['≥ 12', 'Impending respiratory failure — ICU / airway management'],
],
},
murmurGrade: {
title: 'Heart-murmur grading (Levine 16)',
icon: 'fa-wave-square',
rows: [
['1/6', 'Very faint — heard only with concentration'],
['2/6', 'Soft but readily heard'],
['3/6', 'Moderately loud, no thrill'],
['4/6', 'Loud WITH a palpable thrill'],
['5/6', 'Very loud; audible with stethoscope just off the chest'],
['6/6', 'Audible without the stethoscope touching the chest'],
],
},
pulseAmp: {
title: 'Pulse amplitude grade (04)',
icon: 'fa-heart-pulse',
rows: [
['0', 'Absent'],
['1+', 'Diminished, thready'],
['2+', 'Normal'],
['3+', 'Bounding'],
['4+', 'Bounding with visible pulsation (e.g., aortic regurgitation)'],
],
},
capRefill: {
title: 'Capillary refill time',
icon: 'fa-hand',
rows: [
['< 2 sec', 'Normal'],
['23 sec', 'Borderline — consider hydration / perfusion'],
['≥ 3 sec', 'Delayed — dehydration, shock, low cardiac output'],
],
},
};
export const SYSTEM_SCALES: Record<string, string[]> = {
msk: ['atr', 'beighton'],
neuro: ['mrc', 'dtr', 'plantar'],
resp: ['rr', 'spo2', 'silverman', 'westley'],
cv: ['murmurGrade', 'pulseAmp', 'capRefill'],
};
// APTM — the 5 classic cardiac auscultation points
export interface AptmEntry {
letter: string;
color: string;
title: string;
location: string;
listen: string;
innocent?: string;
}
export const APTM_LEGEND: AptmEntry[] = [
{ letter: 'A', color: '#dc2626', title: 'Aortic area', location: '2nd ICS, right sternal border', listen: 'S2 (aortic component), aortic stenosis, aortic regurgitation' },
{ letter: 'P', color: '#2563eb', title: 'Pulmonic area', location: '2nd ICS, left sternal border', listen: 'S2 (pulmonic component), pulmonic stenosis, PDA, physiologic split of S2',
innocent: 'Pulmonary flow murmur (children, adolescents) — upper left sternal border' },
{ letter: 'E', color: '#059669', title: 'Erb\'s point', location: '3rd ICS, left sternal border', listen: 'Aortic regurgitation (best here), transitional zone murmurs',
innocent: 'Still\'s murmur classically radiates to Erb\'s / LLSB' },
{ letter: 'T', color: '#d97706', title: 'Tricuspid area', location: '4th5th ICS, lower left sternal border', listen: 'Tricuspid regurgitation, VSD, S3/S4, holosystolic murmurs',
innocent: 'Still\'s murmur — vibratory, musical, age 37 y (loudest between LLSB and apex)' },
{ letter: 'M', color: '#7c3aed', title: 'Mitral area (apex)', location: '5th ICS, mid-clavicular line', listen: 'S1, mitral regurgitation, mitral stenosis (with bell, left-lateral decubitus)' },
];
// Innocent (benign) childhood murmurs
export interface InnocentMurmur {
name: string;
age: string;
location: string;
character: string;
confirm: string;
}
export const INNOCENT_MURMURS: InnocentMurmur[] = [
{ name: 'Still\'s (vibratory) murmur',
age: '37 y (most common in children)',
location: 'LLSB, radiating to apex',
character: 'Low-frequency vibratory / musical systolic, grade 23/6, mid-systolic, "twanging-string" quality',
confirm: 'Louder supine, softer or disappears on standing or Valsalva. No radiation to neck/back. Normal S2.' },
{ name: 'Pulmonary flow murmur',
age: 'School-age and adolescents, thin chest',
location: 'Upper left sternal border (2nd3rd ICS)',
character: 'Soft blowing early systolic ejection, grade 12/6, higher-pitched',
confirm: 'No ejection click. Physiologic split of S2. Louder supine, softer on standing. No radiation.' },
{ name: 'Venous hum',
age: 'Ages 38, disappears by adolescence',
location: 'Supraclavicular or infraclavicular area, usually right',
character: 'Soft continuous hum, louder in diastole. Only innocent continuous murmur.',
confirm: 'Disappears when supine OR when jugular vein is gently compressed (key maneuver). Turning head to opposite side also alters it.' },
{ name: 'Carotid bruit / supraclavicular bruit',
age: 'Children and adolescents',
location: 'Supraclavicular fossa, right > left; may radiate to carotid',
character: 'Brief early systolic, grade 23/6, higher-pitched than Still\'s',
confirm: 'Softer or disappears with hyperextension of the shoulders. Normal cardiac exam otherwise. No radiation below the clavicles.' },
{ name: 'Peripheral pulmonary stenosis (PPS, neonatal)',
age: 'Newborns and infants < 612 months',
location: 'Upper LSB, radiates to BOTH axillae and the back',
character: 'Soft systolic ejection murmur, grade 12/6',
confirm: 'Typical age + radiation to back/axillae. Resolves by age 1 as branch pulmonary arteries grow. Persistence or louder grade warrants echo.' },
];
// Respiratory sounds library — real recordings served from /public/audio/respiratory/
export interface SoundEntry {
key: string;
src: string;
title: string;
where: string;
rate?: string;
features: string;
clinical: string;
}
export const RESP_SOUNDS: SoundEntry[] = [
{ key: 'normal', src: '/audio/respiratory/normal-vesicular.ogg', title: 'Normal vesicular breath sounds',
where: 'Peripheral lung fields',
features: 'Soft, rustling. Inspiration louder and longer than expiration.',
clinical: 'Baseline — deviation elsewhere is what you listen for.' },
{ key: 'wheeze', src: '/audio/respiratory/wheeze.ogg', title: 'Wheeze',
where: 'Diffuse in asthma; localised in foreign body',
features: 'Continuous, high-pitched, musical. Usually expiratory; biphasic if severe.',
clinical: 'Lower-airway narrowing — asthma, bronchiolitis, foreign body, bronchomalacia. Silent chest in severe asthma is an ominous sign.' },
{ key: 'stridor', src: '/audio/respiratory/stridor.ogg', title: 'Stridor',
where: 'Louder over neck than chest — upper airway',
features: 'Continuous, high-pitched, harsh. Classically inspiratory (extrathoracic obstruction); biphasic if fixed.',
clinical: 'Croup, epiglottitis, foreign body, laryngomalacia (infant). Distinguish from wheeze by auscultating the neck — stridor is loudest there.' },
{ key: 'finecrackles', src: '/audio/respiratory/crackles-fine.ogg', title: 'Fine (end-inspiratory) crackles',
where: 'Bibasilar in pulmonary edema/fibrosis; focal in pneumonia',
features: 'Discontinuous, brief, high-pitched. "Velcro" quality. Late inspiratory, do NOT clear with cough.',
clinical: 'Alveolar opening — pulmonary fibrosis, pulmonary edema, early pneumonia, atelectasis.' },
{ key: 'coarsecrackles', src: '/audio/respiratory/crackles-coarse.ogg', title: 'Coarse crackles',
where: 'Lower lobes; either side',
features: 'Discontinuous, longer and louder than fine crackles. Lower-pitched. Can be early or late inspiratory; often clear partly with cough.',
clinical: 'Secretions in larger airways — bronchitis, later pneumonia, bronchiectasis, aspiration.' },
{ key: 'rhonchi', src: '/audio/respiratory/rhonchi.ogg', title: 'Rhonchi',
where: 'Central or anywhere with airway secretions',
features: 'Continuous, low-pitched, snore-like. Typically expiratory. Clear or change with cough.',
clinical: 'Large-airway secretions — bronchitis, pneumonia with large-airway involvement, cystic fibrosis, bronchiectasis.' },
{ key: 'pleuralrub', src: '/audio/respiratory/pleural-rub.ogg', title: 'Pleural friction rub',
where: 'Focal, often lateral or posterior lower chest',
features: 'Grating, creaky — "leather on leather". Biphasic (heard in inspiration and expiration). Does NOT clear with cough.',
clinical: 'Pleural inflammation — pleuritis, pulmonary embolism, pneumonia with pleural involvement, viral pleurisy.' },
];
// Cardiac sounds library — real recordings from Wikimedia Commons
export const CARDIAC_SOUNDS: SoundEntry[] = [
{ key: 'normal', src: '/audio/cardiac/normal.ogg', title: 'Normal heart sounds (S1, S2)',
where: 'All four classic auscultation points', rate: '~61 bpm reference',
features: '"lub-dub": S1 (closure of mitral + tricuspid) louder at apex; S2 (closure of aortic + pulmonic) louder at base. Physiologic S2 split on inspiration.',
clinical: 'Reference for rhythm, rate, and the normal S1S2 interval. Listen for what\'s changed — not just what\'s added.' },
{ key: 'infant-normal', src: '/audio/cardiac/infant-normal.ogg', title: 'Infant normal heart sounds',
where: 'Infant chest — rate will be higher than adult', rate: 'Pediatric reference (120160 bpm range)',
features: 'Same S1S2 pattern, faster rate. Short diastole makes murmurs easier to miss — careful auscultation needed.',
clinical: 'Reference for neonatal/infant rhythm. Any murmur in the first 72 h should prompt pre/postductal sat screening.' },
{ key: 'vsd', src: '/audio/cardiac/vsd.wav', title: 'Ventricular septal defect (VSD)',
where: 'Lower left sternal border (4th ICS)',
features: 'Harsh, blowing, holosystolic (pansystolic) murmur — plateau shape through all of systole. Often accompanied by a thrill if large.',
clinical: 'Most common congenital heart defect. Small VSD: loud murmur, usually asymptomatic, may close spontaneously. Large VSD: softer murmur (less pressure gradient) but signs of heart failure, pulmonary hypertension.' },
{ key: 'mvp', src: '/audio/cardiac/mitral-prolapse.wav', title: 'Mitral valve prolapse (MVP) — click + late systolic murmur',
where: 'Apex (5th ICS, mid-clavicular line)',
features: 'Mid-systolic click followed by a late-systolic crescendo murmur. Timing of click changes with maneuvers: earlier with standing or Valsalva, later with squatting.',
clinical: 'Often benign, especially in thin young women. Features suggesting need for echo: thickened/redundant leaflets, associated MR, symptoms (palpitations, chest pain), arrhythmias.' },
{ key: 'stills', src: '/audio/cardiac/stills-murmur.ogg', title: 'Still\'s murmur (innocent)',
where: 'LLSB, radiating to apex', rate: 'Classic age 37 y (this recording is a toddler)',
features: 'Low-frequency vibratory / musical systolic, grade 23/6, mid-systolic, "twanging-string" quality.',
clinical: 'The most common innocent murmur of childhood. Louder supine, softer or disappears on standing or Valsalva. Normal S2. No radiation to neck or back. No workup needed when classic.' },
{ key: 'functional', src: '/audio/cardiac/functional-murmur.wav', title: 'Functional (innocent) murmur — adult female',
where: 'Left sternal border, soft systolic',
features: 'Soft systolic murmur in a structurally normal heart — often from increased cardiac output, thin chest wall, anemia, hyperthyroidism, or pregnancy.',
clinical: 'Benign if it meets the 7 S criteria. Investigate if loud (≥3/6), holosystolic, diastolic, radiating, or with thrill / symptoms.' },
];