fh / src /utils /rfpAnalyzer.ts
Varun10000's picture
Upload 57 files
d8635c9 verified
Raw
History Blame Contribute Delete
26.5 kB
// 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<AnalyzedAnswer> {
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<string, { keywords: string[], weight: number, category: string, responseType: string }> = {
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<StructuredPreviousAnswer[]> {
// 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<AIAnalyzedAnswer> {
// 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<string, string[]> = {
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);
}