diff --git a/backend/PERPLEXITY_INTEGRATION.md b/backend/PERPLEXITY_INTEGRATION.md index fd59da46..6278461f 100644 --- a/backend/PERPLEXITY_INTEGRATION.md +++ b/backend/PERPLEXITY_INTEGRATION.md @@ -5,6 +5,7 @@ This document describes the integration of the Perplexity API in the Medical Rep ## Overview The integration enables the backend to leverage Perplexity's AI capabilities to: + 1. Explain medical text in simpler terms 2. Support custom chat completions for more flexible AI interactions @@ -47,25 +48,12 @@ The API key is securely managed using AWS Secrets Manager: 2. The application retrieves the key at runtime using the AWS SDK 3. The key is cached for 15 minutes to minimize API calls to Secrets Manager -### Service Functionality - -The `PerplexityService` provides the following methods: - -1. `createChatCompletion`: Sends a chat completion request to the Perplexity API -2. `explainMedicalText`: Specializes in explaining medical text in simple terms - -### API Endpoints - -The `PerplexityController` exposes the following endpoints: - -1. `POST /api/perplexity/explain`: Explains medical text in simpler terms -2. `POST /api/perplexity/chat/completions`: Creates a custom chat completion - ## Setup Instructions ### AWS Secrets Manager Setup 1. Create a secret in AWS Secrets Manager: + ``` aws secretsmanager create-secret \ --name med-ai-perplexity-key \ @@ -80,9 +68,7 @@ The `PerplexityController` exposes the following endpoints: "Statement": [ { "Effect": "Allow", - "Action": [ - "secretsmanager:GetSecretValue" - ], + "Action": ["secretsmanager:GetSecretValue"], "Resource": "arn:aws:secretsmanager:region:account-id:secret:med-ai-perplexity-key-*" } ] @@ -93,12 +79,12 @@ The `PerplexityController` exposes the following endpoints: Configure the following environment variables: -| Variable | Description | Default Value | -|----------|-------------|---------------| +| Variable | Description | Default Value | +| -------------------------------- | ----------------------------------------- | ----------------------- | | `PERPLEXITY_API_KEY_SECRET_NAME` | Name of the secret in AWS Secrets Manager | `med-ai-perplexity-key` | -| `PERPLEXITY_MODEL` | Perplexity model to use | `mixtral-8x7b-instruct` | -| `PERPLEXITY_MAX_TOKENS` | Maximum tokens to generate | `2048` | -| `AWS_REGION` | AWS region for Secrets Manager | `us-east-1` | +| `PERPLEXITY_MODEL` | Perplexity model to use | `mixtral-8x7b-instruct` | +| `PERPLEXITY_MAX_TOKENS` | Maximum tokens to generate | `2048` | +| `AWS_REGION` | AWS region for Secrets Manager | `us-east-1` | ## Local Development @@ -114,18 +100,6 @@ Then modify the `getApiKey` method in `PerplexityService` to check for this envi The frontend can interact with the Perplexity API through the following endpoints: -### Explain Medical Text - -```typescript -// Example frontend code -const explainMedicalText = async (text: string) => { - const response = await axios.post('/api/perplexity/explain', { - medicalText: text - }); - return response.data.explanation; -}; -``` - ### Custom Chat Completion ```typescript @@ -133,8 +107,8 @@ const explainMedicalText = async (text: string) => { const createChatCompletion = async (messages: any[]) => { const response = await axios.post('/api/perplexity/chat/completions', { messages: messages, - temperature: 0.7 + temperature: 0.7, }); return response.data.explanation; }; -``` \ No newline at end of file +``` diff --git a/backend/src/app.module.spec.ts b/backend/src/app.module.spec.ts index 018a5d00..f4acb322 100644 --- a/backend/src/app.module.spec.ts +++ b/backend/src/app.module.spec.ts @@ -50,7 +50,6 @@ describe('AppModule', () => { generateResponse: vi.fn().mockResolvedValue('test response'), analyzeMedicalDocument: vi.fn().mockResolvedValue({ labValues: [], - diagnoses: [], metadata: { isMedicalReport: true, confidence: 0.9, diff --git a/backend/src/controllers/perplexity/perplexity.controller.ts b/backend/src/controllers/perplexity/perplexity.controller.ts index acc0e888..7b402930 100644 --- a/backend/src/controllers/perplexity/perplexity.controller.ts +++ b/backend/src/controllers/perplexity/perplexity.controller.ts @@ -1,6 +1,6 @@ import { Body, Controller, Post, HttpException, HttpStatus, Logger } from '@nestjs/common'; import { PerplexityService, PerplexityMessage } from '../../services/perplexity.service'; -import { ExplainMedicalTextDto, ChatCompletionDto, PerplexityResponseDto } from './perplexity.dto'; +import { ChatCompletionDto, PerplexityResponseDto } from './perplexity.dto'; /** * Controller for Perplexity API endpoints @@ -11,27 +11,6 @@ export class PerplexityController { constructor(private readonly perplexityService: PerplexityService) {} - /** - * Explains medical text in simpler terms - * - * @param dto The DTO containing medical text to explain - * @returns The simplified explanation - */ - @Post('explain') - async explainMedicalText(@Body() dto: ExplainMedicalTextDto): Promise { - try { - const explanation = await this.perplexityService.explainMedicalText(dto.medicalText); - return { explanation }; - } catch (error: unknown) { - const errorMessage = error instanceof Error ? error.message : 'Unknown error'; - this.logger.error(`Failed to explain medical text: ${errorMessage}`); - throw new HttpException( - 'Failed to process medical text explanation', - HttpStatus.INTERNAL_SERVER_ERROR, - ); - } - } - /** * Creates a custom chat completion * diff --git a/backend/src/document-processor/controllers/document-processor.controller.ts b/backend/src/document-processor/controllers/document-processor.controller.ts index 8c7e3f50..488c33e0 100644 --- a/backend/src/document-processor/controllers/document-processor.controller.ts +++ b/backend/src/document-processor/controllers/document-processor.controller.ts @@ -155,8 +155,7 @@ export class DocumentProcessorController { report.missingInformation = result.analysis.metadata.missingInformation; } - // Create summary from simplified explanation or diagnoses - report.summary = result.simplifiedExplanation!; + report.medicalComments = result.analysis.medicalComments!; report.updatedAt = new Date().toISOString(); diff --git a/backend/src/document-processor/services/aws-bedrock.service.spec.ts b/backend/src/document-processor/services/aws-bedrock.service.spec.ts index 6548cf55..9e55f77f 100644 --- a/backend/src/document-processor/services/aws-bedrock.service.spec.ts +++ b/backend/src/document-processor/services/aws-bedrock.service.spec.ts @@ -80,7 +80,6 @@ vi.mock('@aws-sdk/client-bedrock-runtime', () => { suggestions: 'Continue regular health maintenance.', }, ], - diagnoses: [], metadata: { isMedicalReport: true, confidence: 0.95, @@ -142,7 +141,7 @@ describe('AwsBedrockService', () => { suggestions: 'Continue regular health maintenance.', }, ], - diagnoses: [], + medicalComments: 'Patient hemoglobin levels are within normal range.', metadata: { isMedicalReport: true, confidence: 0.95, @@ -293,10 +292,9 @@ describe('AwsBedrockService', () => { const invalidResponses = [ null, {}, - { labValues: [], diagnoses: [] }, // Missing metadata + { labValues: [] }, // Missing metadata { labValues: [], - diagnoses: [], metadata: { isMedicalReport: 'not a boolean', confidence: 0.5, missingInformation: [] }, }, ]; @@ -325,7 +323,7 @@ describe('AwsBedrockService', () => { suggestions: 'No action needed', }, ], - diagnoses: [], + medicalComments: '', metadata: { isMedicalReport: true, confidence: 0.9, diff --git a/backend/src/document-processor/services/aws-bedrock.service.ts b/backend/src/document-processor/services/aws-bedrock.service.ts index 4b851d60..eff4d96a 100644 --- a/backend/src/document-processor/services/aws-bedrock.service.ts +++ b/backend/src/document-processor/services/aws-bedrock.service.ts @@ -24,7 +24,7 @@ export interface MedicalDocumentAnalysis { conclusion: string; suggestions: string; }>; - diagnoses: Array<{ condition: string; details: string; recommendations: string }>; + medicalComments: string; metadata: { isMedicalReport: boolean; confidence: number; @@ -50,7 +50,7 @@ export class AwsBedrockService { 1. Title/subject from content 2. Category: "heart" (cardiac focus), "brain" (neurological focus), or "general" (all else) 3. Lab values with ranges and status (normal/high/low) -4. Diagnoses, findings, and recommendations +4. Medical comments, if there are any, if not, return empty string 5. Medical document verification with confidence level Reference trusted sources: Mayo Clinic, Cleveland Clinic, CDC, NIH, WHO, AMA, etc. @@ -60,7 +60,7 @@ Return ONLY a JSON object with this structure: "title": string, "category": string, "labValues": [{"name": string, "value": string, "unit": string, "normalRange": string, "status": "normal" | "high" | "low", "isCritical": boolean, "conclusion": string, "suggestions": string}], - "diagnoses": [{"condition": string, "details": string, "recommendations": string}], + "medicalComments": string, "metadata": { "isMedicalReport": boolean, "confidence": number, @@ -365,7 +365,6 @@ Document text: typeof response.title !== 'string' || typeof response.category !== 'string' || !Array.isArray(response.labValues) || - !Array.isArray(response.diagnoses) || !response.metadata ) { throw new BadRequestException('Invalid medical analysis response structure'); 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 c29c54b7..d97f8237 100644 --- a/backend/src/document-processor/services/document-processor.service.spec.ts +++ b/backend/src/document-processor/services/document-processor.service.spec.ts @@ -37,7 +37,7 @@ describe('DocumentProcessorService', () => { title: 'Test Report', category: 'general', labValues: [], - diagnoses: [], + medicalComments: '', metadata: { isMedicalReport: true, confidence: 0.9, @@ -45,17 +45,15 @@ describe('DocumentProcessorService', () => { }, }; - const simplifiedExplanation = 'This is a simple explanation of the medical document.'; - // Create a new test-specific instance with proper mocking const testTextractService = { extractText: vi.fn() }; const testBedrockService = { analyzeMedicalDocument: vi.fn() }; - const testPerplexityService = { explainMedicalText: vi.fn() }; + const testPerplexityService = { reviewMedicalAnalysis: vi.fn() }; // Set up mocks testTextractService.extractText.mockResolvedValue(extractedTextResult); testBedrockService.analyzeMedicalDocument.mockResolvedValue(medicalAnalysis); - testPerplexityService.explainMedicalText.mockResolvedValue(simplifiedExplanation); + testPerplexityService.reviewMedicalAnalysis.mockResolvedValue(medicalAnalysis); // Create a fresh service instance with our mocks const testService = new DocumentProcessorService( @@ -73,13 +71,10 @@ describe('DocumentProcessorService', () => { extractedTextResult.rawText, userId, ); - expect(testPerplexityService.explainMedicalText).toHaveBeenCalledWith( - extractedTextResult.rawText, - ); + expect(testPerplexityService.reviewMedicalAnalysis).toHaveBeenCalled(); expect(result).toEqual({ extractedText: extractedTextResult, analysis: medicalAnalysis, - simplifiedExplanation, processingMetadata: expect.objectContaining({ fileSize: fileBuffer.length, }), @@ -94,7 +89,7 @@ describe('DocumentProcessorService', () => { // Create test-specific service with proper mocking const testTextractService = { extractText: vi.fn() }; const testBedrockService = { analyzeMedicalDocument: vi.fn() }; - const testPerplexityService = { explainMedicalText: vi.fn() }; + const testPerplexityService = { reviewMedicalAnalysis: vi.fn() }; // Make the mock reject with an error testTextractService.extractText.mockRejectedValue(new Error('Failed to extract text')); @@ -125,7 +120,7 @@ describe('DocumentProcessorService', () => { // Create test-specific service with proper mocking const testTextractService = { extractText: vi.fn() }; const testBedrockService = { analyzeMedicalDocument: vi.fn() }; - const testPerplexityService = { explainMedicalText: vi.fn() }; + const testPerplexityService = { reviewMedicalAnalysis: vi.fn() }; // Create a fresh service instance with our mocks const testService = new DocumentProcessorService( @@ -148,14 +143,13 @@ describe('DocumentProcessorService', () => { title: 'Document 1 Report', category: 'general', labValues: [], - diagnoses: [], + medicalComments: '', metadata: { isMedicalReport: true, confidence: 0.9, missingInformation: [], }, }, - simplifiedExplanation: 'Simple explanation for document 1', processingMetadata: { processingTimeMs: 100, fileSize: 4, @@ -173,14 +167,13 @@ describe('DocumentProcessorService', () => { title: 'Document 2 Report', category: 'general', labValues: [], - diagnoses: [], + medicalComments: '', metadata: { isMedicalReport: true, confidence: 0.9, missingInformation: [], }, }, - simplifiedExplanation: 'Simple explanation for document 2', processingMetadata: { processingTimeMs: 100, fileSize: 4, diff --git a/backend/src/document-processor/services/document-processor.service.ts b/backend/src/document-processor/services/document-processor.service.ts index e51233c7..e248e2b2 100644 --- a/backend/src/document-processor/services/document-processor.service.ts +++ b/backend/src/document-processor/services/document-processor.service.ts @@ -10,7 +10,6 @@ import { PerplexityService } from '../../services/perplexity.service'; export interface ProcessedDocumentResult { extractedText: ExtractedTextResult; analysis: MedicalDocumentAnalysis; - simplifiedExplanation?: string; processingMetadata: { processingTimeMs: number; fileSize: number; @@ -62,6 +61,7 @@ export class DocumentProcessorService { // Step 3: Review and verify analysis using Perplexity this.logger.log('Reviewing medical analysis with Perplexity'); + let analysis: MedicalDocumentAnalysis; try { @@ -79,39 +79,18 @@ export class DocumentProcessorService { analysis = initialAnalysis; } - // Step 4: Generate simplified explanation using Perplexity - let simplifiedExplanation: string | undefined; - - try { - if (analysis.metadata.isMedicalReport && extractedText.rawText) { - this.logger.log('Generating simplified explanation'); - simplifiedExplanation = await this.perplexityService.explainMedicalText( - extractedText.rawText, - ); - this.logger.log('Simplified explanation generated successfully'); - } - } catch (explanationError) { - this.logger.error('Error generating simplified explanation', { - error: explanationError instanceof Error ? explanationError.message : 'Unknown error', - }); - // We don't want to fail the entire process if explanation fails - simplifiedExplanation = undefined; - } - const processingTime = Date.now() - startTime; this.logger.log(`Document processing completed in ${processingTime}ms`, { isMedicalReport: analysis.metadata.isMedicalReport, confidence: analysis.metadata.confidence, labValueCount: analysis.labValues.length, - hasExplanation: !!simplifiedExplanation, }); // Return combined result return { extractedText, analysis, - simplifiedExplanation, processingMetadata: { processingTimeMs: processingTime, fileSize: fileBuffer.length, @@ -175,14 +154,13 @@ export class DocumentProcessorService { title: 'Failed Document', category: 'general', labValues: [], - diagnoses: [], + medicalComments: '', metadata: { isMedicalReport: false, confidence: 0, missingInformation: ['Document processing failed'], }, }, - simplifiedExplanation: undefined, processingMetadata: { processingTimeMs: 0, fileSize: doc.buffer.length, diff --git a/backend/src/reports/models/report.model.ts b/backend/src/reports/models/report.model.ts index 3ac3373c..439fd399 100644 --- a/backend/src/reports/models/report.model.ts +++ b/backend/src/reports/models/report.model.ts @@ -50,8 +50,8 @@ export class Report { suggestions: string; }>; - @ApiProperty({ description: 'Summary of the report' }) - summary: string; + @ApiProperty({ description: 'Medical comments related to the report' }) + medicalComments: string; @ApiProperty({ description: 'Confidence score of the analysis (0-100)' }) confidence: number; diff --git a/backend/src/reports/reports.service.ts b/backend/src/reports/reports.service.ts index 18985a99..d752e767 100644 --- a/backend/src/reports/reports.service.ts +++ b/backend/src/reports/reports.service.ts @@ -357,7 +357,7 @@ export class ReportsService { category: '', processingStatus: ProcessingStatus.UNPROCESSED, labValues: [], - summary: '', + medicalComments: '', confidence: 0, status: ReportStatus.UNREAD, createdAt: new Date().toISOString(), @@ -418,7 +418,7 @@ export class ReportsService { { field: 'category', value: report.category }, { field: 'processingStatus', value: report.processingStatus }, { field: 'labValues', value: report.labValues }, - { field: 'summary', value: report.summary }, + { field: 'medicalComments', value: report.medicalComments }, { field: 'confidence', value: report.confidence }, { field: 'status', value: report.status }, { field: 'missingInformation', value: report.missingInformation || [] }, diff --git a/backend/src/services/README.md b/backend/src/services/README.md deleted file mode 100644 index 6f238ac1..00000000 --- a/backend/src/services/README.md +++ /dev/null @@ -1,210 +0,0 @@ -# Document Processor Service - -This service integrates AWS Textract for text extraction and AWS Bedrock for medical analysis to process medical documents. - -## Overview - -The Document Processor Service provides a unified interface for processing medical documents through a two-step approach: - -1. Extract text from medical documents (images or PDFs) using AWS Textract -2. Analyze the extracted text using AWS Bedrock (Claude) to provide structured medical information - -## Components - -The integration consists of the following components: - -1. **DocumentProcessorService**: Main service that orchestrates the document processing workflow -2. **AwsTextractService**: Extracts text, tables, and form data from medical documents -3. **AwsBedrockService**: Analyzes medical text using Claude model to extract structured information -4. **DocumentProcessorController**: Exposes HTTP endpoints for document upload and processing - -## Data Models - -### ProcessedDocumentResult - -The result of document processing includes: - -```typescript -export interface ProcessedDocumentResult { - extractedText: ExtractedTextResult; - analysis: MedicalDocumentAnalysis; - processingMetadata: { - processingTimeMs: number; - fileSize: number; - }; -} -``` - -### ExtractedTextResult - -The raw text extraction from Textract: - -```typescript -export interface ExtractedTextResult { - rawText: string; - lines: string[]; - tables: Array<{ - rows: string[][]; - }>; - keyValuePairs: Array<{ - key: string; - value: string; - }>; -} -``` - -### MedicalDocumentAnalysis - -The structured medical information from Bedrock: - -```typescript -export interface MedicalDocumentAnalysis { - labValues: Array<{ - name: string; - value: string; - unit: string; - normalRange: string; - isNormal: 'normal' | 'high' | 'low'; - }>; - diagnoses: Array<{ condition: string; details: string; recommendations: string }>; - metadata: { - isMedicalReport: boolean; - confidence: number; - missingInformation: string[]; - }; -} -``` - -## API Endpoints - -### Process a Document - -``` -POST /api/document-processor/analyze -``` - -**Request Format:** -- Content-Type: `multipart/form-data` -- Body: Form with a file upload field named `file` -- Authorization: Bearer token required - -**Example Request:** -```bash -curl -X POST \ - "http://localhost:3000/api/document-processor/analyze" \ - -H "Authorization: Bearer YOUR_JWT_TOKEN" \ - -H "Content-Type: multipart/form-data" \ - -F "file=@/path/to/medical_report.pdf" -``` - -**Response:** -```json -{ - "extractedText": { - "rawText": "BLOOD TEST RESULTS\nPatient: John Doe\nHemoglobin: 14.2 g/dL (Normal: 13.5-17.5)", - "lines": ["BLOOD TEST RESULTS", "Patient: John Doe", "Hemoglobin: 14.2 g/dL (Normal: 13.5-17.5)"], - "tables": [], - "keyValuePairs": [ - { "key": "Patient", "value": "John Doe" }, - { "key": "Hemoglobin", "value": "14.2 g/dL (Normal: 13.5-17.5)" } - ] - }, - "analysis": { - "labValues": [ - { - "name": "Hemoglobin", - "value": "14.2", - "unit": "g/dL", - "normalRange": "13.5-17.5", - "isNormal": "normal" - } - ], - "diagnoses": [], - "metadata": { - "isMedicalReport": true, - "confidence": 0.95, - "missingInformation": [] - } - }, - "processingMetadata": { - "processingTimeMs": 2345, - "fileSize": 12345 - } -} -``` - -## Usage from Code - -```typescript -// Inject the service -constructor(private readonly documentProcessorService: DocumentProcessorService) {} - -// Process a document -async processReport(fileBuffer: Buffer, userId: string) { - try { - const result = await this.documentProcessorService.processDocument( - fileBuffer, - userId - ); - - // Use the structured medical data - const labValues = result.analysis.labValues; - const abnormalValues = labValues.filter(lab => lab.isNormal !== 'normal'); - - return result; - } catch (error) { - console.error('Error processing medical document:', error); - throw error; - } -} -``` - -## Rate Limiting - -Both services implement rate limiting based on user ID: -- AWS Textract: 20 document requests per minute by default (configurable) -- AWS Bedrock: 20 model invocations per minute by default (configurable) - -## Batch Processing - -The service supports batch processing of multiple documents: - -```typescript -const results = await documentProcessorService.processBatch( - [ - { buffer: fileBuffer1 }, - { buffer: fileBuffer2 } - ], - userId -); -``` - -## Configuration - -Configure the services through environment variables: - -```bash -# AWS Region -AWS_REGION=us-east-1 - -# AWS Credentials (if not using IAM roles) -AWS_ACCESS_KEY_ID=your-access-key -AWS_SECRET_ACCESS_KEY=your-secret-key - -# AWS Bedrock -AWS_BEDROCK_MODEL=us.anthropic.claude-3-7-sonnet-20250219-v1:0 -AWS_BEDROCK_MAX_TOKENS=2048 -AWS_BEDROCK_REQUESTS_PER_MINUTE=20 - -# AWS Textract -AWS_TEXTRACT_MAX_BATCH_SIZE=10 -AWS_TEXTRACT_DOCS_PER_MINUTE=10 -``` - -## Future Enhancements - -Planned future enhancements: -- Support for multi-page PDF processing using async APIs -- Enhanced lab report detection and categorization -- Integration with medical terminology databases -- OCR preprocessing for low-quality images \ No newline at end of file diff --git a/backend/src/services/perplexity.service.spec.ts b/backend/src/services/perplexity.service.spec.ts index f004eff1..8780b575 100644 --- a/backend/src/services/perplexity.service.spec.ts +++ b/backend/src/services/perplexity.service.spec.ts @@ -1,7 +1,7 @@ import { Test, TestingModule } from '@nestjs/testing'; import { ConfigService } from '@nestjs/config'; import axios from 'axios'; -import { PerplexityService, PerplexityMessage, PerplexityResponse } from './perplexity.service'; +import { PerplexityService, PerplexityMessage } from './perplexity.service'; import { AwsSecretsService } from './aws-secrets.service'; import { describe, it, expect, beforeEach, vi, beforeAll, afterAll } from 'vitest'; @@ -186,114 +186,4 @@ describe('PerplexityService', () => { ); }); }); - - describe('explainMedicalText', () => { - it('should call createChatCompletion with appropriate messages', async () => { - // Arrange - const medicalText = 'Medical jargon here'; - const mockResponse: Partial = { - choices: [ - { - index: 0, - message: { role: 'assistant', content: 'Simple explanation' }, - finish_reason: 'stop', - }, - ], - }; - - // Spy on the createChatCompletion method - const createChatCompletionSpy = vi - .spyOn(service, 'createChatCompletion') - .mockResolvedValueOnce(mockResponse as PerplexityResponse); - - // Act - const result = await service.explainMedicalText(medicalText); - - // Assert - expect(createChatCompletionSpy).toHaveBeenCalledWith( - expect.arrayContaining([ - expect.objectContaining({ role: 'system' }), - expect.objectContaining({ - role: 'user', - content: expect.stringContaining(medicalText), - }), - ]), - ); - expect(result).toBe('Simple explanation'); - }); - - it('should handle very long medical text', async () => { - // Arrange - const longMedicalText = 'A'.repeat(5000); // 5000 characters - const mockResponse: Partial = { - choices: [ - { - index: 0, - message: { role: 'assistant', content: 'Long text explanation' }, - finish_reason: 'stop', - }, - ], - }; - - const createChatCompletionSpy = vi - .spyOn(service, 'createChatCompletion') - .mockResolvedValueOnce(mockResponse as PerplexityResponse); - - // Act - const result = await service.explainMedicalText(longMedicalText); - - // Assert - expect(createChatCompletionSpy).toHaveBeenCalled(); - expect(result).toBe('Long text explanation'); - }); - - it('should handle errors in the underlying API call', async () => { - // Arrange - const medicalText = 'Test medical text'; - vi.spyOn(service, 'createChatCompletion').mockRejectedValueOnce(new Error('API error')); - - // Act & Assert - await expect(service.explainMedicalText(medicalText)).rejects.toThrow('API error'); - }); - - it('should handle empty medical text', async () => { - // Arrange - const emptyText = ''; - const mockResponse: Partial = { - choices: [ - { - index: 0, - message: { role: 'assistant', content: 'No text to explain' }, - finish_reason: 'stop', - }, - ], - }; - - vi.spyOn(service, 'createChatCompletion').mockResolvedValueOnce( - mockResponse as PerplexityResponse, - ); - - // Act - const result = await service.explainMedicalText(emptyText); - - // Assert - expect(service.createChatCompletion).toHaveBeenCalled(); - expect(result).toBe('No text to explain'); - }); - - it('should handle case when API returns no choices', async () => { - // Arrange - const medicalText = 'Test medical text'; - const emptyResponse: Partial = { - choices: [], - }; - - vi.spyOn(service, 'createChatCompletion').mockResolvedValueOnce( - emptyResponse as PerplexityResponse, - ); - - // Act & Assert - await expect(service.explainMedicalText(medicalText)).rejects.toThrow(); - }); - }); }); diff --git a/backend/src/services/perplexity.service.ts b/backend/src/services/perplexity.service.ts index 16028a9f..4c7f1039 100644 --- a/backend/src/services/perplexity.service.ts +++ b/backend/src/services/perplexity.service.ts @@ -246,29 +246,6 @@ export class PerplexityService { } } - /** - * Generates a simplified explanation of medical text - * - * @param medicalText The medical text to explain - * @returns The simplified explanation - */ - async explainMedicalText(medicalText: string): Promise { - const systemPrompt = - 'You are an AI assistant that specializes in explaining complex medical information in simple terms.\n' + - 'Your goal is to help patients understand their medical reports by translating medical jargon into plain language.\n' + - 'You must be accurate, concise, comprehensive, and easy to understand. Use everyday analogies when helpful.\n'; - - const userPrompt = `Please explain the following medical text in simple terms, in a single paragraph that's between 10 to 200 words, all in normal text NOT .md style, the more concise the better:\n\n${medicalText}`; - - const messages: PerplexityMessage[] = [ - { role: 'system', content: systemPrompt }, - { role: 'user', content: userPrompt }, - ]; - - const response = await this.createChatCompletion(messages); - return response.choices[0].message.content.trim(); - } - /** * Reviews and verifies a medical document analysis against trusted medical sources * diff --git a/frontend/src/common/models/medicalReport.ts b/frontend/src/common/models/medicalReport.ts index 065968b5..977fd930 100644 --- a/frontend/src/common/models/medicalReport.ts +++ b/frontend/src/common/models/medicalReport.ts @@ -50,7 +50,7 @@ export interface MedicalReport { bookmarked: boolean; processingStatus: ProcessingStatus; labValues: LabValue[]; - summary: string; + medicalComments: string; confidence: number; filePath: string; originalFilename: string; diff --git a/frontend/src/pages/Reports/components/OriginalReportTab.tsx b/frontend/src/pages/Reports/components/OriginalReportTab.tsx index 8c8db892..7e78fede 100644 --- a/frontend/src/pages/Reports/components/OriginalReportTab.tsx +++ b/frontend/src/pages/Reports/components/OriginalReportTab.tsx @@ -68,7 +68,7 @@ const OriginalReportTab: React.FC = ({ reportData }) => {/* Medical Comments Section */}

Medical Comments:

-
{reportData.summary}
+
{reportData.medicalComments}
{/* Uploaded File Section */}