diff --git a/public/index.html b/public/index.html index 8f8dd24..8d58a52 100644 --- a/public/index.html +++ b/public/index.html @@ -485,7 +485,7 @@ - + diff --git a/public/js/peGuide.js b/public/js/peGuide.js index 66b3a37..73369e6 100644 --- a/public/js/peGuide.js +++ b/public/js/peGuide.js @@ -15,8 +15,6 @@ // step array and produces a Technique + Findings narrative. // ============================================================ -(function () { - // ──────────────────────────────────────────────────────────── // GRADING SCALES — universal reference shown at top of each system // ──────────────────────────────────────────────────────────── @@ -1710,5 +1708,3 @@ if (s == null) return ''; return String(s).replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"'); } - -})(); diff --git a/src/routes/clinicalAssistant.js b/src/routes/clinicalAssistant.js index 230fabc..10e3ab6 100644 --- a/src/routes/clinicalAssistant.js +++ b/src/routes/clinicalAssistant.js @@ -44,73 +44,6 @@ var GREETING_RE = /^(hi|hello|hey|yo|good\s+(morning|afternoon|evening)|thanks|t var MAX_SAVED_CHATS_PER_USER = 100; var MAX_SAVED_CHAT_PAYLOAD = 250000; var MAX_SAVED_CHAT_TITLE = 160; -var EXAMPLE_CANDIDATES = [ - { label: 'Status asthma escalation', prompt: 'In a 4-year-old with acute wheeze, when should magnesium sulfate be considered and what dose is recommended?' }, - { label: 'Febrile neonate', prompt: 'What initial workup and empiric antibiotics are recommended for a well-appearing febrile neonate?' }, - { label: 'Sepsis vasoactives', prompt: 'In pediatric septic shock, when should fluid boluses be limited and vasoactives started?' }, - { label: 'Toxic ingestion', prompt: 'How should an unknown pediatric ingestion be initially assessed and when is activated charcoal appropriate?' }, - { label: 'Meningitis antibiotics', prompt: 'What empiric antibiotics are recommended for suspected bacterial meningitis by pediatric age group?' }, - { label: 'Sickle fever', prompt: 'How should fever in a child with sickle cell disease be evaluated and treated?' }, - { label: 'Head injury CT', prompt: 'Which pediatric head injury features should prompt CT imaging or ED observation?' }, - { label: 'Anaphylaxis IM epi', prompt: 'What is the recommended IM epinephrine dose for pediatric anaphylaxis and when should it be repeated?' }, - { label: 'Dehydration fluids', prompt: 'How should dehydration severity guide oral rehydration versus IV fluids in children?' }, - { label: 'Neonatal jaundice', prompt: 'Which neonatal jaundice risk factors change phototherapy thresholds or urgency?' }, - { label: 'UTI imaging', prompt: 'When should children with febrile UTI receive renal ultrasound or further imaging?' }, - { label: 'Kawasaki workup', prompt: 'What clinical and laboratory features support incomplete Kawasaki disease evaluation?' }, - { label: 'Intussusception', prompt: 'What are the red flags, diagnostic steps, and reduction approach for suspected intussusception?' }, - { label: 'NAT red flags', prompt: 'What injury patterns and history features should raise concern for non-accidental trauma?' }, - { label: 'Status epilepticus', prompt: 'Outline first- and second-line treatment for pediatric status epilepticus with typical dosing.' }, - { label: 'Croup escalation', prompt: 'When should a child with croup receive nebulized epinephrine, and how long should they be observed?' }, - { label: 'Bilious vomiting', prompt: 'What are the red flags for bilious vomiting in neonates, and what immediate workup is recommended?' }, - { label: 'Bronchiolitis oxygen', prompt: 'What supportive care and oxygen/admission criteria are recommended for infant bronchiolitis?' }, - { label: 'DKA cerebral edema', prompt: 'What warning signs suggest cerebral edema during pediatric DKA treatment, and what immediate treatment is recommended?' }, - { label: 'Neonatal resuscitation', prompt: 'Summarize the first minute steps in neonatal resuscitation and when positive pressure ventilation is indicated.' } -]; -var TOPIC_SUGGESTIONS = { - asthma: [ - 'How should acute pediatric asthma exacerbation severity guide SABA frequency, ipratropium, steroids, magnesium sulfate, and disposition?', - 'When should magnesium sulfate, continuous albuterol, noninvasive ventilation, ICU transfer, or intubation be considered in status asthmaticus?', - 'How can bronchiolitis, viral-induced wheeze, and early asthma be distinguished in infants and preschool children?', - 'What discharge criteria, observation period, relapse risks, and follow-up are recommended after an ED asthma exacerbation?', - 'When should controller therapy be started or stepped up after an ED asthma visit?' - ], - bronchiolitis: [ - 'What supportive care, suctioning, hydration, oxygen targets, and feeding guidance are recommended for infant bronchiolitis?', - 'When is a bronchodilator trial reasonable in bronchiolitis, and when should it be stopped?', - 'What apnea risk, work of breathing, oxygen saturation, hydration, and social factors support admission for bronchiolitis?', - 'How can bronchiolitis be distinguished from early asthma, pneumonia, cardiac disease, or foreign body aspiration?', - 'When should high-flow nasal cannula, CPAP, ICU transfer, or escalation be considered in bronchiolitis?' - ], - fever: [ - 'How should fever without source be evaluated by age group in neonates, young infants, and older children?', - 'What cultures, inflammatory markers, LP decisions, empiric antibiotics, and disposition are recommended for febrile neonates?', - 'Which clinical and laboratory features stratify risk for serious bacterial infection in febrile infants?', - 'When is lumbar puncture indicated or avoidable in febrile infants by age and risk category?', - 'How should fever be approached in immunocompromised children, central lines, or incomplete immunization?' - ], - seizure: [ - 'What is the timed pediatric status epilepticus algorithm including benzodiazepine dosing and second-line choices?', - 'How should simple versus complex febrile seizures be evaluated, treated, and explained to caregivers?', - 'When should levetiracetam, fosphenytoin, phenobarbital, valproate, anesthetic infusion, or ICU transfer be used?', - 'Which seizure presentations require neuroimaging, labs, toxicology, LP, EEG, or admission?', - 'What common seizure mimics in children should be considered and how are they distinguished?' - ], - croup: [ - 'How should croup severity guide dexamethasone, nebulized epinephrine, observation, and disposition?', - 'When is nebulized epinephrine indicated for croup and how long should the child be observed afterward?', - 'What dexamethasone dose and route are recommended for mild, moderate, and severe croup?', - 'What discharge, admission, ICU, and airway escalation criteria apply after croup treatment?', - 'What alternative diagnoses should be considered in a child with stridor or atypical croup?' - ], - dehydration: [ - 'Which clinical signs best estimate mild, moderate, and severe dehydration in children?', - 'What oral rehydration protocol, volumes, antiemetic use, and reassessment are recommended for gastroenteritis?', - 'When are IV boluses, maintenance fluids, deficit replacement, glucose, or electrolyte checks indicated?', - 'How should hypernatremic dehydration be recognized and corrected safely?', - 'What discharge, admission, and follow-up criteria apply after pediatric gastroenteritis or dehydration?' - ] -}; - var promptPool = createClinicalPromptPool({ redisCache: redisCache, callAI: callAI, @@ -118,9 +51,7 @@ var promptPool = createClinicalPromptPool({ semanticSearch: semanticSearch, dedupeSources: dedupeSources, normalizeMcpSearchResponse: normalizeMcpSearchResponse, - getIndexedTopicExamples: getIndexedTopicExamples, - exampleCandidates: EXAMPLE_CANDIDATES, - topicSuggestions: TOPIC_SUGGESTIONS + getIndexedTopicExamples: getIndexedTopicExamples }); function positiveInt(value, fallback) { @@ -386,18 +317,6 @@ async function prepareAssistantChat(body) { } var history = Array.isArray(body.history) ? body.history.slice(-8) : []; - var contextualHistory = priorClinicalHistory(history, message); - var suggestions = getTopicSuggestions(message); - if (!contextualHistory.length && suggestions.length > 0 && message.split(/\s+/).length < 4) { - return { direct: { - success: true, - answer: buildSuggestionAnswer(message, suggestions), - suggestions: suggestions, - sources: [], - model: null, - search: { skipped: true, reason: 'topic_suggestions' } - } }; - } var searchQuery = await rewriteSearchQuery(message, history, chatModel).catch(function(e) { console.warn('[clinical-assistant] query rewrite skipped:', e.message); @@ -503,13 +422,6 @@ function needsContextualRewrite(message) { return words.length <= 8 || /\b(it|this|that|they|them|he|she|dose|dosing|how much|what about|next|admit|discharge|criteria|side effects?|contraindications?|monitor|monitoring)\b/i.test(text); } -function priorClinicalHistory(history, message) { - var latest = String(message || '').trim(); - return (Array.isArray(history) ? history : []).filter(function(m) { - return m && (m.role === 'user' || m.role === 'assistant') && m.content && String(m.content).trim() !== latest; - }); -} - function deterministicFollowupQuery(message, history) { var text = String(message || '').trim().toLowerCase(); if (!/^(dose|dosing|what dose|dose\?|how much|med dose|medication dose)$/i.test(text)) return ''; @@ -716,22 +628,4 @@ function assistantErrorMessage(e) { return msg || 'Assistant request failed'; } -function getTopicSuggestions(message) { - var normalized = String(message || '').toLowerCase().replace(/[^a-z0-9\s-]/g, ' ').replace(/\s+/g, ' ').trim(); - if (!normalized) return []; - var direct = TOPIC_SUGGESTIONS[normalized]; - if (direct) return direct; - var keys = Object.keys(TOPIC_SUGGESTIONS); - for (var i = 0; i < keys.length; i++) { - if (normalized.indexOf(keys[i]) !== -1) return TOPIC_SUGGESTIONS[keys[i]]; - } - return []; -} - -function buildSuggestionAnswer(topic, suggestions) { - return 'I can look up **' + topic + '**. Pick a specific clinical question so I retrieve the right cited pages instead of giving a broad summary:\n\n' + - suggestions.map(function(s) { return '- ' + s; }).join('\n') + - '\n\nOr type your own specific question, for example: "management of acute asthma exacerbation in a 6-year-old".'; -} - module.exports = router; diff --git a/src/utils/clinicalPromptPool.js b/src/utils/clinicalPromptPool.js index 0a6a694..9d0c6b9 100644 --- a/src/utils/clinicalPromptPool.js +++ b/src/utils/clinicalPromptPool.js @@ -41,10 +41,7 @@ function createClinicalPromptPool(opts) { refreshIfNeeded(false).catch(function() {}); - var examples = []; - rotateExamples(opts.exampleCandidates || [], now).slice(0, 9).forEach(function(item) { - examples.push({ label: item.label, prompt: item.prompt }); - }); + var examples = await fallbackIndexedExamples(); memoryCache = { expiresAt: now + cacheMs, examples: examples }; return examples; diff --git a/test/clinical-assistant-prompt-pool.test.js b/test/clinical-assistant-prompt-pool.test.js new file mode 100644 index 0000000..cb6278b --- /dev/null +++ b/test/clinical-assistant-prompt-pool.test.js @@ -0,0 +1,20 @@ +const { test } = require('node:test'); +const assert = require('node:assert/strict'); +const fs = require('node:fs'); +const path = require('node:path'); + +const root = path.join(__dirname, '..'); + +function read(relativePath) { + return fs.readFileSync(path.join(root, relativePath), 'utf8'); +} + +test('clinical assistant starter prompts are Redis or indexed-source backed', () => { + const route = read('src/routes/clinicalAssistant.js'); + const pool = read('src/utils/clinicalPromptPool.js'); + assert.doesNotMatch(route, /EXAMPLE_CANDIDATES|TOPIC_SUGGESTIONS|topic_suggestions|buildSuggestionAnswer/); + assert.match(route, /createClinicalPromptPool\(\{[\s\S]*redisCache: redisCache/); + assert.match(pool, /opts\.redisCache\.getJson\(redisKey\)/); + assert.match(pool, /opts\.redisCache\.setJson\(redisKey, payload, ttlSeconds\)/); + assert.match(pool, /var examples = await fallbackIndexedExamples\(\)/); +}); diff --git a/test/module-entrypoints.test.js b/test/module-entrypoints.test.js index ccc7f6a..1e17e91 100644 --- a/test/module-entrypoints.test.js +++ b/test/module-entrypoints.test.js @@ -20,6 +20,7 @@ const moduleEntrypoints = [ 'public/js/milestones.js', 'public/js/nextcloud.js', 'public/js/notes.js', + 'public/js/peGuide.js', 'public/js/sickVisit.js', 'public/js/soap.js', 'public/js/secureStorage.js',