Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions backend/actions/CaseReport/buildDocumentDataForAISummary.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
'use strict';

/**
* Load full documents from the app DB for each case-report document entry (for AI prompts).
* @param {import('mongoose').Connection} db
* @param {Array<{ documentId?: unknown, document?: unknown, documentModel: string, notes?: string }>} docEntries
*/
module.exports = async function buildDocumentDataForAISummary(db, docEntries) {
const documentData = [];
if (!Array.isArray(docEntries)) {
return documentData;
}
for (const docEntry of docEntries) {
if (!docEntry || !docEntry.documentModel) {
continue;
}
const rawId = docEntry.documentId != null ? docEntry.documentId : docEntry.document;
if (rawId == null || rawId === '') {
continue;
}
let documentId = rawId;
if (documentId != null && typeof documentId === 'object' && typeof documentId.toString === 'function') {
documentId = documentId.toString();
} else {
documentId = String(documentId);
}
try {
const Model = db.models[docEntry.documentModel];
if (!Model) {
continue;
}
const doc = await Model.findById(documentId).setOptions({ sanitizeFilter: true }).lean();
if (doc) {
documentData.push({
model: docEntry.documentModel,
documentId: documentId.toString(),
data: doc,
notes: docEntry.notes || ''
});
}
} catch (err) {
console.error(`Error fetching document ${documentId} from model ${docEntry.documentModel}:`, err);
}
}
return documentData;
};
93 changes: 93 additions & 0 deletions backend/actions/CaseReport/createCaseReport.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
'use strict';

const Archetype = require('archetype');
const authorize = require('../../authorize');

function escapeRegExp(str) {
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}

const DocumentsParams = new Archetype({
documentId: {
$type: 'string',
$required: true
},
documentModel: {
$type: 'string'
},
highlights: {
$type: ['string']
},
notes: {
$type: 'string'
}
}).compile('DocumentsParams');

const CreateCaseReportParams = new Archetype({
name: {
$type: 'string',
$required: true
},
// Array of documents associated with this case report
documents: {
$type: [DocumentsParams]
},
roles: {
$type: ['string']
}
}).compile('CreateCaseReportParams');

module.exports = ({ db }) => async function createCaseReport(params) {
const { name, documents, roles } = new CreateCaseReportParams(params);
const CaseReport = db.model('__Studio_CaseReport');

await authorize('CaseReport.createCaseReport', roles);

const normalizedName = name.trim();

// Count existing case reports with this base name or suffixed with (x)
const base = escapeRegExp(normalizedName);
const namePattern = new RegExp(`^${base}( \\(\\d+\\))?$`);
const existingCount = await CaseReport.countDocuments({ name: { $regex: namePattern } });

const finalName = existingCount > 0 ? `${normalizedName} (${existingCount})` : normalizedName;
const docs = Array.isArray(documents)
? documents
.filter(doc => doc && doc.documentId != null && doc.documentModel)
.map(doc => {
let documentId = doc.documentId;
if (documentId != null && typeof documentId === 'object' && typeof documentId.toString === 'function') {
documentId = documentId.toString();
} else if (documentId != null) {
documentId = String(documentId);
}
return {
documentId,
documentModel: doc.documentModel,
...(doc.highlightedFields ? { highlightedFields: doc.highlightedFields } : {}),
...(doc.notes ? { notes: doc.notes } : {})
};
})
: [];
console.log('document created');
let created = null;
try {
created = await CaseReport.create({
name: finalName,
documents: docs
});
} catch (err) {
console.log('mongoose error', err);
}
console.log('creating the document', created);

const caseReport = await CaseReport.findById(created._id).lean();
if (!caseReport) {
throw new Error('Case report created but could not be read back');
}

console.log('document queried');

console.log('document is lean');
return { caseReport };
};
82 changes: 82 additions & 0 deletions backend/actions/CaseReport/generateCaseReportAISummary.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
'use strict';

const Archetype = require('archetype');
const authorize = require('../../authorize');
const callLLM = require('../../integrations/callLLM');
const buildDocumentDataForAISummary = require('./buildDocumentDataForAISummary');

const GenerateCaseReportAISummaryParams = new Archetype({
caseReportId: {
$type: 'string',
$required: true
},
roles: {
$type: ['string']
}
}).compile('GenerateCaseReportAISummaryParams');

module.exports = ({ db, options }) => async function generateCaseReportAISummary(params) {
const { caseReportId: rawCaseReportId, roles } = new GenerateCaseReportAISummaryParams(params);
const CaseReport = db.model('__Studio_CaseReport');

await authorize('CaseReport.generateCaseReportAISummary', roles);

const caseReportId = rawCaseReportId != null && typeof rawCaseReportId === 'object' && typeof rawCaseReportId.toString === 'function'
? rawCaseReportId.toString()
: String(rawCaseReportId ?? '');
if (!caseReportId) {
throw new Error('Case report ID is required');
}

const existing = await CaseReport.findById(caseReportId).lean();
if (!existing) {
throw new Error('Case report not found');
}

const summary = typeof existing.summary === 'string' ? existing.summary : '';
if (!summary.trim()) {
throw new Error('Add a case summary before generating an AI summary.');
}

const documents = Array.isArray(existing.documents) ? existing.documents : [];
const documentData = await buildDocumentDataForAISummary(db, documents);

const documentsContext = documentData.map(doc => {
return `Model: ${doc.model}\nDocument ID: ${doc.documentId}\n${doc.notes ? `Notes: ${doc.notes}\n` : ''}Data: ${JSON.stringify(doc.data, null, 2)}`;
}).join('\n\n---\n\n');

const systemPrompt = 'You are a technical writing assistant that improves case report summaries. Your task is to enhance the user\'s summary by incorporating specific details from the document data and investigation notes. Write in clear, professional markdown format. Use the document data to illustrate and support the points made in the summary.';

const userPrompt = `Improve and enhance the following case report summary using the document data and investigation notes provided below. Make it more detailed, professional, and well-structured. Use specific examples from the document data to illustrate key points. Format the response as markdown.\n\nUser's Summary:\n${summary}\n\nDocument Data and Investigation Notes:\n${documentsContext}`;

let aiSummary = null;
try {
const llmResponse = await callLLM(
[{
role: 'user',
content: [{
type: 'text',
text: userPrompt
}]
}],
systemPrompt,
options
);
aiSummary = llmResponse.text;
} catch (err) {
console.error('Error generating AI summary:', err);
throw new Error(err && err.message ? err.message : 'Error generating AI summary');
}

const caseReport = await CaseReport.findByIdAndUpdate(
caseReportId,
{ AISummary: aiSummary },
{ new: true }
).lean();

if (!caseReport) {
throw new Error('Case report not found');
}

return { caseReport, aiSummary };
};
28 changes: 28 additions & 0 deletions backend/actions/CaseReport/getCaseReport.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
'use strict';

const Archetype = require('archetype');
const authorize = require('../../authorize');

const GetCaseReportParams = new Archetype({
caseReportId: {
$type: 'string',
$required: true
},
roles: {
$type: ['string']
}
}).compile('GetCaseReportParams');

module.exports = ({ db }) => async function getCaseReport(params) {
const { caseReportId, roles } = new GetCaseReportParams(params);
const CaseReport = db.model('__Studio_CaseReport');

await authorize('CaseReport.getCaseReports', roles);

const caseReport = await CaseReport.findById(caseReportId).lean();
if (!caseReport) {
throw new Error('Case report not found');
}

return { caseReport };
};
21 changes: 21 additions & 0 deletions backend/actions/CaseReport/getCaseReports.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
'use strict';

const Archetype = require('archetype');
const authorize = require('../../authorize');

const GetCaseReportsParams = new Archetype({
roles: {
$type: ['string']
}
}).compile('GetCaseReportsParams');

module.exports = ({ db }) => async function getCaseReports(params) {
const { roles } = new GetCaseReportsParams(params);
const CaseReport = db.model('__Studio_CaseReport');

await authorize('CaseReport.getCaseReports', roles);

const caseReports = await CaseReport.find({}).sort({ createdAt: -1 }).lean();

return { caseReports };
};
7 changes: 7 additions & 0 deletions backend/actions/CaseReport/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
'use strict';

exports.createCaseReport = require('./createCaseReport');
exports.getCaseReports = require('./getCaseReports');
exports.getCaseReport = require('./getCaseReport');
exports.updateCaseReport = require('./updateCaseReport');
exports.generateCaseReportAISummary = require('./generateCaseReportAISummary');
Loading
Loading