UNPKG

mcp-product-manager

Version:

MCP Orchestrator for task and project management with web interface

170 lines 6.67 kB
// Historical trends endpoint - provides usage patterns and analytics over time import { Router } from 'express'; import { success, error } from '../../utils/response.js'; const router = Router(); // GET /api/usage/trends - Get historical usage trends router.get('/', async (req, res, next) => { try { const usageService = req.app.locals.usageService; const { days = 7 } = req.query; if (!usageService || !usageService.isInitialized) { return error(res, 'Usage tracking service not initialized', 503); } // Validate days parameter const daysNum = parseInt(days); if (isNaN(daysNum) || daysNum < 1 || daysNum > 30) { return error(res, 'Days parameter must be between 1 and 30', 400); } const trends = await usageService.getHistoricalTrends(daysNum); if (trends.error) { return error(res, trends.error, 500); } // Process daily data for charting const dailyChart = trends.daily.map(day => ({ date: day.date, totalCost: day.totalCost, totalTokens: day.totalTokens, models: (day.modelBreakdowns || []).map(m => ({ name: m.modelName, cost: m.cost, tokens: m.totalTokens })) })); // Process block data for detailed analysis const blockAnalysis = trends.blocks.map(block => ({ id: block.blockId, startTime: block.startTime, endTime: block.endTime, duration: block.elapsedMinutes, cost: block.costUSD, tokens: block.totalTokens, burnRate: block.burnRate?.costPerHour || 0 })); const response = { period: { days: daysNum, startDate: trends.daily[0]?.date, endDate: trends.daily[trends.daily.length - 1]?.date }, summary: { totalCost: trends.patterns.totalCost, averageDailyCost: trends.patterns.averageDailyCost, opusCost: trends.patterns.opusCost, sonnetCost: trends.patterns.sonnetCost, opusPercentage: trends.patterns.opusPercentage, trend: trends.patterns.trend }, daily: dailyChart, blocks: blockAnalysis, insights: generateInsights(trends.patterns, trends.daily), timestamp: trends.timestamp }; success(res, response); } catch (err) { console.error('Error in /api/usage/trends:', err); next(err); } }); // GET /api/usage/trends/patterns - Get usage patterns and anomalies router.get('/patterns', async (req, res, next) => { try { const usageService = req.app.locals.usageService; const trends = await usageService.getHistoricalTrends(7); if (trends.error) { return error(res, trends.error, 500); } // Identify patterns const patterns = { highCostDays: [], modelShifts: [], burnRateSpikes: [] }; // Find high cost days (>1.5x average) const avgCost = trends.patterns.averageDailyCost; trends.daily.forEach(day => { if (day.totalCost > avgCost * 1.5) { patterns.highCostDays.push({ date: day.date, cost: day.totalCost, percentAboveAverage: ((day.totalCost - avgCost) / avgCost) * 100 }); } }); // Detect model usage shifts trends.daily.forEach((day, index) => { if (index > 0 && day.modelBreakdowns) { const prevDay = trends.daily[index - 1]; const opusToday = day.modelBreakdowns.find(m => m.modelName?.includes('opus'))?.cost || 0; const opusYesterday = prevDay.modelBreakdowns?.find(m => m.modelName?.includes('opus'))?.cost || 0; if (opusToday > opusYesterday * 2 && opusToday > 50) { patterns.modelShifts.push({ date: day.date, type: 'opus_increase', previousCost: opusYesterday, currentCost: opusToday }); } } }); // Find burn rate spikes in blocks const avgBurnRate = trends.blocks.reduce((sum, b) => sum + (b.burnRate?.costPerHour || 0), 0) / trends.blocks.length; trends.blocks.forEach(block => { const burnRate = block.burnRate?.costPerHour || 0; if (burnRate > avgBurnRate * 1.5 && burnRate > 100) { patterns.burnRateSpikes.push({ blockId: block.blockId, time: block.startTime, burnRate: burnRate, percentAboveAverage: ((burnRate - avgBurnRate) / avgBurnRate) * 100 }); } }); success(res, patterns); } catch (err) { console.error('Error in /api/usage/trends/patterns:', err); next(err); } }); function generateInsights(patterns, dailyData) { const insights = []; // Trend insight if (patterns.trend === 'increasing') { insights.push({ type: 'trend', severity: 'warning', message: 'Usage costs are trending upward', recommendation: 'Review agent activity and model selection' }); } else if (patterns.trend === 'decreasing') { insights.push({ type: 'trend', severity: 'info', message: 'Usage costs are trending downward', recommendation: 'Good cost management - maintain current practices' }); } // Model usage insight if (patterns.opusPercentage > 70) { insights.push({ type: 'model_usage', severity: 'warning', message: `Opus accounts for ${patterns.opusPercentage.toFixed(0)}% of costs`, recommendation: 'Consider using Sonnet for routine tasks to reduce costs by up to 80%' }); } // Daily cost insight if (patterns.averageDailyCost > 400) { insights.push({ type: 'budget', severity: 'critical', message: `Average daily cost ($${patterns.averageDailyCost.toFixed(2)}) approaching budget limit`, recommendation: 'Implement stricter agent limits or pause non-critical work' }); } return insights; } export default router; //# sourceMappingURL=trends.js.map