Files
cim_summary/backend/src/models/__tests__/DocumentModel.test.ts
Jon 57770fd99d feat: Implement hybrid LLM approach with enhanced prompts for CIM analysis
🎯 Major Features:
- Hybrid LLM configuration: Claude 3.7 Sonnet (primary) + GPT-4.5 (fallback)
- Task-specific model selection for optimal performance
- Enhanced prompts for all analysis types with proven results

🔧 Technical Improvements:
- Enhanced financial analysis with fiscal year mapping (100% success rate)
- Business model analysis with scalability assessment
- Market positioning analysis with TAM/SAM extraction
- Management team assessment with succession planning
- Creative content generation with GPT-4.5

📊 Performance & Cost Optimization:
- Claude 3.7 Sonnet: /5 per 1M tokens (82.2% MATH score)
- GPT-4.5: Premium creative content (5/50 per 1M tokens)
- ~80% cost savings using Claude for analytical tasks
- Automatic fallback system for reliability

 Proven Results:
- Successfully extracted 3-year financial data from STAX CIM
- Correctly mapped fiscal years (2023→FY-3, 2024→FY-2, 2025E→FY-1, LTM Mar-25→LTM)
- Identified revenue: 4M→1M→1M→6M (LTM)
- Identified EBITDA: 8.9M→3.9M→1M→7.2M (LTM)

🚀 Files Added/Modified:
- Enhanced LLM service with task-specific model selection
- Updated environment configuration for hybrid approach
- Enhanced prompt builders for all analysis types
- Comprehensive testing scripts and documentation
- Updated frontend components for improved UX

📚 References:
- Eden AI Model Comparison: Claude 3.7 Sonnet vs GPT-4.5
- Artificial Analysis Benchmarks for performance metrics
- Cost optimization based on model strengths and pricing
2025-07-28 16:46:06 -04:00

338 lines
10 KiB
TypeScript

import { DocumentModel } from '../DocumentModel';
import { CreateDocumentInput } from '../types';
// Mock the database pool
jest.mock('../../config/database', () => ({
query: jest.fn()
}));
// Mock the logger
jest.mock('../../utils/logger', () => ({
info: jest.fn(),
error: jest.fn(),
warn: jest.fn()
}));
describe('DocumentModel', () => {
let mockPool: any;
beforeEach(() => {
jest.clearAllMocks();
mockPool = require('../../config/database');
});
describe('create', () => {
it('should create a new document successfully', async () => {
const documentData: CreateDocumentInput = {
user_id: '123e4567-e89b-12d3-a456-426614174000',
original_file_name: 'test.pdf',
file_path: '/uploads/test.pdf',
file_size: 1024000
};
const mockDocument = {
id: '123e4567-e89b-12d3-a456-426614174001',
...documentData,
uploaded_at: new Date(),
status: 'uploaded',
created_at: new Date(),
updated_at: new Date()
};
mockPool.query.mockResolvedValueOnce({ rows: [mockDocument] });
const result = await DocumentModel.create(documentData);
expect(mockPool.query).toHaveBeenCalledWith(
expect.stringContaining('INSERT INTO documents'),
[documentData.user_id, documentData.original_file_name, documentData.file_path, documentData.file_size, 'uploaded'],
);
expect(result).toEqual(mockDocument);
});
it('should handle database errors', async () => {
const documentData: CreateDocumentInput = {
user_id: '123e4567-e89b-12d3-a456-426614174000',
original_file_name: 'test.pdf',
file_path: '/uploads/test.pdf',
file_size: 1024000
};
const error = new Error('Database error');
mockPool.query.mockRejectedValueOnce(error);
await expect(DocumentModel.create(documentData)).rejects.toThrow('Database error');
});
});
describe('findById', () => {
it('should find document by ID successfully', async () => {
const documentId = '123e4567-e89b-12d3-a456-426614174001';
const mockDocument = {
id: documentId,
user_id: '123e4567-e89b-12d3-a456-426614174000',
original_file_name: 'test.pdf',
file_path: '/uploads/test.pdf',
file_size: 1024000,
uploaded_at: new Date(),
status: 'uploaded',
created_at: new Date(),
updated_at: new Date()
};
mockPool.query.mockResolvedValueOnce({ rows: [mockDocument] });
const result = await DocumentModel.findById(documentId);
expect(mockPool.query).toHaveBeenCalledWith(
'SELECT * FROM documents WHERE id = $1',
[documentId]
);
expect(result).toEqual(mockDocument);
});
it('should return null when document not found', async () => {
const documentId = '123e4567-e89b-12d3-a456-426614174001';
mockPool.query.mockResolvedValueOnce({ rows: [] });
const result = await DocumentModel.findById(documentId);
expect(result).toBeNull();
});
});
describe('findByUserId', () => {
it('should find documents by user ID successfully', async () => {
const userId = '123e4567-e89b-12d3-a456-426614174000';
const mockDocuments = [
{
id: '123e4567-e89b-12d3-a456-426614174001',
user_id: userId,
original_file_name: 'test1.pdf',
file_path: '/uploads/test1.pdf',
file_size: 1024000,
uploaded_at: new Date(),
status: 'uploaded',
created_at: new Date(),
updated_at: new Date()
},
{
id: '123e4567-e89b-12d3-a456-426614174002',
user_id: userId,
original_file_name: 'test2.pdf',
file_path: '/uploads/test2.pdf',
file_size: 2048000,
uploaded_at: new Date(),
status: 'completed',
created_at: new Date(),
updated_at: new Date()
}
];
mockPool.query.mockResolvedValueOnce({ rows: mockDocuments });
const result = await DocumentModel.findByUserId(userId);
expect(mockPool.query).toHaveBeenCalledWith(
expect.stringContaining('SELECT * FROM documents'),
[userId, 50, 0]
);
expect(result).toEqual(mockDocuments);
});
});
describe('updateStatus', () => {
it('should update document status successfully', async () => {
const documentId = '123e4567-e89b-12d3-a456-426614174001';
const newStatus = 'processing_llm';
const mockUpdatedDocument = {
id: documentId,
user_id: '123e4567-e89b-12d3-a456-426614174000',
original_file_name: 'test.pdf',
file_path: '/uploads/test.pdf',
file_size: 1024000,
uploaded_at: new Date(),
status: newStatus,
processing_started_at: new Date(),
created_at: new Date(),
updated_at: new Date()
};
mockPool.query.mockResolvedValueOnce({ rows: [mockUpdatedDocument] });
const result = await DocumentModel.updateStatus(documentId, newStatus);
expect(mockPool.query).toHaveBeenCalledWith(
expect.stringContaining('UPDATE documents'),
[newStatus, documentId]
);
expect(result).toEqual(mockUpdatedDocument);
});
});
describe('updateExtractedText', () => {
it('should update extracted text successfully', async () => {
const documentId = '123e4567-e89b-12d3-a456-426614174001';
const extractedText = 'This is the extracted text from the PDF';
const mockUpdatedDocument = {
id: documentId,
user_id: '123e4567-e89b-12d3-a456-426614174000',
original_file_name: 'test.pdf',
file_path: '/uploads/test.pdf',
file_size: 1024000,
uploaded_at: new Date(),
status: 'extracting_text',
extracted_text: extractedText,
created_at: new Date(),
updated_at: new Date()
};
mockPool.query.mockResolvedValueOnce({ rows: [mockUpdatedDocument] });
const result = await DocumentModel.updateExtractedText(documentId, extractedText);
expect(mockPool.query).toHaveBeenCalledWith(
expect.stringContaining('UPDATE documents'),
[extractedText, documentId]
);
expect(result).toEqual(mockUpdatedDocument);
});
});
describe('updateGeneratedSummary', () => {
it('should update generated summary successfully', async () => {
const documentId = '123e4567-e89b-12d3-a456-426614174001';
const summary = 'Generated summary content';
const markdownPath = '/summaries/test.md';
const pdfPath = '/summaries/test.pdf';
const mockUpdatedDocument = {
id: documentId,
user_id: '123e4567-e89b-12d3-a456-426614174000',
original_file_name: 'test.pdf',
file_path: '/uploads/test.pdf',
file_size: 1024000,
uploaded_at: new Date(),
status: 'completed',
generated_summary: summary,
summary_markdown_path: markdownPath,
summary_pdf_path: pdfPath,
created_at: new Date(),
updated_at: new Date()
};
mockPool.query.mockResolvedValueOnce({ rows: [mockUpdatedDocument] });
const result = await DocumentModel.updateGeneratedSummary(documentId, summary, markdownPath, pdfPath);
expect(mockPool.query).toHaveBeenCalledWith(
expect.stringContaining('UPDATE documents'),
[summary, markdownPath, pdfPath, documentId]
);
expect(result).toEqual(mockUpdatedDocument);
});
});
describe('delete', () => {
it('should delete document successfully', async () => {
const documentId = '123e4567-e89b-12d3-a456-426614174001';
mockPool.query.mockResolvedValueOnce({ rows: [{ id: documentId }] });
const result = await DocumentModel.delete(documentId);
expect(mockPool.query).toHaveBeenCalledWith(
'DELETE FROM documents WHERE id = $1 RETURNING id',
[documentId]
);
expect(result).toBe(true);
});
it('should return false when document not found', async () => {
const documentId = '123e4567-e89b-12d3-a456-426614174001';
mockPool.query.mockResolvedValueOnce({ rows: [] });
const result = await DocumentModel.delete(documentId);
expect(result).toBe(false);
});
});
describe('countByUser', () => {
it('should return correct document count for user', async () => {
const userId = '123e4567-e89b-12d3-a456-426614174000';
const expectedCount = 5;
mockPool.query.mockResolvedValueOnce({ rows: [{ count: expectedCount.toString() }] });
const result = await DocumentModel.countByUser(userId);
expect(mockPool.query).toHaveBeenCalledWith(
'SELECT COUNT(*) FROM documents WHERE user_id = $1',
[userId]
);
expect(result).toBe(expectedCount);
});
});
describe('findByStatus', () => {
it('should find documents by status successfully', async () => {
const status = 'completed';
const mockDocuments = [
{
id: '123e4567-e89b-12d3-a456-426614174001',
user_id: '123e4567-e89b-12d3-a456-426614174000',
original_file_name: 'test1.pdf',
file_path: '/uploads/test1.pdf',
file_size: 1024000,
uploaded_at: new Date(),
status,
created_at: new Date(),
updated_at: new Date()
}
];
mockPool.query.mockResolvedValueOnce({ rows: mockDocuments });
const result = await DocumentModel.findByStatus(status);
expect(mockPool.query).toHaveBeenCalledWith(
expect.stringContaining('SELECT * FROM documents'),
[status, 50, 0]
);
expect(result).toEqual(mockDocuments);
});
});
describe('findPendingProcessing', () => {
it('should find pending processing documents', async () => {
const mockDocuments = [
{
id: '123e4567-e89b-12d3-a456-426614174001',
user_id: '123e4567-e89b-12d3-a456-426614174000',
original_file_name: 'test.pdf',
file_path: '/uploads/test.pdf',
file_size: 1024000,
uploaded_at: new Date(),
status: 'uploaded',
created_at: new Date(),
updated_at: new Date()
}
];
mockPool.query.mockResolvedValueOnce({ rows: mockDocuments });
const result = await DocumentModel.findPendingProcessing();
expect(mockPool.query).toHaveBeenCalledWith(
expect.stringContaining('SELECT * FROM documents'),
[10]
);
expect(result).toEqual(mockDocuments);
});
});
});