diff --git a/public/components/bedside.html b/public/components/bedside.html index c2c936b..6e3ff05 100644 --- a/public/components/bedside.html +++ b/public/components/bedside.html @@ -65,6 +65,7 @@ + @@ -519,5 +520,88 @@
+ + + diff --git a/public/js/app.js b/public/js/app.js index 87bf6c4..d2d6443 100644 --- a/public/js/app.js +++ b/public/js/app.js @@ -888,6 +888,61 @@ function suggestBillingCodes(outputElementId, noteText, noteType, patientAge, vi function escHtml(s) { return String(s || '').replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"'); } +// ── Don't-miss tooltip (called after sick visit / encounter HPI generates) ── +// Same insertion pattern as suggestBillingCodes — a side card next to the note +// output. Hard-capped at 5 items by the server prompt. Silent on empty / failure. +function suggestDontMiss(outputElementId, noteText, noteType, patientAge, chiefComplaint) { + var outputEl = document.getElementById(outputElementId); + if (!outputEl || !noteText) return; + var card = outputEl.closest('.card, .output-card'); + if (!card) return; + + var containerId = outputElementId.replace('-text', '') + '-dont-miss'; + var container = document.getElementById(containerId); + if (!container) { + container = document.createElement('div'); + container.id = containerId; + container.className = 'card dont-miss-card'; + container.style.cssText = 'margin-top:10px;border-left:3px solid #f59e0b;'; + outputEl.parentNode.insertBefore(container, outputEl.nextSibling); + } + + container.innerHTML = '
Reviewing note for high-yield items...
'; + + fetch('/api/dont-miss', { + method: 'POST', + headers: getAuthHeaders(), + body: JSON.stringify({ + noteText: noteText, + noteType: noteType || '', + patientAge: patientAge || '', + chiefComplaint: chiefComplaint || '' + }) + }) + .then(function(r) { return r.json(); }) + .then(function(data) { + if (!data.success || !data.points || data.points.length === 0) { + container.classList.add('hidden'); + return; + } + var html = '

Don\'t Miss

' + + 'High-yield items. Suggestions only.
' + + '
'; + data.points.forEach(function(p) { + var why = p.why ? '
' + escHtml(p.why) + '
' : ''; + html += '
' + + '
' + escHtml(p.point) + '
' + + why + '
'; + }); + html += '
'; + container.classList.remove('hidden'); + container.innerHTML = html; + }) + .catch(function() { + container.classList.add('hidden'); + }); +} + function refineDocument(outputElementId, inputElementId) { var doc = document.getElementById(outputElementId); var input = document.getElementById(inputElementId); diff --git a/public/js/bedside/index.js b/public/js/bedside/index.js index 8327170..6076354 100644 --- a/public/js/bedside/index.js +++ b/public/js/bedside/index.js @@ -23,6 +23,7 @@ import * as antimicrobials from './antimicrobials.js'; import * as burns from './burns.js'; import * as toxicology from './toxicology.js'; import * as trauma from './trauma.js'; +import * as sutures from './sutures.js'; [ ageWeight, @@ -43,4 +44,5 @@ import * as trauma from './trauma.js'; burns, toxicology, trauma, + sutures, ].forEach(function(m) { if (m && typeof m.init === 'function') m.init(); }); diff --git a/public/js/bedside/sub-nav.js b/public/js/bedside/sub-nav.js index 11e7d15..9582128 100644 --- a/public/js/bedside/sub-nav.js +++ b/public/js/bedside/sub-nav.js @@ -5,7 +5,7 @@ // same sub-section the user was last looking at. // ============================================================ -var SECTIONS = ['neonatal','airway','cardiac','respiratory','ventilation','seizure','sepsis','anaphylaxis','sedation','agitation','antiemetics','antimicrobials','burns','toxicology','trauma']; +var SECTIONS = ['neonatal','airway','cardiac','respiratory','ventilation','seizure','sepsis','anaphylaxis','sedation','agitation','antiemetics','antimicrobials','burns','toxicology','trauma','sutures']; function activate(emKey) { var pill = document.querySelector('[data-em="' + emKey + '"]'); diff --git a/public/js/bedside/sutures.js b/public/js/bedside/sutures.js new file mode 100644 index 0000000..701e50b --- /dev/null +++ b/public/js/bedside/sutures.js @@ -0,0 +1,320 @@ +// ============================================================ +// bedside/sutures.js +// SUTURE SELECTOR — site × age × tension × wound state → +// material, size, technique, removal day, alternatives, and +// contraindication warnings. +// +// Sources cited inline in the result via S.ref(): +// Roberts & Hedges' Clinical Procedures in Emergency Medicine, 7e (2019) +// Fleisher & Ludwig's Textbook of Pediatric Emergency Medicine, 8e (2021) +// AAP Section on Emergency Medicine — Pediatric laceration repair +// UpToDate: Pope JV — Skin laceration repair with sutures +// ============================================================ + +import { S } from './shared.js'; + +// ── Site table ────────────────────────────────────────────────────── +// sutureSize/removalDays are the "moderate tension, clean, school-age" defaults. +// recommendFn() below adjusts for tension, age, contamination. +var SITES = { + face: { + label: 'Face (non-cosmetic-critical)', + materialFamily: 'monofilament-nonabsorbable', // nylon / polypropylene + sutureSize: '6-0', + removalDays: '3–5', + glueOK: true, + glueNote: 'Tissue adhesive (Dermabond) excellent for clean linear lacs ≤4 cm with low tension and good apposition. Avoid near eyes, lips, joints, mucosa.', + techniqueDefault: 'Simple interrupted', + notes: 'Smallest practical size to minimize scar. Approximate edges precisely — eversion is more important than knot count.' + }, + eyelid_eyebrow: { + label: 'Eyelid / eyebrow', + materialFamily: 'monofilament-nonabsorbable', + sutureSize: '6-0', + removalDays: '3–5', + glueOK: false, + glueNote: 'No tissue adhesive — risk of ocular irritation.', + techniqueDefault: 'Simple interrupted', + notes: 'Do NOT shave eyebrow (regrowth unpredictable). Refer full-thickness eyelid, lid-margin, or lacrimal-system lacerations to ophthalmology / plastics.' + }, + lip_vermilion: { + label: 'Lip — crossing vermilion border', + materialFamily: 'monofilament-nonabsorbable', + sutureSize: '6-0', + removalDays: '3–5', + glueOK: false, + glueNote: 'No glue across vermilion.', + techniqueDefault: 'Simple interrupted, FIRST stitch precisely aligning the vermilion border', + notes: 'Vermilion misalignment as small as 1 mm is visible. If a deep layer (orbicularis oris) is involved, close it with 5-0 absorbable first. Intraoral side: 4-0 chromic or Vicryl Rapide.' + }, + intraoral: { + label: 'Intraoral / tongue / mucosa', + materialFamily: 'absorbable-chromic-or-vicryl', + sutureSize: '4-0 or 5-0', + removalDays: null, // absorbable + glueOK: false, + glueNote: 'No glue on mucosa.', + techniqueDefault: 'Simple interrupted, generous bites (mucosa is friable)', + notes: 'Many intraoral lacs heal well without closure. Suture if: through-and-through, gaping >1 cm, flap, or interfering with feeding/airway. Tongue: sedation often required; absorbable only.' + }, + ear: { + label: 'Ear', + materialFamily: 'monofilament-nonabsorbable', + sutureSize: '5-0', + removalDays: '4–5', + glueOK: false, + glueNote: 'No glue on ear.', + techniqueDefault: 'Simple interrupted', + notes: 'Cover exposed cartilage with skin (do not suture cartilage in ED). Hematoma → drain to prevent cauliflower ear. Auricular block before repair.' + }, + scalp: { + label: 'Scalp', + materialFamily: 'monofilament-nonabsorbable', + sutureSize: '3-0 or 4-0', + removalDays: '7–10', + glueOK: true, + glueNote: 'Hair apposition technique (HAT) — twist hair across the wound and secure with a drop of glue — reasonable for clean linear scalp lacs ≤10 cm with hair ≥3 cm.', + techniqueDefault: 'Simple interrupted, or staples (faster, equivalent cosmesis under hair)', + notes: 'Highly vascular — pressure controls bleeding. Galea aponeurotica may need closure with 3-0 absorbable for hemostasis. Staples acceptable in non-visible scalp.' + }, + neck: { + label: 'Neck', + materialFamily: 'monofilament-nonabsorbable', + sutureSize: '5-0', + removalDays: '5–7', + glueOK: true, + glueNote: 'Glue acceptable for superficial low-tension lacs only.', + techniqueDefault: 'Simple interrupted', + notes: 'Rule out deep neck-structure injury (zone-based assessment) before closing. Posterior neck heals well; anterior neck — be alert for vascular/airway involvement.' + }, + trunk: { + label: 'Trunk', + materialFamily: 'monofilament-nonabsorbable', + sutureSize: '4-0 or 3-0', + removalDays: '7–10', + glueOK: false, + glueNote: 'Glue not first-line for trunk; tension is too variable.', + techniqueDefault: 'Simple interrupted; deep dermal 4-0 absorbable for wounds >0.5 cm deep', + notes: 'Layered closure for any wound deeper than mid-dermis. Watch for through-and-through abdominal/chest injury; image if mechanism warrants.' + }, + upper_ext: { + label: 'Upper extremity (arm / forearm)', + materialFamily: 'monofilament-nonabsorbable', + sutureSize: '4-0', + removalDays: '7–10', + glueOK: true, + glueNote: 'Glue OK for clean linear lacs ≤5 cm with low tension; not over joints.', + techniqueDefault: 'Simple interrupted', + notes: 'Examine neurovascular function distally before and after repair.' + }, + lower_ext: { + label: 'Lower extremity (thigh / leg)', + materialFamily: 'monofilament-nonabsorbable', + sutureSize: '4-0 or 3-0', + removalDays: '10–14', + glueOK: true, + glueNote: 'Glue OK for clean linear lacs ≤5 cm with low tension; not over joints; not on shin where tension is high.', + techniqueDefault: 'Simple interrupted', + notes: 'Pre-tibial skin is thin and fragile — handle gently, avoid horizontal mattress. Leave in longer than upper extremity.' + }, + hand: { + label: 'Hand / fingers', + materialFamily: 'monofilament-nonabsorbable', + sutureSize: '5-0', + removalDays: '10–14', + glueOK: false, + glueNote: 'Generally avoid glue on hand (high tension, frequent movement).', + techniqueDefault: 'Simple interrupted', + notes: 'Examine tendon function (extensor and flexor) and 2-point discrimination BEFORE local anesthetic. Image if mechanism suggests fracture or foreign body. Bite to hand → consider not closing.' + }, + foot: { + label: 'Foot / toes', + materialFamily: 'monofilament-nonabsorbable', + sutureSize: '4-0 or 5-0', + removalDays: '10–14', + glueOK: false, + glueNote: 'Generally avoid glue on plantar surface.', + techniqueDefault: 'Simple interrupted', + notes: 'Plantar punctures: high infection risk — irrigate, do not close primarily. Consider Pseudomonas coverage in shoe-puncture.' + }, + joint_surface: { + label: 'Over a joint (knee, elbow, etc.)', + materialFamily: 'monofilament-nonabsorbable', + sutureSize: '4-0', + removalDays: '10–14', + glueOK: false, + glueNote: 'No glue across joint surfaces — repeated flexion separates the bond.', + techniqueDefault: 'Simple interrupted; consider vertical mattress for high tension', + notes: 'Splint joint in slight flexion for 5–7 d to reduce wound tension. Rule out joint capsule penetration (saline-load test if uncertain).' + }, + genitalia: { + label: 'Genitalia / perineum', + materialFamily: 'absorbable-chromic-or-vicryl', + sutureSize: '5-0 or 4-0', + removalDays: null, // absorbable + glueOK: false, + glueNote: 'No glue on genitalia.', + techniqueDefault: 'Simple interrupted', + notes: 'Significant labial / scrotal / penile lacs — consider OR repair under sedation. Always consider non-accidental trauma evaluation in pediatric genital injuries.' + }, + fingertip: { + label: 'Fingertip / nail bed', + materialFamily: 'absorbable-chromic-or-vicryl', + sutureSize: '6-0', + removalDays: null, + glueOK: false, + glueNote: 'No glue on nail bed.', + techniqueDefault: 'Simple interrupted with 6-0 absorbable for nail bed; replace and secure nail as a stent', + notes: 'Painful subungual hematoma — trephinate (especially if 25–50% or more of the nail), provided the nail is intact and there is no displaced distal-phalanx fracture. Open nail-bed lac → repair with absorbable, suture nail back as biologic dressing or use Coban. X-ray any crush mechanism (tuft fracture is common).' + } +}; + +// ── Recommendation engine ─────────────────────────────────────────── +function recommend(input) { + var s = SITES[input.site]; + if (!s) return null; + var notes = []; + var warnings = []; + + // Closure decision (contamination + age of wound) + var closure = 'Primary closure'; + if (input.contamination === 'heavy' || input.contamination === 'bite_other') { + closure = 'Consider DELAYED primary closure (cleanse, pack, reassess in 3–5 days) or healing by secondary intention'; + warnings.push('Heavily contaminated wound — primary closure increases infection risk. Irrigate copiously before any closure decision.'); + } + if (input.contamination === 'bite_human' || input.contamination === 'bite_cat') { + closure = 'Generally DO NOT close primarily'; + warnings.push((input.contamination === 'bite_cat' ? 'Cat bite' : 'Human bite') + ' — leave open or use loose approximation. High infection risk (Pasteurella for cat, Eikenella for human). Irrigate, debride, give amox-clavulanate prophylaxis. Consider hand-surgery consult for hand bites.'); + } + if (input.contamination === 'bite_dog' && input.site === 'hand') { + warnings.push('Dog bite to the hand — many EPs do not close primarily. If closing, loose approximation only.'); + } + if (input.hours === '>12' && input.site !== 'face' && input.site !== 'scalp') { + closure = closure === 'Primary closure' ? 'Delayed primary closure (>12 h since injury, non-face/scalp)' : closure; + notes.push('>12 h since injury for a non-face/scalp wound — closure decision is judgment-based; clean linear wounds in well-perfused areas can still be closed.'); + } + + // Material — switch to absorbable for very young if non-mucosal default + var material = s.materialFamily; + if (s.materialFamily === 'monofilament-nonabsorbable' && (input.age === 'lt1' || input.age === '1to5')) { + if (input.removalAvoid === 'yes') { + material = 'fast-absorbing-gut'; + notes.push('Young child — fast-absorbing gut suture (or Vicryl Rapide) avoids the trauma of suture removal. Slightly more reactive than nylon but cosmetic outcome at 3 months is similar in low-tension wounds.'); + } + } + var materialDisplay = ({ + 'monofilament-nonabsorbable': 'Monofilament non-absorbable (nylon — Ethilon, OR polypropylene — Prolene)', + 'absorbable-chromic-or-vicryl': 'Absorbable (chromic gut OR Vicryl / Vicryl Rapide)', + 'fast-absorbing-gut': 'Fast-absorbing plain gut (or Vicryl Rapide) — no removal needed' + })[material]; + + // Size — bump up one for high tension, down one for cosmetic priority + low tension face + var size = s.sutureSize; + if (input.tension === 'high' && size === '4-0') size = '3-0'; + if (input.tension === 'high' && size === '5-0') size = '4-0'; + if (input.tension === 'high' && size === '6-0') size = '5-0'; + if (input.cosmetic === 'yes' && input.tension === 'low' && size === '4-0') size = '5-0'; + + // Technique + var technique = s.techniqueDefault; + if (input.tension === 'high') technique = 'Vertical mattress (or simple interrupted with deep dermal absorbable layer first)'; + if (input.cosmetic === 'yes' && input.tension === 'low' && (input.site === 'face' || input.site === 'eyelid_eyebrow')) { + technique = 'Simple interrupted; consider running subcuticular if skill permits — best cosmesis'; + } + + // Glue alternative (if applicable) + var glue = null; + if (s.glueOK && input.tension !== 'high' && input.contamination === 'clean') { + glue = s.glueNote; + } + + // Removal day range (null when an absorbable material was selected) + var removal = s.removalDays; + if (material !== 'monofilament-nonabsorbable') removal = null; + + // Tetanus + var tetanus = 'Confirm tetanus status. Tdap if last booster >5 y for tetanus-prone (contaminated, deep, crush, burn, bite); >10 y for clean wound. Add TIG if unimmunized or unknown immunization history with tetanus-prone wound.'; + + return { + site: s.label, + closure: closure, + material: materialDisplay, + size: size, + technique: technique, + glue: glue, + removalDays: removal, + notes: [s.notes].concat(notes), + warnings: warnings, + tetanus: tetanus + }; +} + +// ── Render ────────────────────────────────────────────────────────── +function readInput() { + return { + site: getVal('sut-site'), + age: getVal('sut-age'), + tension: getVal('sut-tension'), + cosmetic: getVal('sut-cosmetic'), + contamination: getVal('sut-contam'), + hours: getVal('sut-hours'), + removalAvoid: getVal('sut-removal-avoid') + }; +} +function getVal(id) { var el = document.getElementById(id); return el ? el.value : ''; } + +function render() { + var el = document.getElementById('sut-result'); + if (!el) return; + var inp = readInput(); + if (!inp.site) { el.innerHTML = '

Select a site to see recommendation.

'; return; } + var r = recommend(inp); + if (!r) { el.innerHTML = '

Unknown site.

'; return; } + + var html = '
' + + 'Suture recommendation — ' + r.site + '
'; + + html += '
'; + html += '
Closure plan: ' + r.closure + '
'; + html += '
Material: ' + r.material + '
'; + html += '
Size: ' + r.size + '
'; + html += '
Technique: ' + r.technique + '
'; + if (r.removalDays) html += '
Suture removal: ' + r.removalDays + ' days
'; + else html += '
Suture removal: not required (absorbable)
'; + html += '
'; + + if (r.glue) { + html += '
' + + 'Adhesive alternative: ' + r.glue + '
'; + } + + if (r.warnings.length) { + html += '
' + + 'Warning:
'; + } + + if (r.notes.length) { + html += '
' + + 'Site notes:
'; + } + + html += '
Tetanus: ' + r.tetanus + '
'; + + html += S.ref('Roberts & Hedges Clinical Procedures in Emergency Medicine, 7e (2019), Ch. 36. Fleisher & Ludwig Pediatric Emergency Medicine, 8e (2021). AAP Section on Emergency Medicine — Pediatric laceration repair. UpToDate (Pope JV) — Skin laceration repair with sutures. Suggestions only — verify against your local protocol and the patient in front of you.'); + + el.innerHTML = html; +} + +export function init() { + document.addEventListener('change', function(e) { + if (!e.target || !e.target.id) return; + if (e.target.id.indexOf('sut-') === 0) render(); + }); + document.addEventListener('click', function(e) { + var pill = e.target.closest && e.target.closest('[data-em="sutures"]'); + if (pill) setTimeout(render, 0); + }); + document.addEventListener('tabChanged', function() { setTimeout(render, 100); }); +} diff --git a/public/js/liveEncounter.js b/public/js/liveEncounter.js index 4554cfd..a528e8d 100644 --- a/public/js/liveEncounter.js +++ b/public/js/liveEncounter.js @@ -214,6 +214,7 @@ outputCard.scrollIntoView({ behavior: 'smooth' }); showToast('HPI generated!', 'success'); if (typeof suggestBillingCodes === 'function') suggestBillingCodes('enc-hpi-text', data.hpi, 'hpi', document.getElementById('enc-age').value, document.getElementById('enc-setting').value); + if (typeof suggestDontMiss === 'function') suggestDontMiss('enc-hpi-text', data.hpi, 'hpi', document.getElementById('enc-age').value); } else showToast(data.error || 'Failed', 'error'); }) .catch(function(err) { hideBusy(); showToast(err.message, 'error'); }); diff --git a/public/js/sickVisit.js b/public/js/sickVisit.js index 660b3c5..f5dbe64 100644 --- a/public/js/sickVisit.js +++ b/public/js/sickVisit.js @@ -339,6 +339,7 @@ if (outCard) { outCard.classList.remove('hidden'); outCard.scrollIntoView({ behavior: 'smooth' }); } showToast('Sick visit note generated!', 'success'); if (typeof suggestBillingCodes === 'function') suggestBillingCodes('sick-note-text', data.note, 'sickvisit', document.getElementById('sick-age').value); + if (typeof suggestDontMiss === 'function') suggestDontMiss('sick-note-text', data.note, 'sickvisit', document.getElementById('sick-age').value, document.getElementById('sick-cc').value); } else { showToast(data.error || 'Generation failed', 'error'); } diff --git a/server.js b/server.js index d05152c..4dc100a 100644 --- a/server.js +++ b/server.js @@ -298,6 +298,7 @@ app.use('/api/sessions', require('./src/routes/sessions')); app.use('/api', require('./src/routes/wellVisit')); app.use('/api', require('./src/routes/sickVisit')); app.use('/api', require('./src/routes/edEncounters')); +app.use('/api', require('./src/routes/dontMiss')); app.use('/api/user', require('./src/routes/userPreferences')); app.use('/api/admin/learning', require('./src/routes/learningAI')); diff --git a/src/routes/dontMiss.js b/src/routes/dontMiss.js new file mode 100644 index 0000000..6c86e6d --- /dev/null +++ b/src/routes/dontMiss.js @@ -0,0 +1,79 @@ +// ============================================================ +// DON'T-MISS TOOLTIP ROUTE — post-note "what to clarify or +// consider" suggestions for sick visit + encounter HPI tabs. +// Hard cap of 5 points. Separate AI pass after note generation +// so the existing note-generation routes stay untouched. +// ============================================================ + +var express = require('express'); +var router = express.Router(); +var { callAI } = require('../utils/ai'); +var PROMPTS = require('../utils/prompts'); +var { authMiddleware } = require('../middleware/auth'); +var logger = require('../utils/logger'); +var { wrapUserText, INJECTION_GUARD } = require('../utils/promptSafe'); + +router.use(authMiddleware); + +function extractJson(raw) { + var t = String(raw || '').trim(); + t = t.replace(/^```(?:json)?\s*/i, '').replace(/\s*```$/i, ''); + var s = t.indexOf('{'); + if (s > 0) t = t.substring(s); + var e = t.lastIndexOf('}'); + if (e > -1 && e < t.length - 1) t = t.substring(0, e + 1); + try { return JSON.parse(t); } catch (err) { return null; } +} + +// ── POST /dont-miss — return up to 5 high-yield items ───── +// Body: +// noteText — generated note (required) +// noteType — 'hpi' | 'sickvisit' (informational, included in context) +// patientAge — string, optional but strongly preferred +// chiefComplaint — string, optional (sick visit has it; encounter HPI doesn't) +// model — optional override +// Returns: { success, points: [{point, why}], model } +router.post('/dont-miss', async function (req, res) { + var start = Date.now(); + try { + var { noteText, noteType, patientAge, chiefComplaint, model } = req.body; + if (!noteText || !noteText.trim()) { + return res.status(400).json({ error: 'noteText is required' }); + } + + var context = 'NOTE TYPE: ' + (noteType || 'unknown') + '\n'; + context += 'PATIENT AGE: ' + (patientAge || 'unknown') + '\n'; + if (chiefComplaint && chiefComplaint.trim()) { + context += 'CHIEF COMPLAINT: ' + wrapUserText('chief_complaint', chiefComplaint) + '\n'; + } + context += '\nGENERATED NOTE:\n' + wrapUserText('note', noteText); + + var result = await callAI([ + { role: 'system', content: PROMPTS.dontMissTooltip + INJECTION_GUARD }, + { role: 'user', content: context } + ], { model: model, maxTokens: 1500 }); + + var parsed = extractJson(result.content); + var points = (parsed && Array.isArray(parsed.points)) ? parsed.points : []; + points = points + .filter(function (p) { return p && p.point; }) + .map(function (p) { return { point: String(p.point).trim(), why: String(p.why || '').trim() }; }) + .slice(0, 5); // hard cap defense, even if the model ignored the prompt cap + + var dur = Date.now() - start; + logger.apiCall(req.user.id, '/api/dont-miss', { + model: result.model, + tokensInput: result.usage && result.usage.input_tokens, + tokensOutput: result.usage && result.usage.output_tokens, + duration: dur, + statusCode: 200 + }); + + res.json({ success: true, points: points, model: result.model }); + } catch (e) { + logger.error('[dontMiss] failed', e.message); + res.status(500).json({ error: 'Request failed' }); + } +}); + +module.exports = router; diff --git a/src/utils/prompts.js b/src/utils/prompts.js index dfc5d66..fc8d5e3 100644 --- a/src/utils/prompts.js +++ b/src/utils/prompts.js @@ -448,7 +448,35 @@ CRITICAL RULES: - Use ONLY information present in the note and transcript - Do NOT invent data points, interventions, or diagnoses - Be conservative when ambiguous — pick the lower level -- The levelRationale must reference specific elements actually documented in the encounter` +- The levelRationale must reference specific elements actually documented in the encounter`, + + // ======================== DON'T-MISS TOOLTIP (post-note) ======================== + // Used after sick visit / encounter HPI generation. Hard cap of 5 points. + // Returns JSON only — rendered next to the note as a side panel. + dontMissTooltip: `You are an expert pediatrician reviewing a generated clinical note for a colleague. Your job is to surface the highest-yield "don't miss" items the physician should clarify, document, or consider — tailored to the note's chief complaint and the patient's age. +${CORE_RULES} + +OUTPUT FORMAT — STRICT JSON ONLY, no preamble, no code fences, no commentary: +{ + "points": [ + {"point": "", "why": ""}, + ... + ] +} + +HARD CAP: at most 5 points. Quality over quantity. If only 2 high-yield items exist, return 2. + +WHAT TO SURFACE: +- Important questions the physician should clarify (e.g. "Ask about toxic ingestion" for an obtunded toddler) +- Documentation gaps a chart-reviewer would flag (e.g. "Document immunization status for this fever in infant <3mo") +- High-yield differentials worth ruling out for this age + complaint +- Reassessment / return precautions worth emphasizing + +WHAT TO AVOID: +- Items already clearly documented in the note +- Generic boilerplate ("consider differential diagnosis") +- Treatment recommendations (this is a documentation/clinical-reasoning prompt, not a treatment plan) +- Padding to reach 5 — fewer is fine` }; // ── DB override support ────────────────────────────────────────────────────