Merge: Transport Tracker Agent API endpoints

This commit is contained in:
Vinnie Esposito 2025-11-23 11:01:44 -06:00
commit 3e2c268399
4 changed files with 394 additions and 0 deletions

View file

@ -0,0 +1,80 @@
/**
* API Route: TransportTrackerAgent Analytics
* GET /api/agents/transport-tracker/analytics - Get network stats and user analytics
* GET /api/agents/transport-tracker/analytics?userId=xxx - Get specific user analytics
*/
import type { NextApiRequest, NextApiResponse } from 'next';
import { getTransportTrackerAgent } from '../../../../lib/agents/TransportTrackerAgent';
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
if (req.method !== 'GET') {
return res.status(405).json({ success: false, error: 'Method not allowed' });
}
try {
const agent = getTransportTrackerAgent();
const { userId } = req.query;
// If userId provided, return user-specific analytics
if (userId && typeof userId === 'string') {
const userAnalysis = agent.getUserAnalysis(userId);
if (!userAnalysis) {
return res.status(404).json({
success: false,
error: `No analytics found for user: ${userId}`
});
}
return res.status(200).json({
success: true,
data: {
user: userAnalysis,
recommendations: userAnalysis.recommendations,
efficiency: {
rating: userAnalysis.efficiency,
carbonPerKm: userAnalysis.carbonPerKm,
totalCarbonKg: userAnalysis.totalCarbonKg
}
}
});
}
// Otherwise, return network-wide analytics
const networkStats = agent.getNetworkStats();
if (!networkStats) {
return res.status(200).json({
success: true,
data: {
message: 'No network statistics available yet. Run the agent to collect data.',
networkStats: null
}
});
}
res.status(200).json({
success: true,
data: {
networkStats,
insights: {
avgCarbonPerEvent: networkStats.avgCarbonPerEvent,
avgDistancePerEvent: networkStats.avgDistancePerEvent,
greenTransportPercentage: networkStats.greenTransportPercentage,
topMethods: Object.entries(networkStats.methodDistribution)
.sort(([, a], [, b]) => (b as number) - (a as number))
.slice(0, 5)
.map(([method, count]) => ({ method, count }))
},
trends: networkStats.dailyTrends
}
});
} catch (error: any) {
console.error('Error fetching transport tracker analytics:', error);
res.status(500).json({ success: false, error: error.message || 'Internal server error' });
}
}

View file

@ -0,0 +1,115 @@
/**
* API Route: TransportTrackerAgent Management
* GET /api/agents/transport-tracker - Get agent status and metrics
* POST /api/agents/transport-tracker - Control agent (start/stop/run)
*/
import type { NextApiRequest, NextApiResponse } from 'next';
import { getTransportTrackerAgent } from '../../../../lib/agents/TransportTrackerAgent';
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
const agent = getTransportTrackerAgent();
if (req.method === 'GET') {
try {
const metrics = agent.getMetrics();
const alerts = agent.getAlerts();
const networkStats = agent.getNetworkStats();
const patterns = agent.getPatterns();
res.status(200).json({
success: true,
data: {
agent: {
id: agent.config.id,
name: agent.config.name,
description: agent.config.description,
status: agent.status,
priority: agent.config.priority,
intervalMs: agent.config.intervalMs,
enabled: agent.config.enabled
},
metrics,
alerts: alerts.filter(a => !a.acknowledged).slice(-10),
summary: {
networkStats: networkStats ? {
totalEvents: networkStats.totalEvents,
totalDistanceKm: networkStats.totalDistanceKm,
totalCarbonKg: networkStats.totalCarbonKg,
greenTransportPercentage: networkStats.greenTransportPercentage
} : null,
patternsDetected: patterns.length,
highSeverityPatterns: patterns.filter(p => p.severity === 'high').length
}
}
});
} catch (error: any) {
console.error('Error getting transport tracker agent status:', error);
res.status(500).json({ success: false, error: error.message || 'Internal server error' });
}
} else if (req.method === 'POST') {
try {
const { action } = req.body;
if (!action || typeof action !== 'string') {
return res.status(400).json({
success: false,
error: 'Action is required (start, stop, run)'
});
}
let result: any = {};
switch (action) {
case 'start':
await agent.start();
result = { message: 'Agent started', status: agent.status };
break;
case 'stop':
await agent.stop();
result = { message: 'Agent stopped', status: agent.status };
break;
case 'run':
// Run a single execution cycle
const taskResult = await agent.runOnce();
result = {
message: 'Agent cycle completed',
status: agent.status,
taskResult
};
break;
case 'pause':
agent.pause();
result = { message: 'Agent paused', status: agent.status };
break;
case 'resume':
agent.resume();
result = { message: 'Agent resumed', status: agent.status };
break;
default:
return res.status(400).json({
success: false,
error: `Unknown action: ${action}. Valid actions: start, stop, run, pause, resume`
});
}
res.status(200).json({
success: true,
data: result
});
} catch (error: any) {
console.error('Error controlling transport tracker agent:', error);
res.status(500).json({ success: false, error: error.message || 'Internal server error' });
}
} else {
res.status(405).json({ success: false, error: 'Method not allowed' });
}
}

View file

@ -0,0 +1,110 @@
/**
* API Route: TransportTrackerAgent Pattern Detection
* GET /api/agents/transport-tracker/patterns - Get detected inefficiency patterns
*/
import type { NextApiRequest, NextApiResponse } from 'next';
import { getTransportTrackerAgent } from '../../../../lib/agents/TransportTrackerAgent';
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
if (req.method !== 'GET') {
return res.status(405).json({ success: false, error: 'Method not allowed' });
}
try {
const agent = getTransportTrackerAgent();
const { severity, type } = req.query;
let patterns = agent.getPatterns();
// Filter by severity if provided
if (severity && typeof severity === 'string') {
const validSeverities = ['low', 'medium', 'high'];
if (!validSeverities.includes(severity)) {
return res.status(400).json({
success: false,
error: `Invalid severity. Valid values: ${validSeverities.join(', ')}`
});
}
patterns = patterns.filter(p => p.severity === severity);
}
// Filter by type if provided
if (type && typeof type === 'string') {
const validTypes = ['inefficient_route', 'high_carbon', 'excessive_handling', 'cold_chain_break'];
if (!validTypes.includes(type)) {
return res.status(400).json({
success: false,
error: `Invalid type. Valid values: ${validTypes.join(', ')}`
});
}
patterns = patterns.filter(p => p.type === type);
}
// Calculate summary statistics
const summary = {
total: patterns.length,
bySeverity: {
high: patterns.filter(p => p.severity === 'high').length,
medium: patterns.filter(p => p.severity === 'medium').length,
low: patterns.filter(p => p.severity === 'low').length
},
byType: {
inefficient_route: patterns.filter(p => p.type === 'inefficient_route').length,
high_carbon: patterns.filter(p => p.type === 'high_carbon').length,
excessive_handling: patterns.filter(p => p.type === 'excessive_handling').length,
cold_chain_break: patterns.filter(p => p.type === 'cold_chain_break').length
},
totalPotentialSavingsKg: patterns.reduce((sum, p) => sum + p.potentialSavingsKg, 0)
};
res.status(200).json({
success: true,
data: {
patterns,
summary,
recommendations: generatePatternRecommendations(patterns)
}
});
} catch (error: any) {
console.error('Error fetching transport patterns:', error);
res.status(500).json({ success: false, error: error.message || 'Internal server error' });
}
}
/**
* Generate actionable recommendations based on detected patterns
*/
function generatePatternRecommendations(patterns: any[]): string[] {
const recommendations: string[] = [];
const highCarbonCount = patterns.filter(p => p.type === 'high_carbon').length;
const excessiveHandlingCount = patterns.filter(p => p.type === 'excessive_handling').length;
const coldChainBreaks = patterns.filter(p => p.type === 'cold_chain_break').length;
if (highCarbonCount > 3) {
recommendations.push('Multiple high-carbon transport events detected. Consider implementing a fleet electrification program or partnering with eco-friendly logistics providers.');
}
if (excessiveHandlingCount > 2) {
recommendations.push('Products are being handled too many times. Review supply chain to consolidate shipments and reduce touchpoints.');
}
if (coldChainBreaks > 0) {
recommendations.push('Cold chain integrity issues detected. Implement temperature monitoring IoT devices and establish clear handoff protocols.');
}
const totalSavings = patterns.reduce((sum, p) => sum + p.potentialSavingsKg, 0);
if (totalSavings > 100) {
recommendations.push(`Addressing detected patterns could save approximately ${totalSavings.toFixed(1)} kg of CO2 emissions.`);
}
if (recommendations.length === 0) {
recommendations.push('Transport patterns look healthy. Continue monitoring for optimization opportunities.');
}
return recommendations;
}

View file

@ -0,0 +1,89 @@
/**
* API Route: TransportTrackerAgent Savings Calculator
* GET /api/agents/transport-tracker/savings - Calculate carbon savings vs conventional logistics
*/
import type { NextApiRequest, NextApiResponse } from 'next';
import { getTransportTrackerAgent } from '../../../../lib/agents/TransportTrackerAgent';
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
if (req.method !== 'GET') {
return res.status(405).json({ success: false, error: 'Method not allowed' });
}
try {
const agent = getTransportTrackerAgent();
const savings = agent.calculateSavingsVsConventional();
const networkStats = agent.getNetworkStats();
if (!networkStats || networkStats.totalEvents === 0) {
return res.status(200).json({
success: true,
data: {
message: 'No transport data available for savings calculation.',
savings: null
}
});
}
// Calculate additional context metrics
const context = {
// Equivalent miles driven by average car (0.404 kg CO2 per mile)
equivalentCarMiles: Math.round(savings.savedKg / 0.404),
// Equivalent trees needed to absorb this CO2 (21 kg per tree per year)
equivalentTreeYears: Math.round((savings.savedKg / 21) * 10) / 10,
// Equivalent gallons of gasoline (8.887 kg CO2 per gallon)
equivalentGallonsSaved: Math.round((savings.savedKg / 8.887) * 10) / 10,
// Equivalent smartphone charges (0.0085 kg CO2 per charge)
equivalentPhoneCharges: Math.round(savings.savedKg / 0.0085)
};
// Generate insights based on performance
const insights: string[] = [];
if (savings.savedPercentage >= 90) {
insights.push('Exceptional performance! Your local supply chain is operating at near-optimal carbon efficiency.');
} else if (savings.savedPercentage >= 70) {
insights.push('Great job! Your local sourcing strategy is significantly reducing carbon emissions.');
} else if (savings.savedPercentage >= 50) {
insights.push('Good progress. There are still opportunities to further reduce your carbon footprint.');
} else if (savings.savedPercentage > 0) {
insights.push('Your local network is making a positive impact. Consider expanding local sourcing to improve further.');
}
if (networkStats.greenTransportPercentage >= 50) {
insights.push(`${networkStats.greenTransportPercentage}% of your transport uses green methods - excellent sustainability focus!`);
}
res.status(200).json({
success: true,
data: {
savings: {
localGreenCarbonKg: savings.localGreenCarbon,
conventionalCarbonKg: savings.conventionalCarbon,
savedKg: savings.savedKg,
savedPercentage: savings.savedPercentage
},
context,
insights,
methodology: {
description: 'Savings calculated by comparing LocalGreenChain transport to conventional supply chain assumptions.',
assumptions: [
'Conventional: Average 1,500 miles transport per item at 0.2 kg CO2/mile',
'LocalGreenChain: Actual tracked distances and transport methods',
'Green methods include: walking, bicycle, electric vehicles, rail'
]
}
}
});
} catch (error: any) {
console.error('Error calculating transport savings:', error);
res.status(500).json({ success: false, error: error.message || 'Internal server error' });
}
}