New top-level tab (positioned after Catch-Up Schedule) combining two
functions:
1. Study reference — for each (age group, system) shows OSCE-style
components with technique, expected normal finding, and abnormal-
feature watch-list.
2. Documentation generator — physician marks each component
Normal / Abnormal (with free-text detail) / Skip; AI produces a
two-section report (Technique + Findings), narrative or structured
list format.
Scope v1: MSK + Neuro × 6 age groups (newborn, infant, toddler,
preschool, school-age, adolescent). More systems can be added to the
embedded PE_DATA in peGuide.js without route changes.
Files:
- src/routes/peGuide.js — POST /api/generate-pe-narrative (mirrors
milestone-narrative pattern: AppRole-level
injection guard, clinical audit category,
PHI redaction upstream already in place)
- src/utils/prompts.js — peGuideNarrative + peGuideList prompts,
structured two-section output
- public/components/pe-guide.html — demographics bar + sub-pills + cards
- public/js/peGuide.js — embedded PE_DATA (all clinical content),
render + state + AI call
- public/index.html — tab button, section, script include
- server.js — mount route at /api
No schema change. No PHI stored — findings live in memory only, exported
via existing copy/read-aloud/Nextcloud actions.
511 lines
37 KiB
JavaScript
511 lines
37 KiB
JavaScript
// ============================================================
|
||
// PHYSICAL EXAM GUIDE — OSCE reference + narrative generator
|
||
// ============================================================
|
||
// Data is embedded here rather than fetched: the knowledge is static and
|
||
// small, and it needs to be available offline-in-browser without an API
|
||
// round-trip. Scope for v1: MSK + Neuro × 6 age groups. Expand later.
|
||
//
|
||
// Each exam component has:
|
||
// name — what to document
|
||
// technique — how to perform (OSCE-style)
|
||
// normal — expected finding text (used verbatim when user picks Normal)
|
||
// abnormalHints — red flags / things to document if abnormal (reference only)
|
||
// ============================================================
|
||
|
||
(function () {
|
||
|
||
// ────────────────────────────────────────────────────────────
|
||
// DATA
|
||
// ────────────────────────────────────────────────────────────
|
||
var PE_DATA = {
|
||
newborn: {
|
||
label: 'Newborn (0–28 days)',
|
||
msk: {
|
||
overview: 'Focus: birth injuries, congenital anomalies, and DDH screening (universal Barlow/Ortolani through 6 months). Examine warm, quiet, undressed infant.',
|
||
components: [
|
||
{ name: 'Resting posture', technique: 'Observe supine. Note symmetry of limb position.',
|
||
normal: 'Symmetric flexion at hips, knees, elbows; hands loosely fisted',
|
||
abnormalHints: ['Frog-leg (hypotonia)', 'Asymmetric arm posture (brachial plexus, clavicle fx)', 'Opisthotonic posturing (CNS)'] },
|
||
{ name: 'Clavicles', technique: 'Palpate each clavicle from sternoclavicular joint to acromion.',
|
||
normal: 'Smooth, continuous bilaterally; no crepitus or step-off',
|
||
abnormalHints: ['Crepitus or step-off (fracture — LGA / shoulder dystocia)', 'Pain with passive arm abduction'] },
|
||
{ name: 'Hips (Barlow/Ortolani)', technique: 'Flex hips 90°, grasp thighs with thumbs medial. Barlow: adduct + downward pressure. Ortolani: abduct + lift.',
|
||
normal: 'Hips stable; no clunk on Barlow or Ortolani; symmetric abduction ≥75°',
|
||
abnormalHints: ['Palpable clunk (positive Ortolani = dislocatable or dislocated)', 'Asymmetric thigh/gluteal folds', 'Limited abduction', 'Leg-length discrepancy (Galeazzi)'] },
|
||
{ name: 'Spine / back', technique: 'Prone. Palpate midline from C-spine to coccyx. Inspect sacral area.',
|
||
normal: 'Straight midline; no cutaneous markers; skin intact; no sacral mass',
|
||
abnormalHints: ['Sacral dimple >5mm or >2.5cm from anus (imaging)', 'Midline hair tuft, hemangioma, lipoma (occult dysraphism)', 'Palpable defect'] },
|
||
{ name: 'Upper extremities', technique: 'Observe spontaneous movement, count digits, inspect palmar creases.',
|
||
normal: 'Symmetric antigravity movement; 5 digits each hand; normal palmar creases',
|
||
abnormalHints: ['Erb/Klumpke palsy (asymmetric movement)', 'Polydactyly, syndactyly', 'Single transverse palmar crease (trisomy 21 — soft sign)'] },
|
||
{ name: 'Lower extremities / feet', technique: 'Range hips/knees/ankles. Stroke lateral border of foot. Attempt passive correction.',
|
||
normal: 'Full range; foot correctable to neutral with ease; no hindfoot deformity',
|
||
abnormalHints: ['Rigid clubfoot (talipes equinovarus — non-correctable)', 'Metatarsus adductus (usually flexible)', 'Rocker-bottom foot (trisomy 18)'] }
|
||
]
|
||
},
|
||
neuro: {
|
||
overview: 'Focus: primitive reflexes (present and symmetric), tone (passive and active), and absence of focal deficit. A full newborn neuro exam takes 3–5 min on a quiet, satiated baby.',
|
||
components: [
|
||
{ name: 'Level of alertness', technique: 'Observe spontaneous alertness and response to voice, touch, light.',
|
||
normal: 'Alert periods with spontaneous eye opening; responds to voice and light; consolable',
|
||
abnormalHints: ['Persistent lethargy / hard to arouse', 'Jitteriness', 'Irritability not consoled by feeding'] },
|
||
{ name: 'Cranial nerves', technique: 'Pupils to light; symmetric facial movement at rest and with crying; intact suck; visual blink to light.',
|
||
normal: 'Pupils equal and reactive; face symmetric; strong coordinated suck; blinks to bright light',
|
||
abnormalHints: ['Asymmetric facial movement (CN VII injury)', 'Poor or uncoordinated suck', 'Fixed pupil', 'Absent blink'] },
|
||
{ name: 'Tone (passive)', technique: 'Pull-to-sit (head lag ≤ minimal at term), ventral suspension (head lifts to horizontal), arm recoil, popliteal angle.',
|
||
normal: 'Age-appropriate flexor tone; brief head lag on pull-to-sit; head level with spine on ventral suspension',
|
||
abnormalHints: ['Significant head lag (hypotonia)', 'Slip-through on vertical suspension', 'Hypertonia / scissoring'] },
|
||
{ name: 'Spontaneous movement', technique: 'Observe limb movement for symmetry, range, antigravity effort.',
|
||
normal: 'Symmetric, antigravity movements of all four limbs',
|
||
abnormalHints: ['Focal paucity of movement (stroke, brachial plexus)', 'Jittery movements that stop on passive flexion', 'Clonic or tonic seizure activity'] },
|
||
{ name: 'Moro reflex', technique: 'Support head, allow ~30° head drop or clap.',
|
||
normal: 'Symmetric arm extension followed by flexion and often a cry',
|
||
abnormalHints: ['Absent (severe CNS depression)', 'Asymmetric (brachial plexus injury, clavicle fx, hemiparesis)'] },
|
||
{ name: 'Rooting / sucking', technique: 'Stroke cheek / touch lips. Insert gloved finger.',
|
||
normal: 'Turns toward stimulus and opens mouth; strong rhythmic suck',
|
||
abnormalHints: ['Absent rooting or suck (CNS depression, prematurity)', 'Uncoordinated suck-swallow'] },
|
||
{ name: 'Palmar / plantar grasp', technique: 'Apply pressure to palm; then to ball of foot.',
|
||
normal: 'Strong, symmetric finger flexion; toe curl to plantar pressure',
|
||
abnormalHints: ['Absent or asymmetric palmar grasp', 'Absent plantar grasp'] },
|
||
{ name: 'Babinski', technique: 'Stroke lateral sole from heel to toes.',
|
||
normal: 'Up-going great toe with fanning of other toes (expected in neonates)',
|
||
abnormalHints: ['Asymmetric response is always abnormal'] }
|
||
]
|
||
}
|
||
},
|
||
|
||
infant: {
|
||
label: 'Infant (1–12 months)',
|
||
msk: {
|
||
overview: 'Focus: continued DDH screen through 6 months, gross-motor progression, and symmetry of tone and movement.',
|
||
components: [
|
||
{ name: 'Hips (Barlow/Ortolani through 6mo; abduction thereafter)', technique: 'Barlow/Ortolani in first 6 months. After 6 months use limited abduction (<70°), Galeazzi sign, asymmetric skin folds.',
|
||
normal: 'Stable hips with symmetric abduction ≥70°; no Galeazzi; symmetric skin folds',
|
||
abnormalHints: ['Positive Ortolani / Barlow (before 3mo)', 'Limited abduction after 3mo', 'Asymmetric thigh folds', 'Galeazzi (shortened femur on affected side)'] },
|
||
{ name: 'Gross-motor observation', technique: 'Observe head control in prone and pull-to-sit; rolling; sitting; standing — appropriate to age.',
|
||
normal: 'Age-appropriate motor milestones (e.g., sits without support by 6mo, pulls to stand by 9mo)',
|
||
abnormalHints: ['Asymmetric movement', 'Persistent fisting past 3mo', 'Scissoring', 'Delayed or regressed milestones'] },
|
||
{ name: 'Spine', technique: 'Palpate midline when sitting. Observe kyphosis/lordosis as development progresses.',
|
||
normal: 'Midline spine; physiologic gentle kyphosis in young infant; no masses',
|
||
abnormalHints: ['Fixed kyphoscoliosis', 'Sacral dimple with cutaneous marker', 'Palpable step-off'] },
|
||
{ name: 'Extremities', technique: 'Passive range of all major joints. Inspect feet.',
|
||
normal: 'Full range of all joints; feet correctable and aligning; no rigid deformity',
|
||
abnormalHints: ['Rigid clubfoot', 'Persistent asymmetric tone', 'Joint swelling / warmth (septic arthritis)'] },
|
||
{ name: 'Clavicles', technique: 'Palpate bilaterally (if not done at newborn).',
|
||
normal: 'Intact, smooth, symmetric',
|
||
abnormalHints: ['Healed fracture callus palpable (usually benign finding)'] }
|
||
]
|
||
},
|
||
neuro: {
|
||
overview: 'Focus: primitive reflexes should be fading on a predictable schedule; protective reflexes should be emerging; milestones and tone tracked together.',
|
||
components: [
|
||
{ name: 'Level of alertness', technique: 'Engage with face, voice, and age-appropriate object (rattle, toy).',
|
||
normal: 'Alert, socially engaged, tracks and reaches appropriately for age',
|
||
abnormalHints: ['Poor social engagement', 'Absent visual tracking past 3mo'] },
|
||
{ name: 'Cranial nerves', technique: 'Visual tracking (180° by 3mo), pupil response, facial symmetry with smile/cry, intact suck, symmetric tongue.',
|
||
normal: 'Tracks past midline; pupils equal reactive; symmetric facial movement',
|
||
abnormalHints: ['Persistent nystagmus', 'Strabismus past 4mo', 'Asymmetric facial movement'] },
|
||
{ name: 'Tone', technique: 'Pull-to-sit (no head lag by 4mo), ventral suspension, vertical suspension.',
|
||
normal: 'Age-appropriate tone; no head lag by 4mo; no slip-through on vertical suspension',
|
||
abnormalHints: ['Persistent head lag past 4mo (hypotonia)', 'Hypertonia / scissoring (UMN / CP)', 'Mixed tone (dyskinetic)'] },
|
||
{ name: 'Primitive reflex status', technique: 'Assess Moro, grasp, tonic neck, Galant — all should be integrated (gone) by 6 months.',
|
||
normal: 'Primitive reflexes fading on schedule (Moro absent by 6mo, palmar grasp by 5–6mo, tonic neck by 4–6mo)',
|
||
abnormalHints: ['Persistence of any primitive reflex past 6mo warrants evaluation (CP, CNS injury)'] },
|
||
{ name: 'Protective / parachute reflex', technique: '6–9mo and beyond: hold prone, tilt head-down suddenly. Observe protective arm extension.',
|
||
normal: 'Symmetric arm extension protectively by 9mo (emerges 6–9mo)',
|
||
abnormalHints: ['Absent parachute after 12mo (significant concern)', 'Asymmetric response'] },
|
||
{ name: 'Gait (if walking)', technique: 'Observe barefoot walking on flat surface if baby is ambulating.',
|
||
normal: 'Wide-based, arms high guard; improving symmetry; appropriate for age',
|
||
abnormalHints: ['Persistent toe-walking', 'Asymmetric stride / limp', 'Ataxic or scissoring gait'] }
|
||
]
|
||
}
|
||
},
|
||
|
||
toddler: {
|
||
label: 'Toddler (1–3 years)',
|
||
msk: {
|
||
overview: 'Focus: gait, physiologic alignment changes (bow-legs → knock-knees around age 2–3), in-toeing, and joint range.',
|
||
components: [
|
||
{ name: 'Gait', technique: 'Observe barefoot walking, running if age-appropriate. Note base, symmetry, heel-strike.',
|
||
normal: 'Symmetric stride; base narrowing; heel-strike present; arms swinging reciprocally',
|
||
abnormalHints: ['Persistent toe-walking past 2y (idiopathic vs CP)', 'Limp (Legg-Calvé-Perthes, transient synovitis, SCFE)', 'Wide-based ataxic gait'] },
|
||
{ name: 'Alignment — knees', technique: 'Stand with feet together. Measure intercondylar (varum) or intermalleolar (valgum) distance if abnormal.',
|
||
normal: 'Physiologic genu varum resolves by 18–24mo; genu valgum common by 2–4y and peaks at 3–4y',
|
||
abnormalHints: ['Persistent varum past 2y', 'Severe valgum (>8cm intermalleolar)', 'Unilateral alignment abnormality (Blount disease, rickets)'] },
|
||
{ name: 'Alignment — feet', technique: 'Observe foot position on gait and stance. Check forefoot adduction, heel alignment.',
|
||
normal: 'Mild in-toeing and flexible flat feet are physiologic',
|
||
abnormalHints: ['Rigid flat foot (tarsal coalition)', 'Fixed metatarsus adductus', 'Persistent severe in-toeing (femoral anteversion, tibial torsion)'] },
|
||
{ name: 'Joint range of motion', technique: 'Passive range of hips, knees, ankles, shoulders, elbows, wrists.',
|
||
normal: 'Full symmetric range at all joints; no pain, crepitus, or swelling',
|
||
abnormalHints: ['Joint effusion (septic vs reactive)', 'Guarded motion', 'Asymmetric limitation'] },
|
||
{ name: 'Spine', technique: 'Inspect standing. Palpate midline. Observe symmetry of shoulders and hips.',
|
||
normal: 'Midline spine, symmetric shoulders and hips; mild lumbar lordosis is normal',
|
||
abnormalHints: ['Fixed scoliosis', 'Abnormal kyphosis', 'Palpable step-off', 'Pain with palpation'] }
|
||
]
|
||
},
|
||
neuro: {
|
||
overview: 'Focus: transition to adult-pattern exam. Primitive reflexes should be absent. Cooperation varies — use play-based techniques.',
|
||
components: [
|
||
{ name: 'Mental status', technique: 'Observe engagement, language use, ability to follow simple commands.',
|
||
normal: 'Alert, interactive, age-appropriate language and commands',
|
||
abnormalHints: ['Language regression', 'Poor engagement', 'Repetitive behaviors (developmental concern)'] },
|
||
{ name: 'Cranial nerves', technique: 'Follow finger or toy for EOMs; smile/grimace for facial symmetry; open mouth and tongue protrusion.',
|
||
normal: 'EOMs intact; symmetric face; tongue midline',
|
||
abnormalHints: ['Strabismus', 'Facial asymmetry', 'Tongue deviation'] },
|
||
{ name: 'Motor — tone and bulk', technique: 'Passive range all four extremities; observe bulk.',
|
||
normal: 'Normal tone and bulk throughout; no contractures',
|
||
abnormalHints: ['Hypotonia', 'Spasticity (especially catch in lower extremities)', 'Muscle wasting'] },
|
||
{ name: 'Motor — strength (functional)', technique: 'Observe rising from floor (Gowers maneuver), climbing, squatting.',
|
||
normal: 'Rises from floor without Gowers; climbs onto furniture without difficulty',
|
||
abnormalHints: ['Gowers sign (proximal weakness — Duchenne muscular dystrophy)', 'Inability to climb at expected age'] },
|
||
{ name: 'Deep tendon reflexes', technique: 'Patellar, biceps with distraction (toy). Achilles if tolerated.',
|
||
normal: '2+ symmetric DTRs throughout',
|
||
abnormalHints: ['Hyperreflexia / clonus (UMN)', 'Absent reflexes (LMN, neuropathy)'] },
|
||
{ name: 'Coordination / gait', technique: 'Observe walking, running, climbing stairs, kicking ball if age-appropriate.',
|
||
normal: 'Age-appropriate coordination; able to run, climb stairs with rail',
|
||
abnormalHints: ['Ataxic gait', 'Tremor with action', 'Clumsiness beyond age expectation'] },
|
||
{ name: 'Plantar response', technique: 'Stroke lateral sole from heel toward toes.',
|
||
normal: 'Down-going (plantar flexion) by age 2',
|
||
abnormalHints: ['Up-going after age 2 is an upper-motor-neuron sign (Babinski positive)'] }
|
||
]
|
||
}
|
||
},
|
||
|
||
preschool: {
|
||
label: 'Preschool (3–5 years)',
|
||
msk: {
|
||
overview: 'Focus: functional gait maneuvers, scoliosis screen, and resolving physiologic alignment.',
|
||
components: [
|
||
{ name: 'Gait (normal, heel, toe, tandem)', technique: 'Have child walk normally, on heels, on toes, heel-to-toe. Hop on one foot (4y+).',
|
||
normal: 'Symmetric smooth gait; able to heel-walk, toe-walk, and tandem; hops on one foot by 4–5y',
|
||
abnormalHints: ['Persistent toe-walking', 'Asymmetric stance', 'Difficulty with tandem'] },
|
||
{ name: 'Alignment', technique: 'Stand barefoot. Check knees, feet alignment.',
|
||
normal: 'Mild residual genu valgum resolving; neutral by 6–7y; medial arch developing',
|
||
abnormalHints: ['Persistent unilateral varum', 'Rigid flat foot'] },
|
||
{ name: 'Scoliosis screen (Adam forward-bend)', technique: 'Child bends forward at waist, arms hanging. Observe for rib or lumbar hump.',
|
||
normal: 'Symmetric paraspinal contour; no rib hump or lumbar prominence',
|
||
abnormalHints: ['Rib hump (thoracic scoliosis)', 'Asymmetric shoulder / scapular height', 'Leg-length discrepancy causing apparent curve'] },
|
||
{ name: 'Joint range and stability', technique: 'Active and passive range all joints. Check knee stability, elbow carrying angle.',
|
||
normal: 'Full symmetric range; stable joints; normal carrying angle',
|
||
abnormalHints: ['Hypermobility (Beighton score)', 'Joint effusion', 'Pain with motion'] },
|
||
{ name: 'Feet', technique: 'Stand and on tiptoes. Observe arch formation.',
|
||
normal: 'Flexible flat feet common; arch forms on tiptoe (flexible); symmetric',
|
||
abnormalHints: ['Rigid flat foot (does not form arch on tiptoe)', 'Pain'] }
|
||
]
|
||
},
|
||
neuro: {
|
||
overview: 'Focus: more formal exam now possible — most 4- and 5-year-olds cooperate with cranial nerves, strength, coordination, and sensory testing.',
|
||
components: [
|
||
{ name: 'Mental status / language', technique: 'Observe speech intelligibility, complexity, and ability to follow 2- and 3-step commands.',
|
||
normal: 'Speech clear to strangers by 4y; complex sentences; follows multi-step commands',
|
||
abnormalHints: ['Dysarthria', 'Receptive or expressive language delay', 'Poor engagement'] },
|
||
{ name: 'Cranial nerves (II–XII)', technique: 'Vision by charts if age-appropriate; EOMs with toy; facial sensation with light touch; symmetric smile; tongue protrusion; shoulder shrug; uvula elevation.',
|
||
normal: 'Intact throughout',
|
||
abnormalHints: ['Strabismus', 'Facial asymmetry', 'Uvula deviation', 'Tongue fasciculations'] },
|
||
{ name: 'Motor — tone, bulk, strength', technique: 'Passive range. Test 5/5 strength in major groups (stand from squat, grip squeeze, arm lift against resistance).',
|
||
normal: 'Normal tone and bulk; 5/5 strength symmetrically',
|
||
abnormalHints: ['Spasticity', 'Weakness (Gowers if proximal)', 'Atrophy'] },
|
||
{ name: 'Deep tendon reflexes', technique: 'Biceps, patellar, Achilles. Compare side to side.',
|
||
normal: '2+ symmetric DTRs throughout; down-going plantar response bilaterally',
|
||
abnormalHints: ['Hyperreflexia with clonus', 'Asymmetry', 'Up-going plantar (Babinski)'] },
|
||
{ name: 'Coordination', technique: 'Finger-to-nose, heel-to-shin, rapid alternating hand movements, tandem walk.',
|
||
normal: 'Smooth finger-to-nose, intact tandem; rapid alternating movements coordinated',
|
||
abnormalHints: ['Dysmetria', 'Dysdiadochokinesia', 'Ataxic tandem'] },
|
||
{ name: 'Sensory (light touch)', technique: 'Light touch distally on all four extremities with eyes closed (if cooperative).',
|
||
normal: 'Symmetric light touch throughout',
|
||
abnormalHints: ['Focal sensory loss', 'Stocking-glove pattern'] },
|
||
{ name: 'Gait (multiple patterns)', technique: 'Normal, heel, toe, tandem, hop on one foot, Romberg (5y+).',
|
||
normal: 'All maneuvers performed without difficulty',
|
||
abnormalHints: ['Romberg positive (dorsal column)', 'Ataxic tandem (cerebellar)'] }
|
||
]
|
||
}
|
||
},
|
||
|
||
school: {
|
||
label: 'School-age (6–11 years)',
|
||
msk: {
|
||
overview: 'Focus: scoliosis screening (especially peri-puberty), sports-related overuse, and resolution of alignment.',
|
||
components: [
|
||
{ name: 'Scoliosis screen (forward-bend)', technique: 'Adam forward-bend test. Consider scoliometer if available (angle of trunk rotation).',
|
||
normal: 'No rib or lumbar prominence; ATR < 5°; symmetric shoulders and hips',
|
||
abnormalHints: ['Rib hump', 'ATR ≥ 7° warrants referral', 'Shoulder or scapular asymmetry'] },
|
||
{ name: 'Alignment', technique: 'Standing exam of knees and feet.',
|
||
normal: 'Neutral knee alignment; medial arch present; symmetric',
|
||
abnormalHints: ['Residual valgum', 'Pes cavus (possible CMT)', 'Asymmetric alignment'] },
|
||
{ name: 'Joint stability and range', technique: 'Active range all joints. Knee Lachman/anterior drawer if sports-active. Shoulder impingement tests.',
|
||
normal: 'Full stable range at all joints',
|
||
abnormalHints: ['Joint laxity or hypermobility', 'Sports-related instability', 'Pain with specific maneuvers'] },
|
||
{ name: 'Gait and functional movement', technique: 'Observe gait, running, squatting, single-leg stance.',
|
||
normal: 'Symmetric efficient gait; able to squat and single-leg stand',
|
||
abnormalHints: ['Trendelenburg sign (hip abductor weakness)', 'Antalgic gait', 'Asymmetric squat depth'] },
|
||
{ name: 'Sports-specific exam (if relevant)', technique: 'Targeted to activity — shoulder, knee, ankle per chief complaint.',
|
||
normal: 'No pain, instability, or impingement in tested maneuvers',
|
||
abnormalHints: ['Overuse injury signs (Osgood-Schlatter, Sever, osteochondritis dissecans)', 'Apophysitis'] }
|
||
]
|
||
},
|
||
neuro: {
|
||
overview: 'Focus: adult-pattern exam across all six components (mental status, cranial nerves, motor, reflexes, sensory, coordination/gait).',
|
||
components: [
|
||
{ name: 'Mental status', technique: 'Orientation, attention (count backward), memory (3-item recall), appropriate language.',
|
||
normal: 'Alert, oriented, attention intact, age-appropriate language',
|
||
abnormalHints: ['Inattention / ADHD signs', 'Language concerns', 'Memory deficit'] },
|
||
{ name: 'Cranial nerves (II–XII)', technique: 'Full formal exam including visual fields, EOMs, facial sensation, hearing, palate elevation, tongue.',
|
||
normal: 'All cranial nerves intact',
|
||
abnormalHints: ['Any focal cranial nerve deficit warrants workup'] },
|
||
{ name: 'Motor', technique: 'Inspect bulk; test tone; strength 5/5 in major groups (deltoids, biceps, triceps, grip, hip flexors, knee extensors, dorsiflexion).',
|
||
normal: 'Normal bulk and tone; 5/5 strength symmetric',
|
||
abnormalHints: ['Focal weakness', 'Spasticity', 'Atrophy'] },
|
||
{ name: 'Deep tendon reflexes', technique: 'Biceps, triceps, brachioradialis, patellar, Achilles. Plantar response.',
|
||
normal: '2+ symmetric DTRs; down-going plantar',
|
||
abnormalHints: ['Hyperreflexia with clonus (UMN)', 'Hyporeflexia (LMN, neuropathy)', 'Asymmetry', 'Up-going Babinski'] },
|
||
{ name: 'Sensory', technique: 'Light touch and pain distally; vibration at distal IP joints; proprioception at great toe.',
|
||
normal: 'Intact light touch, pain, vibration, proprioception throughout',
|
||
abnormalHints: ['Stocking-glove loss', 'Dermatomal pattern', 'Loss of vibration or proprioception (dorsal column)'] },
|
||
{ name: 'Coordination', technique: 'Finger-nose-finger, heel-shin, rapid alternating movements.',
|
||
normal: 'Smooth coordinated movements bilaterally',
|
||
abnormalHints: ['Dysmetria', 'Intention tremor', 'Dysdiadochokinesia'] },
|
||
{ name: 'Gait and Romberg', technique: 'Normal, heel, toe, tandem. Romberg with eyes closed.',
|
||
normal: 'All gait patterns smooth and symmetric; Romberg negative',
|
||
abnormalHints: ['Wide-based gait (cerebellar)', 'Steppage (peripheral neuropathy)', 'Romberg positive (dorsal column)'] }
|
||
]
|
||
}
|
||
},
|
||
|
||
adolescent: {
|
||
label: 'Adolescent (12–21 years)',
|
||
msk: {
|
||
overview: 'Focus: sports-related injuries, late-onset scoliosis, apophyseal overuse injuries, and screening for hypermobility spectrum.',
|
||
components: [
|
||
{ name: 'Scoliosis screen', technique: 'Adam forward-bend with scoliometer if available. Consider recurrent screening through adolescent growth spurt.',
|
||
normal: 'No rib or lumbar prominence; ATR < 5°',
|
||
abnormalHints: ['Progressive scoliosis (especially girls 10–14)', 'ATR ≥ 7°', 'Shoulder asymmetry'] },
|
||
{ name: 'Back / spine', technique: 'Palpate midline and paraspinal muscles. Single-leg hyperextension test if back pain present (spondylolysis).',
|
||
normal: 'Non-tender spine and paraspinals; no pain with hyperextension',
|
||
abnormalHints: ['Midline tenderness (spondylolysis/spondylolisthesis)', 'Positive stork test', 'Radicular symptoms'] },
|
||
{ name: 'Joint stability and sports-specific exam', technique: 'Shoulder, knee, ankle exams per activity and complaint. Beighton score if hypermobile presentation.',
|
||
normal: 'Stable joints; no effusion; Beighton < 4',
|
||
abnormalHints: ['ACL/MCL laxity', 'Shoulder apprehension', 'Ankle instability', 'Beighton ≥ 5 (hypermobility spectrum)'] },
|
||
{ name: 'Alignment and gait', technique: 'Observe standing posture and walking.',
|
||
normal: 'Neutral alignment; symmetric efficient gait',
|
||
abnormalHints: ['Antalgic gait', 'Leg-length discrepancy', 'Trendelenburg'] },
|
||
{ name: 'Overuse / apophysitis screen', technique: 'Palpate tibial tubercle, calcaneal apophysis, iliac apophyses if sports-active.',
|
||
normal: 'Non-tender apophyses',
|
||
abnormalHints: ['Osgood-Schlatter (tibial tubercle)', 'Sever (calcaneal)', 'Iliac apophysitis in sprinters'] }
|
||
]
|
||
},
|
||
neuro: {
|
||
overview: 'Focus: adult-pattern full exam. Screen specifically for concussion sequelae if sports-active; frontal release signs should be absent.',
|
||
components: [
|
||
{ name: 'Mental status', technique: 'Orientation, attention, memory, language, executive function (MMSE-style questions tailored to age).',
|
||
normal: 'Alert, oriented, attention/memory intact, age-appropriate executive function',
|
||
abnormalHints: ['Post-concussion cognitive symptoms', 'Mood changes', 'Subtle executive dysfunction'] },
|
||
{ name: 'Cranial nerves (II–XII)', technique: 'Full formal adult-pattern exam.',
|
||
normal: 'All cranial nerves intact',
|
||
abnormalHints: ['Any focal deficit'] },
|
||
{ name: 'Motor', technique: 'Bulk, tone, 5/5 strength in all major groups. Fine motor (finger tapping).',
|
||
normal: 'Normal bulk, tone, strength throughout',
|
||
abnormalHints: ['Focal weakness', 'Spasticity', 'Subtle tremor'] },
|
||
{ name: 'Deep tendon reflexes', technique: 'Biceps, triceps, brachioradialis, patellar, Achilles. Plantar response.',
|
||
normal: '2+ symmetric; down-going plantar',
|
||
abnormalHints: ['Hyperreflexia, clonus, or up-going Babinski (UMN)', 'Hyporeflexia (peripheral nerve, myopathy, hypothyroid)'] },
|
||
{ name: 'Sensory', technique: 'Light touch, pain, vibration, proprioception, two-point discrimination.',
|
||
normal: 'Intact modalities throughout',
|
||
abnormalHints: ['Dermatomal loss', 'Length-dependent sensory loss', 'Dorsal column signs'] },
|
||
{ name: 'Coordination', technique: 'Finger-nose-finger, heel-shin, rapid alternating, tandem gait.',
|
||
normal: 'Smooth coordinated movements; tandem intact',
|
||
abnormalHints: ['Cerebellar signs', 'Intention tremor'] },
|
||
{ name: 'Gait and Romberg', technique: 'Normal, heel, toe, tandem, Romberg.',
|
||
normal: 'All smooth; Romberg negative',
|
||
abnormalHints: ['Ataxic gait', 'Steppage', 'Romberg positive'] },
|
||
{ name: 'Frontal release / primitive reflexes', technique: 'Test grasp, snout, glabellar, palmomental. Should be absent.',
|
||
normal: 'Absent — no frontal release signs',
|
||
abnormalHints: ['Presence suggests frontal lobe pathology, neurodegenerative disease, or severe TBI — consider in post-concussion setting'] }
|
||
]
|
||
}
|
||
}
|
||
};
|
||
|
||
// ────────────────────────────────────────────────────────────
|
||
// RENDER + STATE
|
||
// ────────────────────────────────────────────────────────────
|
||
var _inited = false;
|
||
var state = {}; // key = 'msk-0' etc → { name, status, note, normal_finding }
|
||
var currentSystem = 'msk';
|
||
var currentAgeGroup = '';
|
||
|
||
document.addEventListener('tabChanged', function (e) {
|
||
if (e.detail.tab !== 'peguide' || _inited) return;
|
||
_inited = true;
|
||
init();
|
||
});
|
||
|
||
function init() {
|
||
var ageSelect = document.getElementById('pe-age-group');
|
||
var content = document.getElementById('pe-content');
|
||
var actions = document.getElementById('pe-actions');
|
||
|
||
ageSelect.addEventListener('change', function () {
|
||
currentAgeGroup = ageSelect.value;
|
||
state = {};
|
||
document.getElementById('pe-output').classList.add('hidden');
|
||
if (!currentAgeGroup || !PE_DATA[currentAgeGroup]) {
|
||
content.innerHTML = '<p style="text-align:center;color:#9ca3af;padding:40px;">Select an age group above.</p>';
|
||
actions.style.display = 'none';
|
||
return;
|
||
}
|
||
renderSystem();
|
||
actions.style.display = 'flex';
|
||
});
|
||
|
||
document.querySelectorAll('[data-pesystem]').forEach(function (btn) {
|
||
btn.addEventListener('click', function () {
|
||
document.querySelectorAll('[data-pesystem]').forEach(function (b) { b.classList.remove('active'); });
|
||
btn.classList.add('active');
|
||
currentSystem = btn.dataset.pesystem;
|
||
state = {};
|
||
document.getElementById('pe-output').classList.add('hidden');
|
||
if (currentAgeGroup) renderSystem();
|
||
});
|
||
});
|
||
|
||
document.getElementById('pe-all-normal').addEventListener('click', setAllNormal);
|
||
document.getElementById('pe-all-clear').addEventListener('click', clearAll);
|
||
document.getElementById('pe-generate-btn').addEventListener('click', generate);
|
||
}
|
||
|
||
function renderSystem() {
|
||
var content = document.getElementById('pe-content');
|
||
var group = PE_DATA[currentAgeGroup];
|
||
if (!group || !group[currentSystem]) {
|
||
content.innerHTML = '<p style="text-align:center;color:#9ca3af;padding:40px;">No data for this combination.</p>';
|
||
return;
|
||
}
|
||
var section = group[currentSystem];
|
||
var html = '';
|
||
html += '<div class="card" style="margin-bottom:12px;">';
|
||
html += ' <div class="card-header"><h3><i class="fas fa-circle-info"></i> OSCE Overview — ' + esc(group.label) + ' (' + (currentSystem === 'msk' ? 'MSK' : 'Neuro') + ')</h3></div>';
|
||
html += ' <div style="padding:12px 16px;font-size:13px;line-height:1.6;color:var(--g700);">' + esc(section.overview) + '</div>';
|
||
html += '</div>';
|
||
|
||
section.components.forEach(function (c, i) {
|
||
var key = currentSystem + '-' + i;
|
||
if (!state[key]) state[key] = { name: c.name, status: null, note: '', normal_finding: c.normal, technique: c.technique };
|
||
html += '<div class="card" style="margin-bottom:10px;" data-pe-key="' + key + '">';
|
||
html += ' <div class="card-header" style="display:flex;align-items:center;gap:8px;flex-wrap:wrap;">';
|
||
html += ' <h3 style="margin:0;flex:1;min-width:0;">' + esc(c.name) + '</h3>';
|
||
html += ' <div style="display:flex;gap:4px;">';
|
||
html += ' <button class="visit-status-btn ' + (state[key].status === 'normal' ? 'done' : '') + '" data-pe-status="normal" data-pe-key="' + key + '">Normal</button>';
|
||
html += ' <button class="visit-status-btn ' + (state[key].status === 'abnormal' ? 'refused' : '') + '" data-pe-status="abnormal" data-pe-key="' + key + '">Abnormal</button>';
|
||
html += ' <button class="visit-status-btn ' + (state[key].status === null ? 'not-due' : '') + '" data-pe-status="skip" data-pe-key="' + key + '">Skip</button>';
|
||
html += ' </div>';
|
||
html += ' </div>';
|
||
html += ' <div style="padding:10px 16px;font-size:12px;color:var(--g700);line-height:1.6;">';
|
||
html += ' <div style="margin-bottom:6px;"><strong>How to perform:</strong> ' + esc(c.technique) + '</div>';
|
||
html += ' <div style="margin-bottom:6px;"><strong>Expected normal:</strong> ' + esc(c.normal) + '</div>';
|
||
if (c.abnormalHints && c.abnormalHints.length) {
|
||
html += ' <div style="color:var(--g600);"><strong>Watch for:</strong> ' + c.abnormalHints.map(esc).join(' · ') + '</div>';
|
||
}
|
||
html += ' <div class="pe-abnormal-detail" style="margin-top:8px;' + (state[key].status === 'abnormal' ? '' : 'display:none;') + '">';
|
||
html += ' <input type="text" class="pe-note" data-pe-key="' + key + '" placeholder="Describe the abnormal finding (e.g., positive Ortolani left hip)" value="' + esc(state[key].note || '') + '" style="width:100%;padding:6px 8px;font-size:13px;border:1px solid var(--g300);border-radius:6px;">';
|
||
html += ' </div>';
|
||
html += ' </div>';
|
||
html += '</div>';
|
||
});
|
||
|
||
content.innerHTML = html;
|
||
|
||
// Wire events
|
||
content.querySelectorAll('[data-pe-status]').forEach(function (btn) {
|
||
btn.addEventListener('click', function () { handleStatus(btn); });
|
||
});
|
||
content.querySelectorAll('.pe-note').forEach(function (inp) {
|
||
inp.addEventListener('input', function () {
|
||
var key = inp.dataset.peKey;
|
||
if (state[key]) state[key].note = inp.value;
|
||
});
|
||
});
|
||
}
|
||
|
||
function handleStatus(btn) {
|
||
var key = btn.dataset.peKey;
|
||
var status = btn.dataset.peStatus;
|
||
if (!state[key]) return;
|
||
state[key].status = (status === 'skip') ? null : status;
|
||
// Redraw this card's buttons + abnormal input visibility
|
||
var card = btn.closest('[data-pe-key]');
|
||
if (card) {
|
||
card.querySelectorAll('[data-pe-status]').forEach(function (b) {
|
||
b.classList.remove('done', 'refused', 'not-due');
|
||
if (b.dataset.peStatus === 'normal' && state[key].status === 'normal') b.classList.add('done');
|
||
if (b.dataset.peStatus === 'abnormal' && state[key].status === 'abnormal') b.classList.add('refused');
|
||
if (b.dataset.peStatus === 'skip' && state[key].status === null) b.classList.add('not-due');
|
||
});
|
||
var detail = card.querySelector('.pe-abnormal-detail');
|
||
if (detail) detail.style.display = (state[key].status === 'abnormal') ? '' : 'none';
|
||
}
|
||
}
|
||
|
||
function setAllNormal() {
|
||
Object.keys(state).forEach(function (k) { if (k.indexOf(currentSystem + '-') === 0) state[k].status = 'normal'; });
|
||
renderSystem();
|
||
}
|
||
function clearAll() {
|
||
Object.keys(state).forEach(function (k) { if (k.indexOf(currentSystem + '-') === 0) { state[k].status = null; state[k].note = ''; } });
|
||
renderSystem();
|
||
}
|
||
|
||
function generate() {
|
||
if (!currentAgeGroup || !PE_DATA[currentAgeGroup]) return;
|
||
var components = Object.keys(state)
|
||
.filter(function (k) { return k.indexOf(currentSystem + '-') === 0; })
|
||
.map(function (k) { return state[k]; });
|
||
|
||
var assessed = components.filter(function (c) { return c.status !== null; });
|
||
if (assessed.length === 0) { showToast('Mark at least one component Normal or Abnormal', 'error'); return; }
|
||
|
||
var btn = document.getElementById('pe-generate-btn');
|
||
btn.disabled = true; btn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Generating...';
|
||
|
||
var modelSel = document.querySelector('#peguide-tab .tab-model-select');
|
||
var model = modelSel ? modelSel.value : '';
|
||
var format = document.getElementById('pe-format').value;
|
||
|
||
fetch('/api/generate-pe-narrative', {
|
||
method: 'POST',
|
||
headers: getAuthHeaders(),
|
||
body: JSON.stringify({
|
||
components: components,
|
||
ageGroup: PE_DATA[currentAgeGroup].label,
|
||
system: currentSystem,
|
||
patientAge: document.getElementById('pe-age').value,
|
||
patientGender: document.getElementById('pe-gender').value,
|
||
model: model,
|
||
format: format
|
||
})
|
||
})
|
||
.then(function (r) { return r.json(); })
|
||
.then(function (data) {
|
||
if (!data.success) { showToast(data.error || 'Generation failed', 'error'); return; }
|
||
var out = document.getElementById('pe-output');
|
||
out.classList.remove('hidden');
|
||
document.getElementById('pe-narrative-text').textContent = data.narrative;
|
||
var tag = document.getElementById('pe-model-tag');
|
||
if (tag) tag.textContent = data.model || '';
|
||
var bar = document.getElementById('pe-summary-bar');
|
||
if (bar && data.summary) {
|
||
bar.classList.remove('hidden');
|
||
bar.innerHTML = '<span style="color:#10b981;">' + data.summary.normal + ' normal</span> · <span style="color:#ef4444;">' + data.summary.abnormal + ' abnormal</span> · <span style="color:#9ca3af;">' + data.summary.notAssessed + ' skipped</span>';
|
||
}
|
||
})
|
||
.catch(function (err) { console.error('[PEGuide] generate error', err); showToast('Request failed', 'error'); })
|
||
.finally(function () {
|
||
btn.disabled = false; btn.innerHTML = '<i class="fas fa-wand-magic-sparkles"></i> Generate Exam Report';
|
||
});
|
||
}
|
||
|
||
function esc(s) {
|
||
if (s == null) return '';
|
||
return String(s).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
||
}
|
||
|
||
})();
|