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
JavaScript
/**
* 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