Merge: Transport Tracker Agent API endpoints
This commit is contained in:
commit
3e2c268399
4 changed files with 394 additions and 0 deletions
80
pages/api/agents/transport-tracker/analytics.ts
Normal file
80
pages/api/agents/transport-tracker/analytics.ts
Normal 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' });
|
||||||
|
}
|
||||||
|
}
|
||||||
115
pages/api/agents/transport-tracker/index.ts
Normal file
115
pages/api/agents/transport-tracker/index.ts
Normal 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' });
|
||||||
|
}
|
||||||
|
}
|
||||||
110
pages/api/agents/transport-tracker/patterns.ts
Normal file
110
pages/api/agents/transport-tracker/patterns.ts
Normal 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;
|
||||||
|
}
|
||||||
89
pages/api/agents/transport-tracker/savings.ts
Normal file
89
pages/api/agents/transport-tracker/savings.ts
Normal 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' });
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue