// RFP Analysis Engine - Analyzes current RFP questions and provides structured insights // Combines previous answers with AI-powered gap analysis and suggestions import { vectorStorage, type SearchResult } from './vectorStorage'; import { backendService } from './backendService'; export interface AnalyzedAnswer { questionId: string; question: string; previousAnswers: StructuredPreviousAnswer[]; aiGeneratedAnswer: AIAnalyzedAnswer; gapAnalysis: GapAnalysis; suggestions: string[]; confidence: number; } export interface StructuredPreviousAnswer { id: string; source: string; content: string; relevanceScore: number; category: string; pageNumber?: number; keyPoints: string[]; metrics: ExtractedMetric[]; dateAnswered?: string; projectContext?: string; } export interface AIAnalyzedAnswer { answer: string; wordCount: number; addressedPoints: string[]; strengths: string[]; citations: string[]; tone: 'technical' | 'business' | 'mixed'; comprehensiveness: number; // 0-1 score } export interface GapAnalysis { coveredPoints: CoveredPoint[]; missingPoints: MissingPoint[]; weakPoints: WeakPoint[]; recommendations: string[]; overallCoverage: number; // 0-1 score } export interface CoveredPoint { point: string; source: string; confidence: number; evidence: string; } export interface MissingPoint { point: string; importance: 'critical' | 'high' | 'medium' | 'low'; suggestion: string; examples?: string[]; } export interface WeakPoint { point: string; currentCoverage: string; improvementNeeded: string; suggestions: string[]; } export interface ExtractedMetric { type: 'number' | 'percentage' | 'currency' | 'duration'; value: string; context: string; } export interface StressPoint { keyword: string; weight: number; category: string; expectedResponseType: string; } /** * Main RFP Analysis function * Analyzes current RFP question against previous answers and generates comprehensive insights */ export async function analyzeRFPQuestion( question: string, currentRFPContext?: string ): Promise { console.log('🔍 Starting RFP Question Analysis...'); console.log(`📝 Question: ${question.substring(0, 100)}...`); // Step 1: Identify stress points and key requirements in the question const stressPoints = identifyStressPoints(question, currentRFPContext); console.log(`🎯 Identified ${stressPoints.length} stress points:`, stressPoints.map(sp => sp.keyword)); // Step 2: Retrieve and structure previous answers const previousAnswers = await retrieveStructuredAnswers(question, stressPoints); console.log(`📚 Found ${previousAnswers.length} relevant previous answers`); // Step 3: Generate AI answer considering stress points const aiAnswer = await generateAIAnswerWithStressPoints(question, previousAnswers, stressPoints, currentRFPContext); console.log(`🤖 Generated AI answer: ${aiAnswer.wordCount} words`); // Step 4: Perform gap analysis const gapAnalysis = performGapAnalysis(question, previousAnswers, aiAnswer, stressPoints); console.log(`📊 Gap analysis complete: ${gapAnalysis.overallCoverage * 100}% coverage`); // Step 5: Generate actionable suggestions const suggestions = generateActionableSuggestions(gapAnalysis, stressPoints); console.log(`💡 Generated ${suggestions.length} suggestions`); return { questionId: generateQuestionId(question), question, previousAnswers, aiGeneratedAnswer: aiAnswer, gapAnalysis, suggestions, confidence: calculateOverallConfidence(previousAnswers, aiAnswer, gapAnalysis) }; } /** * Identify stress points and key requirements in the RFP question */ function identifyStressPoints(question: string, context?: string): StressPoint[] { const stressPoints: StressPoint[] = []; const textToAnalyze = `${question} ${context || ''}`.toLowerCase(); // Define stress point patterns with weights const patterns: Record = { experience: { keywords: ['experience', 'years', 'track record', 'history', 'previous', 'past projects', 'portfolio'], weight: 0.9, category: 'Qualification', responseType: 'Specific examples with metrics and timeframes' }, technical: { keywords: ['technical', 'technology', 'platform', 'architecture', 'infrastructure', 'system', 'tools'], weight: 0.85, category: 'Technical', responseType: 'Detailed technical specifications and capabilities' }, methodology: { keywords: ['approach', 'methodology', 'process', 'framework', 'method', 'procedure', 'workflow'], weight: 0.8, category: 'Approach', responseType: 'Step-by-step process with phases and deliverables' }, team: { keywords: ['team', 'staff', 'personnel', 'resources', 'qualifications', 'certifications', 'expertise'], weight: 0.85, category: 'Resources', responseType: 'Team structure, qualifications, and availability' }, timeline: { keywords: ['timeline', 'schedule', 'deadline', 'duration', 'timeframe', 'delivery', 'milestones'], weight: 0.75, category: 'Schedule', responseType: 'Detailed timeline with milestones and dependencies' }, cost: { keywords: ['cost', 'price', 'budget', 'rate', 'fee', 'pricing', 'financial'], weight: 0.8, category: 'Financial', responseType: 'Pricing structure with justification and options' }, quality: { keywords: ['quality', 'standards', 'assurance', 'compliance', 'certification', 'audit'], weight: 0.7, category: 'Quality', responseType: 'QA processes, standards, and compliance measures' }, risk: { keywords: ['risk', 'mitigation', 'contingency', 'backup', 'disaster recovery', 'security'], weight: 0.75, category: 'Risk Management', responseType: 'Risk identification, assessment, and mitigation strategies' }, innovation: { keywords: ['innovative', 'innovation', 'cutting-edge', 'advanced', 'modern', 'latest'], weight: 0.65, category: 'Innovation', responseType: 'Innovative approaches and competitive advantages' }, scalability: { keywords: ['scalable', 'scalability', 'growth', 'expansion', 'flexible', 'adaptable'], weight: 0.7, category: 'Scalability', responseType: 'Scalability measures and future-proofing strategies' }, metrics: { keywords: ['metrics', 'kpi', 'measurement', 'success criteria', 'performance', 'roi'], weight: 0.75, category: 'Performance', responseType: 'Specific metrics, KPIs, and success measurements' }, references: { keywords: ['references', 'case study', 'client', 'similar project', 'comparable', 'example'], weight: 0.8, category: 'Proof Points', responseType: 'Specific client examples with measurable outcomes' } }; // Analyze text for stress points Object.entries(patterns).forEach(([key, pattern]) => { const matches = pattern.keywords.filter(keyword => textToAnalyze.includes(keyword) ); if (matches.length > 0) { stressPoints.push({ keyword: key, weight: pattern.weight * (matches.length / pattern.keywords.length), // Adjust weight by match density category: pattern.category, expectedResponseType: pattern.responseType }); } }); // Sort by weight (most important first) return stressPoints.sort((a, b) => b.weight - a.weight); } /** * Retrieve and structure previous answers with detailed extraction */ async function retrieveStructuredAnswers( question: string, stressPoints: StressPoint[] ): Promise { // Use enhanced semantic search const searchResults = await backendService.searchDocuments(question, { topK: 15, useSemanticSearch: true }); const structuredAnswers: StructuredPreviousAnswer[] = []; for (let i = 0; i < searchResults.length && i < 10; i++) { const result = searchResults[i]; const structured: StructuredPreviousAnswer = { id: `prev_${i + 1}`, source: result.source, content: result.content, relevanceScore: result.relevanceScore, category: result.category || 'general', keyPoints: extractKeyPoints(result.content, stressPoints), metrics: extractMetrics(result.content), projectContext: extractProjectContext(result.content) }; structuredAnswers.push(structured); } return structuredAnswers; } /** * Extract key points from content based on stress points */ function extractKeyPoints(content: string, stressPoints: StressPoint[]): string[] { const keyPoints: string[] = []; const sentences = content.split(/[.!?]+/).filter(s => s.trim().length > 20); // Look for sentences containing stress point keywords stressPoints.forEach(sp => { const relevantSentences = sentences.filter(sentence => sentence.toLowerCase().includes(sp.keyword) ); relevantSentences.slice(0, 2).forEach(sentence => { const cleaned = sentence.trim(); if (cleaned && !keyPoints.includes(cleaned)) { keyPoints.push(cleaned); } }); }); // Also extract bullet points const bulletPattern = /[•\-\*]\s*(.+?)(?=[•\-\*\n]|$)/g; let match; while ((match = bulletPattern.exec(content)) !== null) { const point = match[1].trim(); if (point.length > 15 && point.length < 200 && !keyPoints.includes(point)) { keyPoints.push(point); } } return keyPoints.slice(0, 10); // Return top 10 key points } /** * Extract metrics (numbers, percentages, etc.) from content */ function extractMetrics(content: string): ExtractedMetric[] { const metrics: ExtractedMetric[] = []; // Extract percentages const percentagePattern = /(\d+(?:\.\d+)?)\s*%/g; let match; while ((match = percentagePattern.exec(content)) !== null) { const context = content.substring(Math.max(0, match.index - 50), Math.min(content.length, match.index + 100)); metrics.push({ type: 'percentage', value: `${match[1]}%`, context: context.trim() }); } // Extract currency const currencyPattern = /\$\s*(\d+(?:,\d{3})*(?:\.\d{2})?)\s*(million|billion|M|B)?/gi; while ((match = currencyPattern.exec(content)) !== null) { const context = content.substring(Math.max(0, match.index - 50), Math.min(content.length, match.index + 100)); metrics.push({ type: 'currency', value: match[0], context: context.trim() }); } // Extract durations const durationPattern = /(\d+)\s*(years?|months?|weeks?|days?)/gi; while ((match = durationPattern.exec(content)) !== null) { const context = content.substring(Math.max(0, match.index - 50), Math.min(content.length, match.index + 100)); metrics.push({ type: 'duration', value: match[0], context: context.trim() }); } // Extract general numbers with context const numberPattern = /(\d{2,})\s*([+]|\w+)/g; while ((match = numberPattern.exec(content)) !== null && metrics.length < 20) { const context = content.substring(Math.max(0, match.index - 50), Math.min(content.length, match.index + 100)); if (!context.toLowerCase().includes('page') && !context.toLowerCase().includes('section')) { metrics.push({ type: 'number', value: match[1], context: context.trim() }); } } return metrics.slice(0, 15); // Limit to top 15 metrics } /** * Extract project context from content */ function extractProjectContext(content: string): string | undefined { const projectPatterns = [ /project for ([^.]+)/i, /client:?\s*([^.]+)/i, /implemented ([^.]+)/i, /delivered ([^.]+)/i ]; for (const pattern of projectPatterns) { const match = content.match(pattern); if (match) { return match[1].trim().substring(0, 150); } } return undefined; } /** * Generate AI answer with stress points consideration */ async function generateAIAnswerWithStressPoints( question: string, previousAnswers: StructuredPreviousAnswer[], stressPoints: StressPoint[], currentRFPContext?: string ): Promise { // Build comprehensive context for AI const referenceContext = previousAnswers.map((ans, idx) => ({ title: `Reference ${idx + 1}: ${ans.source}`, content: ans.content, keyPoints: ans.keyPoints, metrics: ans.metrics.map(m => `${m.value} (${m.context})`).join('; ') })); // Add stress points to prompt const stressPointsContext = stressPoints.map(sp => `- ${sp.category}: ${sp.expectedResponseType} (Priority: ${Math.round(sp.weight * 100)}%)` ).join('\n'); try { const result = await backendService.comprehensiveAnswer( `${question}\n\nKEY REQUIREMENTS TO ADDRESS:\n${stressPointsContext}\n\n${currentRFPContext || ''}` ); const addressedPoints = analyzeAddressedPoints(result.generatedAnswer, stressPoints); const strengths = identifyAnswerStrengths(result.generatedAnswer); const tone = detectTone(result.generatedAnswer); return { answer: result.generatedAnswer, wordCount: result.wordCount, addressedPoints, strengths, citations: result.citations?.map(c => c.citation || c.raw) || [], tone, comprehensiveness: calculateComprehensiveness(result.generatedAnswer, stressPoints) }; } catch (error) { console.error('Error generating AI answer:', error); // Fallback answer return { answer: generateFallbackAnswer(question, previousAnswers, stressPoints), wordCount: 0, addressedPoints: [], strengths: [], citations: [], tone: 'business', comprehensiveness: 0.5 }; } } /** * Analyze which stress points are addressed in the answer */ function analyzeAddressedPoints(answer: string, stressPoints: StressPoint[]): string[] { const addressed: string[] = []; const answerLower = answer.toLowerCase(); stressPoints.forEach(sp => { if (answerLower.includes(sp.keyword) || answerLower.includes(sp.category.toLowerCase())) { addressed.push(`${sp.category}: ${sp.keyword}`); } }); return addressed; } /** * Identify strengths in the generated answer */ function identifyAnswerStrengths(answer: string): string[] { const strengths: string[] = []; // Check for specific examples if (answer.match(/for example|such as|specifically|instance/i)) { strengths.push('Includes specific examples'); } // Check for metrics if (answer.match(/\d+%|\d+ years?|\$\d+/)) { strengths.push('Contains quantifiable metrics'); } // Check for structured format if (answer.match(/^#{1,3}\s|\*\*[^*]+\*\*/m)) { strengths.push('Well-structured with headers'); } // Check for citations if (answer.match(/\[Doc\d+\]|\[Ref\d+\]/)) { strengths.push('Includes citations to source documents'); } // Check length if (answer.split(/\s+/).length > 500) { strengths.push('Comprehensive and detailed'); } return strengths; } /** * Detect the tone of the answer */ function detectTone(answer: string): 'technical' | 'business' | 'mixed' { const technicalTerms = (answer.match(/\b(system|architecture|infrastructure|API|database|server|cloud|deployment)\b/gi) || []).length; const businessTerms = (answer.match(/\b(client|customer|value|ROI|business|stakeholder|strategic|partnership)\b/gi) || []).length; if (technicalTerms > businessTerms * 1.5) return 'technical'; if (businessTerms > technicalTerms * 1.5) return 'business'; return 'mixed'; } /** * Calculate how comprehensive the answer is */ function calculateComprehensiveness(answer: string, stressPoints: StressPoint[]): number { let score = 0; const answerLower = answer.toLowerCase(); stressPoints.forEach(sp => { if (answerLower.includes(sp.keyword)) { score += sp.weight; } }); return Math.min(score / stressPoints.reduce((sum, sp) => sum + sp.weight, 0), 1); } /** * Perform gap analysis comparing AI answer against requirements */ function performGapAnalysis( question: string, previousAnswers: StructuredPreviousAnswer[], aiAnswer: AIAnalyzedAnswer, stressPoints: StressPoint[] ): GapAnalysis { const coveredPoints = identifyCoveredPoints(aiAnswer, stressPoints, previousAnswers); const missingPoints = identifyMissingPoints(stressPoints, coveredPoints, question); const weakPoints = identifyWeakPoints(aiAnswer, stressPoints); const recommendations = generateRecommendations(missingPoints, weakPoints); const overallCoverage = coveredPoints.length / (coveredPoints.length + missingPoints.length); return { coveredPoints, missingPoints, weakPoints, recommendations, overallCoverage }; } /** * Identify covered points in the answer */ function identifyCoveredPoints( aiAnswer: AIAnalyzedAnswer, stressPoints: StressPoint[], previousAnswers: StructuredPreviousAnswer[] ): CoveredPoint[] { const covered: CoveredPoint[] = []; const answerLower = aiAnswer.answer.toLowerCase(); stressPoints.forEach(sp => { if (answerLower.includes(sp.keyword)) { // Find evidence const sentences = aiAnswer.answer.split(/[.!?]+/); const evidence = sentences.find(s => s.toLowerCase().includes(sp.keyword)) || ''; // Find source const source = previousAnswers.find(ans => ans.content.toLowerCase().includes(sp.keyword) ); covered.push({ point: sp.category, source: source?.source || 'AI Generated', confidence: sp.weight, evidence: evidence.trim().substring(0, 200) }); } }); return covered; } /** * Identify missing points that should be addressed */ function identifyMissingPoints( stressPoints: StressPoint[], coveredPoints: CoveredPoint[], question: string ): MissingPoint[] { const missing: MissingPoint[] = []; const coveredCategories = new Set(coveredPoints.map(cp => cp.point)); stressPoints.forEach(sp => { if (!coveredCategories.has(sp.category)) { const importance = sp.weight > 0.8 ? 'critical' : sp.weight > 0.7 ? 'high' : sp.weight > 0.6 ? 'medium' : 'low'; missing.push({ point: sp.category, importance: importance as 'critical' | 'high' | 'medium' | 'low', suggestion: `Add ${sp.expectedResponseType.toLowerCase()}`, examples: generateExamplesForPoint(sp) }); } }); return missing.sort((a, b) => { const order = { critical: 0, high: 1, medium: 2, low: 3 }; return order[a.importance] - order[b.importance]; }); } /** * Identify weak points that need improvement */ function identifyWeakPoints( aiAnswer: AIAnalyzedAnswer, stressPoints: StressPoint[] ): WeakPoint[] { const weak: WeakPoint[] = []; // Check if answer lacks specific examples if (!aiAnswer.answer.match(/for example|such as|specifically/i)) { weak.push({ point: 'Specific Examples', currentCoverage: 'Generic statements without concrete examples', improvementNeeded: 'Add 2-3 specific project examples with outcomes', suggestions: [ 'Include client names and project types', 'Add measurable results (percentages, cost savings, etc.)', 'Provide timeline context (when projects were completed)' ] }); } // Check if answer lacks metrics if ((aiAnswer.answer.match(/\d+/g) || []).length < 5) { weak.push({ point: 'Quantifiable Metrics', currentCoverage: 'Few or no specific numbers provided', improvementNeeded: 'Include specific metrics and KPIs', suggestions: [ 'Add years of experience', 'Include number of projects completed', 'Provide team size and certifications count', 'Add performance metrics (%, time savings, cost reductions)' ] }); } // Check comprehensiveness if (aiAnswer.comprehensiveness < 0.7) { weak.push({ point: 'Overall Comprehensiveness', currentCoverage: `Only ${Math.round(aiAnswer.comprehensiveness * 100)}% of key points addressed`, improvementNeeded: 'Address all critical stress points in the question', suggestions: [ 'Review the question requirements carefully', 'Ensure all key aspects are covered', 'Add more detail to superficial sections' ] }); } return weak; } /** * Generate examples for a stress point */ function generateExamplesForPoint(stressPoint: StressPoint): string[] { const examples: Record = { experience: [ '"Over 15 years of experience with 200+ completed projects"', '"Successfully delivered similar projects for Fortune 500 clients"', '"Maintained 98% client satisfaction rate across all engagements"' ], technical: [ '"Expertise in AWS, Azure, and GCP with 50+ certified architects"', '"Implemented microservices architecture for 30+ enterprise clients"', '"24/7 monitoring using Datadog, Prometheus, and Grafana"' ], methodology: [ '"Agile/Scrum methodology with 2-week sprints and daily standups"', '"Comprehensive QA process including automated and manual testing"', '"Phased delivery approach: Discovery → Design → Build → Deploy"' ], team: [ '"15 PMP-certified project managers with 10+ years experience"', '"50+ AWS/Azure certified cloud architects and engineers"', '"Dedicated team of 5-7 resources assigned per project"' ] }; return examples[stressPoint.keyword] || [ 'Add specific details relevant to this requirement', 'Include measurable data and timeframes', 'Provide concrete examples from past projects' ]; } /** * Generate actionable recommendations */ function generateRecommendations( missingPoints: MissingPoint[], weakPoints: WeakPoint[] ): string[] { const recommendations: string[] = []; // Critical missing points const critical = missingPoints.filter(mp => mp.importance === 'critical'); if (critical.length > 0) { recommendations.push( `🚨 CRITICAL: Address ${critical.length} critical missing point(s): ${critical.map(c => c.point).join(', ')}` ); } // High priority missing points const high = missingPoints.filter(mp => mp.importance === 'high'); if (high.length > 0) { recommendations.push( `⚠️ HIGH PRIORITY: Include ${high.map(h => h.point).join(', ')}` ); } // Weak points if (weakPoints.length > 0) { recommendations.push( `💪 STRENGTHEN: Improve ${weakPoints.map(wp => wp.point).join(', ')}` ); } // General recommendations if (missingPoints.length === 0 && weakPoints.length === 0) { recommendations.push('✅ Response covers all key requirements well'); } else { recommendations.push('📝 Review and enhance the answer before submission'); } return recommendations; } /** * Generate actionable suggestions for improvement */ function generateActionableSuggestions( gapAnalysis: GapAnalysis, stressPoints: StressPoint[] ): string[] { const suggestions: string[] = []; // Suggestions for missing points gapAnalysis.missingPoints.forEach(mp => { suggestions.push(`➕ ADD ${mp.point}: ${mp.suggestion}`); if (mp.examples && mp.examples.length > 0) { suggestions.push(` Example: ${mp.examples[0]}`); } }); // Suggestions for weak points gapAnalysis.weakPoints.forEach(wp => { suggestions.push(`🔧 IMPROVE ${wp.point}: ${wp.improvementNeeded}`); if (wp.suggestions.length > 0) { suggestions.push(` → ${wp.suggestions[0]}`); } }); // Overall suggestions if (gapAnalysis.overallCoverage < 0.8) { suggestions.push('📊 Overall coverage is below 80% - review all question requirements'); } if (suggestions.length === 0) { suggestions.push('✅ Response is comprehensive - ready for review'); } return suggestions; } /** * Generate fallback answer when AI fails */ function generateFallbackAnswer( question: string, previousAnswers: StructuredPreviousAnswer[], stressPoints: StressPoint[] ): string { let answer = `## Response to: ${question}\n\n`; answer += '**Based on Previous RFP Responses:**\n\n'; previousAnswers.slice(0, 3).forEach((ans, idx) => { answer += `### Reference ${idx + 1}: ${ans.source}\n`; if (ans.keyPoints.length > 0) { answer += ans.keyPoints.slice(0, 3).map(kp => `• ${kp}`).join('\n'); answer += '\n\n'; } }); answer += '**Key Requirements to Address:**\n'; stressPoints.forEach(sp => { answer += `• ${sp.category}: ${sp.expectedResponseType}\n`; }); answer += '\n*Please review and enhance this response with specific details from your organization.*'; return answer; } // Helper functions function generateQuestionId(question: string): string { return `q_${Date.now()}_${question.substring(0, 20).replace(/\W/g, '_')}`; } function calculateOverallConfidence( previousAnswers: StructuredPreviousAnswer[], aiAnswer: AIAnalyzedAnswer, gapAnalysis: GapAnalysis ): number { const hasGoodReferences = previousAnswers.length >= 3 ? 0.3 : previousAnswers.length * 0.1; const aiQuality = aiAnswer.comprehensiveness * 0.4; const coverage = gapAnalysis.overallCoverage * 0.3; return Math.min(hasGoodReferences + aiQuality + coverage, 1); }