UNPKG

syia-mcp-vessel-accounts

Version:

MCP server for vessel account management including EyeShare API integration, vessel expenses, and purchase orders

364 lines (329 loc) 18.7 kB
/** * Sampling utility for requesting AI completions from MCP clients * This allows the server to ask the AI to analyze, summarize, or process data * * Note: Currently provides structured data for AI analysis. * When MCP SDK adds requestSampling support, this will be enhanced. */ import { logger } from "./logger.js"; export class SamplingHandler { constructor(server) { this.server = server; } /** * Generate structured prompt for AI to summarize vessel expenses data */ async summarizeVesselExpenses(expenses, vesselCode) { try { logger.info(`Generating AI analysis prompt for ${expenses.length} expenses for vessel ${vesselCode}`); // Calculate basic statistics const totalAmount = expenses.reduce((sum, exp) => sum + (parseFloat(exp.amount) || 0), 0); const categories = expenses.reduce((cats, exp) => { const cat = exp.category || exp.account_name || 'Unknown'; cats[cat] = (cats[cat] || 0) + (parseFloat(exp.amount) || 0); return cats; }, {}); const topExpenses = expenses .sort((a, b) => (parseFloat(b.amount) || 0) - (parseFloat(a.amount) || 0)) .slice(0, 5); return `# AI Analysis: Vessel Expenses for ${vesselCode} ## Summary Statistics - **Total Expenses**: ${totalAmount.toLocaleString()} - **Number of Transactions**: ${expenses.length} - **Categories Found**: ${Object.keys(categories).length} ## Top Categories by Spending ${Object.entries(categories) .sort(([, a], [, b]) => b - a) .slice(0, 10) .map(([cat, amount]) => `- **${cat}**: ${amount.toLocaleString()}`) .join('\n')} ## Largest Individual Expenses ${topExpenses.map((exp, i) => `${i + 1}. ${(parseFloat(exp.amount) || 0).toLocaleString()} - ${exp.description || exp.account_name || 'No description'}`).join('\n')} ## Data Quality Insights - Records with amounts: ${expenses.filter(e => e.amount && parseFloat(e.amount) > 0).length}/${expenses.length} - Records with categories: ${expenses.filter(e => e.category || e.account_name).length}/${expenses.length} - Date range: ${expenses.length > 0 ? 'Available in data' : 'No data'} ## Recommendations 1. **Cost Control**: Focus on top spending categories for potential savings 2. **Data Quality**: Ensure all transactions have proper categorization 3. **Budget Monitoring**: Track spending patterns against budget allocations *This analysis provides structured data for AI-powered insights. When MCP Sampling is available, this will include advanced AI analysis.*`; } catch (error) { logger.error("Error in summarizeVesselExpenses:", error); throw new Error(`Failed to generate expense summary: ${error.message}`); } } /** * Generate structured analysis of invoice data */ async analyzeInvoices(invoices, vesselCode) { try { logger.info(`Generating invoice analysis for ${invoices.length} invoices for vessel ${vesselCode}`); // Calculate invoice statistics const totalAmount = invoices.reduce((sum, inv) => sum + (parseFloat(inv.Amount) || 0), 0); const suppliers = invoices.reduce((sups, inv) => { const supplier = inv.SupplierName || inv.Supplier || 'Unknown'; sups[supplier] = (sups[supplier] || 0) + (parseFloat(inv.Amount) || 0); return sups; }, {}); const statusCounts = invoices.reduce((status, inv) => { const stat = inv.Status || 'Unknown'; status[stat] = (status[stat] || 0) + 1; return status; }, {}); return `# AI Analysis: Invoice Data for ${vesselCode} ## Financial Summary - **Total Invoice Amount**: ${totalAmount.toLocaleString()} - **Number of Invoices**: ${invoices.length} - **Average Invoice Value**: ${(totalAmount / invoices.length || 0).toLocaleString()} ## Top Suppliers by Value ${Object.entries(suppliers) .sort(([, a], [, b]) => b - a) .slice(0, 10) .map(([supplier, amount]) => `- **${supplier}**: ${amount.toLocaleString()}`) .join('\n')} ## Invoice Status Distribution ${Object.entries(statusCounts) .map(([status, count]) => `- **${status}**: ${count} invoices`) .join('\n')} ## Key Insights - **Supplier Concentration**: Top 3 suppliers represent ${Math.round((Object.values(suppliers).sort((a, b) => b - a).slice(0, 3).reduce((a, b) => a + b, 0) / totalAmount) * 100)}% of total value - **Processing Status**: ${statusCounts['Approved'] || 0} approved, ${statusCounts['Pending'] || 0} pending - **Data Quality**: ${invoices.filter(i => i.Amount && parseFloat(i.Amount) > 0).length}/${invoices.length} invoices have valid amounts *Enhanced AI analysis will be available when MCP Sampling feature is supported.*`; } catch (error) { logger.error("Error in analyzeInvoices:", error); throw new Error(`Failed to generate invoice analysis: ${error.message}`); } } /** * Generate structured analysis of purchase order data */ async analyzePurchaseOrders(purchaseOrders, vesselCode) { try { logger.info(`Generating purchase order analysis for ${purchaseOrders.length} POs for vessel ${vesselCode}`); // Calculate PO statistics const totalValue = purchaseOrders.reduce((sum, po) => sum + (parseFloat(po.total_amount) || parseFloat(po.Amount) || 0), 0); const suppliers = purchaseOrders.reduce((sups, po) => { const supplier = po.supplier_name || po.Supplier || 'Unknown'; sups[supplier] = (sups[supplier] || 0) + (parseFloat(po.total_amount) || parseFloat(po.Amount) || 0); return sups; }, {}); const statusCounts = purchaseOrders.reduce((status, po) => { const stat = po.status || po.Status || 'Unknown'; status[stat] = (status[stat] || 0) + 1; return status; }, {}); return `# AI Analysis: Purchase Orders for ${vesselCode} ## Procurement Summary - **Total PO Value**: ${totalValue.toLocaleString()} - **Number of Purchase Orders**: ${purchaseOrders.length} - **Average PO Value**: ${(totalValue / purchaseOrders.length || 0).toLocaleString()} ## Top Suppliers by PO Value ${Object.entries(suppliers) .sort(([, a], [, b]) => b - a) .slice(0, 10) .map(([supplier, amount]) => `- **${supplier}**: ${amount.toLocaleString()}`) .join('\n')} ## Purchase Order Status ${Object.entries(statusCounts) .map(([status, count]) => `- **${status}**: ${count} POs`) .join('\n')} ## Procurement Insights - **Supplier Diversity**: ${Object.keys(suppliers).length} unique suppliers - **Average Order Size**: ${(totalValue / purchaseOrders.length || 0).toLocaleString()} - **Data Completeness**: ${purchaseOrders.filter(po => po.total_amount || po.Amount).length}/${purchaseOrders.length} POs have amounts ## Recommendations 1. **Supplier Management**: Monitor top suppliers for risk concentration 2. **Order Optimization**: Review order sizes for bulk discount opportunities 3. **Process Efficiency**: Track PO approval and fulfillment times *Advanced procurement analytics will be enhanced with MCP Sampling capabilities.*`; } catch (error) { logger.error("Error in analyzePurchaseOrders:", error); throw new Error(`Failed to generate purchase order analysis: ${error.message}`); } } /** * Generate comprehensive committed cost analysis */ async analyzeCommittedCosts(expenseData, poData, vesselCode) { try { logger.info(`Generating committed cost analysis for vessel ${vesselCode}`); // Calculate totals const totalExpenses = expenseData.reduce((sum, exp) => sum + (parseFloat(exp.amount) || 0), 0); const totalPOs = poData.reduce((sum, po) => sum + (parseFloat(po.total_amount) || parseFloat(po.Amount) || 0), 0); const totalCommitted = totalExpenses + totalPOs; // Category analysis const expenseCategories = expenseData.reduce((cats, exp) => { const cat = exp.category || exp.account_name || 'Uncategorized'; cats[cat] = (cats[cat] || 0) + (parseFloat(exp.amount) || 0); return cats; }, {}); return `# Executive Summary: Committed Cost Analysis for ${vesselCode} ## Financial Overview - **Total Committed Costs**: ${totalCommitted.toLocaleString()} - **Actual Expenses**: ${totalExpenses.toLocaleString()} (${Math.round((totalExpenses / totalCommitted) * 100)}%) - **Purchase Orders**: ${totalPOs.toLocaleString()} (${Math.round((totalPOs / totalCommitted) * 100)}%) - **Expense Records**: ${expenseData.length} transactions - **Purchase Orders**: ${poData.length} orders ## Cost Breakdown by Category ${Object.entries(expenseCategories) .sort(([, a], [, b]) => b - a) .slice(0, 10) .map(([cat, amount]) => `- **${cat}**: ${amount.toLocaleString()}`) .join('\n')} ## Key Financial Metrics - **Average Expense**: ${(totalExpenses / expenseData.length || 0).toLocaleString()} - **Average PO Value**: ${(totalPOs / poData.length || 0).toLocaleString()} - **Commitment Ratio**: ${Math.round((totalPOs / totalExpenses) * 100)}% (PO value vs actual expenses) ## Risk Assessment - **Budget Utilization**: Analysis requires budget baseline for comparison - **Cash Flow Impact**: ${totalCommitted.toLocaleString()} committed across ${expenseData.length + poData.length} transactions - **Data Quality**: ${Math.round(((expenseData.filter(e => e.amount).length + poData.filter(p => p.total_amount || p.Amount).length) / (expenseData.length + poData.length)) * 100)}% of records have valid amounts ## Strategic Recommendations 1. **Cost Control**: Monitor high-value categories for optimization opportunities 2. **Cash Flow**: Plan for ${Math.round(totalPOs / 1000)}K in outstanding PO commitments 3. **Budget Planning**: Use historical data for future budget allocations 4. **Supplier Management**: Consolidate procurement for better terms *This analysis provides foundational insights. Advanced AI-powered variance analysis and forecasting will be available with MCP Sampling.*`; } catch (error) { logger.error("Error in analyzeCommittedCosts:", error); throw new Error(`Failed to generate committed cost analysis: ${error.message}`); } } /** * Generate executive summary from actual committed cost report data */ async summarizeCommittedCostReports(summaryData, vesselCode) { try { logger.info(`Generating executive summary for committed cost reports: ${vesselCode}`); // Extract key metrics from the actual committed cost calculation const totalCCAmount = summaryData.total_cc_amount_usd || 0; const recordsCount = summaryData.records_count || 0; const totalQuantityDiff = summaryData.total_quantity_diff || 0; // Calculate averages const avgCCPerRecord = recordsCount > 0 ? totalCCAmount / recordsCount : 0; const avgQuantityPerRecord = recordsCount > 0 ? totalQuantityDiff / recordsCount : 0; return `# Executive Summary: Committed Cost Analysis for ${vesselCode} ## 📊 **Key Financial Metrics** - **Total Committed Cost Amount**: ${totalCCAmount.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })} - **Outstanding Commitment Records**: ${recordsCount.toLocaleString()} line items - **Total Outstanding Quantity**: ${totalQuantityDiff.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })} units ## 💡 **Analysis Insights** - **Average Commitment per Line**: ${avgCCPerRecord.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })} - **Average Outstanding Quantity per Line**: ${avgQuantityPerRecord.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })} units ## 🎯 **Business Impact** ${totalCCAmount > 0 ? ` - **Cash Flow Planning**: Prepare for ${(totalCCAmount / 1000000).toFixed(1)}M in outstanding commitments - **Budget Management**: ${recordsCount} purchase order lines require monitoring - **Risk Assessment**: ${totalCCAmount > 1000000 ? 'HIGH' : totalCCAmount > 500000 ? 'MEDIUM' : 'LOW'} financial exposure level ` : ` - **Status**: No outstanding committed costs identified - **Budget Impact**: All purchase orders appear to be fully invoiced or closed `} ## 📈 **Strategic Recommendations** ${totalCCAmount > 0 ? ` 1. **Cash Flow Management**: Plan for ${(totalCCAmount / 1000000).toFixed(1)}M in upcoming expenditures 2. **Supplier Coordination**: Monitor delivery schedules for ${recordsCount} outstanding line items 3. **Budget Tracking**: Regular review of commitment vs. actual spending patterns 4. **Risk Mitigation**: Identify and address any overdue or stalled purchase orders ` : ` 1. **Procurement Efficiency**: Current commitments are well-managed 2. **Budget Planning**: Use historical patterns for future procurement cycles 3. **Process Optimization**: Maintain current commitment tracking practices `} ## 📋 **Report Details** - **Analysis Date**: ${new Date().toLocaleDateString()} - **Calculation Method**: Complex multi-step analysis including: - Purchase order line items vs. invoiced quantities - Exchange rate conversions to USD - TCD (Total Cost Distribution) adjustments - Status filtering (excluding closed orders) *This summary is based on the comprehensive committed cost calculation that processes purchase orders, vessel expenses, EyeShare invoice data, and PO line details through sophisticated financial algorithms.* --- **📁 Generated Reports Available:** - Purchase Orders Report - Vessel Expenses Report - PO Lines Report - Committed Cost Report - Complete Analysis Report`; } catch (error) { logger.error("Error in summarizeCommittedCostReports:", error); throw new Error(`Failed to generate committed cost summary: ${error.message}`); } } /** * Detect anomalies and data quality issues in vessel data */ async detectAnomalies(data, dataType, vesselCode) { try { logger.info(`Analyzing ${dataType} data for anomalies for vessel ${vesselCode}`); const anomalies = []; // Data completeness checks const totalRecords = data.length; const recordsWithAmounts = data.filter(item => { const amount = parseFloat(item.amount || item.Amount || item.total_amount || '0'); return amount > 0; }).length; if (recordsWithAmounts < totalRecords * 0.9) { anomalies.push(`**Missing Amount Data**: ${totalRecords - recordsWithAmounts}/${totalRecords} records lack valid amounts`); } // Outlier detection (simple statistical approach) const amounts = data.map(item => parseFloat(item.amount || item.Amount || item.total_amount || '0')).filter(a => a > 0); if (amounts.length > 0) { amounts.sort((a, b) => a - b); const q1 = amounts[Math.floor(amounts.length * 0.25)]; const q3 = amounts[Math.floor(amounts.length * 0.75)]; const iqr = q3 - q1; const upperBound = q3 + (1.5 * iqr); const outliers = amounts.filter(a => a > upperBound); if (outliers.length > 0) { anomalies.push(`**High-Value Outliers**: ${outliers.length} transactions exceed ${upperBound.toLocaleString()} (1.5x IQR above Q3)`); } } // Duplicate detection const descriptions = data.map(item => item.description || item.Description || '').filter(d => d); const duplicateDescs = descriptions.filter((desc, index) => descriptions.indexOf(desc) !== index); if (duplicateDescs.length > 0) { anomalies.push(`**Potential Duplicates**: ${duplicateDescs.length} records with duplicate descriptions`); } // Date consistency checks const dates = data.map(item => item.date || item.Date || item.created_date).filter(d => d); const validDates = dates.filter(d => !isNaN(Date.parse(d))); if (validDates.length < dates.length) { anomalies.push(`**Invalid Dates**: ${dates.length - validDates.length} records with invalid date formats`); } return `# Anomaly Detection Report: ${dataType.charAt(0).toUpperCase() + dataType.slice(1)} for ${vesselCode} ## Data Quality Summary - **Total Records Analyzed**: ${totalRecords} - **Records with Valid Amounts**: ${recordsWithAmounts}/${totalRecords} (${Math.round((recordsWithAmounts / totalRecords) * 100)}%) - **Anomalies Detected**: ${anomalies.length} ## Detected Anomalies ${anomalies.length > 0 ? anomalies.map(anomaly => `- ${anomaly}`).join('\n') : '✅ No significant anomalies detected in this dataset'} ## Data Quality Metrics - **Amount Completeness**: ${Math.round((recordsWithAmounts / totalRecords) * 100)}% - **Date Validity**: ${Math.round((validDates.length / dates.length) * 100)}% (${validDates.length}/${dates.length}) - **Description Coverage**: ${Math.round((descriptions.length / totalRecords) * 100)}% ## Recommendations ${anomalies.length > 0 ? `1. **Data Cleanup**: Address missing or invalid data fields 2. **Process Review**: Investigate high-value outliers for legitimacy 3. **Duplicate Prevention**: Implement validation to prevent duplicate entries 4. **Regular Monitoring**: Set up automated anomaly detection alerts` : `1. **Maintain Quality**: Current data quality is good 2. **Regular Monitoring**: Continue periodic anomaly checks 3. **Process Documentation**: Document current data quality standards`} *Advanced pattern recognition and machine learning-based anomaly detection will be available with MCP Sampling capabilities.*`; } catch (error) { logger.error("Error in detectAnomalies:", error); throw new Error(`Failed to detect anomalies: ${error.message}`); } } } //# sourceMappingURL=sampling.js.map