Skip to content
Merged
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
22 changes: 12 additions & 10 deletions backend/src/document-processor/services/aws-bedrock.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,24 @@ import {
import { RateLimiter } from '../../utils/security.utils';
import { createHash } from 'crypto';

export interface LabValue {
name: string;
value: string;
unit: string;
normalRange: string;
status: 'normal' | 'high' | 'low';
isCritical: boolean;
conclusion: string;
suggestions: string;
}

/**
* Interface for medical document analysis result
*/
export interface MedicalDocumentAnalysis {
title: string;
category: string;
labValues: Array<{
name: string;
value: string;
unit: string;
normalRange: string;
status: 'normal' | 'high' | 'low';
isCritical: boolean;
conclusion: string;
suggestions: string;
}>;
labValues: LabValue[];
medicalComments: string;
metadata: {
isMedicalReport: boolean;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,12 @@ describe('DocumentProcessorService', () => {
// Create a new test-specific instance with proper mocking
const testTextractService = { extractText: vi.fn() };
const testBedrockService = { analyzeMedicalDocument: vi.fn() };
const testPerplexityService = { reviewMedicalAnalysis: vi.fn() };
const testPerplexityService = { reviewLabValuesAnalysis: vi.fn() };

// Set up mocks
testTextractService.extractText.mockResolvedValue(extractedTextResult);
testBedrockService.analyzeMedicalDocument.mockResolvedValue(medicalAnalysis);
testPerplexityService.reviewMedicalAnalysis.mockResolvedValue(medicalAnalysis);
testPerplexityService.reviewLabValuesAnalysis.mockResolvedValue(medicalAnalysis);

// Create a fresh service instance with our mocks
const testService = new DocumentProcessorService(
Expand All @@ -71,7 +71,7 @@ describe('DocumentProcessorService', () => {
extractedTextResult.rawText,
userId,
);
expect(testPerplexityService.reviewMedicalAnalysis).toHaveBeenCalled();
expect(testPerplexityService.reviewLabValuesAnalysis).toHaveBeenCalled();
expect(result).toEqual({
extractedText: extractedTextResult,
analysis: medicalAnalysis,
Expand All @@ -89,7 +89,7 @@ describe('DocumentProcessorService', () => {
// Create test-specific service with proper mocking
const testTextractService = { extractText: vi.fn() };
const testBedrockService = { analyzeMedicalDocument: vi.fn() };
const testPerplexityService = { reviewMedicalAnalysis: vi.fn() };
const testPerplexityService = { reviewLabValuesAnalysis: vi.fn() };

// Make the mock reject with an error
testTextractService.extractText.mockRejectedValue(new Error('Failed to extract text'));
Expand Down Expand Up @@ -120,7 +120,7 @@ describe('DocumentProcessorService', () => {
// Create test-specific service with proper mocking
const testTextractService = { extractText: vi.fn() };
const testBedrockService = { analyzeMedicalDocument: vi.fn() };
const testPerplexityService = { reviewMedicalAnalysis: vi.fn() };
const testPerplexityService = { reviewLabValuesAnalysis: vi.fn() };

// Create a fresh service instance with our mocks
const testService = new DocumentProcessorService(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,29 +54,26 @@ export class DocumentProcessorService {
});

// Step 2: Analyze extracted text using AWS Bedrock
const initialAnalysis = await this.bedrockService.analyzeMedicalDocument(
const analysis = await this.bedrockService.analyzeMedicalDocument(
extractedText.rawText,
userId,
);

// Step 3: Review and verify analysis using Perplexity
this.logger.log('Reviewing medical analysis with Perplexity');

let analysis: MedicalDocumentAnalysis;

try {
const verifiedAnalysis = await this.perplexityService.reviewMedicalAnalysis(
initialAnalysis,
const verifiedLabValues = await this.perplexityService.reviewLabValuesAnalysis(
analysis.labValues,
extractedText.rawText,
);
analysis = verifiedAnalysis;
analysis.labValues = verifiedLabValues;

this.logger.log('Analysis verified and possibly corrected by Perplexity');
} catch (reviewError) {
this.logger.error('Error reviewing analysis with Perplexity', {
error: reviewError instanceof Error ? reviewError.message : 'Unknown error',
});
// Fall back to initial analysis if review fails
analysis = initialAnalysis;
}

const processingTime = Date.now() - startTime;
Expand Down
42 changes: 19 additions & 23 deletions backend/src/services/perplexity.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Injectable, Logger } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import axios from 'axios';
import { AwsSecretsService } from './aws-secrets.service';
import { MedicalDocumentAnalysis } from 'src/document-processor/services/aws-bedrock.service';
import { LabValue } from 'src/document-processor/services/aws-bedrock.service';

export interface PerplexityMessage {
role: 'system' | 'user' | 'assistant';
Expand Down Expand Up @@ -247,31 +247,27 @@ export class PerplexityService {
}

/**
* Reviews and verifies a medical document analysis against trusted medical sources
* Reviews and verifies a medical document lab values against trusted medical sources
*
* @param analysis The medical document analysis to review
* @param labValues The medical document lab values to review
* @param originalText The original text of the medical document
* @returns The corrected medical document analysis
* @returns The corrected medical document lab values
*/
async reviewMedicalAnalysis(
analysis: MedicalDocumentAnalysis,
originalText: string,
): Promise<any> {
this.logger.log('Reviewing medical document analysis with Perplexity');
async reviewLabValuesAnalysis(labValues: LabValue[], originalText: string): Promise<LabValue[]> {
this.logger.log('Reviewing medical document lab values with Perplexity');

const systemPrompt =
'Medical information verification specialist. Verify analysis against trusted sources (Mayo Clinic, Cleveland Clinic, CDC, NIH, WHO, medical journals). Ensure accuracy of lab ranges, interpretations, and recommendations. Return only corrected JSON. IMPORTANT: Do not modify the metadata object.';
'Medical information verification specialist. Verify lab values against trusted sources (Mayo Clinic, Cleveland Clinic, CDC, NIH, WHO, medical journals). Ensure accuracy of lab ranges, interpretations, and recommendations. Return only corrected JSON. IMPORTANT: Do not modify the metadata object.';

const analysisJson = JSON.stringify(analysis, null, 2);
const labValuesJson = JSON.stringify(labValues, null, 2);

const userPrompt =
`Review this medical analysis for accuracy. Verify:\n` +
`Review this medical lab values for accuracy. Verify:\n` +
`1. Lab value reference ranges\n` +
`2. Interpretations of abnormal values\n` +
`3. Medical conclusions and recommendations\n` +
`4. Lab value categorizations\n\n` +
`CRITICAL INSTRUCTION: Do NOT modify the metadata object.\n\n` +
`Analysis JSON:\n${analysisJson}\n\n` +
`Analysis JSON:\n${labValuesJson}\n\n` +
`Original Text:\n${originalText}\n\n` +
`Return ONLY corrected JSON with identical structure. No preamble, explanation, or text before/after JSON.`;

Expand All @@ -283,30 +279,30 @@ export class PerplexityService {
try {
const response = await this.createChatCompletion(messages, {
temperature: 0.3, // Lower temperature for more accurate/factual responses
maxTokens: 4000, // Ensure there's enough space for the full corrected analysis
maxTokens: 4000, // Ensure there's enough space for the full corrected labValues
responseFormat: { type: 'json_object' }, // Use JSON mode for reliable JSON response
});

// Parse the response to get the corrected analysis
// Parse the response to get the corrected labValues
const responseText = response.choices[0].message.content.trim();

try {
// Try to parse as JSON - Perplexity should return the corrected JSON
const correctedAnalysis = JSON.parse(responseText);
return correctedAnalysis;
const correctedLabValues = JSON.parse(responseText);
return correctedLabValues;
} catch (jsonParseError) {
// If parsing fails, log the error but return the original analysis
// If parsing fails, log the error but return the original labValues
this.logger.error(
`Failed to parse Perplexity review response as JSON: ${jsonParseError instanceof Error ? jsonParseError.message : 'Unknown error'}`,
);
return analysis;
return labValues;
}
} catch (error) {
// If the API call fails, log the error but return the original analysis
// If the API call fails, log the error but return the original labValues
this.logger.error(
`Error during medical analysis review: ${error instanceof Error ? error.message : 'Unknown error'}`,
`Error during medical labValues review: ${error instanceof Error ? error.message : 'Unknown error'}`,
);
return analysis;
return labValues;
}
}
}