UNPKG

@spaik/mcp-server-roi

Version:

MCP server for AI ROI prediction and tracking with Monte Carlo simulations

688 lines 31.4 kB
import { z } from 'zod'; import { createLogger } from '../utils/logger.js'; /** * Conversational Bridge Service * * Transforms structured responses into natural, conversational language * that feels like talking to a knowledgeable advisor rather than a system. */ // Conversation schemas export const ConversationStyleSchema = z.enum([ 'professional', 'friendly', 'executive', 'technical', 'educational' ]); export const ConversationalResponseSchema = z.object({ message: z.string(), tone: z.object({ style: ConversationStyleSchema, formality: z.number().min(0).max(1), enthusiasm: z.number().min(0).max(1), confidence: z.number().min(0).max(1) }), elements: z.object({ greeting: z.string().optional(), context_acknowledgment: z.string().optional(), main_content: z.array(z.string()), clarifications: z.array(z.string()).optional(), closing: z.string().optional() }), interactive: z.object({ follow_up_questions: z.array(z.string()).optional(), suggested_topics: z.array(z.string()).optional(), clarification_prompts: z.array(z.string()).optional() }), metadata: z.object({ word_count: z.number(), reading_time_seconds: z.number(), complexity_level: z.enum(['simple', 'moderate', 'complex']), jargon_count: z.number() }) }); export class ConversationalBridge { logger = createLogger({ component: 'ConversationalBridge' }); // Conversation templates CONVERSATION_TEMPLATES = { greetings: { professional: ['Let me analyze this for you.', 'I\'ve reviewed the data.', 'Here\'s what I found.'], friendly: ['Great question! Let me break this down.', 'I\'d be happy to help with that!', 'Let\'s explore this together.'], executive: ['Executive summary:', 'Key findings:', 'Bottom line:'], technical: ['Technical analysis indicates:', 'Based on the data:', 'System analysis shows:'], educational: ['Let me explain what this means.', 'Here\'s a helpful way to think about it.', 'To understand this better:'] }, transitions: { to_positive: ['On the bright side,', 'The good news is', 'Encouragingly,'], to_negative: ['However,', 'That said,', 'One consideration is'], to_detail: ['Diving deeper,', 'Looking more closely,', 'Specifically,'], to_summary: ['In summary,', 'The key takeaway is', 'Overall,'] }, closings: { professional: ['Would you like more details on any aspect?', 'Please let me know if you need clarification.'], friendly: ['Hope this helps! Any other questions?', 'Feel free to ask if you\'d like to explore further!'], executive: ['Ready to proceed with implementation?', 'Shall we discuss next steps?'], technical: ['Additional technical details available upon request.', 'Further analysis can be provided.'], educational: ['Does this make sense? Happy to explain differently!', 'Would you like me to elaborate on any part?'] } }; /** * Transform response into conversational format */ async transformToConversation(response, style = 'professional', context) { this.logger.debug('Transforming to conversational format', { style, context }); // Determine tone based on style and context const tone = this.determineTone(style, context); // Build conversation elements const elements = await this.buildConversationElements(response, style, tone, context); // Generate interactive elements const interactive = this.generateInteractiveElements(response, context); // Compose final message const message = this.composeMessage(elements, tone); // Calculate metadata const metadata = this.calculateMetadata(message, elements); return { message, tone, elements, interactive, metadata }; } /** * Generate natural language explanations */ async explainInNaturalLanguage(concept, data, targetAudience = 'general') { this.logger.debug('Generating natural language explanation', { concept, targetAudience }); switch (concept) { case 'roi': return this.explainROI(data, targetAudience); case 'payback_period': return this.explainPaybackPeriod(data, targetAudience); case 'risk_factors': return this.explainRiskFactors(data, targetAudience); case 'implementation_timeline': return this.explainTimeline(data, targetAudience); case 'cost_breakdown': return this.explainCosts(data, targetAudience); default: return this.explainGeneric(concept, data, targetAudience); } } /** * Create conversational narratives from data */ async createNarrative(data, narrativeType) { this.logger.debug('Creating narrative', { narrativeType }); switch (narrativeType) { case 'success_story': return this.createSuccessStory(data); case 'cautionary_tale': return this.createCautionaryTale(data); case 'comparison': return this.createComparisonNarrative(data); case 'journey': return this.createJourneyNarrative(data); default: return this.createGenericNarrative(data); } } /** * Handle conversational context and memory */ async maintainConversationFlow(currentResponse, conversationHistory) { this.logger.debug('Maintaining conversation flow', { history_length: conversationHistory.length }); // Analyze conversation history const context = this.analyzeConversationContext(conversationHistory); // Build contextual response const contextualResponse = this.buildContextualResponse(currentResponse, context, conversationHistory); // Find references to past interactions const references = this.findPastReferences(currentResponse, conversationHistory); // Calculate conversation continuity score const continuity = this.calculateContinuity(conversationHistory, contextualResponse); return { contextual_response: contextualResponse, references_to_past: references, conversation_continuity: continuity }; } /** * Adapt language complexity based on audience */ async adaptComplexity(content, targetComplexity, preserveAccuracy = true) { this.logger.debug('Adapting language complexity', { targetComplexity }); let adaptedContent = content; const changes = []; if (targetComplexity === 'simple') { // Simplify jargon adaptedContent = this.simplifyJargon(adaptedContent); changes.push('Replaced technical terms with simpler language'); // Shorten sentences adaptedContent = this.shortenSentences(adaptedContent); changes.push('Broke down complex sentences'); // Add analogies adaptedContent = this.addSimpleAnalogies(adaptedContent); changes.push('Added relatable analogies'); } else if (targetComplexity === 'complex') { // Add technical details adaptedContent = this.addTechnicalDetails(adaptedContent); changes.push('Enhanced with technical specifications'); // Include precise terminology adaptedContent = this.usePreciseTerminology(adaptedContent); changes.push('Used industry-specific terminology'); } // Verify accuracy wasn't compromised const accuracyPreserved = preserveAccuracy ? this.verifyAccuracy(content, adaptedContent) : true; return { adapted_content: adaptedContent, complexity_changes: changes, accuracy_preserved: accuracyPreserved }; } // Private helper methods determineTone(style, context) { const toneMap = { professional: { formality: 0.8, enthusiasm: 0.4, confidence: 0.8 }, friendly: { formality: 0.4, enthusiasm: 0.8, confidence: 0.7 }, executive: { formality: 0.9, enthusiasm: 0.3, confidence: 0.9 }, technical: { formality: 0.7, enthusiasm: 0.5, confidence: 0.8 }, educational: { formality: 0.5, enthusiasm: 0.7, confidence: 0.7 } }; const baseTone = toneMap[style]; // Adjust based on context if (context?.urgency === 'high') { baseTone.formality += 0.1; baseTone.enthusiasm -= 0.1; } if (context?.user_expertise === 'beginner') { baseTone.formality -= 0.1; baseTone.enthusiasm += 0.1; } return { style, formality: Math.max(0, Math.min(1, baseTone.formality)), enthusiasm: Math.max(0, Math.min(1, baseTone.enthusiasm)), confidence: Math.max(0, Math.min(1, baseTone.confidence)) }; } async buildConversationElements(response, style, tone, context) { const elements = { main_content: [] }; // Add greeting if appropriate if (!context?.previous_interactions || context.previous_interactions < 2) { elements.greeting = this.selectGreeting(style, tone); } // Acknowledge context if relevant if (context?.urgency === 'high') { elements.context_acknowledgment = "I understand this is time-sensitive."; } // Build main content elements.main_content = this.buildMainContent(response, style); // Add clarifications if needed if (this.needsClarification(response)) { elements.clarifications = this.generateClarifications(response); } // Add closing elements.closing = this.selectClosing(style, context); return elements; } generateInteractiveElements(response, context) { const interactive = {}; // Generate follow-up questions interactive.follow_up_questions = this.generateFollowUpQuestions(response); // Suggest related topics interactive.suggested_topics = this.suggestRelatedTopics(response); // Add clarification prompts if complexity is high if (this.isComplex(response)) { interactive.clarification_prompts = [ "Would you like me to explain any of these terms?", "Should I break down the calculations?", "Need more context on the assumptions?" ]; } return interactive; } composeMessage(elements, tone) { let message = ''; if (elements.greeting) { message += elements.greeting + ' '; } if (elements.context_acknowledgment) { message += elements.context_acknowledgment + ' '; } message += elements.main_content.join(' '); if (elements.clarifications && elements.clarifications.length > 0) { message += '\n\n' + elements.clarifications.join(' '); } if (elements.closing) { message += '\n\n' + elements.closing; } return message.trim(); } calculateMetadata(message, elements) { const words = message.split(/\s+/).length; const readingTime = Math.ceil(words / 200); // 200 words per minute const jargonTerms = this.countJargon(message); const complexity = this.assessComplexity(message); return { word_count: words, reading_time_seconds: readingTime * 60, complexity_level: complexity, jargon_count: jargonTerms }; } selectGreeting(style, tone) { const greetings = this.CONVERSATION_TEMPLATES.greetings[style]; return greetings[Math.floor(Math.random() * greetings.length)]; } selectClosing(style, context) { const closings = this.CONVERSATION_TEMPLATES.closings[style]; return closings[Math.floor(Math.random() * closings.length)]; } buildMainContent(response, style) { const content = []; // Start with the key finding if (response.executive_summary?.key_insight) { content.push(this.phraseKeyInsight(response.executive_summary.key_insight, style)); } // Add supporting details with transitions if (response.insights?.primary) { const transition = this.CONVERSATION_TEMPLATES.transitions.to_detail[0]; content.push(`${transition} ${response.insights.primary[0]}`); } // Include risks with appropriate framing if (response.insights?.risks && response.insights.risks.length > 0) { const transition = this.CONVERSATION_TEMPLATES.transitions.to_negative[0]; content.push(`${transition} ${this.phraseRisk(response.insights.risks[0], style)}`); } // End with recommendation if (response.recommendations?.next_action) { const transition = this.CONVERSATION_TEMPLATES.transitions.to_summary[0]; content.push(`${transition} I recommend ${response.recommendations.next_action.toLowerCase()}.`); } return content; } phraseKeyInsight(insight, style) { switch (style) { case 'executive': return `The critical insight: ${insight}`; case 'friendly': return `Here's what stands out: ${insight}`; case 'technical': return `Analysis reveals: ${insight}`; case 'educational': return `The main learning here is that ${insight}`; default: return insight; } } phraseRisk(risk, style) { switch (style) { case 'executive': return `a key risk to manage is ${risk}`; case 'friendly': return `something to keep an eye on is ${risk}`; case 'technical': return `risk analysis indicates ${risk}`; default: return `there's a risk of ${risk}`; } } needsClarification(response) { // Check if response contains complex terms or calculations return response.metadata?.confidence_score < 0.8 || response.insights?.risks?.length > 3 || response.summary?.payback_period_months > 24; } generateClarifications(response) { const clarifications = []; if (response.metadata?.confidence_score < 0.8) { clarifications.push("Note that confidence is moderate due to some assumptions in the data."); } if (response.summary?.payback_period_months > 24) { clarifications.push("The extended payback period reflects the scale of investment required."); } return clarifications; } generateFollowUpQuestions(response) { const questions = []; if (response.insights?.opportunities?.length > 0) { questions.push("Would you like to explore the growth opportunities in more detail?"); } if (response.insights?.risks?.length > 0) { questions.push("Should we discuss risk mitigation strategies?"); } if (response.recommendations?.alternatives?.length > 0) { questions.push("Want to compare alternative approaches?"); } return questions; } suggestRelatedTopics(response) { const topics = []; if (response.summary?.expected_roi > 100) { topics.push("Implementation roadmap planning"); topics.push("Quick wins identification"); } if (response.insights?.patterns?.length > 0) { topics.push("Industry benchmark comparison"); topics.push("Best practices from similar projects"); } return topics; } isComplex(response) { return response.use_cases?.length > 5 || response.financial_metrics?.scenarios?.length > 3 || (response.metadata?.assumptions?.length || 0) > 5; } countJargon(text) { const jargonTerms = [ 'ROI', 'NPV', 'IRR', 'payback period', 'automation', 'scalability', 'implementation', 'optimization', 'integration', 'deployment' ]; let count = 0; jargonTerms.forEach(term => { const regex = new RegExp(term, 'gi'); const matches = text.match(regex); count += matches ? matches.length : 0; }); return count; } assessComplexity(text) { const avgSentenceLength = text.split(/[.!?]/).filter(s => s.trim()).map(s => s.split(/\s+/).length).reduce((a, b) => a + b, 0) / (text.split(/[.!?]/).length || 1); const jargonDensity = this.countJargon(text) / (text.split(/\s+/).length || 1); if (avgSentenceLength < 15 && jargonDensity < 0.05) return 'simple'; if (avgSentenceLength > 25 || jargonDensity > 0.1) return 'complex'; return 'moderate'; } // Natural language explanation methods explainROI(data, audience) { const roi = data.summary?.expected_roi || data; switch (audience) { case 'executive': return `This investment is projected to return ${roi}% over 5 years, meaning every dollar invested will generate $${(roi / 100).toFixed(2)} in value.`; case 'technical': return `ROI calculation: ((Total Benefits - Total Costs) / Total Costs) × 100 = ${roi}%. This includes discounted cash flows at ${data.discount_rate || 10}% annual rate.`; case 'general': return `Think of ROI like this: if you invest $100, you'll get back $${100 + roi} total. That's a ${roi}% return on your investment.`; default: return `ROI: ${roi}%`; } } explainPaybackPeriod(data, audience) { const months = data.summary?.payback_period_months || data; const years = Math.floor(months / 12); const remainingMonths = months % 12; const timeStr = years > 0 ? `${years} year${years > 1 ? 's' : ''}${remainingMonths > 0 ? ` and ${remainingMonths} month${remainingMonths > 1 ? 's' : ''}` : ''}` : `${months} month${months > 1 ? 's' : ''}`; switch (audience) { case 'executive': return `You'll recoup your investment in ${timeStr}, after which all benefits are pure profit.`; case 'technical': return `Payback achieved when cumulative discounted cash flows equal initial investment: ${timeStr} based on projected monthly benefits.`; case 'general': return `It will take ${timeStr} to earn back what you invested. After that, it's all profit!`; default: return `Payback period: ${timeStr}`; } } explainRiskFactors(data, audience) { const risks = data.insights?.risks || data; const topRisk = Array.isArray(risks) ? risks[0] : risks; switch (audience) { case 'executive': return `Primary risk exposure: ${topRisk}. Mitigation strategies are available and should be implemented proactively.`; case 'technical': return `Risk assessment identifies ${topRisk} with potential impact on timeline and budget. Recommend implementing controls.`; case 'general': return `The main thing to watch out for is ${topRisk}. We can plan ahead to handle this.`; default: return `Key risk: ${topRisk}`; } } explainTimeline(data, audience) { const months = data.timeline_months || data; switch (audience) { case 'executive': return `Full implementation requires ${months} months, with value realization beginning in month ${Math.ceil(months * 0.3)}.`; case 'technical': return `Project timeline: ${months} months including ${Math.ceil(months * 0.25)} months planning, ${Math.ceil(months * 0.5)} months implementation, ${Math.ceil(months * 0.25)} months optimization.`; case 'general': return `The project will take about ${months} months from start to finish. You'll start seeing benefits about ${Math.ceil(months * 0.3)} months in.`; default: return `Timeline: ${months} months`; } } explainCosts(data, audience) { const cost = data.summary?.total_investment || data; const costK = Math.round(cost / 1000); switch (audience) { case 'executive': return `Total investment requirement: $${costK}K, with ${data.breakdown?.development || 60}% for development and ${data.breakdown?.infrastructure || 40}% for infrastructure.`; case 'technical': return `Cost breakdown: $${costK}K total, comprising development hours, infrastructure, licensing, and ongoing operational expenses.`; case 'general': return `You'll need to invest about $${costK}K total. This covers everything from building the solution to keeping it running.`; default: return `Total cost: $${costK}K`; } } explainGeneric(concept, data, audience) { return `${concept}: ${JSON.stringify(data)}`; } // Narrative creation methods createSuccessStory(data) { const roi = data.summary?.expected_roi || 0; const payback = data.summary?.payback_period_months || 0; const narrative = `This is a story of transformation. Starting with an investment of $${Math.round((data.summary?.total_investment || 0) / 1000)}K, ` + `the organization embarked on a journey that would yield ${roi}% returns. ` + `Within just ${payback} months, the investment paid for itself. ` + `The key was focusing on ${data.insights?.primary?.[0] || 'strategic automation'}. ` + `Today, the benefits continue to compound, validating the initial vision.`; return { narrative, key_points: [ `${roi}% ROI achieved`, `${payback} month payback period`, 'Successful transformation' ], emotional_arc: ['challenge', 'decision', 'implementation', 'success', 'growth'] }; } createCautionaryTale(data) { const risks = data.insights?.risks || []; const narrative = `While the opportunity showed promise, several risks demanded attention. ` + `${risks[0] || 'Implementation complexity'} posed the greatest challenge. ` + `Without proper mitigation, what started as a ${data.summary?.expected_roi || 0}% ROI opportunity ` + `could easily turn into a costly lesson. The key learning: thorough preparation and risk management ` + `are not optional—they're essential for success.`; return { narrative, key_points: risks.slice(0, 3), emotional_arc: ['optimism', 'discovery', 'concern', 'caution', 'wisdom'] }; } createComparisonNarrative(data) { const narrative = `When comparing options, the differences become clear. ` + `Option A offers ${data.option_a?.roi || 0}% ROI with a ${data.option_a?.payback || 0}-month payback. ` + `Option B provides ${data.option_b?.roi || 0}% ROI but takes ${data.option_b?.payback || 0} months to pay back. ` + `The choice depends on your priorities: immediate returns or long-term value.`; return { narrative, key_points: ['Clear tradeoffs', 'Different timelines', 'Strategic choice required'], emotional_arc: ['analysis', 'comparison', 'insight', 'decision'] }; } createJourneyNarrative(data) { const narrative = `Every transformation begins with a single step. ` + `Month 1-3: Foundation building and planning. ` + `Month 4-9: Implementation and initial results. ` + `Month 10-${data.timeline_months || 12}: Optimization and scaling. ` + `By month ${data.summary?.payback_period_months || 18}, the journey reaches a milestone: full payback. ` + `But this isn't the end—it's just the beginning of sustained value creation.`; return { narrative, key_points: ['Phased approach', 'Clear milestones', 'Long-term value'], emotional_arc: ['beginning', 'progress', 'challenges', 'breakthrough', 'momentum', 'success'] }; } createGenericNarrative(data) { return { narrative: `This analysis reveals key insights about the opportunity at hand.`, key_points: ['Data-driven insights', 'Clear recommendations', 'Actionable next steps'], emotional_arc: ['discovery', 'analysis', 'conclusion'] }; } // Conversation flow methods analyzeConversationContext(history) { const topics = new Set(); const sentiment = { positive: 0, negative: 0, neutral: 0 }; let lastTopic = ''; history.forEach(entry => { // Extract topics (simplified) if (entry.content.toLowerCase().includes('roi')) topics.add('roi'); if (entry.content.toLowerCase().includes('risk')) topics.add('risk'); if (entry.content.toLowerCase().includes('timeline')) topics.add('timeline'); // Track sentiment (simplified) if (entry.content.includes('great') || entry.content.includes('excellent')) { sentiment.positive++; } else if (entry.content.includes('concern') || entry.content.includes('worry')) { sentiment.negative++; } else { sentiment.neutral++; } }); if (history.length > 0) { const lastEntry = history[history.length - 1].content.toLowerCase(); if (lastEntry.includes('roi')) lastTopic = 'roi'; else if (lastEntry.includes('risk')) lastTopic = 'risk'; else if (lastEntry.includes('timeline')) lastTopic = 'timeline'; } return { topics: Array.from(topics), sentiment, lastTopic }; } buildContextualResponse(currentResponse, context, history) { let response = ''; // Reference previous topics if relevant if (context.topics.includes('roi') && currentResponse.summary?.expected_roi) { response += `As we discussed earlier about ROI, `; } // Acknowledge sentiment if (context.sentiment.negative > context.sentiment.positive) { response += `I understand your concerns. Let me address them: `; } // Build on last topic if (context.lastTopic && currentResponse[context.lastTopic]) { response += `Building on our ${context.lastTopic} discussion, `; } // Add current response content response += this.buildMainContent(currentResponse, 'professional').join(' '); return response; } findPastReferences(current, history) { const references = []; history.forEach((entry, idx) => { // Look for related content if (entry.content.includes('roi') && current.summary?.expected_roi) { references.push(`Relates to ROI discussion from earlier`); } if (entry.content.includes('timeline') && current.timeline_months) { references.push(`Connects to timeline mentioned previously`); } }); return Array.from(new Set(references)); } calculateContinuity(history, response) { if (history.length === 0) return 1.0; // Simple continuity score based on topic overlap const lastEntry = history[history.length - 1].content.toLowerCase(); const responseWords = response.toLowerCase().split(/\s+/); const lastWords = lastEntry.split(/\s+/); const overlap = responseWords.filter(word => lastWords.includes(word) && word.length > 3).length; return Math.min(1, overlap / 10); } // Complexity adaptation methods simplifyJargon(text) { const jargonMap = { 'ROI': 'return on investment', 'NPV': 'net present value', 'payback period': 'time to earn back investment', 'implementation': 'setup and installation', 'scalability': 'ability to grow', 'automation': 'automatic processing' }; let simplified = text; Object.entries(jargonMap).forEach(([jargon, simple]) => { simplified = simplified.replace(new RegExp(jargon, 'gi'), simple); }); return simplified; } shortenSentences(text) { const sentences = text.split(/(?<=[.!?])\s+/); return sentences.map(sentence => { const words = sentence.split(/\s+/); if (words.length > 20) { // Break into smaller sentences const mid = Math.floor(words.length / 2); return words.slice(0, mid).join(' ') + '. ' + words.slice(mid).join(' '); } return sentence; }).join(' '); } addSimpleAnalogies(text) { if (text.includes('return on investment')) { text += ' (like earning interest on savings)'; } if (text.includes('payback')) { text += ' (like paying off a loan)'; } return text; } addTechnicalDetails(text) { if (text.includes('ROI')) { text = text.replace('ROI', 'ROI (calculated using DCF methodology)'); } if (text.includes('implementation')) { text = text.replace('implementation', 'implementation (including CI/CD pipeline setup)'); } return text; } usePreciseTerminology(text) { const preciseTerms = { 'setup': 'implementation and deployment', 'money saved': 'cost reduction', 'making money': 'revenue generation', 'getting better': 'optimization' }; let precise = text; Object.entries(preciseTerms).forEach(([simple, technical]) => { precise = precise.replace(new RegExp(simple, 'gi'), technical); }); return precise; } verifyAccuracy(original, adapted) { // Check that key numbers haven't changed const originalNumbers = original.match(/\d+/g) || []; const adaptedNumbers = adapted.match(/\d+/g) || []; return originalNumbers.every(num => adaptedNumbers.includes(num)); } } // Export singleton instance export const conversationalBridge = new ConversationalBridge(); //# sourceMappingURL=conversational-bridge.js.map