|
|
|
|
|
|
| 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;
|
| }
|
|
|
| export interface GapAnalysis {
|
| coveredPoints: CoveredPoint[];
|
| missingPoints: MissingPoint[];
|
| weakPoints: WeakPoint[];
|
| recommendations: string[];
|
| overallCoverage: number;
|
| }
|
|
|
| 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;
|
| }
|
|
|
| |
| |
| |
|
|
| export async function analyzeRFPQuestion(
|
| question: string,
|
| currentRFPContext?: string
|
| ): Promise<AnalyzedAnswer> {
|
| console.log('π Starting RFP Question Analysis...');
|
| console.log(`π Question: ${question.substring(0, 100)}...`);
|
|
|
|
|
| const stressPoints = identifyStressPoints(question, currentRFPContext);
|
| console.log(`π― Identified ${stressPoints.length} stress points:`, stressPoints.map(sp => sp.keyword));
|
|
|
|
|
| const previousAnswers = await retrieveStructuredAnswers(question, stressPoints);
|
| console.log(`π Found ${previousAnswers.length} relevant previous answers`);
|
|
|
|
|
| const aiAnswer = await generateAIAnswerWithStressPoints(question, previousAnswers, stressPoints, currentRFPContext);
|
| console.log(`π€ Generated AI answer: ${aiAnswer.wordCount} words`);
|
|
|
|
|
| const gapAnalysis = performGapAnalysis(question, previousAnswers, aiAnswer, stressPoints);
|
| console.log(`π Gap analysis complete: ${gapAnalysis.overallCoverage * 100}% coverage`);
|
|
|
|
|
| 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)
|
| };
|
| }
|
|
|
| |
| |
|
|
| function identifyStressPoints(question: string, context?: string): StressPoint[] {
|
| const stressPoints: StressPoint[] = [];
|
| const textToAnalyze = `${question} ${context || ''}`.toLowerCase();
|
|
|
|
|
| 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'
|
| }
|
| };
|
|
|
|
|
| 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),
|
| category: pattern.category,
|
| expectedResponseType: pattern.responseType
|
| });
|
| }
|
| });
|
|
|
|
|
| return stressPoints.sort((a, b) => b.weight - a.weight);
|
| }
|
|
|
| |
| |
|
|
| async function retrieveStructuredAnswers(
|
| question: string,
|
| stressPoints: StressPoint[]
|
| ): Promise<StructuredPreviousAnswer[]> {
|
|
|
|
|
| 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;
|
| }
|
|
|
| |
| |
|
|
| function extractKeyPoints(content: string, stressPoints: StressPoint[]): string[] {
|
| const keyPoints: string[] = [];
|
| const sentences = content.split(/[.!?]+/).filter(s => s.trim().length > 20);
|
|
|
|
|
| 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);
|
| }
|
| });
|
| });
|
|
|
|
|
| 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);
|
| }
|
|
|
| |
| |
|
|
| function extractMetrics(content: string): ExtractedMetric[] {
|
| const metrics: ExtractedMetric[] = [];
|
|
|
|
|
| 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()
|
| });
|
| }
|
|
|
|
|
| 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()
|
| });
|
| }
|
|
|
|
|
| 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()
|
| });
|
| }
|
|
|
|
|
| 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);
|
| }
|
|
|
| |
| |
|
|
| 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;
|
| }
|
|
|
| |
| |
|
|
| async function generateAIAnswerWithStressPoints(
|
| question: string,
|
| previousAnswers: StructuredPreviousAnswer[],
|
| stressPoints: StressPoint[],
|
| currentRFPContext?: string
|
| ): Promise<AIAnalyzedAnswer> {
|
|
|
|
|
| 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('; ')
|
| }));
|
|
|
|
|
| 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);
|
|
|
|
|
| return {
|
| answer: generateFallbackAnswer(question, previousAnswers, stressPoints),
|
| wordCount: 0,
|
| addressedPoints: [],
|
| strengths: [],
|
| citations: [],
|
| tone: 'business',
|
| comprehensiveness: 0.5
|
| };
|
| }
|
| }
|
|
|
| |
| |
|
|
| 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;
|
| }
|
|
|
| |
| |
|
|
| function identifyAnswerStrengths(answer: string): string[] {
|
| const strengths: string[] = [];
|
|
|
|
|
| if (answer.match(/for example|such as|specifically|instance/i)) {
|
| strengths.push('Includes specific examples');
|
| }
|
|
|
|
|
| if (answer.match(/\d+%|\d+ years?|\$\d+/)) {
|
| strengths.push('Contains quantifiable metrics');
|
| }
|
|
|
|
|
| if (answer.match(/^#{1,3}\s|\*\*[^*]+\*\*/m)) {
|
| strengths.push('Well-structured with headers');
|
| }
|
|
|
|
|
| if (answer.match(/\[Doc\d+\]|\[Ref\d+\]/)) {
|
| strengths.push('Includes citations to source documents');
|
| }
|
|
|
|
|
| if (answer.split(/\s+/).length > 500) {
|
| strengths.push('Comprehensive and detailed');
|
| }
|
|
|
| return strengths;
|
| }
|
|
|
| |
| |
|
|
| 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';
|
| }
|
|
|
| |
| |
|
|
| 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);
|
| }
|
|
|
| |
| |
|
|
| 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
|
| };
|
| }
|
|
|
| |
| |
|
|
| 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)) {
|
|
|
| const sentences = aiAnswer.answer.split(/[.!?]+/);
|
| const evidence = sentences.find(s => s.toLowerCase().includes(sp.keyword)) || '';
|
|
|
|
|
| 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;
|
| }
|
|
|
| |
| |
|
|
| 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];
|
| });
|
| }
|
|
|
| |
| |
|
|
| function identifyWeakPoints(
|
| aiAnswer: AIAnalyzedAnswer,
|
| stressPoints: StressPoint[]
|
| ): WeakPoint[] {
|
| const weak: WeakPoint[] = [];
|
|
|
|
|
| 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)'
|
| ]
|
| });
|
| }
|
|
|
|
|
| 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)'
|
| ]
|
| });
|
| }
|
|
|
|
|
| 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;
|
| }
|
|
|
| |
| |
|
|
| 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'
|
| ];
|
| }
|
|
|
| |
| |
|
|
| function generateRecommendations(
|
| missingPoints: MissingPoint[],
|
| weakPoints: WeakPoint[]
|
| ): string[] {
|
| const recommendations: string[] = [];
|
|
|
|
|
| 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(', ')}`
|
| );
|
| }
|
|
|
|
|
| const high = missingPoints.filter(mp => mp.importance === 'high');
|
| if (high.length > 0) {
|
| recommendations.push(
|
| `β οΈ HIGH PRIORITY: Include ${high.map(h => h.point).join(', ')}`
|
| );
|
| }
|
|
|
|
|
| if (weakPoints.length > 0) {
|
| recommendations.push(
|
| `πͺ STRENGTHEN: Improve ${weakPoints.map(wp => wp.point).join(', ')}`
|
| );
|
| }
|
|
|
|
|
| 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;
|
| }
|
|
|
| |
| |
|
|
| function generateActionableSuggestions(
|
| gapAnalysis: GapAnalysis,
|
| stressPoints: StressPoint[]
|
| ): string[] {
|
| const suggestions: string[] = [];
|
|
|
|
|
| gapAnalysis.missingPoints.forEach(mp => {
|
| suggestions.push(`β ADD ${mp.point}: ${mp.suggestion}`);
|
| if (mp.examples && mp.examples.length > 0) {
|
| suggestions.push(` Example: ${mp.examples[0]}`);
|
| }
|
| });
|
|
|
|
|
| gapAnalysis.weakPoints.forEach(wp => {
|
| suggestions.push(`π§ IMPROVE ${wp.point}: ${wp.improvementNeeded}`);
|
| if (wp.suggestions.length > 0) {
|
| suggestions.push(` β ${wp.suggestions[0]}`);
|
| }
|
| });
|
|
|
|
|
| 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;
|
| }
|
|
|
| |
| |
|
|
| 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;
|
| }
|
|
|
|
|
| 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);
|
| }
|
|
|