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