Clean up and optimize backend code - Remove large log files (13MB total) - Remove dist directory (1.9MB, can be regenerated) - Remove unused dependencies: bcrypt, bull, langchain, @langchain/openai, form-data, express-validator - Remove unused service files: advancedLLMProcessor, enhancedCIMProcessor, enhancedLLMService, financialAnalysisEngine, qualityValidationService - Keep essential services: uploadProgressService, sessionService, vectorDatabaseService, vectorDocumentProcessor, ragDocumentProcessor - Maintain all working functionality while reducing bundle size and improving maintainability

This commit is contained in:
Jon
2025-07-29 00:49:56 -04:00
parent df7bbe47f6
commit 70c02df6e7
6 changed files with 0 additions and 2942 deletions

View File

@@ -17,21 +17,15 @@
}, },
"dependencies": { "dependencies": {
"@anthropic-ai/sdk": "^0.57.0", "@anthropic-ai/sdk": "^0.57.0",
"@langchain/openai": "^0.6.3",
"axios": "^1.11.0", "axios": "^1.11.0",
"bcrypt": "^6.0.0",
"bcryptjs": "^2.4.3", "bcryptjs": "^2.4.3",
"bull": "^4.12.0",
"cors": "^2.8.5", "cors": "^2.8.5",
"dotenv": "^16.3.1", "dotenv": "^16.3.1",
"express": "^4.18.2", "express": "^4.18.2",
"express-rate-limit": "^7.1.5", "express-rate-limit": "^7.1.5",
"express-validator": "^7.0.1",
"form-data": "^4.0.4",
"helmet": "^7.1.0", "helmet": "^7.1.0",
"joi": "^17.11.0", "joi": "^17.11.0",
"jsonwebtoken": "^9.0.2", "jsonwebtoken": "^9.0.2",
"langchain": "^0.3.30",
"morgan": "^1.10.0", "morgan": "^1.10.0",
"multer": "^1.4.5-lts.1", "multer": "^1.4.5-lts.1",
"openai": "^5.10.2", "openai": "^5.10.2",

View File

@@ -1,805 +0,0 @@
import { logger } from '../utils/logger';
import { llmService } from './llmService';
import { CIMReview } from './llmSchemas';
import { vectorDocumentProcessor } from './vectorDocumentProcessor';
export interface AdvancedProcessingOptions {
documentId: string;
enableRAGEnhancement?: boolean;
enableIterativeRefinement?: boolean;
enableSpecializedAgents?: boolean;
qualityThreshold?: number;
}
export interface ProcessingAgentResult {
agentName: string;
success: boolean;
data: any;
confidence: number;
processingTime: number;
error: string | undefined;
}
export interface AdvancedProcessingResult {
success: boolean;
finalResult: CIMReview;
agentResults: ProcessingAgentResult[];
processingStrategy: string;
qualityScore: number;
totalProcessingTime: number;
error?: string;
}
class AdvancedLLMProcessor {
/**
* Process CIM document using advanced multi-agent approach
*/
async processWithAdvancedStrategy(
text: string,
options: AdvancedProcessingOptions
): Promise<AdvancedProcessingResult> {
const startTime = Date.now();
logger.info('Starting advanced LLM processing', { documentId: options.documentId });
try {
// Step 1: Document Understanding Agent
const documentAgent = await this.runDocumentUnderstandingAgent(text);
// Step 2: Specialized Analysis Agents (parallel execution)
const specializedAgents = await this.runSpecializedAgents(text, options, documentAgent.data);
// Step 3: Financial Deep Dive Agent
const financialAgent = await this.runFinancialAnalysisAgent(text, options);
// Step 4: Investment Thesis Agent
const investmentAgent = await this.runInvestmentThesisAgent(text, options, {
documentUnderstanding: documentAgent.data,
specializedAnalysis: specializedAgents,
financialAnalysis: financialAgent.data
});
// Step 5: Synthesis and Quality Validation
const synthesisAgent = await this.runSynthesisAgent(text, options, {
document: documentAgent.data,
specialized: specializedAgents,
financial: financialAgent.data,
investment: investmentAgent.data
});
// Step 6: Quality assessment and iterative refinement
const qualityScore = this.assessQuality(synthesisAgent.data);
let finalResult = synthesisAgent.data;
if (options.enableIterativeRefinement && qualityScore < (options.qualityThreshold || 0.85)) {
logger.info('Quality below threshold, running refinement', { qualityScore });
const refinementAgent = await this.runRefinementAgent(text, options, finalResult, qualityScore);
finalResult = refinementAgent.data;
}
const agentResults = [documentAgent, ...specializedAgents, financialAgent, investmentAgent, synthesisAgent];
const totalProcessingTime = Date.now() - startTime;
return {
success: true,
finalResult,
agentResults,
processingStrategy: 'advanced_multi_agent',
qualityScore: this.assessQuality(finalResult),
totalProcessingTime,
};
} catch (error) {
logger.error('Advanced LLM processing failed', error);
return {
success: false,
finalResult: {} as CIMReview,
agentResults: [],
processingStrategy: 'advanced_multi_agent',
qualityScore: 0,
totalProcessingTime: Date.now() - startTime,
error: error instanceof Error ? error.message : 'Unknown error'
};
}
}
/**
* Document Understanding Agent - High-level document comprehension
*/
private async runDocumentUnderstandingAgent(
text: string
): Promise<ProcessingAgentResult> {
const startTime = Date.now();
try {
const prompt = this.buildDocumentUnderstandingPrompt(text);
const systemPrompt = this.getDocumentUnderstandingSystemPrompt();
const result = await llmService.processCIMDocument(text, '', {
prompt,
systemPrompt,
agentName: 'document_understanding'
});
return {
agentName: 'document_understanding',
success: result.success,
data: result.jsonOutput || {},
confidence: this.calculateConfidence(result),
processingTime: Date.now() - startTime,
error: result.error
};
} catch (error) {
return {
agentName: 'document_understanding',
success: false,
data: {},
confidence: 0,
processingTime: Date.now() - startTime,
error: error instanceof Error ? error.message : 'Unknown error'
};
}
}
/**
* Run specialized analysis agents in parallel
*/
private async runSpecializedAgents(
text: string,
_options: AdvancedProcessingOptions,
documentContext: any
): Promise<ProcessingAgentResult[]> {
const agents = [
this.runBusinessModelAgent(text, _options, documentContext),
this.runMarketAnalysisAgent(text, _options, documentContext),
this.runCompetitiveAnalysisAgent(text, _options, documentContext),
this.runManagementAnalysisAgent(text, _options, documentContext)
];
return await Promise.all(agents);
}
/**
* Business Model Analysis Agent
*/
private async runBusinessModelAgent(
text: string,
_options: AdvancedProcessingOptions,
context: any
): Promise<ProcessingAgentResult> {
const startTime = Date.now();
try {
// Use RAG enhancement if enabled
let enhancedText = text;
if (_options.enableRAGEnhancement) {
const relevantSections = await vectorDocumentProcessor.searchRelevantContent(
'business model revenue streams products services',
{ documentId: _options.documentId, limit: 5 }
);
enhancedText = this.combineTextWithRAG(text, relevantSections);
}
const prompt = this.buildBusinessModelPrompt(enhancedText, context);
const systemPrompt = this.getBusinessModelSystemPrompt();
const result = await llmService.processCIMDocument(enhancedText, '', {
prompt,
systemPrompt,
agentName: 'business_model'
});
return {
agentName: 'business_model',
success: result.success,
data: result.jsonOutput || {},
confidence: this.calculateConfidence(result),
processingTime: Date.now() - startTime,
error: result.error
};
} catch (error) {
return {
agentName: 'business_model',
success: false,
data: {},
confidence: 0,
processingTime: Date.now() - startTime,
error: error instanceof Error ? error.message : 'Unknown error'
};
}
}
/**
* Financial Analysis Deep Dive Agent
*/
private async runFinancialAnalysisAgent(
text: string,
_options: AdvancedProcessingOptions
): Promise<ProcessingAgentResult> {
const startTime = Date.now();
try {
// Extract and enhance financial data using RAG
let enhancedText = text;
if (_options.enableRAGEnhancement) {
const financialSections = await vectorDocumentProcessor.searchRelevantContent(
'revenue EBITDA profit margin cash flow financial performance growth',
{ documentId: _options.documentId, limit: 10 }
);
enhancedText = this.combineTextWithRAG(text, financialSections);
}
const prompt = this.buildFinancialAnalysisPrompt(enhancedText);
const systemPrompt = this.getFinancialAnalysisSystemPrompt();
const result = await llmService.processCIMDocument(enhancedText, '', {
prompt,
systemPrompt,
agentName: 'financial_analysis'
});
return {
agentName: 'financial_analysis',
success: result.success,
data: result.jsonOutput || {},
confidence: this.calculateConfidence(result),
processingTime: Date.now() - startTime,
error: result.error
};
} catch (error) {
return {
agentName: 'financial_analysis',
success: false,
data: {},
confidence: 0,
processingTime: Date.now() - startTime,
error: error instanceof Error ? error.message : 'Unknown error'
};
}
}
/**
* Market Analysis Agent
*/
private async runMarketAnalysisAgent(
text: string,
_options: AdvancedProcessingOptions,
context: any
): Promise<ProcessingAgentResult> {
const startTime = Date.now();
try {
let enhancedText = text;
if (_options.enableRAGEnhancement) {
const marketSections = await vectorDocumentProcessor.searchRelevantContent(
'market size growth trends competition industry analysis',
{ documentId: _options.documentId, limit: 7 }
);
enhancedText = this.combineTextWithRAG(text, marketSections);
}
const prompt = this.buildMarketAnalysisPrompt(enhancedText, context);
const systemPrompt = this.getMarketAnalysisSystemPrompt();
const result = await llmService.processCIMDocument(enhancedText, '', {
prompt,
systemPrompt,
agentName: 'market_analysis'
});
return {
agentName: 'market_analysis',
success: result.success,
data: result.jsonOutput || {},
confidence: this.calculateConfidence(result),
processingTime: Date.now() - startTime,
error: result.error
};
} catch (error) {
return {
agentName: 'market_analysis',
success: false,
data: {},
confidence: 0,
processingTime: Date.now() - startTime,
error: error instanceof Error ? error.message : 'Unknown error'
};
}
}
/**
* Competitive Analysis Agent
*/
private async runCompetitiveAnalysisAgent(
text: string,
_options: AdvancedProcessingOptions,
context: any
): Promise<ProcessingAgentResult> {
const startTime = Date.now();
try {
let enhancedText = text;
if (_options.enableRAGEnhancement) {
const competitiveSections = await vectorDocumentProcessor.searchRelevantContent(
'competitors competitive advantage market position differentiation',
{ documentId: _options.documentId, limit: 5 }
);
enhancedText = this.combineTextWithRAG(text, competitiveSections);
}
const prompt = this.buildCompetitiveAnalysisPrompt(enhancedText, context);
const systemPrompt = this.getCompetitiveAnalysisSystemPrompt();
const result = await llmService.processCIMDocument(enhancedText, '', {
prompt,
systemPrompt,
agentName: 'competitive_analysis'
});
return {
agentName: 'competitive_analysis',
success: result.success,
data: result.jsonOutput || {},
confidence: this.calculateConfidence(result),
processingTime: Date.now() - startTime,
error: result.error
};
} catch (error) {
return {
agentName: 'competitive_analysis',
success: false,
data: {},
confidence: 0,
processingTime: Date.now() - startTime,
error: error instanceof Error ? error.message : 'Unknown error'
};
}
}
/**
* Management Analysis Agent
*/
private async runManagementAnalysisAgent(
text: string,
_options: AdvancedProcessingOptions,
context: any
): Promise<ProcessingAgentResult> {
const startTime = Date.now();
try {
let enhancedText = text;
if (_options.enableRAGEnhancement) {
const managementSections = await vectorDocumentProcessor.searchRelevantContent(
'management team CEO CFO leadership experience background',
{ documentId: _options.documentId, limit: 5 }
);
enhancedText = this.combineTextWithRAG(text, managementSections);
}
const prompt = this.buildManagementAnalysisPrompt(enhancedText, context);
const systemPrompt = this.getManagementAnalysisSystemPrompt();
const result = await llmService.processCIMDocument(enhancedText, '', {
prompt,
systemPrompt,
agentName: 'management_analysis'
});
return {
agentName: 'management_analysis',
success: result.success,
data: result.jsonOutput || {},
confidence: this.calculateConfidence(result),
processingTime: Date.now() - startTime,
error: result.error
};
} catch (error) {
return {
agentName: 'management_analysis',
success: false,
data: {},
confidence: 0,
processingTime: Date.now() - startTime,
error: error instanceof Error ? error.message : 'Unknown error'
};
}
}
/**
* Investment Thesis Agent
*/
private async runInvestmentThesisAgent(
text: string,
_options: AdvancedProcessingOptions,
allContext: any
): Promise<ProcessingAgentResult> {
const startTime = Date.now();
try {
const prompt = this.buildInvestmentThesisPrompt(text, allContext);
const systemPrompt = this.getInvestmentThesisSystemPrompt();
const result = await llmService.processCIMDocument(text, '', {
prompt,
systemPrompt,
agentName: 'investment_thesis'
});
return {
agentName: 'investment_thesis',
success: result.success,
data: result.jsonOutput || {},
confidence: this.calculateConfidence(result),
processingTime: Date.now() - startTime,
error: result.error
};
} catch (error) {
return {
agentName: 'investment_thesis',
success: false,
data: {},
confidence: 0,
processingTime: Date.now() - startTime,
error: error instanceof Error ? error.message : 'Unknown error'
};
}
}
/**
* Synthesis Agent - Combines all agent outputs into final CIM review
*/
private async runSynthesisAgent(
text: string,
_options: AdvancedProcessingOptions,
allResults: any
): Promise<ProcessingAgentResult> {
const startTime = Date.now();
try {
const prompt = this.buildSynthesisPrompt(text, allResults);
const systemPrompt = this.getSynthesisSystemPrompt();
const result = await llmService.processCIMDocument(text, '', {
prompt,
systemPrompt,
agentName: 'synthesis'
});
return {
agentName: 'synthesis',
success: result.success,
data: result.jsonOutput || {},
confidence: this.calculateConfidence(result),
processingTime: Date.now() - startTime,
error: result.error
};
} catch (error) {
return {
agentName: 'synthesis',
success: false,
data: {},
confidence: 0,
processingTime: Date.now() - startTime,
error: error instanceof Error ? error.message : 'Unknown error'
};
}
}
/**
* Refinement Agent - Improves quality based on feedback
*/
private async runRefinementAgent(
text: string,
_options: AdvancedProcessingOptions,
previousResult: any,
qualityScore: number
): Promise<ProcessingAgentResult> {
const startTime = Date.now();
try {
const prompt = this.buildRefinementPrompt(text, previousResult, qualityScore);
const systemPrompt = this.getRefinementSystemPrompt();
const result = await llmService.processCIMDocument(text, '', {
prompt,
systemPrompt,
agentName: 'refinement'
});
return {
agentName: 'refinement',
success: result.success,
data: result.jsonOutput || {},
confidence: this.calculateConfidence(result),
processingTime: Date.now() - startTime,
error: result.error
};
} catch (error) {
return {
agentName: 'refinement',
success: false,
data: {},
confidence: 0,
processingTime: Date.now() - startTime,
error: error instanceof Error ? error.message : 'Unknown error'
};
}
}
// System prompts for specialized agents
private getDocumentUnderstandingSystemPrompt(): string {
return `You are a senior investment analyst at BPCP specializing in initial document comprehension and high-level analysis. Your role is to understand the overall structure, key themes, and document quality of CIM documents.
Focus on:
- Document structure and organization quality
- Key themes and investment narrative
- Information completeness and gaps
- Overall document professionalism
- Initial red flags or positive indicators
Provide a structured analysis that will guide specialized agents.`;
}
private getBusinessModelSystemPrompt(): string {
return `You are a business model expert at BPCP with deep expertise in analyzing revenue streams, value propositions, and operational models. Your role is to dissect and evaluate the target company's business model.
Focus on:
- Revenue model sustainability and predictability
- Value proposition strength and differentiation
- Customer acquisition and retention strategies
- Unit economics and scalability
- Operational efficiency and leverage
Provide detailed insights that will inform the investment decision.`;
}
private getFinancialAnalysisSystemPrompt(): string {
return `You are a senior financial analyst at BPCP with expertise in private equity financial modeling and due diligence. Your role is to provide deep financial analysis beyond basic metrics.
Focus on:
- Quality of earnings assessment
- Cash flow sustainability and working capital dynamics
- Financial forecasting and growth assumptions
- Capital structure optimization opportunities
- Financial risk assessment and mitigation
Provide analysis suitable for BPCP's investment committee.`;
}
private getMarketAnalysisSystemPrompt(): string {
return `You are a market research expert at BPCP with deep knowledge of industry dynamics, market sizing, and growth drivers. Your role is to assess market opportunities and risks.
Focus on:
- Market sizing accuracy and growth potential
- Industry trends and secular drivers
- Competitive intensity and market dynamics
- Regulatory environment and barriers to entry
- Market positioning and expansion opportunities
Provide market intelligence that informs investment attractiveness.`;
}
private getCompetitiveAnalysisSystemPrompt(): string {
return `You are a competitive strategy expert at BPCP with experience in analyzing competitive positioning and sustainable advantages. Your role is to assess competitive dynamics.
Focus on:
- Competitive landscape mapping
- Sustainable competitive advantages
- Threat assessment from existing and new competitors
- Competitive response scenarios
- Market share dynamics and defensibility
Provide competitive intelligence for strategic planning.`;
}
private getManagementAnalysisSystemPrompt(): string {
return `You are an executive assessment expert at BPCP with experience in evaluating management teams and organizational capabilities. Your role is to assess leadership quality.
Focus on:
- Management experience and track record
- Organizational structure and governance
- Key person risk and succession planning
- Management incentive alignment
- Cultural fit with BPCP partnership model
Provide leadership assessment for investment decisions.`;
}
private getInvestmentThesisSystemPrompt(): string {
return `You are a senior investment professional at BPCP responsible for synthesizing all analysis into a coherent investment thesis. Your role is to build the investment case.
Focus on:
- Value creation opportunities and levers
- Risk assessment and mitigation strategies
- Strategic rationale and fit with BPCP portfolio
- Exit strategy and value realization
- Investment recommendation and next steps
Provide investment thesis suitable for BPCP partners.`;
}
private getSynthesisSystemPrompt(): string {
return `You are the lead analyst at BPCP responsible for creating the final CIM review that synthesizes all specialized analysis. Your role is to produce the definitive assessment.
Focus on:
- Integrating all specialized analyses coherently
- Identifying key insights and recommendations
- Highlighting critical questions and diligence areas
- Providing clear investment recommendation
- Ensuring BPCP template compliance
Produce the final CIM review for BPCP investment committee.`;
}
private getRefinementSystemPrompt(): string {
return `You are a quality assurance expert at BPCP responsible for improving and refining CIM analyses. Your role is to enhance accuracy, completeness, and insight quality.
Focus on:
- Addressing identified quality gaps
- Enhancing analytical depth and insight
- Improving accuracy and factual consistency
- Strengthening investment reasoning
- Ensuring template compliance and professionalism
Refine the analysis to meet BPCP's high standards.`;
}
// Helper methods
private buildDocumentUnderstandingPrompt(text: string): string {
return `Analyze this CIM document for overall structure, key themes, and quality. Focus on document organization, completeness, and initial assessment.
CIM Document:
${text.substring(0, 20000)}
Provide a structured analysis of document understanding.`;
}
private buildBusinessModelPrompt(text: string, context: any): string {
return `Analyze the business model of this company in detail. Focus on revenue streams, value proposition, and operational model.
CIM Document:
${text.substring(0, 15000)}
Document Context:
${JSON.stringify(context, null, 2)}
Provide detailed business model analysis.`;
}
private buildFinancialAnalysisPrompt(text: string): string {
return `Conduct deep financial analysis of this company. Focus on quality of earnings, cash flow dynamics, and financial forecasting.
CIM Document:
${text.substring(0, 25000)}
Provide comprehensive financial analysis suitable for private equity investment.`;
}
private buildMarketAnalysisPrompt(text: string, context: any): string {
return `Analyze the market opportunity and competitive dynamics for this company.
CIM Document:
${text.substring(0, 15000)}
Context:
${JSON.stringify(context, null, 2)}
Provide detailed market and industry analysis.`;
}
private buildCompetitiveAnalysisPrompt(text: string, context: any): string {
return `Analyze the competitive positioning and advantages of this company.
CIM Document:
${text.substring(0, 15000)}
Context:
${JSON.stringify(context, null, 2)}
Provide comprehensive competitive analysis.`;
}
private buildManagementAnalysisPrompt(text: string, context: any): string {
return `Evaluate the management team and organizational capabilities of this company.
CIM Document:
${text.substring(0, 15000)}
Context:
${JSON.stringify(context, null, 2)}
Provide detailed management assessment.`;
}
private buildInvestmentThesisPrompt(text: string, allContext: any): string {
return `Based on all the specialized analysis, build a comprehensive investment thesis for this opportunity.
All Analysis Context:
${JSON.stringify(allContext, null, 2)}
CIM Document (Reference):
${text.substring(0, 10000)}
Build the investment thesis with value creation levers, risks, and recommendation.`;
}
private buildSynthesisPrompt(text: string, allResults: any): string {
return `Synthesize all specialized agent analyses into the final BPCP CIM Review Template format.
All Agent Results:
${JSON.stringify(allResults, null, 2)}
Original CIM Document:
${text.substring(0, 5000)}
Create the final structured CIM review following BPCP template exactly.`;
}
private buildRefinementPrompt(text: string, previousResult: any, qualityScore: number): string {
return `Refine and improve this CIM analysis based on quality assessment. Current quality score: ${qualityScore}
Previous Analysis:
${JSON.stringify(previousResult, null, 2)}
Original Document:
${text.substring(0, 10000)}
Focus on improving accuracy, completeness, and insight quality to exceed quality threshold.`;
}
private combineTextWithRAG(originalText: string, ragSections: any[]): string {
const relevantContext = ragSections
.map((section: any) => section.chunkContent || section.content)
.join('\n\n--- RELEVANT CONTEXT ---\n\n');
return `${originalText}\n\n=== ADDITIONAL RELEVANT CONTEXT ===\n\n${relevantContext}`;
}
private calculateConfidence(result: any): number {
if (!result.success) return 0;
// Basic confidence calculation based on response completeness and structure
const hasOutput = result.jsonOutput && Object.keys(result.jsonOutput).length > 0;
const hasValidation = !result.validationIssues || result.validationIssues.length === 0;
const hasCost = result.cost > 0;
let confidence = 0.5; // Base confidence
if (hasOutput) confidence += 0.3;
if (hasValidation) confidence += 0.2;
if (hasCost) confidence += 0.1; // Indicates successful API call
return Math.min(confidence, 1.0);
}
private assessQuality(result: any): number {
if (!result || typeof result !== 'object') return 0;
let qualityScore = 0;
let maxScore = 0;
// Check completeness of each section
const sections = [
'dealOverview', 'businessDescription', 'marketIndustryAnalysis',
'financialSummary', 'managementTeamOverview', 'preliminaryInvestmentThesis',
'keyQuestionsNextSteps'
];
sections.forEach(section => {
maxScore += 10;
if (result[section]) {
const sectionData = result[section];
const fields = Object.keys(sectionData);
const completedFields = fields.filter(field =>
sectionData[field] &&
sectionData[field] !== 'Not specified in CIM' &&
sectionData[field].length > 10
);
qualityScore += (completedFields.length / fields.length) * 10;
}
});
return maxScore > 0 ? qualityScore / maxScore : 0;
}
}
export const advancedLLMProcessor = new AdvancedLLMProcessor();

View File

@@ -1,464 +0,0 @@
import { logger } from '../utils/logger';
import { advancedLLMProcessor } from './advancedLLMProcessor';
import { financialAnalysisEngine } from './financialAnalysisEngine';
import { qualityValidationService } from './qualityValidationService';
import { vectorDatabaseService } from './vectorDatabaseService';
import { CIMReview } from './llmSchemas';
import { DocumentModel } from '../models/DocumentModel';
import { ProcessingJobModel } from '../models/ProcessingJobModel';
import { uploadProgressService } from './uploadProgressService';
export interface EnhancedProcessingOptions {
documentId: string;
userId: string;
enableAdvancedPrompting?: boolean;
enableRAGEnhancement?: boolean;
enableFinancialDeepDive?: boolean;
enableQualityValidation?: boolean;
enableIterativeRefinement?: boolean;
qualityThreshold?: number;
maxProcessingTime?: number; // milliseconds
}
export interface EnhancedProcessingResult {
success: boolean;
cimAnalysis: CIMReview;
processingStrategy: string;
qualityMetrics: {
overallScore: number;
completeness: number;
accuracy: number;
depth: number;
relevance: number;
consistency: number;
};
financialAnalysis?: any;
processingTime: number;
agentResults?: any[];
refinementIterations?: number;
error?: string;
}
class EnhancedCIMProcessor {
private readonly DEFAULT_OPTIONS: Partial<EnhancedProcessingOptions> = {
enableAdvancedPrompting: true,
enableRAGEnhancement: true,
enableFinancialDeepDive: true,
enableQualityValidation: true,
enableIterativeRefinement: true,
qualityThreshold: 85,
maxProcessingTime: 10 * 60 * 1000 // 10 minutes
};
/**
* Process CIM document with enhanced capabilities
*/
async processDocument(
text: string,
options: EnhancedProcessingOptions
): Promise<EnhancedProcessingResult> {
const startTime = Date.now();
const mergedOptions = { ...this.DEFAULT_OPTIONS, ...options };
logger.info('Starting enhanced CIM processing', {
documentId: options.documentId,
userId: options.userId,
enabledFeatures: {
advancedPrompting: mergedOptions.enableAdvancedPrompting,
ragEnhancement: mergedOptions.enableRAGEnhancement,
financialDeepDive: mergedOptions.enableFinancialDeepDive,
qualityValidation: mergedOptions.enableQualityValidation,
iterativeRefinement: mergedOptions.enableIterativeRefinement
}
});
try {
// Initialize progress tracking
uploadProgressService.updateProgress(
options.documentId,
'analysis',
5,
'Starting enhanced CIM analysis...'
);
// Step 1: Create document chunks for vector search (if RAG enabled)
if (mergedOptions.enableRAGEnhancement) {
await this.createDocumentChunks(text, options.documentId);
uploadProgressService.updateProgress(
options.documentId,
'analysis',
15,
'Creating vector embeddings for enhanced analysis...'
);
}
// Step 2: Advanced LLM Processing
let cimAnalysis: CIMReview;
let agentResults: any[] = [];
if (mergedOptions.enableAdvancedPrompting) {
uploadProgressService.updateProgress(
options.documentId,
'analysis',
25,
'Running specialized analysis agents...'
);
const advancedResult = await advancedLLMProcessor.processWithAdvancedStrategy(text, {
documentId: options.documentId,
enableRAGEnhancement: mergedOptions.enableRAGEnhancement || false,
enableIterativeRefinement: false, // We'll handle this separately
enableSpecializedAgents: true,
qualityThreshold: mergedOptions.qualityThreshold || 0.8
});
if (!advancedResult.success) {
throw new Error(`Advanced processing failed: ${advancedResult.error}`);
}
cimAnalysis = advancedResult.finalResult;
agentResults = advancedResult.agentResults;
} else {
// Fallback to basic processing
uploadProgressService.updateProgress(
options.documentId,
'analysis',
40,
'Running basic CIM analysis...'
);
const { llmService } = await import('./llmService');
const basicResult = await llmService.processCIMDocument(text, '');
if (!basicResult.success || !basicResult.jsonOutput) {
throw new Error('Basic CIM processing failed');
}
cimAnalysis = basicResult.jsonOutput as CIMReview;
}
uploadProgressService.updateProgress(
options.documentId,
'analysis',
60,
'CIM analysis completed, running quality validation...'
);
// Step 3: Financial Deep Dive (if enabled)
let financialAnalysis = undefined;
if (mergedOptions.enableFinancialDeepDive) {
uploadProgressService.updateProgress(
options.documentId,
'analysis',
70,
'Performing detailed financial analysis...'
);
try {
financialAnalysis = await financialAnalysisEngine.performComprehensiveAnalysis(
text,
options.documentId
);
// Enhance CIM analysis with financial insights
cimAnalysis = this.integrateFinancialAnalysis(cimAnalysis, financialAnalysis);
} catch (error) {
logger.warn('Financial deep dive failed, continuing without it', { error, documentId: options.documentId });
}
}
// Step 4: Quality Validation
let qualityMetrics: any = {
overallScore: 75,
completeness: 75,
accuracy: 75,
depth: 75,
relevance: 75,
consistency: 75
};
let refinementIterations = 0;
if (mergedOptions.enableQualityValidation) {
uploadProgressService.updateProgress(
options.documentId,
'validation',
80,
'Validating analysis quality...'
);
const validation = await qualityValidationService.validateQuality(
cimAnalysis,
text,
options.documentId
);
qualityMetrics = {
overallScore: validation.qualityMetrics.overallScore,
completeness: validation.qualityMetrics.completeness.score,
accuracy: validation.qualityMetrics.accuracy.score,
depth: validation.qualityMetrics.depth.score,
relevance: validation.qualityMetrics.relevance.score,
consistency: validation.qualityMetrics.consistency.score
};
// Step 5: Iterative Refinement (if needed and enabled)
if (mergedOptions.enableIterativeRefinement &&
!validation.passed &&
validation.qualityMetrics.overallScore < (mergedOptions.qualityThreshold || 85)) {
uploadProgressService.updateProgress(
options.documentId,
'analysis',
85,
'Refining analysis based on quality feedback...'
);
const refinementResult = await qualityValidationService.performIterativeRefinement(
cimAnalysis,
text,
options.documentId,
mergedOptions.qualityThreshold
);
if (refinementResult.success) {
cimAnalysis = refinementResult.finalResult;
qualityMetrics.overallScore = refinementResult.qualityImprovement.finalScore;
refinementIterations = refinementResult.iterations;
}
}
}
// Step 6: Save results
uploadProgressService.updateProgress(
options.documentId,
'storage',
95,
'Saving enhanced analysis results...'
);
await this.saveResults(cimAnalysis, options.documentId, qualityMetrics);
const processingTime = Date.now() - startTime;
uploadProgressService.updateProgress(
options.documentId,
'storage',
100,
'Enhanced CIM analysis completed successfully!'
);
logger.info('Enhanced CIM processing completed successfully', {
documentId: options.documentId,
processingTime,
qualityScore: qualityMetrics.overallScore,
refinementIterations,
agentCount: agentResults.length
});
return {
success: true,
cimAnalysis,
processingStrategy: mergedOptions.enableAdvancedPrompting ? 'advanced_multi_agent' : 'basic',
qualityMetrics,
financialAnalysis,
processingTime,
agentResults,
refinementIterations
};
} catch (error) {
const processingTime = Date.now() - startTime;
logger.error('Enhanced CIM processing failed', {
error,
documentId: options.documentId,
userId: options.userId,
processingTime
});
uploadProgressService.updateProgress(
options.documentId,
'validation',
0,
`Processing failed: ${error instanceof Error ? error.message : 'Unknown error'}`
);
return {
success: false,
cimAnalysis: {} as CIMReview,
processingStrategy: 'failed',
qualityMetrics: {
overallScore: 0,
completeness: 0,
accuracy: 0,
depth: 0,
relevance: 0,
consistency: 0
},
processingTime,
error: error instanceof Error ? error.message : 'Unknown error'
};
}
}
/**
* Create and store document chunks for vector search
*/
private async createDocumentChunks(text: string, documentId: string): Promise<void> {
try {
const chunkSize = 1000;
const overlap = 200;
const chunks: Array<{
id: string;
documentId: string;
content: string;
metadata: {
chunkIndex: number;
startPosition: number;
endPosition: number;
};
embedding: number[];
}> = [];
// Split text into chunks
for (let i = 0; i < text.length; i += chunkSize - overlap) {
const chunk = text.substring(i, i + chunkSize);
if (chunk.trim().length > 50) { // Skip tiny chunks
chunks.push({
id: `${documentId}_chunk_${chunks.length}`,
documentId,
content: chunk.trim(),
metadata: {
chunkIndex: chunks.length,
startPosition: i,
endPosition: i + chunk.length
},
embedding: []
});
}
}
// Generate embeddings and store chunks
for (const chunk of chunks) {
chunk.embedding = await vectorDatabaseService.generateEmbeddings(chunk.content);
}
await vectorDatabaseService.storeDocumentChunks(chunks.map(chunk => ({
id: chunk.id,
documentId: chunk.documentId,
content: chunk.content,
metadata: chunk.metadata,
embedding: chunk.embedding,
chunkIndex: chunk.metadata.chunkIndex,
createdAt: new Date(),
updatedAt: new Date()
})));
logger.info(`Created and stored ${chunks.length} document chunks`, { documentId });
} catch (error) {
logger.error('Failed to create document chunks', { error, documentId });
// Don't throw - continue without RAG enhancement
}
}
/**
* Integrate financial analysis insights into CIM analysis
*/
private integrateFinancialAnalysis(cimAnalysis: CIMReview, financialAnalysis: any): CIMReview {
try {
// Enhance financial summary with deep analysis insights
if (financialAnalysis.summary) {
const enhancedFinancialSummary = {
...cimAnalysis.financialSummary,
qualityOfEarnings: this.combineInsights(
cimAnalysis.financialSummary.qualityOfEarnings,
`Quality Assessment: ${financialAnalysis.qualityOfEarnings?.overallScore}/10. ${financialAnalysis.summary.strengths.join('; ')}`
),
revenueGrowthDrivers: this.combineInsights(
cimAnalysis.financialSummary.revenueGrowthDrivers,
financialAnalysis.valueCreation?.revenueOpportunities?.map((op: any) => op.opportunity).join('; ')
),
marginStabilityAnalysis: this.combineInsights(
cimAnalysis.financialSummary.marginStabilityAnalysis,
`Risk Assessment: ${financialAnalysis.riskAssessment?.operational?.marginStability} stability`
)
};
// Enhance investment thesis with financial insights
const enhancedInvestmentThesis = {
...cimAnalysis.preliminaryInvestmentThesis,
keyAttractions: this.combineInsights(
cimAnalysis.preliminaryInvestmentThesis.keyAttractions,
financialAnalysis.summary.strengths.join('; ')
),
potentialRisks: this.combineInsights(
cimAnalysis.preliminaryInvestmentThesis.potentialRisks,
financialAnalysis.summary.keyRisks.join('; ')
),
valueCreationLevers: this.combineInsights(
cimAnalysis.preliminaryInvestmentThesis.valueCreationLevers,
`Value Creation Potential: ${financialAnalysis.summary.valueCreationPotential}. Key opportunities: ${financialAnalysis.valueCreation?.revenueOpportunities?.slice(0, 2).map((op: any) => op.opportunity).join('; ')}`
)
};
return {
...cimAnalysis,
financialSummary: enhancedFinancialSummary,
preliminaryInvestmentThesis: enhancedInvestmentThesis
};
}
return cimAnalysis;
} catch (error) {
logger.error('Failed to integrate financial analysis', { error });
return cimAnalysis; // Return original on error
}
}
/**
* Combine insights intelligently
*/
private combineInsights(original: string, enhancement: string): string {
if (!original || original === 'Not specified in CIM') {
return enhancement || 'Not specified in CIM';
}
if (!enhancement) {
return original;
}
return `${original} | Enhanced Analysis: ${enhancement}`;
}
/**
* Save processing results to database
*/
private async saveResults(
cimAnalysis: CIMReview,
documentId: string,
qualityMetrics: any
): Promise<void> {
try {
// Update document with analysis results
await DocumentModel.updateAnalysisResults(documentId, {
analysis_data: cimAnalysis,
processing_completed_at: new Date(),
status: 'completed'
});
// Update processing job status
await ProcessingJobModel.updateStatus(documentId, 'completed', {
qualityScore: qualityMetrics.overallScore,
completeness: qualityMetrics.completeness,
accuracy: qualityMetrics.accuracy
});
logger.info('Results saved successfully', { documentId, qualityScore: qualityMetrics.overallScore });
} catch (error) {
logger.error('Failed to save results', { error, documentId });
throw error;
}
}
}
export const enhancedCIMProcessor = new EnhancedCIMProcessor();

View File

@@ -1,410 +0,0 @@
import { logger } from '../utils/logger';
import { config } from '../config/env';
import { llmService } from './llmService';
export interface TaskType {
type: 'financial' | 'business' | 'market' | 'management' | 'creative' | 'reasoning' | 'general';
complexity: 'simple' | 'complex';
priority: 'cost' | 'quality' | 'speed';
}
export interface EnhancedLLMRequest {
prompt: string;
systemPrompt?: string;
taskType: TaskType;
maxTokens?: number;
temperature?: number;
fallbackToGPT?: boolean;
}
export interface EnhancedLLMResponse {
success: boolean;
content: string;
model: string;
provider: string;
usage?: {
promptTokens: number;
completionTokens: number;
totalTokens: number;
};
error?: string;
}
class EnhancedLLMService {
private llmService: typeof llmService;
constructor() {
this.llmService = llmService;
}
/**
* Select the optimal model based on task type and requirements
*/
private selectOptimalModel(taskType: TaskType): { model: string; provider: string } {
const { enableHybridApproach, useClaudeForFinancial, useGPTForCreative } = config.llm;
if (!enableHybridApproach) {
// Fallback to default provider
return {
model: config.llm.model,
provider: config.llm.provider
};
}
// Task-specific model selection
switch (taskType.type) {
case 'financial':
if (useClaudeForFinancial) {
return {
model: config.llm.financialModel,
provider: 'anthropic'
};
}
break;
case 'reasoning':
return {
model: config.llm.reasoningModel,
provider: 'anthropic'
};
case 'creative':
if (useGPTForCreative) {
return {
model: config.llm.creativeModel,
provider: 'openai'
};
}
break;
case 'business':
case 'market':
case 'management':
// Use Claude for analytical tasks
return {
model: config.llm.reasoningModel,
provider: 'anthropic'
};
default:
// Use Claude as default for cost efficiency
return {
model: config.llm.model,
provider: 'anthropic'
};
}
// Fallback to default
return {
model: config.llm.model,
provider: config.llm.provider
};
}
/**
* Process request with optimal model selection
*/
async processRequest(request: EnhancedLLMRequest): Promise<EnhancedLLMResponse> {
const { taskType, fallbackToGPT = true } = request;
// Select optimal model
const { model, provider } = this.selectOptimalModel(taskType);
logger.info('Enhanced LLM processing', {
taskType: taskType.type,
complexity: taskType.complexity,
selectedModel: model,
selectedProvider: provider
});
try {
// First attempt with optimal model
const result = await this.callLLMWithProvider(request, model, provider);
if (result.success) {
return {
...result,
model,
provider
};
}
// Fallback to GPT if enabled and first attempt failed
if (fallbackToGPT && provider !== 'openai') {
logger.info('Falling back to GPT-4.5', { originalModel: model });
const fallbackResult = await this.callLLMWithProvider(
request,
config.llm.fallbackModel,
'openai'
);
return {
...fallbackResult,
model: config.llm.fallbackModel,
provider: 'openai'
};
}
return {
...result,
model: config.llm.model,
provider: config.llm.provider
};
} catch (error) {
logger.error('Enhanced LLM processing failed', error);
return {
success: false,
content: '',
model,
provider,
error: error instanceof Error ? error.message : 'Unknown error'
};
}
}
/**
* Call LLM with specific provider
*/
private async callLLMWithProvider(
request: EnhancedLLMRequest,
_model: string,
_provider: string
): Promise<{ success: boolean; content: string; usage?: any; error?: string }> {
// Temporarily override the provider for this call
const originalProvider = config.llm.provider;
config.llm.provider = _provider;
try {
const result = await this.llmService.processCIMDocument(request.prompt, '', {
prompt: request.prompt,
systemPrompt: request.systemPrompt || '',
agentName: 'enhanced_analysis'
});
return {
success: result.success,
content: result.jsonOutput ? JSON.stringify(result.jsonOutput) : '',
usage: undefined,
...(result.error && { error: result.error })
};
} finally {
// Restore original provider
config.llm.provider = originalProvider;
}
}
/**
* Enhanced financial analysis with Claude
*/
async analyzeFinancialData(text: string): Promise<EnhancedLLMResponse> {
const prompt = this.buildEnhancedFinancialPrompt(text);
return this.processRequest({
prompt,
taskType: {
type: 'financial',
complexity: 'complex',
priority: 'quality'
},
maxTokens: 4000,
temperature: 0.1
});
}
/**
* Enhanced business analysis with Claude
*/
async analyzeBusinessModel(text: string): Promise<EnhancedLLMResponse> {
const prompt = this.buildEnhancedBusinessPrompt(text);
return this.processRequest({
prompt,
taskType: {
type: 'business',
complexity: 'complex',
priority: 'quality'
},
maxTokens: 4000,
temperature: 0.1
});
}
/**
* Enhanced market analysis with Claude
*/
async analyzeMarketPosition(text: string): Promise<EnhancedLLMResponse> {
const prompt = this.buildEnhancedMarketPrompt(text);
return this.processRequest({
prompt,
taskType: {
type: 'market',
complexity: 'complex',
priority: 'quality'
},
maxTokens: 4000,
temperature: 0.1
});
}
/**
* Enhanced management analysis with Claude
*/
async analyzeManagementTeam(text: string): Promise<EnhancedLLMResponse> {
const prompt = this.buildEnhancedManagementPrompt(text);
return this.processRequest({
prompt,
taskType: {
type: 'management',
complexity: 'complex',
priority: 'quality'
},
maxTokens: 4000,
temperature: 0.1
});
}
/**
* Creative content generation with GPT-4.5
*/
async generateCreativeContent(text: string): Promise<EnhancedLLMResponse> {
const prompt = this.buildCreativePrompt(text);
return this.processRequest({
prompt,
taskType: {
type: 'creative',
complexity: 'complex',
priority: 'quality'
},
maxTokens: 4000,
temperature: 0.3
});
}
// Enhanced prompt builders
private buildEnhancedFinancialPrompt(text: string): string {
return `You are a senior financial analyst specializing in private equity due diligence.
IMPORTANT: Extract and analyze financial data with precision. Look for:
- Revenue figures and growth trends
- EBITDA and profitability metrics
- Cash flow and working capital data
- Financial tables and structured data
- Pro forma adjustments and normalizations
- Historical performance (3+ years)
- Projections and forecasts
MAP FISCAL YEARS CORRECTLY:
- FY-3: Oldest year (e.g., 2022, 2023)
- FY-2: Second oldest year (e.g., 2023, 2024)
- FY-1: Most recent full year (e.g., 2024, 2025)
- LTM: Last Twelve Months, TTM, or most recent period
DOCUMENT TEXT:
${text.substring(text.length - 8000)} // Focus on end where financial data typically appears
Return structured financial analysis with actual numbers where available. Use "Not found" for missing data.`;
}
private buildEnhancedBusinessPrompt(text: string): string {
return `You are a business analyst specializing in private equity investment analysis.
FOCUS ON EXTRACTING:
- Core business model and revenue streams
- Customer segments and value proposition
- Key products/services and market positioning
- Operational model and scalability factors
- Competitive advantages and moats
- Growth drivers and expansion opportunities
- Risk factors and dependencies
ANALYZE:
- Business model sustainability
- Market positioning effectiveness
- Operational efficiency indicators
- Scalability potential
- Competitive landscape positioning
DOCUMENT TEXT:
${text.substring(0, 15000)}
Provide comprehensive business analysis suitable for investment decision-making.`;
}
private buildEnhancedMarketPrompt(text: string): string {
return `You are a market research analyst specializing in private equity market analysis.
EXTRACT AND ANALYZE:
- Total Addressable Market (TAM) and Serviceable Market (SAM)
- Market growth rates and trends
- Competitive landscape and positioning
- Market entry barriers and moats
- Regulatory environment impact
- Industry tailwinds and headwinds
- Market segmentation and opportunities
EVALUATE:
- Market attractiveness and size
- Competitive intensity and positioning
- Growth potential and sustainability
- Risk factors and market dynamics
- Investment timing considerations
DOCUMENT TEXT:
${text.substring(0, 15000)}
Provide detailed market analysis for investment evaluation.`;
}
private buildEnhancedManagementPrompt(text: string): string {
return `You are a management assessment specialist for private equity investments.
ANALYZE MANAGEMENT TEAM:
- Key leadership profiles and experience
- Industry-specific expertise and track record
- Operational and strategic capabilities
- Succession planning and retention risk
- Post-transaction intentions and alignment
- Team dynamics and organizational structure
ASSESS:
- Management quality and experience
- Cultural fit and alignment potential
- Operational capabilities and gaps
- Retention risk and succession planning
- Value creation potential
DOCUMENT TEXT:
${text.substring(0, 15000)}
Provide comprehensive management team assessment.`;
}
private buildCreativePrompt(text: string): string {
return `You are a creative investment analyst crafting compelling investment narratives.
CREATE:
- Engaging investment thesis presentation
- Persuasive value proposition messaging
- Compelling risk-reward narratives
- Professional presentation materials
- Stakeholder communication content
FOCUS ON:
- Emotional intelligence and persuasion
- Clear and compelling storytelling
- Professional tone and presentation
- Engaging and memorable content
- Strategic messaging alignment
DOCUMENT TEXT:
${text.substring(0, 15000)}
Generate creative, engaging content for investment presentations and communications.`;
}
}
export const enhancedLLMService = new EnhancedLLMService();

View File

@@ -1,608 +0,0 @@
import { logger } from '../utils/logger';
import { llmService } from './llmService';
export interface FinancialMetrics {
revenue: {
fy3: number;
fy2: number;
fy1: number;
ltm: number;
cagr3yr: number;
ltmGrowth: number;
};
profitability: {
grossMargin: { fy3: number; fy2: number; fy1: number; ltm: number };
ebitdaMargin: { fy3: number; fy2: number; fy1: number; ltm: number };
ebitda: { fy3: number; fy2: number; fy1: number; ltm: number };
};
cashFlow: {
operatingCashFlow: { fy3: number; fy2: number; fy1: number; ltm: number };
freeCashFlow: { fy3: number; fy2: number; fy1: number; ltm: number };
capexIntensity: { fy3: number; fy2: number; fy1: number; ltm: number };
};
workingCapital: {
daysReceivable: number;
daysPayable: number;
daysInventory: number;
cashConversionCycle: number;
};
}
export interface QualityOfEarningsAssessment {
revenueQuality: {
score: number; // 1-10
factors: string[];
recurring: number; // % of revenue that is recurring
seasonality: string;
customerConcentration: number; // % from top 10 customers
};
profitabilityQuality: {
score: number; // 1-10
factors: string[];
adjustments: Array<{ item: string; amount: number; reason: string }>;
normalizedEbitda: { fy3: number; fy2: number; fy1: number; ltm: number };
};
cashFlowQuality: {
score: number; // 1-10
factors: string[];
cashConversion: number; // % of EBITDA converted to cash
workingCapitalTrend: 'improving' | 'stable' | 'deteriorating';
};
overallScore: number; // 1-10
}
export interface FinancialRiskAssessment {
liquidity: {
score: number; // 1-10 (10 = excellent)
currentRatio: number;
quickRatio: number;
cashPosition: number;
debtMaturityProfile: string;
};
leverage: {
score: number; // 1-10
totalDebtToEbitda: number;
netDebtToEbitda: number;
interestCoverage: number;
debtStructure: string;
};
operational: {
score: number; // 1-10
marginStability: 'high' | 'medium' | 'low';
costStructure: 'fixed' | 'variable' | 'mixed';
cyclicality: 'high' | 'medium' | 'low';
};
overallRisk: 'low' | 'medium' | 'high';
}
export interface ValueCreationAnalysis {
revenueOpportunities: Array<{
opportunity: string;
impact: 'high' | 'medium' | 'low';
timeframe: string;
investmentRequired: number;
riskLevel: 'low' | 'medium' | 'high';
}>;
profitabilityImprovements: Array<{
improvement: string;
impact: 'high' | 'medium' | 'low';
timeframe: string;
investmentRequired: number;
riskLevel: 'low' | 'medium' | 'high';
}>;
operationalEfficiencies: Array<{
efficiency: string;
impact: 'high' | 'medium' | 'low';
timeframe: string;
investmentRequired: number;
riskLevel: 'low' | 'medium' | 'high';
}>;
strategicInitiatives: Array<{
initiative: string;
impact: 'high' | 'medium' | 'low';
timeframe: string;
investmentRequired: number;
riskLevel: 'low' | 'medium' | 'high';
}>;
}
export interface ComprehensiveFinancialAnalysis {
metrics: FinancialMetrics;
qualityOfEarnings: QualityOfEarningsAssessment;
riskAssessment: FinancialRiskAssessment;
valueCreation: ValueCreationAnalysis;
summary: {
strengths: string[];
weaknesses: string[];
keyRisks: string[];
valueCreationPotential: 'high' | 'medium' | 'low';
investmentRecommendation: 'strong buy' | 'buy' | 'hold' | 'pass';
rationale: string;
};
}
class FinancialAnalysisEngine {
/**
* Perform comprehensive financial analysis of a CIM document
*/
async performComprehensiveAnalysis(
cimText: string,
documentId: string
): Promise<ComprehensiveFinancialAnalysis> {
logger.info('Starting comprehensive financial analysis', { documentId });
try {
// Step 1: Extract and parse financial data
const financialMetrics = await this.extractFinancialMetrics(cimText);
// Step 2: Assess quality of earnings
const qualityOfEarnings = await this.assessQualityOfEarnings(cimText, financialMetrics);
// Step 3: Evaluate financial risks
const riskAssessment = await this.evaluateFinancialRisks(cimText, financialMetrics);
// Step 4: Identify value creation opportunities
const valueCreation = await this.identifyValueCreationOpportunities(cimText, financialMetrics);
// Step 5: Generate summary and recommendations
const summary = this.generateFinancialSummary(financialMetrics, qualityOfEarnings, riskAssessment, valueCreation);
return {
metrics: financialMetrics,
qualityOfEarnings,
riskAssessment,
valueCreation,
summary
};
} catch (error) {
logger.error('Comprehensive financial analysis failed', { error, documentId });
throw new Error(`Financial analysis failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
}
/**
* Extract and calculate financial metrics from CIM text
*/
private async extractFinancialMetrics(cimText: string): Promise<FinancialMetrics> {
const prompt = `
Analyze the financial data in this CIM document and extract key financial metrics.
Focus on revenue, profitability, cash flow, and working capital metrics for the last 4 years (FY-3, FY-2, FY-1, LTM).
Calculate:
- Revenue figures and growth rates (CAGR and LTM growth)
- Gross margin and EBITDA margin trends
- EBITDA figures
- Operating cash flow and free cash flow
- Capex intensity (Capex as % of revenue)
- Working capital metrics (DSO, DPO, DIO, cash conversion cycle)
Document text:
${cimText.substring(0, 30000)}
Return structured financial metrics with actual numbers where available, or reasonable estimates based on industry norms if specific figures are not provided.
`;
const systemPrompt = `You are a senior financial analyst specializing in private equity financial due diligence. Extract and calculate financial metrics with precision and attention to detail. Focus on accuracy and provide estimates only when specific data is unavailable.`;
try {
const result = await llmService.processCIMDocument(cimText, '', {
prompt,
systemPrompt,
agentName: 'financial_metrics_extraction'
});
if (!result.success || !result.jsonOutput) {
throw new Error('Failed to extract financial metrics');
}
return this.parseFinancialMetrics(result.jsonOutput);
} catch (error) {
logger.error('Financial metrics extraction failed', error);
throw error;
}
}
/**
* Assess quality of earnings
*/
private async assessQualityOfEarnings(
cimText: string,
metrics: FinancialMetrics
): Promise<QualityOfEarningsAssessment> {
const prompt = `
Assess the quality of earnings for this company based on the CIM document and financial metrics.
Evaluate:
1. Revenue Quality:
- Revenue recognition policies
- Customer concentration and recurring revenue
- Seasonality and cyclicality
- One-time vs. recurring revenue
2. Profitability Quality:
- Non-recurring items and adjustments
- Quality of gross margins
- SG&A efficiency
- One-time costs or benefits
3. Cash Flow Quality:
- Cash conversion from EBITDA
- Working capital management
- Capex requirements
- Free cash flow sustainability
Document text:
${cimText.substring(0, 25000)}
Financial Metrics Context:
${JSON.stringify(metrics, null, 2)}
Provide a detailed quality of earnings assessment with scores (1-10) and specific factors.
`;
const systemPrompt = `You are an expert in quality of earnings analysis for private equity investments. Provide thorough analysis of revenue quality, profitability sustainability, and cash flow conversion. Be critical and identify potential red flags.`;
try {
const result = await llmService.processCIMDocument(cimText, '', {
prompt,
systemPrompt,
agentName: 'quality_of_earnings'
});
if (!result.success || !result.jsonOutput) {
throw new Error('Failed to assess quality of earnings');
}
return this.parseQualityOfEarnings(result.jsonOutput);
} catch (error) {
logger.error('Quality of earnings assessment failed', error);
throw error;
}
}
/**
* Evaluate financial risks
*/
private async evaluateFinancialRisks(
cimText: string,
metrics: FinancialMetrics
): Promise<FinancialRiskAssessment> {
const prompt = `
Evaluate the financial risks associated with this investment opportunity.
Assess:
1. Liquidity Risk:
- Cash position and liquidity ratios
- Debt maturity profile
- Working capital requirements
- Credit facilities and covenant compliance
2. Leverage Risk:
- Total debt and net debt ratios
- Interest coverage ratios
- Debt structure and terms
- Refinancing risks
3. Operational Risk:
- Margin stability and volatility
- Cost structure flexibility
- Business cyclicality
- Competitive pressures on pricing
Document text:
${cimText.substring(0, 25000)}
Financial Context:
${JSON.stringify(metrics, null, 2)}
Provide comprehensive risk assessment with scores and specific risk factors.
`;
const systemPrompt = `You are a financial risk assessment expert for private equity. Identify and quantify financial risks that could impact investment returns. Focus on liquidity, leverage, and operational risks.`;
try {
const result = await llmService.processCIMDocument(cimText, '', {
prompt,
systemPrompt,
agentName: 'financial_risk_assessment'
});
if (!result.success || !result.jsonOutput) {
throw new Error('Failed to evaluate financial risks');
}
return this.parseRiskAssessment(result.jsonOutput);
} catch (error) {
logger.error('Financial risk assessment failed', error);
throw error;
}
}
/**
* Identify value creation opportunities
*/
private async identifyValueCreationOpportunities(
cimText: string,
metrics: FinancialMetrics
): Promise<ValueCreationAnalysis> {
const prompt = `
Identify specific value creation opportunities for this private equity investment.
Focus on:
1. Revenue Growth Opportunities:
- Market expansion and new customer acquisition
- Product/service line extensions
- Pricing optimization
- Cross-selling and upselling
2. Profitability Improvements:
- Cost reduction initiatives
- Margin enhancement opportunities
- Procurement optimization
- Process improvements
3. Operational Efficiencies:
- Technology investments and automation
- Supply chain optimization
- Working capital improvements
- Organizational restructuring
4. Strategic Initiatives:
- Add-on acquisitions
- Digital transformation
- ESG improvements
- Market consolidation plays
Document text:
${cimText.substring(0, 25000)}
Financial Context:
${JSON.stringify(metrics, null, 2)}
For each opportunity, assess impact, timeframe, investment required, and risk level.
`;
const systemPrompt = `You are a value creation expert for private equity. Identify specific, actionable opportunities to drive value creation. Focus on initiatives that align with BPCP's expertise in operational improvements, technology adoption, and strategic growth.`;
try {
const result = await llmService.processCIMDocument(cimText, '', {
prompt,
systemPrompt,
agentName: 'value_creation_analysis'
});
if (!result.success || !result.jsonOutput) {
throw new Error('Failed to identify value creation opportunities');
}
return this.parseValueCreationAnalysis(result.jsonOutput);
} catch (error) {
logger.error('Value creation analysis failed', error);
throw error;
}
}
/**
* Generate comprehensive financial summary
*/
private generateFinancialSummary(
metrics: FinancialMetrics,
qualityOfEarnings: QualityOfEarningsAssessment,
riskAssessment: FinancialRiskAssessment,
valueCreation: ValueCreationAnalysis
): ComprehensiveFinancialAnalysis['summary'] {
const strengths: string[] = [];
const weaknesses: string[] = [];
const keyRisks: string[] = [];
// Analyze revenue trends
if (metrics.revenue.cagr3yr > 0.15) {
strengths.push(`Strong revenue growth with ${(metrics.revenue.cagr3yr * 100).toFixed(1)}% 3-year CAGR`);
} else if (metrics.revenue.cagr3yr < 0.05) {
weaknesses.push(`Low revenue growth with ${(metrics.revenue.cagr3yr * 100).toFixed(1)}% 3-year CAGR`);
}
// Analyze profitability
const avgEbitdaMargin = (
metrics.profitability.ebitdaMargin.fy3 +
metrics.profitability.ebitdaMargin.fy2 +
metrics.profitability.ebitdaMargin.fy1 +
metrics.profitability.ebitdaMargin.ltm
) / 4;
if (avgEbitdaMargin > 0.20) {
strengths.push(`Strong profitability with ${(avgEbitdaMargin * 100).toFixed(1)}% average EBITDA margin`);
} else if (avgEbitdaMargin < 0.10) {
weaknesses.push(`Low profitability with ${(avgEbitdaMargin * 100).toFixed(1)}% average EBITDA margin`);
}
// Quality of earnings impact
if (qualityOfEarnings.overallScore >= 8) {
strengths.push('High quality of earnings with sustainable profitability');
} else if (qualityOfEarnings.overallScore <= 5) {
weaknesses.push('Quality of earnings concerns with potential adjustments');
}
// Risk assessment impact
if (riskAssessment.overallRisk === 'high') {
keyRisks.push('High overall financial risk profile');
}
if (riskAssessment.leverage.totalDebtToEbitda > 4) {
keyRisks.push(`High leverage with ${riskAssessment.leverage.totalDebtToEbitda.toFixed(1)}x debt-to-EBITDA`);
}
if (riskAssessment.liquidity.score <= 5) {
keyRisks.push('Liquidity concerns requiring attention');
}
// Value creation potential
const highImpactOpportunities = [
...valueCreation.revenueOpportunities.filter(op => op.impact === 'high'),
...valueCreation.profitabilityImprovements.filter(op => op.impact === 'high'),
...valueCreation.operationalEfficiencies.filter(op => op.impact === 'high'),
...valueCreation.strategicInitiatives.filter(op => op.impact === 'high')
];
const valueCreationPotential: 'high' | 'medium' | 'low' =
highImpactOpportunities.length >= 3 ? 'high' :
highImpactOpportunities.length >= 1 ? 'medium' : 'low';
// Generate investment recommendation
let investmentRecommendation: 'strong buy' | 'buy' | 'hold' | 'pass';
let rationale: string;
const positiveFactors = strengths.length;
const negativeFactors = weaknesses.length + keyRisks.length;
const qualityScore = qualityOfEarnings.overallScore;
const riskLevel = riskAssessment.overallRisk;
if (positiveFactors >= 3 && qualityScore >= 7 && riskLevel !== 'high' && valueCreationPotential === 'high') {
investmentRecommendation = 'strong buy';
rationale = 'Strong financial profile with high-quality earnings, manageable risk, and significant value creation potential';
} else if (positiveFactors >= 2 && qualityScore >= 6 && riskLevel !== 'high') {
investmentRecommendation = 'buy';
rationale = 'Solid financial fundamentals with good value creation opportunities, suitable for BPCP investment';
} else if (negativeFactors <= positiveFactors && qualityScore >= 5) {
investmentRecommendation = 'hold';
rationale = 'Mixed financial profile requiring further due diligence and risk mitigation strategies';
} else {
investmentRecommendation = 'pass';
rationale = 'Financial risks and concerns outweigh potential returns, not suitable for current investment criteria';
}
return {
strengths,
weaknesses,
keyRisks,
valueCreationPotential,
investmentRecommendation,
rationale
};
}
// Helper parsing methods
private parseFinancialMetrics(data: any): FinancialMetrics {
// Implementation would parse the LLM response into structured FinancialMetrics
// This is a simplified version - in practice, you'd have robust parsing logic
return {
revenue: {
fy3: data.revenue?.fy3 || 0,
fy2: data.revenue?.fy2 || 0,
fy1: data.revenue?.fy1 || 0,
ltm: data.revenue?.ltm || 0,
cagr3yr: data.revenue?.cagr3yr || 0,
ltmGrowth: data.revenue?.ltmGrowth || 0
},
profitability: {
grossMargin: {
fy3: data.profitability?.grossMargin?.fy3 || 0,
fy2: data.profitability?.grossMargin?.fy2 || 0,
fy1: data.profitability?.grossMargin?.fy1 || 0,
ltm: data.profitability?.grossMargin?.ltm || 0
},
ebitdaMargin: {
fy3: data.profitability?.ebitdaMargin?.fy3 || 0,
fy2: data.profitability?.ebitdaMargin?.fy2 || 0,
fy1: data.profitability?.ebitdaMargin?.fy1 || 0,
ltm: data.profitability?.ebitdaMargin?.ltm || 0
},
ebitda: {
fy3: data.profitability?.ebitda?.fy3 || 0,
fy2: data.profitability?.ebitda?.fy2 || 0,
fy1: data.profitability?.ebitda?.fy1 || 0,
ltm: data.profitability?.ebitda?.ltm || 0
}
},
cashFlow: {
operatingCashFlow: {
fy3: data.cashFlow?.operatingCashFlow?.fy3 || 0,
fy2: data.cashFlow?.operatingCashFlow?.fy2 || 0,
fy1: data.cashFlow?.operatingCashFlow?.fy1 || 0,
ltm: data.cashFlow?.operatingCashFlow?.ltm || 0
},
freeCashFlow: {
fy3: data.cashFlow?.freeCashFlow?.fy3 || 0,
fy2: data.cashFlow?.freeCashFlow?.fy2 || 0,
fy1: data.cashFlow?.freeCashFlow?.fy1 || 0,
ltm: data.cashFlow?.freeCashFlow?.ltm || 0
},
capexIntensity: {
fy3: data.cashFlow?.capexIntensity?.fy3 || 0,
fy2: data.cashFlow?.capexIntensity?.fy2 || 0,
fy1: data.cashFlow?.capexIntensity?.fy1 || 0,
ltm: data.cashFlow?.capexIntensity?.ltm || 0
}
},
workingCapital: {
daysReceivable: data.workingCapital?.daysReceivable || 0,
daysPayable: data.workingCapital?.daysPayable || 0,
daysInventory: data.workingCapital?.daysInventory || 0,
cashConversionCycle: data.workingCapital?.cashConversionCycle || 0
}
};
}
private parseQualityOfEarnings(data: any): QualityOfEarningsAssessment {
return {
revenueQuality: {
score: data.revenueQuality?.score || 5,
factors: data.revenueQuality?.factors || [],
recurring: data.revenueQuality?.recurring || 0,
seasonality: data.revenueQuality?.seasonality || 'Unknown',
customerConcentration: data.revenueQuality?.customerConcentration || 0
},
profitabilityQuality: {
score: data.profitabilityQuality?.score || 5,
factors: data.profitabilityQuality?.factors || [],
adjustments: data.profitabilityQuality?.adjustments || [],
normalizedEbitda: data.profitabilityQuality?.normalizedEbitda || { fy3: 0, fy2: 0, fy1: 0, ltm: 0 }
},
cashFlowQuality: {
score: data.cashFlowQuality?.score || 5,
factors: data.cashFlowQuality?.factors || [],
cashConversion: data.cashFlowQuality?.cashConversion || 0,
workingCapitalTrend: data.cashFlowQuality?.workingCapitalTrend || 'stable'
},
overallScore: data.overallScore || 5
};
}
private parseRiskAssessment(data: any): FinancialRiskAssessment {
return {
liquidity: {
score: data.liquidity?.score || 5,
currentRatio: data.liquidity?.currentRatio || 0,
quickRatio: data.liquidity?.quickRatio || 0,
cashPosition: data.liquidity?.cashPosition || 0,
debtMaturityProfile: data.liquidity?.debtMaturityProfile || 'Unknown'
},
leverage: {
score: data.leverage?.score || 5,
totalDebtToEbitda: data.leverage?.totalDebtToEbitda || 0,
netDebtToEbitda: data.leverage?.netDebtToEbitda || 0,
interestCoverage: data.leverage?.interestCoverage || 0,
debtStructure: data.leverage?.debtStructure || 'Unknown'
},
operational: {
score: data.operational?.score || 5,
marginStability: data.operational?.marginStability || 'medium',
costStructure: data.operational?.costStructure || 'mixed',
cyclicality: data.operational?.cyclicality || 'medium'
},
overallRisk: data.overallRisk || 'medium'
};
}
private parseValueCreationAnalysis(data: any): ValueCreationAnalysis {
return {
revenueOpportunities: data.revenueOpportunities || [],
profitabilityImprovements: data.profitabilityImprovements || [],
operationalEfficiencies: data.operationalEfficiencies || [],
strategicInitiatives: data.strategicInitiatives || []
};
}
}
export const financialAnalysisEngine = new FinancialAnalysisEngine();

View File

@@ -1,649 +0,0 @@
import { logger } from '../utils/logger';
import { llmService } from './llmService';
import { CIMReview, cimReviewSchema } from './llmSchemas';
import { z } from 'zod';
export interface QualityMetrics {
completeness: {
score: number; // 0-100
missingFields: string[];
incompleteFields: string[];
completionRate: number; // % of fields with meaningful content
};
accuracy: {
score: number; // 0-100
factualConsistency: number;
numericalAccuracy: number;
logicalCoherence: number;
potentialErrors: string[];
};
depth: {
score: number; // 0-100
analysisQuality: number;
insightfulness: number;
detailLevel: number;
superficialFields: string[];
};
relevance: {
score: number; // 0-100
bcpAlignment: number; // Alignment with BPCP criteria
investmentFocus: number;
materialityAssessment: number;
irrelevantContent: string[];
};
consistency: {
score: number; // 0-100
internalConsistency: number;
crossReferenceAlignment: number;
contradictions: string[];
};
overallScore: number; // 0-100
}
export interface ValidationResult {
passed: boolean;
qualityMetrics: QualityMetrics;
criticalIssues: string[];
recommendations: string[];
refinementSuggestions: RefinementSuggestion[];
}
export interface RefinementSuggestion {
category: 'completeness' | 'accuracy' | 'depth' | 'relevance' | 'consistency';
priority: 'high' | 'medium' | 'low';
field: string;
issue: string;
suggestion: string;
requiredAction: 'rewrite' | 'enhance' | 'verify' | 'research';
}
export interface IterativeRefinementResult {
success: boolean;
iterations: number;
finalResult: CIMReview;
qualityImprovement: {
initialScore: number;
finalScore: number;
improvement: number;
};
processingTime: number;
error?: string;
}
class QualityValidationService {
private readonly QUALITY_THRESHOLD = 85; // Minimum acceptable quality score
private readonly MAX_REFINEMENT_ITERATIONS = 3;
/**
* Validate CIM analysis quality against BPCP standards
*/
async validateQuality(
cimAnalysis: CIMReview,
originalText: string,
documentId: string
): Promise<ValidationResult> {
logger.info('Starting quality validation', { documentId });
try {
// Step 1: Schema validation
const schemaValidation = this.validateSchema(cimAnalysis);
// Step 2: Completeness assessment
const completeness = await this.assessCompleteness(cimAnalysis);
// Step 3: Accuracy verification
const accuracy = await this.verifyAccuracy(cimAnalysis, originalText);
// Step 4: Depth analysis
const depth = await this.analyzeDepth(cimAnalysis, originalText);
// Step 5: Relevance evaluation
const relevance = await this.evaluateRelevance(cimAnalysis, originalText);
// Step 6: Consistency check
const consistency = await this.checkConsistency(cimAnalysis);
// Calculate overall quality metrics
const qualityMetrics: QualityMetrics = {
completeness,
accuracy,
depth,
relevance,
consistency,
overallScore: this.calculateOverallScore(completeness, accuracy, depth, relevance, consistency)
};
// Generate validation result
const criticalIssues = this.identifyCriticalIssues(qualityMetrics, schemaValidation);
const recommendations = this.generateRecommendations(qualityMetrics);
const refinementSuggestions = this.generateRefinementSuggestions(qualityMetrics);
const passed = qualityMetrics.overallScore >= this.QUALITY_THRESHOLD && criticalIssues.length === 0;
return {
passed,
qualityMetrics,
criticalIssues,
recommendations,
refinementSuggestions
};
} catch (error) {
logger.error('Quality validation failed', { error, documentId });
throw new Error(`Quality validation failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
}
/**
* Perform iterative refinement to improve quality
*/
async performIterativeRefinement(
initialAnalysis: CIMReview,
originalText: string,
documentId: string,
targetQuality: number = this.QUALITY_THRESHOLD
): Promise<IterativeRefinementResult> {
const startTime = Date.now();
logger.info('Starting iterative refinement', { documentId, targetQuality });
try {
let currentAnalysis = initialAnalysis;
let currentValidation = await this.validateQuality(currentAnalysis, originalText, documentId);
let iterations = 0;
const initialScore = currentValidation.qualityMetrics.overallScore;
while (iterations < this.MAX_REFINEMENT_ITERATIONS &&
currentValidation.qualityMetrics.overallScore < targetQuality) {
iterations++;
logger.info(`Refinement iteration ${iterations}`, {
documentId,
currentScore: currentValidation.qualityMetrics.overallScore,
target: targetQuality
});
// Perform refinement based on suggestions
const refinedAnalysis = await this.refineAnalysis(
currentAnalysis,
originalText,
currentValidation.refinementSuggestions,
iterations
);
if (!refinedAnalysis) {
logger.warn('Refinement failed, stopping iterations', { documentId, iterations });
break;
}
currentAnalysis = refinedAnalysis;
currentValidation = await this.validateQuality(currentAnalysis, originalText, documentId);
// Break if quality target is reached
if (currentValidation.qualityMetrics.overallScore >= targetQuality) {
logger.info('Quality target reached', {
documentId,
iterations,
finalScore: currentValidation.qualityMetrics.overallScore
});
break;
}
}
const finalScore = currentValidation.qualityMetrics.overallScore;
const improvement = finalScore - initialScore;
return {
success: true,
iterations,
finalResult: currentAnalysis,
qualityImprovement: {
initialScore,
finalScore,
improvement
},
processingTime: Date.now() - startTime
};
} catch (error) {
logger.error('Iterative refinement failed', { error, documentId });
return {
success: false,
iterations: 0,
finalResult: initialAnalysis,
qualityImprovement: {
initialScore: 0,
finalScore: 0,
improvement: 0
},
processingTime: Date.now() - startTime,
error: error instanceof Error ? error.message : 'Unknown error'
};
}
}
/**
* Validate against schema
*/
private validateSchema(cimAnalysis: CIMReview): z.ZodIssue[] {
try {
cimReviewSchema.parse(cimAnalysis);
return [];
} catch (error) {
if (error instanceof z.ZodError) {
return error.issues;
}
return [];
}
}
/**
* Assess completeness of the analysis
*/
private async assessCompleteness(cimAnalysis: CIMReview): Promise<QualityMetrics['completeness']> {
const allFields = this.getAllFields(cimAnalysis);
const missingFields: string[] = [];
const incompleteFields: string[] = [];
let completedFields = 0;
for (const [fieldPath, value] of allFields) {
if (!value || value === '' || value === 'Not specified in CIM') {
missingFields.push(fieldPath);
} else if (typeof value === 'string' && value.length < 10) {
incompleteFields.push(fieldPath);
} else {
completedFields++;
}
}
const completionRate = (completedFields / allFields.length) * 100;
const score = Math.max(0, completionRate - (missingFields.length * 5) - (incompleteFields.length * 2));
return {
score: Math.min(100, score),
missingFields,
incompleteFields,
completionRate
};
}
/**
* Verify accuracy of the analysis
*/
private async verifyAccuracy(cimAnalysis: CIMReview, originalText: string): Promise<QualityMetrics['accuracy']> {
const prompt = `
Verify the accuracy of this CIM analysis against the original document. Check for:
1. Factual consistency - Are the facts stated in the analysis consistent with the original document?
2. Numerical accuracy - Are financial figures and percentages accurate?
3. Logical coherence - Does the analysis make logical sense and avoid contradictions?
Original Document (first 20,000 chars):
${originalText.substring(0, 20000)}
Analysis to Verify:
${JSON.stringify(cimAnalysis, null, 2)}
Provide accuracy assessment with specific issues identified.
`;
const systemPrompt = `You are an expert fact-checker specializing in financial document analysis. Identify any inaccuracies, inconsistencies, or logical errors in the analysis compared to the source document.`;
try {
const result = await llmService.processCIMDocument(originalText, '', {
prompt,
systemPrompt,
agentName: 'accuracy_verification'
});
const verification = result.jsonOutput || {};
return {
score: (verification as any).accuracyScore || 75,
factualConsistency: (verification as any).factualConsistency || 75,
numericalAccuracy: (verification as any).numericalAccuracy || 80,
logicalCoherence: (verification as any).logicalCoherence || 80,
potentialErrors: (verification as any).potentialErrors || []
};
} catch (error) {
logger.error('Accuracy verification failed', error);
return {
score: 50, // Conservative score on error
factualConsistency: 50,
numericalAccuracy: 50,
logicalCoherence: 50,
potentialErrors: ['Accuracy verification failed']
};
}
}
/**
* Analyze depth of analysis
*/
private async analyzeDepth(cimAnalysis: CIMReview, originalText: string): Promise<QualityMetrics['depth']> {
const prompt = `
Analyze the depth and quality of this CIM analysis. Assess:
1. Analysis quality - Are insights meaningful and well-developed?
2. Insightfulness - Does the analysis provide valuable insights beyond basic facts?
3. Detail level - Is there sufficient detail for investment decision-making?
CIM Analysis:
${JSON.stringify(cimAnalysis, null, 2)}
Original Document Context (first 15,000 chars):
${originalText.substring(0, 15000)}
Evaluate depth and identify superficial areas.
`;
const systemPrompt = `You are a senior investment analyst evaluating the depth and quality of CIM analysis. Focus on whether the analysis provides sufficient depth for private equity investment decisions.`;
try {
const result = await llmService.processCIMDocument(originalText, '', {
prompt,
systemPrompt,
agentName: 'depth_analysis'
});
const analysis = result.jsonOutput || {};
return {
score: (analysis as any).depthScore || 70,
analysisQuality: (analysis as any).analysisQuality || 70,
insightfulness: (analysis as any).insightfulness || 65,
detailLevel: (analysis as any).detailLevel || 75,
superficialFields: (analysis as any).superficialFields || []
};
} catch (error) {
logger.error('Depth analysis failed', error);
return {
score: 60,
analysisQuality: 60,
insightfulness: 60,
detailLevel: 60,
superficialFields: []
};
}
}
/**
* Evaluate relevance to BPCP investment criteria
*/
private async evaluateRelevance(cimAnalysis: CIMReview, originalText: string): Promise<QualityMetrics['relevance']> {
const prompt = `
Evaluate how well this CIM analysis aligns with BPCP's investment criteria and focus areas:
BPCP Focus:
- Companies with 5+MM EBITDA in consumer and industrial end markets
- M&A opportunities, technology & data usage improvements
- Supply chain and human capital optimization
- Preference for founder/family-owned companies
- Geographic preference for companies within driving distance of Cleveland and Charlotte
CIM Analysis:
${JSON.stringify(cimAnalysis, null, 2)}
Assess relevance and investment focus alignment.
`;
const systemPrompt = `You are a BPCP investment professional evaluating analysis relevance to the firm's investment strategy and criteria. Focus on strategic fit and materiality.`;
try {
const result = await llmService.processCIMDocument(originalText, '', {
prompt,
systemPrompt,
agentName: 'relevance_evaluation'
});
const evaluation = result.jsonOutput || {};
return {
score: (evaluation as any).relevanceScore || 75,
bcpAlignment: (evaluation as any).bcpAlignment || 70,
investmentFocus: (evaluation as any).investmentFocus || 75,
materialityAssessment: (evaluation as any).materialityAssessment || 80,
irrelevantContent: (evaluation as any).irrelevantContent || []
};
} catch (error) {
logger.error('Relevance evaluation failed', error);
return {
score: 70,
bcpAlignment: 70,
investmentFocus: 70,
materialityAssessment: 70,
irrelevantContent: []
};
}
}
/**
* Check internal consistency
*/
private async checkConsistency(cimAnalysis: CIMReview): Promise<QualityMetrics['consistency']> {
const prompt = `
Check the internal consistency of this CIM analysis. Look for:
1. Internal consistency - Do different sections align with each other?
2. Cross-reference alignment - Are references between sections accurate?
3. Contradictions - Are there any contradictory statements?
CIM Analysis:
${JSON.stringify(cimAnalysis, null, 2)}
Identify consistency issues and contradictions.
`;
const systemPrompt = `You are a quality control specialist identifying inconsistencies and contradictions in investment analysis. Focus on logical consistency across all sections.`;
try {
const result = await llmService.processCIMDocument('', '', {
prompt,
systemPrompt,
agentName: 'consistency_check'
});
const consistency = result.jsonOutput || {};
return {
score: (consistency as any).consistencyScore || 80,
internalConsistency: (consistency as any).internalConsistency || 80,
crossReferenceAlignment: (consistency as any).crossReferenceAlignment || 75,
contradictions: (consistency as any).contradictions || []
};
} catch (error) {
logger.error('Consistency check failed', error);
return {
score: 75,
internalConsistency: 75,
crossReferenceAlignment: 75,
contradictions: []
};
}
}
/**
* Refine analysis based on quality suggestions
*/
private async refineAnalysis(
currentAnalysis: CIMReview,
originalText: string,
suggestions: RefinementSuggestion[],
iteration: number
): Promise<CIMReview | null> {
const highPrioritySuggestions = suggestions.filter(s => s.priority === 'high');
const mediumPrioritySuggestions = suggestions.filter(s => s.priority === 'medium');
// Focus on high priority issues first
const focusSuggestions = highPrioritySuggestions.length > 0 ?
highPrioritySuggestions : mediumPrioritySuggestions.slice(0, 3);
if (focusSuggestions.length === 0) {
return null; // No actionable suggestions
}
const prompt = `
Refine this CIM analysis based on the following quality improvement suggestions (Iteration ${iteration}):
Current Analysis:
${JSON.stringify(currentAnalysis, null, 2)}
Improvement Suggestions:
${focusSuggestions.map(s => `- ${s.field}: ${s.issue} -> ${s.suggestion}`).join('\n')}
Original Document Reference:
${originalText.substring(0, 25000)}
Improve the analysis by addressing these specific suggestions while maintaining the overall structure and quality.
`;
const systemPrompt = `You are a senior analyst refining CIM analysis based on quality feedback. Focus on the specific suggestions provided while maintaining accuracy and coherence.`;
try {
const result = await llmService.processCIMDocument(originalText, '', {
prompt,
systemPrompt,
agentName: 'analysis_refinement'
});
if (result.success && result.jsonOutput) {
return result.jsonOutput as CIMReview;
}
return null;
} catch (error) {
logger.error('Analysis refinement failed', error);
return null;
}
}
// Helper methods
private getAllFields(obj: any, prefix = ''): Array<[string, any]> {
const fields: Array<[string, any]> = [];
for (const [key, value] of Object.entries(obj)) {
const fieldPath = prefix ? `${prefix}.${key}` : key;
if (value && typeof value === 'object' && !Array.isArray(value)) {
fields.push(...this.getAllFields(value, fieldPath));
} else {
fields.push([fieldPath, value]);
}
}
return fields;
}
private calculateOverallScore(
completeness: QualityMetrics['completeness'],
accuracy: QualityMetrics['accuracy'],
depth: QualityMetrics['depth'],
relevance: QualityMetrics['relevance'],
consistency: QualityMetrics['consistency']
): number {
// Weighted average with emphasis on accuracy and completeness
const weights = {
completeness: 0.25,
accuracy: 0.30,
depth: 0.20,
relevance: 0.15,
consistency: 0.10
};
return Math.round(
completeness.score * weights.completeness +
accuracy.score * weights.accuracy +
depth.score * weights.depth +
relevance.score * weights.relevance +
consistency.score * weights.consistency
);
}
private identifyCriticalIssues(metrics: QualityMetrics, schemaIssues: z.ZodIssue[]): string[] {
const issues: string[] = [];
if (schemaIssues.length > 0) {
issues.push(`Schema validation failed: ${schemaIssues.length} issues found`);
}
if (metrics.accuracy.score < 60) {
issues.push('Critical accuracy issues detected');
}
if (metrics.completeness.score < 50) {
issues.push('Insufficient completeness for investment review');
}
if (metrics.consistency.contradictions.length > 2) {
issues.push('Multiple internal contradictions found');
}
return issues;
}
private generateRecommendations(metrics: QualityMetrics): string[] {
const recommendations: string[] = [];
if (metrics.completeness.score < 80) {
recommendations.push('Focus on completing missing and incomplete fields');
}
if (metrics.accuracy.score < 80) {
recommendations.push('Verify accuracy of financial figures and factual statements');
}
if (metrics.depth.score < 70) {
recommendations.push('Enhance analysis depth with more detailed insights');
}
if (metrics.relevance.bcpAlignment < 75) {
recommendations.push('Better align analysis with BPCP investment criteria');
}
if (metrics.consistency.score < 80) {
recommendations.push('Resolve internal inconsistencies and contradictions');
}
return recommendations;
}
private generateRefinementSuggestions(metrics: QualityMetrics): RefinementSuggestion[] {
const suggestions: RefinementSuggestion[] = [];
// Completeness suggestions
metrics.completeness.missingFields.forEach(field => {
suggestions.push({
category: 'completeness',
priority: 'high',
field,
issue: 'Field is missing or empty',
suggestion: 'Provide meaningful content for this field based on document analysis',
requiredAction: 'rewrite'
});
});
// Accuracy suggestions
metrics.accuracy.potentialErrors.forEach(error => {
suggestions.push({
category: 'accuracy',
priority: 'high',
field: 'general',
issue: error,
suggestion: 'Verify and correct this accuracy issue',
requiredAction: 'verify'
});
});
// Depth suggestions
metrics.depth.superficialFields.forEach(field => {
suggestions.push({
category: 'depth',
priority: 'medium',
field,
issue: 'Analysis is too superficial',
suggestion: 'Provide more detailed analysis and insights',
requiredAction: 'enhance'
});
});
return suggestions;
}
}
export const qualityValidationService = new QualityValidationService();