UNPKG

@spaik/mcp-server-roi

Version:

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

931 lines 36.8 kB
import { z } from 'zod'; import { createLogger } from '../utils/logger.js'; /** * Response Adapter Service * * Adapts responses based on AI agent preferences, format requirements, * and interaction patterns for optimal consumption. */ // Format schemas export const ResponseFormatSchema = z.enum([ 'full_json', 'executive_only', 'narrative', 'structured_summary', 'conversational', 'markdown_report', 'csv_data', 'visual_ready' ]); export const AgentPreferencesSchema = z.object({ preferred_format: ResponseFormatSchema, detail_level: z.enum(['minimal', 'standard', 'comprehensive']), language_style: z.enum(['formal', 'casual', 'technical', 'executive']), include_visuals: z.boolean().default(false), max_response_tokens: z.number().optional(), focus_areas: z.array(z.string()).optional(), skip_sections: z.array(z.string()).optional() }); export const AdaptedResponseSchema = z.object({ format: ResponseFormatSchema, content: z.any(), metadata: z.object({ original_format: z.string(), adaptation_notes: z.array(z.string()), tokens_saved: z.number().optional(), quality_score: z.number().min(0).max(1) }) }); export class ResponseAdapter { logger = createLogger({ component: 'ResponseAdapter' }); // Default preferences for different agent types AGENT_PROFILES = { executive: { preferred_format: 'executive_only', detail_level: 'minimal', language_style: 'executive', include_visuals: true, max_response_tokens: 500 }, analyst: { preferred_format: 'structured_summary', detail_level: 'comprehensive', language_style: 'technical', include_visuals: true, focus_areas: ['financial_metrics', 'risk_analysis'] }, conversational: { preferred_format: 'conversational', detail_level: 'standard', language_style: 'casual', include_visuals: false, max_response_tokens: 1000 } }; /** * Adapt response based on detected or specified preferences */ async adaptResponse(response, preferences, context) { this.logger.debug('Adapting response', { format: preferences?.preferred_format, detail_level: preferences?.detail_level }); // Detect preferences if not provided const effectivePreferences = preferences || this.detectPreferences(context); // Adapt based on format let adaptedContent; switch (effectivePreferences.preferred_format) { case 'executive_only': adaptedContent = this.formatExecutiveOnly(response, effectivePreferences); break; case 'narrative': adaptedContent = this.formatNarrative(response, effectivePreferences); break; case 'structured_summary': adaptedContent = this.formatStructuredSummary(response, effectivePreferences); break; case 'conversational': adaptedContent = this.formatConversational(response, effectivePreferences); break; case 'markdown_report': adaptedContent = this.formatMarkdownReport(response, effectivePreferences); break; case 'csv_data': adaptedContent = this.formatCSVData(response); break; case 'visual_ready': adaptedContent = this.formatVisualReady(response); break; default: adaptedContent = response; // Full JSON } // Apply token limits if specified if (effectivePreferences.max_response_tokens) { adaptedContent = this.applyTokenLimit(adaptedContent, effectivePreferences.max_response_tokens); } // Calculate adaptation metadata const metadata = this.generateAdaptationMetadata(response, adaptedContent, effectivePreferences); return { format: effectivePreferences.preferred_format, content: adaptedContent, metadata }; } /** * Detect agent type and preferences from interaction patterns */ async detectAgentType(queryHistory, currentQuery) { this.logger.debug('Detecting agent type from patterns'); const patterns = this.analyzeInteractionPatterns(queryHistory, currentQuery); const agentType = this.classifyAgentType(patterns); const confidence = this.calculateTypeConfidence(patterns, agentType); return { agent_type: agentType, confidence, detected_patterns: patterns.detected, recommended_preferences: this.AGENT_PROFILES[agentType] || this.AGENT_PROFILES.analyst }; } /** * Optimize response for specific use cases */ async optimizeForUseCase(response, useCase) { this.logger.debug('Optimizing for use case', { useCase }); switch (useCase) { case 'decision_making': return this.optimizeForDecisionMaking(response); case 'exploration': return this.optimizeForExploration(response); case 'reporting': return this.optimizeForReporting(response); case 'monitoring': return this.optimizeForMonitoring(response); default: return response; } } /** * Convert response to specific output formats */ async convertToFormat(response, targetFormat) { this.logger.debug('Converting to format', { targetFormat }); switch (targetFormat) { case 'json': return JSON.stringify(response, null, 2); case 'yaml': return this.convertToYAML(response); case 'xml': return this.convertToXML(response); case 'html': return this.convertToHTML(response); case 'pdf_ready': return this.convertToPDFReady(response); default: return JSON.stringify(response); } } // Private formatting methods formatExecutiveOnly(response, preferences) { const executive = { headline: response.executive_summary?.headline || 'Analysis Complete', key_metrics: this.extractKeyMetrics(response), decision_required: this.extractDecisionPoints(response), recommendations: this.extractTopRecommendations(response, 3), risks: this.extractTopRisks(response, 2), next_steps: response.recommendations?.next_action || 'Review detailed analysis' }; // Apply language style if (preferences.language_style === 'executive') { executive.headline = this.makeExecutiveLanguage(executive.headline); } return executive; } formatNarrative(response, preferences) { let narrative = ''; // Opening narrative += response.executive_summary?.headline || 'Analysis Results'; narrative += '\n\n'; // Context if (response.narrative?.context) { narrative += response.narrative.context + '\n\n'; } // Key insights narrative += 'Key Findings:\n'; const insights = response.insights?.primary || []; insights.forEach((insight, i) => { narrative += `${i + 1}. ${insight}\n`; }); narrative += '\n'; // Recommendations if (response.recommendations) { narrative += `Recommended Action: ${response.recommendations.next_action}\n`; narrative += `Timeline: ${response.recommendations.timeline}\n\n`; } // Conclusion if (response.narrative?.conclusion) { narrative += response.narrative.conclusion; } return narrative; } formatStructuredSummary(response, preferences) { const summary = { overview: { status: response.executive_summary?.confidence || 'complete', summary: response.executive_summary?.headline, key_metric: response.executive_summary?.primary_metric }, insights: {}, recommendations: {}, data_points: {} }; // Structure insights by category if (response.insights) { summary.insights = { opportunities: response.insights.opportunities?.slice(0, 3), risks: response.insights.risks?.slice(0, 3), patterns: response.insights.patterns?.slice(0, 2) }; } // Structure recommendations if (response.recommendations) { summary.recommendations = { immediate: response.recommendations.next_action, timeline: response.recommendations.timeline, prerequisites: response.recommendations.prerequisites?.slice(0, 3) }; } // Key data points based on focus areas if (preferences.focus_areas) { summary.data_points = this.extractFocusAreaData(response, preferences.focus_areas); } return summary; } formatConversational(response, preferences) { let conversation = ''; // Friendly opening const confidence = response.executive_summary?.confidence; if (confidence === 'high') { conversation += "Great news! I've completed the analysis with high confidence. "; } else { conversation += "I've finished analyzing the data. "; } // Main insight in conversational tone conversation += response.executive_summary?.key_insight || "Here's what I found."; conversation += '\n\n'; // Key points conversation += "The main things you should know:\n"; const points = [ response.executive_summary?.primary_metric, response.insights?.primary?.[0], response.recommendations?.next_action ].filter(Boolean); points.forEach((point, i) => { conversation += `• ${point}\n`; }); // Closing with next steps conversation += '\n'; if (response.recommendations?.timeline) { conversation += `I'd recommend ${response.recommendations.timeline.toLowerCase()}. `; } conversation += "Would you like me to dive deeper into any specific area?"; return conversation; } formatMarkdownReport(response, preferences) { let markdown = ''; // Title markdown += `# ${response.executive_summary?.headline || 'Analysis Report'}\n\n`; // Executive Summary markdown += '## Executive Summary\n\n'; markdown += `**Confidence Level:** ${response.executive_summary?.confidence || 'N/A'}\n\n`; markdown += `**Key Insight:** ${response.executive_summary?.key_insight || 'N/A'}\n\n`; if (response.executive_summary?.primary_metric) { markdown += `**Primary Metric:** ${response.executive_summary.primary_metric}\n\n`; } // Insights Section if (response.insights) { markdown += '## Key Insights\n\n'; if (response.insights.primary?.length > 0) { markdown += '### Primary Findings\n'; response.insights.primary.forEach((insight) => { markdown += `- ${insight}\n`; }); markdown += '\n'; } if (response.insights.risks?.length > 0) { markdown += '### Risk Factors\n'; response.insights.risks.forEach((risk) => { markdown += `- ⚠️ ${risk}\n`; }); markdown += '\n'; } if (response.insights.opportunities?.length > 0) { markdown += '### Opportunities\n'; response.insights.opportunities.forEach((opp) => { markdown += `- 💡 ${opp}\n`; }); markdown += '\n'; } } // Recommendations if (response.recommendations) { markdown += '## Recommendations\n\n'; markdown += `**Next Action:** ${response.recommendations.next_action}\n\n`; markdown += `**Timeline:** ${response.recommendations.timeline}\n\n`; if (response.recommendations.success_criteria) { markdown += '**Success Criteria:**\n'; response.recommendations.success_criteria.forEach((criteria) => { markdown += `- ${criteria}\n`; }); markdown += '\n'; } } // Data Quality if (response.metadata) { markdown += '## Metadata\n\n'; markdown += `- **Data Quality:** ${response.metadata.data_quality}\n`; markdown += `- **Confidence Score:** ${(response.metadata.confidence_score * 100).toFixed(0)}%\n`; markdown += `- **Generated:** ${new Date(response.metadata.generated_at).toLocaleString()}\n`; } return markdown; } formatCSVData(response) { const rows = []; // Header row rows.push(['Metric', 'Value', 'Category', 'Confidence']); // Extract flat data if (response.summary) { Object.entries(response.summary).forEach(([key, value]) => { rows.push([key, String(value), 'Summary', 'High']); }); } // Financial metrics if (response.financial_metrics?.expected) { Object.entries(response.financial_metrics.expected).forEach(([key, value]) => { rows.push([key, String(value), 'Financial', 'Expected']); }); } // Convert to CSV return rows.map(row => row.map(cell => cell.includes(',') ? `"${cell}"` : cell).join(',')).join('\n'); } formatVisualReady(response) { return { charts: [ { type: 'bar', title: 'ROI Comparison', data: this.prepareROIChartData(response), options: { responsive: true, maintainAspectRatio: false } }, { type: 'timeline', title: 'Implementation Roadmap', data: this.prepareTimelineData(response), options: { responsive: true } }, { type: 'pie', title: 'Value Distribution', data: this.prepareValueDistribution(response), options: { responsive: true } } ], tables: [ { title: 'Key Metrics', headers: ['Metric', 'Value', 'Target', 'Status'], rows: this.prepareMetricsTable(response) } ], kpis: this.prepareKPIs(response) }; } // Private helper methods detectPreferences(context) { // Simple heuristic-based detection if (context?.query?.includes('executive') || context?.query?.includes('summary only')) { return this.AGENT_PROFILES.executive; } if (context?.query?.includes('tell me') || context?.query?.includes('explain')) { return this.AGENT_PROFILES.conversational; } // Default to analyst profile return this.AGENT_PROFILES.analyst; } extractKeyMetrics(response) { return { roi: response.summary?.expected_roi || response.executive_summary?.primary_metric, payback: response.summary?.payback_period_months, investment: response.summary?.total_investment, confidence: response.metadata?.confidence_score }; } extractDecisionPoints(response) { const decisions = []; if (response.recommendations?.next_action) { decisions.push(`Approve: ${response.recommendations.next_action}`); } if (response.summary?.total_investment > 1000000) { decisions.push('Authorize budget allocation'); } return decisions; } extractTopRecommendations(response, limit) { const recommendations = []; if (response.recommendations?.next_action) { recommendations.push(response.recommendations.next_action); } if (response.insights?.opportunities) { recommendations.push(...response.insights.opportunities.slice(0, limit - 1)); } return recommendations.slice(0, limit); } extractTopRisks(response, limit) { if (response.insights?.risks) { return response.insights.risks.slice(0, limit); } return []; } makeExecutiveLanguage(text) { // Simple transformations for executive communication return text .replace(/We recommend/g, 'Recommendation:') .replace(/You should/g, 'Action required:') .replace(/It is suggested/g, 'Consider:'); } applyTokenLimit(content, maxTokens) { const stringified = JSON.stringify(content); const estimatedTokens = stringified.length / 4; // Rough estimate if (estimatedTokens <= maxTokens) { return content; } // Progressively remove less important sections if (content.detailed_analysis) { delete content.detailed_analysis; } if (content.metadata && estimatedTokens > maxTokens) { content.metadata = { note: 'Full metadata available on request' }; } return content; } generateAdaptationMetadata(original, adapted, preferences) { const originalSize = JSON.stringify(original).length; const adaptedSize = JSON.stringify(adapted).length; const notes = []; if (preferences.detail_level === 'minimal') { notes.push('Reduced to essential information only'); } if (preferences.skip_sections?.length) { notes.push(`Skipped sections: ${preferences.skip_sections.join(', ')}`); } if (preferences.max_response_tokens) { notes.push(`Applied token limit: ${preferences.max_response_tokens}`); } return { original_format: 'full_json', adaptation_notes: notes, tokens_saved: Math.max(0, Math.floor((originalSize - adaptedSize) / 4)), quality_score: this.assessAdaptationQuality(original, adapted, preferences) }; } assessAdaptationQuality(original, adapted, preferences) { let score = 1.0; // Penalize if critical information is missing if (!adapted.executive_summary && !adapted.headline) { score -= 0.2; } // Reward if format matches preference if (preferences.preferred_format === 'executive_only' && !adapted.detailed_analysis) { score += 0.1; } // Check completeness based on detail level if (preferences.detail_level === 'comprehensive' && !adapted.metadata) { score -= 0.1; } return Math.max(0, Math.min(1, score)); } analyzeInteractionPatterns(queryHistory, currentQuery) { const patterns = { detected: [], query_complexity: 'medium', detail_requests: 0, summary_requests: 0, visual_requests: 0 }; // Analyze current query const queryLower = currentQuery?.toLowerCase() || ''; if (queryLower.includes('summary') || queryLower.includes('brief')) { patterns.summary_requests++; patterns.detected.push('Prefers summaries'); } if (queryLower.includes('detail') || queryLower.includes('comprehensive')) { patterns.detail_requests++; patterns.detected.push('Requests detailed analysis'); } if (queryLower.includes('chart') || queryLower.includes('visual')) { patterns.visual_requests++; patterns.detected.push('Prefers visual representations'); } // Analyze history if (queryHistory.length > 0) { const avgQueryLength = queryHistory.reduce((sum, q) => sum + (q.query?.length || 0), 0) / queryHistory.length; if (avgQueryLength < 50) { patterns.detected.push('Short, direct queries'); patterns.query_complexity = 'simple'; } else if (avgQueryLength > 200) { patterns.detected.push('Detailed, complex queries'); patterns.query_complexity = 'complex'; } } return patterns; } classifyAgentType(patterns) { if (patterns.summary_requests > patterns.detail_requests) { return 'executive'; } if (patterns.visual_requests > 0 || patterns.query_complexity === 'complex') { return 'analyst'; } if (patterns.query_complexity === 'simple' && patterns.detected.includes('Short, direct queries')) { return 'conversational'; } return 'analyst'; // Default } calculateTypeConfidence(patterns, agentType) { let confidence = 0.5; // Base confidence // Increase confidence based on pattern matches if (agentType === 'executive' && patterns.summary_requests > 2) { confidence += 0.3; } if (agentType === 'analyst' && patterns.detail_requests > 1) { confidence += 0.2; } if (patterns.detected.length > 3) { confidence += 0.1; } return Math.min(0.95, confidence); } extractFocusAreaData(response, focusAreas) { const data = {}; focusAreas.forEach(area => { switch (area) { case 'financial_metrics': data.financial = { roi: response.summary?.expected_roi, npv: response.summary?.net_present_value, payback: response.summary?.payback_period_months }; break; case 'risk_analysis': data.risks = { factors: response.insights?.risks, mitigation: response.recommendations?.prerequisites }; break; case 'implementation': data.implementation = { timeline: response.recommendations?.timeline, next_steps: response.recommendations?.next_action }; break; } }); return data; } optimizeForDecisionMaking(response) { return { decision_summary: { recommendation: response.recommendations?.next_action || 'Proceed with analysis', confidence: response.executive_summary?.confidence || 'medium', key_factors: [ response.executive_summary?.key_insight, response.executive_summary?.primary_metric ].filter(Boolean) }, supporting_data: { pros: response.insights?.opportunities?.slice(0, 3) || [], cons: response.insights?.risks?.slice(0, 3) || [], alternatives: response.recommendations?.alternatives || [] }, decision_criteria: { roi_threshold_met: (response.summary?.expected_roi || 0) > 50, risk_acceptable: response.metadata?.assumptions_impact !== 'high', timeline_feasible: (response.summary?.payback_period_months || 24) <= 24 } }; } optimizeForExploration(response) { return { overview: response.executive_summary, explore_areas: { insights: { title: 'Key Insights', items: response.insights?.primary || [], drill_down_available: true }, opportunities: { title: 'Growth Opportunities', items: response.insights?.opportunities || [], drill_down_available: true }, risks: { title: 'Risk Factors', items: response.insights?.risks || [], drill_down_available: true } }, suggested_next_queries: [ 'Show me detailed financial projections', 'What are the implementation requirements?', 'Compare with industry benchmarks' ] }; } optimizeForReporting(response) { return { report_metadata: { generated_at: response.metadata?.generated_at || new Date().toISOString(), report_type: 'ROI Analysis', confidence_level: response.metadata?.confidence_score || 0.85 }, executive_summary: response.executive_summary, detailed_findings: { financial_analysis: response.summary, insights_and_patterns: response.insights, recommendations: response.recommendations }, appendices: { methodology: response.metadata?.calculation_context?.methodology, assumptions: response.metadata?.assumptions, data_quality: response.metadata?.data_quality } }; } optimizeForMonitoring(response) { const metrics = response.summary || {}; const previousMetrics = response.previous_period || {}; return { current_status: { health_score: this.calculateHealthScore(response), key_metrics: { roi: metrics.expected_roi, payback: metrics.payback_period_months, confidence: response.metadata?.confidence_score } }, changes: { roi_change: this.calculateChange(metrics.expected_roi, previousMetrics.expected_roi), risk_change: response.insights?.risks?.length || 0, new_opportunities: response.insights?.opportunities?.length || 0 }, alerts: this.generateAlerts(response), trend_indicators: { roi_trend: 'stable', // Would calculate from history risk_trend: 'increasing', timeline_trend: 'on_track' } }; } calculateHealthScore(response) { let score = 0.5; // Base score // Positive factors if (response.summary?.expected_roi > 100) score += 0.2; if (response.summary?.payback_period_months < 12) score += 0.1; if (response.executive_summary?.confidence === 'high') score += 0.1; // Negative factors if (response.insights?.risks?.length > 3) score -= 0.1; if (response.metadata?.data_quality === 'low') score -= 0.1; return Math.max(0, Math.min(1, score)); } calculateChange(current, previous) { if (!current || !previous) return 'N/A'; const change = ((current - previous) / previous) * 100; const direction = change > 0 ? '↑' : change < 0 ? '↓' : '→'; return `${direction} ${Math.abs(change).toFixed(1)}%`; } generateAlerts(response) { const alerts = []; if (response.insights?.risks?.some((r) => r.toLowerCase().includes('critical'))) { alerts.push('⚠️ Critical risk identified'); } if (response.summary?.payback_period_months > 24) { alerts.push('📊 Extended payback period requires attention'); } if (response.metadata?.confidence_score < 0.7) { alerts.push('📉 Low confidence in projections'); } return alerts; } // Format conversion methods convertToYAML(response) { // Simplified YAML conversion const yaml = this.objectToYAML(response, 0); return yaml; } objectToYAML(obj, indent) { let yaml = ''; const indentStr = ' '.repeat(indent); for (const [key, value] of Object.entries(obj)) { if (value === null || value === undefined) { yaml += `${indentStr}${key}: null\n`; } else if (typeof value === 'object' && !Array.isArray(value)) { yaml += `${indentStr}${key}:\n${this.objectToYAML(value, indent + 1)}`; } else if (Array.isArray(value)) { yaml += `${indentStr}${key}:\n`; value.forEach(item => { yaml += `${indentStr}- ${typeof item === 'object' ? '\n' + this.objectToYAML(item, indent + 2) : item}\n`; }); } else { yaml += `${indentStr}${key}: ${value}\n`; } } return yaml; } convertToXML(response) { let xml = '<?xml version="1.0" encoding="UTF-8"?>\n'; xml += '<response>\n'; xml += this.objectToXML(response, 1); xml += '</response>'; return xml; } objectToXML(obj, indent) { let xml = ''; const indentStr = ' '.repeat(indent); for (const [key, value] of Object.entries(obj)) { const safeKey = key.replace(/[^a-zA-Z0-9_-]/g, '_'); if (value === null || value === undefined) { xml += `${indentStr}<${safeKey}/>\n`; } else if (typeof value === 'object' && !Array.isArray(value)) { xml += `${indentStr}<${safeKey}>\n${this.objectToXML(value, indent + 1)}${indentStr}</${safeKey}>\n`; } else if (Array.isArray(value)) { value.forEach(item => { xml += `${indentStr}<${safeKey}>${typeof item === 'object' ? '\n' + this.objectToXML(item, indent + 1) + indentStr : item}</${safeKey}>\n`; }); } else { xml += `${indentStr}<${safeKey}>${this.escapeXML(String(value))}</${safeKey}>\n`; } } return xml; } escapeXML(str) { return str .replace(/&/g, '&amp;') .replace(/</g, '&lt;') .replace(/>/g, '&gt;') .replace(/"/g, '&quot;') .replace(/'/g, '&apos;'); } convertToHTML(response) { const markdown = this.formatMarkdownReport(response, this.AGENT_PROFILES.analyst); // Simple markdown to HTML conversion return markdown .replace(/^# (.+)$/gm, '<h1>$1</h1>') .replace(/^## (.+)$/gm, '<h2>$1</h2>') .replace(/^### (.+)$/gm, '<h3>$1</h3>') .replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>') .replace(/^- (.+)$/gm, '<li>$1</li>') .replace(/\n\n/g, '</p><p>') .replace(/^/, '<p>') .replace(/$/, '</p>'); } convertToPDFReady(response) { // Structure optimized for PDF generation return JSON.stringify({ document: { title: response.executive_summary?.headline || 'ROI Analysis Report', metadata: { created: new Date().toISOString(), author: 'MCP Server ROI', subject: 'AI Investment Analysis' }, sections: [ { type: 'cover', content: { title: response.executive_summary?.headline, subtitle: response.executive_summary?.key_insight, date: new Date().toLocaleDateString() } }, { type: 'summary', title: 'Executive Summary', content: this.formatExecutiveOnly(response, this.AGENT_PROFILES.executive) }, { type: 'details', title: 'Detailed Analysis', content: response } ] } }, null, 2); } // Chart data preparation methods prepareROIChartData(response) { const scenarios = ['Conservative', 'Expected', 'Optimistic']; const values = [ response.financial_metrics?.conservative?.total_monthly_benefit || 0, response.financial_metrics?.expected?.total_monthly_benefit || 0, response.financial_metrics?.optimistic?.total_monthly_benefit || 0 ]; return { labels: scenarios, datasets: [{ label: 'Monthly Benefit', data: values, backgroundColor: ['#FF6384', '#36A2EB', '#4BC0C0'] }] }; } prepareTimelineData(response) { const milestones = []; if (response.recommendations?.timeline) { milestones.push({ date: new Date().toISOString(), label: 'Start', description: 'Project initiation' }); // Add milestones based on timeline const months = parseInt(response.summary?.payback_period_months || '12'); milestones.push({ date: new Date(Date.now() + months * 30 * 24 * 60 * 60 * 1000).toISOString(), label: 'Payback', description: 'Expected payback achieved' }); } return milestones; } prepareValueDistribution(response) { const categories = {}; response.use_cases?.forEach((uc) => { const category = uc.category; if (!categories[category]) { categories[category] = 0; } categories[category] += uc.monthly_benefit || 0; }); return { labels: Object.keys(categories), datasets: [{ data: Object.values(categories), backgroundColor: [ '#FF6384', '#36A2EB', '#FFCE56', '#4BC0C0', '#9966FF' ] }] }; } prepareMetricsTable(response) { const rows = []; if (response.summary) { rows.push([ 'ROI', `${response.summary.expected_roi}%`, '100%', response.summary.expected_roi > 100 ? '✅' : '⚠️' ]); rows.push([ 'Payback Period', `${response.summary.payback_period_months} months`, '18 months', response.summary.payback_period_months <= 18 ? '✅' : '⚠️' ]); rows.push([ 'NPV', `$${(response.summary.net_present_value / 1000).toFixed(0)}K`, '>$0', response.summary.net_present_value > 0 ? '✅' : '❌' ]); } return rows; } prepareKPIs(response) { return [ { label: 'Expected ROI', value: `${response.summary?.expected_roi || 0}%`, trend: 'up', color: 'success' }, { label: 'Payback Period', value: `${response.summary?.payback_period_months || 0}mo`, trend: 'stable', color: 'warning' }, { label: 'Confidence', value: `${((response.metadata?.confidence_score || 0) * 100).toFixed(0)}%`, trend: 'up', color: 'info' } ]; } } // Export singleton instance export const responseAdapter = new ResponseAdapter(); //# sourceMappingURL=response-adapter.js.map