pediatric-ai-scribe-v3/src/routes/hospitalCourse.js
Daniel 020e831b3c v6.2: Session management, password change, audit logging, refine context, UI fixes
Security:
- Add session management: users can view/revoke active sessions in Settings
- Add password change in Settings (requires current password, HIBP check)
- Force logout all sessions on password reset
- Fix logout to destroy server-side session (was only clearing cookie)
- Add trust proxy for correct client IP in rate limiting and audit logs
- Add CORS support for multiple domains (CORS_ORIGINS env var)
- Add HIBP breach check endpoint and inline warnings on password fields

Audit logging:
- Add audit logging to all 24 PHI-handling endpoints across 13 route files
- Covers: generation, transcription, TTS, refine, encounters, documents, Nextcloud
- All fire-and-forget (no response delay)

AI improvements:
- Refine now includes original source material (transcript, notes, labs)
  so AI can reference the full input when modifying output
- Add correction tracking (trackAIOutput) to sick visit and well visit tabs
- Fix sickvisit missing from encounter save noteIdMap

UI fixes:
- Non-blocking busy bar for transcription and AI generation (replaces full-screen overlay)
- Fix encounter recording: hide record button during recording (was showing two stop buttons)
- Fix ROS/PE "All WNL" stacking duplicate event handlers; add Clear buttons
- Enlarge AI instructions textarea in Learning Hub CMS

Domain:
- Primary domain now app.pedshub.com, with scribe.pedshub.com and peds.danvics.com as CORS origins
2026-04-08 20:27:45 +02:00

144 lines
5.2 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');
var logger = require('../utils/logger');
// Generate hospital course
router.post('/generate-hospital-course', authMiddleware, async (req, res) => {
try {
const {
notes, // Array of { date, type, content }
edNote, // { date, content, labs }
hAndP, // { date, content }
labs, // Array of { date, values }
patientAge, patientGender, pmh,
setting, // 'floor' | 'picu' | 'nicu' | 'psych'
los, // length of stay
model,
formatPreference, // 'auto' | 'prose' | 'dayByDay' | 'organSystem'
additionalInstructions,
physicianMemories
} = req.body;
if (!notes || notes.length === 0) {
return res.status(400).json({ error: 'No notes provided' });
}
// Determine format
let prompt;
let format = formatPreference || 'auto';
if (format === 'auto') {
if (setting === 'picu' || setting === 'nicu') {
format = 'organSystem';
} else if (setting === 'psych') {
format = 'psych';
} else if (los && parseInt(los) > 3) {
format = 'dayByDay';
} else {
format = 'prose';
}
}
switch (format) {
case 'organSystem': prompt = PROMPTS.hospitalCourseICU; break;
case 'dayByDay': prompt = PROMPTS.hospitalCourseLong; break;
case 'psych': prompt = PROMPTS.hospitalCoursePsych; break;
default: prompt = PROMPTS.hospitalCourseShort;
}
// Build the complete clinical data
let clinicalData = `Patient: ${patientAge || 'Unknown'}, ${patientGender || 'Unknown'}`;
if (pmh) clinicalData += `\nPMH: ${pmh}`;
if (setting) clinicalData += `\nSetting: ${setting}`;
if (los) clinicalData += `\nLength of Stay: ${los} days`;
clinicalData += `\nFormat: ${format}`;
if (edNote) {
clinicalData += `\n\n=== ED NOTE (${edNote.date || 'date unknown'}) ===\n${edNote.content}`;
if (edNote.labs) clinicalData += `\nED Labs: ${edNote.labs}`;
}
if (hAndP) {
clinicalData += `\n\n=== H&P (${hAndP.date || 'date unknown'}) ===\n${hAndP.content}`;
}
// Sort notes by date
const sortedNotes = [...notes].sort((a, b) => new Date(a.date) - new Date(b.date));
sortedNotes.forEach((note, i) => {
clinicalData += `\n\n=== ${(note.type || 'Progress Note').toUpperCase()} - ${note.date || `Note ${i + 1}`} ===\n${note.content}`;
});
if (labs && labs.length > 0) {
clinicalData += '\n\n=== LABS ===';
labs.forEach(lab => {
clinicalData += `\n${lab.date}: ${lab.values}`;
});
}
if (additionalInstructions) {
prompt += `\n\nADDITIONAL INSTRUCTIONS FROM PHYSICIAN:\n${additionalInstructions}`;
}
if (physicianMemories) clinicalData += '\n\n[STYLE HINTS (low priority — only apply if relevant to the current note, never copy content from these examples)]\n' + physicianMemories + '\n[END STYLE HINTS]';
const result = await callAI([
{ role: 'system', content: prompt },
{ role: 'user', content: clinicalData }
], { model, maxTokens: 6000 });
res.json({
success: true,
hospitalCourse: result.content,
format: format,
model: result.model
});
logger.audit(req.user.id, 'generate_hospital_course', 'Generated hospital course', req, { category: 'clinical' });
} catch (err) {
res.status(500).json({ error: err.message });
}
});
// Ask for clarification/missing info
router.post('/hospital-course-clarify', authMiddleware, async (req, res) => {
try {
const { currentDraft, notes } = req.body;
const result = await callAI([
{ role: 'system', content: PROMPTS.askClarification },
{ role: 'user', content: `Current draft:\n${currentDraft}\n\nSource notes:\n${JSON.stringify(notes)}` }
], { model: req.body.model });
res.json({ success: true, questions: result.content, model: result.model });
} catch (err) {
res.status(500).json({ error: err.message });
}
});
// Add discharge day info
router.post('/hospital-course-update', authMiddleware, async (req, res) => {
try {
const { currentCourse, updates, model, instructions } = req.body;
if (!currentCourse) return res.status(400).json({ error: 'No current course' });
let userMsg = `CURRENT HOSPITAL COURSE:\n${currentCourse}\n\n`;
if (updates) userMsg += `NEW UPDATES TO ADD:\n${updates}\n\n`;
if (instructions) userMsg += `INSTRUCTIONS:\n${instructions}`;
const result = await callAI([
{ role: 'system', content: `You are updating a hospital course document with new information.
Add the new information in the appropriate place. Maintain the same format and style.
If adding discharge day information, add it at the end.
${PROMPTS.refine.split('\n').slice(0, -1).join('\n')}` },
{ role: 'user', content: userMsg }
], { model, maxTokens: 6000 });
res.json({ success: true, hospitalCourse: result.content, model: result.model });
} catch (err) {
res.status(500).json({ error: err.message });
}
});
module.exports = router;