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 @@
+
+
+
Suture Selector
+
Pick a site and modifiers — get material, size, technique, removal day, and warnings. Recommendation only; clinical judgment overrides.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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 = '' +
+ '';
+ 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:' +
+ r.warnings.map(function(w) { return '- ' + w + '
'; }).join('') + '
';
+ }
+
+ if (r.notes.length) {
+ html += '' +
+ '
Site notes:' +
+ r.notes.map(function(n) { return '- ' + n + '
'; }).join('') + '
';
+ }
+
+ 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 ────────────────────────────────────────────────────