mcp-product-manager
Version:
MCP Orchestrator for task and project management with web interface
170 lines • 6.67 kB
JavaScript
// 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