diff --git a/backend/src/document-processor/services/aws-bedrock.service.ts b/backend/src/document-processor/services/aws-bedrock.service.ts index eff4d96..30e2c1f 100644 --- a/backend/src/document-processor/services/aws-bedrock.service.ts +++ b/backend/src/document-processor/services/aws-bedrock.service.ts @@ -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; diff --git a/backend/src/document-processor/services/document-processor.service.spec.ts b/backend/src/document-processor/services/document-processor.service.spec.ts index d97f823..e4b2cfd 100644 --- a/backend/src/document-processor/services/document-processor.service.spec.ts +++ b/backend/src/document-processor/services/document-processor.service.spec.ts @@ -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( @@ -71,7 +71,7 @@ describe('DocumentProcessorService', () => { extractedTextResult.rawText, userId, ); - expect(testPerplexityService.reviewMedicalAnalysis).toHaveBeenCalled(); + expect(testPerplexityService.reviewLabValuesAnalysis).toHaveBeenCalled(); expect(result).toEqual({ extractedText: extractedTextResult, analysis: medicalAnalysis, @@ -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')); @@ -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( diff --git a/backend/src/document-processor/services/document-processor.service.ts b/backend/src/document-processor/services/document-processor.service.ts index e248e2b..6daecf9 100644 --- a/backend/src/document-processor/services/document-processor.service.ts +++ b/backend/src/document-processor/services/document-processor.service.ts @@ -54,7 +54,7 @@ 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, ); @@ -62,21 +62,18 @@ export class DocumentProcessorService { // 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; diff --git a/backend/src/services/perplexity.service.ts b/backend/src/services/perplexity.service.ts index 4c7f103..fe56901 100644 --- a/backend/src/services/perplexity.service.ts +++ b/backend/src/services/perplexity.service.ts @@ -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'; @@ -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 { - this.logger.log('Reviewing medical document analysis with Perplexity'); + async reviewLabValuesAnalysis(labValues: LabValue[], originalText: string): Promise { + 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.`; @@ -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; } } }