fix clinical assistant export cleanup

This commit is contained in:
Daniel 2026-05-07 22:48:51 +02:00
parent baf05198a8
commit ab76a9f2f5

View file

@ -243,7 +243,7 @@ router.post('/clinical-assistant/chat', async function(req, res) {
var answer = stripModelSourcesSection(String(ai.content || '').trim());
logger.audit(req.user.id, 'clinical_assistant_query', 'Clinical assistant query', req, {
category: 'clinical', model: ai.model || chatModel, duration: Date.now() - started
category: 'clinical', model: ai.model || prepared.chatModel, duration: Date.now() - started
});
res.json({
@ -449,44 +449,6 @@ async function prepareAssistantChat(body) {
};
}
router.post('/clinical-assistant/export-summary', async function(req, res) {
try {
if (Array.isArray(req.body.items) && req.body.items.length) {
var exportItems = await summarizeExportItems(req.body.items);
return res.json({ success: true, items: exportItems });
}
var answer = String(req.body.answer || '').trim();
var sources = Array.isArray(req.body.sources) ? req.body.sources.slice(0, 20) : [];
if (!answer) return res.status(400).json({ error: 'Answer is required' });
var chatModel = await getSetting('clinical_assistant.chat_model', '') || await getSetting('models.default', '');
var sourceList = sources.map(function(s, idx) {
var n = s.number || idx + 1;
return '[' + n + '] ' + cleanTitle(s.title || s.resource || 'Source') + (s.page ? ', page ' + s.page : '');
}).join('\n');
var ai = await callAI([
{
role: 'system',
content: 'Create an abridged clinical export summary. Preserve factual meaning. Use only citation numbers already present in the original answer and provided source list. Do not invent, renumber, merge, or move citations. If a claim cannot keep its citation, omit the claim. No references section.'
},
{
role: 'user',
content: 'Original answer:\n' + answer.substring(0, 8000) + '\n\nAvailable cited sources:\n' + sourceList + '\n\nReturn a concise export version with the same citation numbers where supported.'
}
], {
model: chatModel || undefined,
temperature: 0.05,
maxTokens: 900
});
var summary = stripModelSourcesSection(String(ai.content || '').trim());
res.json({ success: true, summary: summary || answer, model: ai.model || chatModel || null });
} catch (e) {
res.status(500).json({ error: assistantErrorMessage(e) });
}
});
async function rewriteSearchQuery(message, history, chatModel) {
message = String(message || '').trim();
history = Array.isArray(history) ? history.filter(function(m) { return m && (m.role === 'user' || m.role === 'assistant') && m.content; }).slice(-8) : [];
@ -557,63 +519,6 @@ function previousAssistantTopic(history) {
return '';
}
async function summarizeExportItems(items) {
var chatModel = await getSetting('clinical_assistant.chat_model', '') || await getSetting('models.default', '');
var clean = items.slice(0, 20).map(function(item, idx) {
return {
question: clip(item.question || ('Question ' + (idx + 1)), 1000),
answer: clip(item.answer || '', 12000),
sources: Array.isArray(item.sources) ? item.sources.slice(0, 25) : []
};
}).filter(function(item) { return item.answer; });
var out = [];
for (var i = 0; i < clean.length; i++) {
out.push(await summarizeExportItem(clean[i], i, chatModel));
}
return out;
}
async function summarizeExportItem(item, idx, chatModel) {
var sourceList = item.sources.map(function(s, sidx) {
var n = s.number || sidx + 1;
return '[' + n + '] ' + cleanTitle(s.title || s.resource || 'Source') + (s.page ? ', page ' + s.page : '');
}).join('\n');
try {
var ai = await callAI([
{
role: 'system',
content: 'Create a clinical export section heading and abridged summary. Return strict JSON only: {"heading":"...","summary":"..."}. The heading must be specific and clinically meaningful, not a vague follow-up like "What dose?". Preserve factual meaning. Use only citation numbers already present in the answer and provided source list. Do not invent, renumber, merge, or move citations. No references section.'
},
{
role: 'user',
content: 'User question:\n' + item.question + '\n\nAnswer:\n' + item.answer + '\n\nAvailable cited sources:\n' + sourceList
}
], {
model: chatModel || undefined,
temperature: 0.05,
maxTokens: 700
});
var parsed = parseJsonObject(String(ai.content || ''));
return {
question: item.question,
heading: cleanExportHeading(parsed.heading || item.question, idx),
summary: stripModelSourcesSection(String(parsed.summary || '').trim()) || item.answer,
answer: item.answer,
sources: item.sources
};
} catch (e) {
return {
question: item.question,
heading: cleanExportHeading(item.question, idx),
summary: item.answer,
answer: item.answer,
sources: item.sources
};
}
}
async function semanticSearch(query, opts) {
return callMcpTool('nc_semantic_search', {
query: query,
@ -1136,23 +1041,6 @@ function cleanSavedChatTitle(title) {
return clip(title, MAX_SAVED_CHAT_TITLE).replace(/[\r\n\t]+/g, ' ').replace(/\s+/g, ' ').trim() || 'Clinical assistant chat';
}
function parseJsonObject(text) {
text = String(text || '').trim().replace(/^```(?:json)?\s*/i, '').replace(/\s*```$/i, '');
try { return JSON.parse(text); } catch (e) {}
var start = text.indexOf('{');
var end = text.lastIndexOf('}');
if (start !== -1 && end > start) {
try { return JSON.parse(text.slice(start, end + 1)); } catch (e2) {}
}
return {};
}
function cleanExportHeading(heading, idx) {
heading = clip(heading, 120).replace(/^#+\s*/, '').replace(/[\r\n\t]+/g, ' ').replace(/\s+/g, ' ').trim();
if (!heading || /^(what|which|when|why|how|and|also|dose\??|what dose\??)$/i.test(heading)) return 'Clinical Question ' + (idx + 1);
return heading;
}
async function getAvailableExamples() {
return promptPool.getAvailableExamples();
}