pediatric-ai-scribe-v3/src/routes/encounters.js
ifedan-ed fe632985c1 Fix: admin models loading, clear refine/instructions on New, bigger HPI areas, unique labels
- Admin models: reset modelsLoaded flag on error so retry works
- Admin default model: fix redundant fetch race condition
- clearTab: now clears refine inputs, instructions, and demographic fields
- Encounter/dictation clear buttons: also clear refine input
- SOAP: instructions textarea already cleared by clearTab (soap-instructions)
- Encounter HPI: bigger transcript (400px) and output (600px) text areas
- Unique label enforcement: 409 error if saving with duplicate label
2026-03-29 01:35:45 +00:00

123 lines
5.7 KiB
JavaScript

// ============================================================
// ENCOUNTERS ROUTES — Save/resume/pause encounter progress
// ============================================================
var express = require('express');
var router = express.Router();
var db = require('../db/database');
var { authMiddleware } = require('../middleware/auth');
var logger = require('../utils/logger');
router.use(authMiddleware);
// ── GET all saved encounters for current user ────────────────────────────
router.get('/encounters/saved', async function(req, res) {
try {
var rows = await db.all(
"SELECT id, label, enc_type, status, created_at, updated_at, expires_at, LEFT(transcript, 200) as transcript_preview, LEFT(generated_note, 200) as note_preview FROM saved_encounters WHERE user_id = $1 AND expires_at > NOW() ORDER BY updated_at DESC",
[req.user.id]
);
res.json({ success: true, encounters: rows });
} catch (e) { logger.error('GET /encounters/saved', e.message); res.status(500).json({ error: e.message }); }
});
// ── GET single saved encounter ───────────────────────────────────────────
router.get('/encounters/saved/:id', async function(req, res) {
try {
var row = await db.get(
'SELECT * FROM saved_encounters WHERE id = $1 AND user_id = $2 AND expires_at > NOW()',
[req.params.id, req.user.id]
);
if (!row) return res.status(404).json({ error: 'Encounter not found or expired' });
res.json({ success: true, encounter: row });
} catch (e) { logger.error('GET /encounters/saved/:id', e.message); res.status(500).json({ error: e.message }); }
});
// ── POST save/update encounter progress ─────────────────────────────────
router.post('/encounters/saved', async function(req, res) {
try {
var { id, label, enc_type, transcript, generated_note, partial_data, status, idempotency_key } = req.body;
var autoDeleteDays = parseInt(await db.getSetting('site.auto_delete_days') || '7', 10);
if (id) {
// Update existing
var existing = await db.get('SELECT id FROM saved_encounters WHERE id = $1 AND user_id = $2', [id, req.user.id]);
if (!existing) return res.status(404).json({ error: 'Not found' });
await db.run(
'UPDATE saved_encounters SET label=$1, transcript=$2, generated_note=$3, partial_data=$4, status=$5, updated_at=NOW() WHERE id=$6 AND user_id=$7',
[
label || 'Untitled',
transcript || '',
generated_note || '',
typeof partial_data === 'object' ? JSON.stringify(partial_data) : (partial_data || '{}'),
status || 'active',
id, req.user.id
]
);
res.json({ success: true, id: id });
} else {
// Enforce unique label per user (within active/non-expired encounters)
if (label && label.trim()) {
var labelDup = await db.get(
"SELECT id FROM saved_encounters WHERE user_id = $1 AND LOWER(label) = LOWER($2) AND expires_at > NOW()",
[req.user.id, label.trim()]
);
if (labelDup) {
return res.status(409).json({ error: 'An encounter with this label already exists. Use a unique label or load the existing one.' });
}
}
// Check for duplicate via idempotency_key
if (idempotency_key) {
var dup = await db.get(
'SELECT id FROM saved_encounters WHERE user_id = $1 AND idempotency_key = $2',
[req.user.id, idempotency_key]
);
if (dup) {
// Update existing instead of creating duplicate
await db.run(
'UPDATE saved_encounters SET label=$1, transcript=$2, generated_note=$3, partial_data=$4, status=$5, updated_at=NOW() WHERE id=$6 AND user_id=$7',
[
label || 'Untitled',
transcript || '',
generated_note || '',
typeof partial_data === 'object' ? JSON.stringify(partial_data) : (partial_data || '{}'),
status || 'active',
dup.id, req.user.id
]
);
return res.json({ success: true, id: dup.id });
}
}
// Create new
var result = await db.run(
'INSERT INTO saved_encounters (user_id, label, enc_type, transcript, generated_note, partial_data, status, idempotency_key, expires_at) VALUES ($1,$2,$3,$4,$5,$6,$7,$8, NOW() + ($9 || \' days\')::INTERVAL)',
[
req.user.id,
label || 'Untitled',
enc_type || 'encounter',
transcript || '',
generated_note || '',
typeof partial_data === 'object' ? JSON.stringify(partial_data) : (partial_data || '{}'),
status || 'active',
idempotency_key || null,
autoDeleteDays
]
);
res.json({ success: true, id: result.lastInsertRowid });
}
} catch (e) { logger.error('POST /encounters/saved', e.message); res.status(500).json({ error: e.message }); }
});
// ── DELETE saved encounter ───────────────────────────────────────────────
router.delete('/encounters/saved/:id', async function(req, res) {
try {
var result = await db.run(
'DELETE FROM saved_encounters WHERE id = $1 AND user_id = $2',
[req.params.id, req.user.id]
);
if (result.changes === 0) return res.status(404).json({ error: 'Not found' });
res.json({ success: true });
} catch (e) { logger.error('DELETE /encounters/saved/:id', e.message); res.status(500).json({ error: e.message }); }
});
module.exports = router;