pediatric-ai-scribe-v3/src/routes/milestones.js
Daniel 5b0c296a88 Prompt-injection wrap: remaining AI routes
Applied the <UNTRUSTED_*> delimiter + INJECTION_GUARD pattern to:
  - src/routes/sickVisit.js  (chief complaint, transcript, dictation,
                               ROS, physical exam, diagnoses, style hints)
  - src/routes/wellVisit.js  (SSHADESS answers + full well-visit context)
  - src/routes/chartReview.js (PMH + all visit content + labs)
  - src/routes/hospitalCourse.js (all notes/H&P/ED + clarification
                                   & update endpoints)
  - src/routes/milestones.js (narrative + summary)

Each wraps patient-derived text in <UNTRUSTED_*>…</UNTRUSTED_*>
tags and appends the INJECTION_GUARD system instruction that tells
the model to treat wrapped content strictly as data. Operator-
supplied `additionalInstructions` stays unwrapped (trusted).
2026-04-14 05:31:54 +02:00

97 lines
4.1 KiB
JavaScript

const express = require('express');
const router = express.Router();
const { callAI } = require('../utils/ai');
const PROMPTS = require('../utils/prompts');
const { authMiddleware } = require('../middleware/auth');
const db = require('../db/database');
var logger = require('../utils/logger');
var { wrapUserText, INJECTION_GUARD } = require('../utils/promptSafe');
// Get all milestones for client (authenticated users)
router.get('/milestones-data', authMiddleware, async (req, res) => {
try {
const result = await db.query(
'SELECT * FROM developmental_milestones ORDER BY age_group, domain, sort_order, id'
);
// Group by age_group and domain to match the static data structure
const grouped = {};
result.rows.forEach(row => {
if (!grouped[row.age_group]) {
grouped[row.age_group] = {};
}
if (!grouped[row.age_group][row.domain]) {
grouped[row.age_group][row.domain] = [];
}
grouped[row.age_group][row.domain].push(row.milestone_text);
});
res.json({ success: true, milestones: grouped });
} catch (err) {
res.status(500).json({ error: 'Request failed' });
}
});
// Narrative format
router.post('/generate-milestone-narrative', authMiddleware, async (req, res) => {
try {
const { milestones, ageGroup, patientAge, patientGender, model, format } = req.body;
if (!milestones) return res.status(400).json({ error: 'No milestones' });
const achieved = milestones.filter(m => m.status === 'yes');
const notAchieved = milestones.filter(m => m.status === 'no');
const notAssessed = milestones.filter(m => m.status === null);
if (achieved.length === 0 && notAchieved.length === 0) {
return res.json({ success: true, narrative: 'No developmental milestones were assessed during this visit.', summary: { achieved: 0, notAchieved: 0, notAssessed: notAssessed.length } });
}
let milestoneText = '';
if (achieved.length > 0) {
milestoneText += 'ACHIEVED:\n';
achieved.forEach(m => { milestoneText += ` - [${m.domain}] ${m.milestone}\n`; });
}
if (notAchieved.length > 0) {
milestoneText += '\nNOT YET ACHIEVED:\n';
notAchieved.forEach(m => { milestoneText += ` - [${m.domain}] ${m.milestone}\n`; });
}
// Choose narrative or list format
const prompt = (format === 'list') ? PROMPTS.milestoneList : PROMPTS.milestoneNarrative;
const result = await callAI([
{ role: 'system', content: prompt + INJECTION_GUARD },
{ role: 'user', content: `Patient: ${patientAge || 'Unknown'}, ${patientGender || 'Unknown'}\nAge Group: ${ageGroup}\n\n` + wrapUserText('milestones', milestoneText) + `\n\nAssessed: ${achieved.length + notAchieved.length} | Achieved: ${achieved.length} | Not achieved: ${notAchieved.length} | Not assessed (OMIT): ${notAssessed.length}` }
], { model });
res.json({
success: true,
narrative: result.content,
model: result.model,
summary: { achieved: achieved.length, notAchieved: notAchieved.length, notAssessed: notAssessed.length }
});
logger.audit(req.user.id, 'generate_milestone_narrative', 'Generated milestone narrative', req, { category: 'clinical' });
} catch (err) {
res.status(500).json({ error: 'Request failed' });
}
});
// 3-sentence summary
router.post('/generate-milestone-summary', authMiddleware, async (req, res) => {
try {
const { narrative, ageGroup, patientAge, patientGender, model } = req.body;
if (!narrative) return res.status(400).json({ error: 'No narrative' });
const result = await callAI([
{ role: 'system', content: PROMPTS.milestoneSummary + INJECTION_GUARD },
{ role: 'user', content: `Patient: ${patientAge || 'Unknown'}, ${patientGender || 'Unknown'}\nAge Group: ${ageGroup}\n\nNARRATIVE:\n` + wrapUserText('narrative', narrative) }
], { model, maxTokens: 500 });
res.json({ success: true, summary: result.content, model: result.model });
logger.audit(req.user.id, 'generate_milestone_summary', 'Generated milestone summary', req, { category: 'clinical' });
} catch (err) {
res.status(500).json({ error: 'Request failed' });
}
});
module.exports = router;