/** * TransportTrackerAgent * Monitors transport events and calculates environmental impact * * Responsibilities: * - Track seed-to-seed transport lifecycle * - Calculate and aggregate carbon footprint * - Monitor food miles across the network * - Detect inefficient transport patterns * - Generate transport optimization recommendations */ import { BaseAgent } from './BaseAgent'; import { AgentConfig, AgentTask } from './types'; import { getTransportChain } from '../transport/tracker'; import { TransportEvent, TransportMethod, TransportEventType, CARBON_FACTORS } from '../transport/types'; interface TransportAnalysis { userId: string; totalEvents: number; totalDistanceKm: number; totalCarbonKg: number; carbonPerKm: number; mostUsedMethod: TransportMethod; methodBreakdown: Record; eventTypeBreakdown: Record; efficiency: 'excellent' | 'good' | 'average' | 'poor'; recommendations: string[]; } interface TransportPattern { type: 'inefficient_route' | 'high_carbon' | 'excessive_handling' | 'cold_chain_break'; description: string; affectedEvents: string[]; potentialSavingsKg: number; severity: 'low' | 'medium' | 'high'; } interface NetworkTransportStats { totalEvents: number; totalDistanceKm: number; totalCarbonKg: number; avgCarbonPerEvent: number; avgDistancePerEvent: number; greenTransportPercentage: number; methodDistribution: Record; dailyTrends: { date: string; events: number; carbon: number }[]; } export class TransportTrackerAgent extends BaseAgent { private userAnalytics: Map = new Map(); private detectedPatterns: TransportPattern[] = []; private networkStats: NetworkTransportStats | null = null; constructor() { const config: AgentConfig = { id: 'transport-tracker-agent', name: 'Transport Tracker Agent', description: 'Monitors transport events and environmental impact', enabled: true, intervalMs: 120000, // Run every 2 minutes priority: 'high', maxRetries: 3, timeoutMs: 30000 }; super(config); } /** * Main execution cycle */ async runOnce(): Promise { const transportChain = getTransportChain(); const chain = transportChain.chain; // Skip genesis block const events = chain.slice(1).map(b => b.transportEvent); // Update network statistics this.networkStats = this.calculateNetworkStats(events); // Analyze by user this.analyzeUserPatterns(events); // Detect inefficient patterns const newPatterns = this.detectInefficiencies(events); this.detectedPatterns = [...this.detectedPatterns, ...newPatterns].slice(-100); // Generate alerts for critical patterns for (const pattern of newPatterns) { if (pattern.severity === 'high') { this.createAlert('warning', `Transport Issue: ${pattern.type}`, pattern.description, { actionRequired: `Potential savings: ${pattern.potentialSavingsKg.toFixed(2)} kg CO2` } ); } } // Check for milestone achievements this.checkMilestones(); return this.createTaskResult('transport_analysis', 'completed', { eventsAnalyzed: events.length, usersTracked: this.userAnalytics.size, patternsDetected: newPatterns.length, networkCarbonKg: this.networkStats?.totalCarbonKg || 0 }); } /** * Calculate network-wide statistics */ private calculateNetworkStats(events: TransportEvent[]): NetworkTransportStats { const methodCounts: Record = {} as any; const dailyMap = new Map(); let totalDistance = 0; let totalCarbon = 0; let greenEvents = 0; const greenMethods: TransportMethod[] = ['walking', 'bicycle', 'electric_vehicle', 'electric_truck', 'rail']; for (const event of events) { totalDistance += event.distanceKm; totalCarbon += event.carbonFootprintKg; // Method distribution methodCounts[event.transportMethod] = (methodCounts[event.transportMethod] || 0) + 1; // Green transport tracking if (greenMethods.includes(event.transportMethod)) { greenEvents++; } // Daily trends const date = event.timestamp.split('T')[0]; const daily = dailyMap.get(date) || { events: 0, carbon: 0 }; daily.events++; daily.carbon += event.carbonFootprintKg; dailyMap.set(date, daily); } const dailyTrends = Array.from(dailyMap.entries()) .map(([date, data]) => ({ date, ...data })) .sort((a, b) => a.date.localeCompare(b.date)) .slice(-30); // Last 30 days return { totalEvents: events.length, totalDistanceKm: Math.round(totalDistance * 100) / 100, totalCarbonKg: Math.round(totalCarbon * 100) / 100, avgCarbonPerEvent: events.length > 0 ? Math.round(totalCarbon / events.length * 100) / 100 : 0, avgDistancePerEvent: events.length > 0 ? Math.round(totalDistance / events.length * 100) / 100 : 0, greenTransportPercentage: events.length > 0 ? Math.round(greenEvents / events.length * 100) : 0, methodDistribution: methodCounts, dailyTrends }; } /** * Analyze patterns by user */ private analyzeUserPatterns(events: TransportEvent[]): void { const userEvents = new Map(); for (const event of events) { // Group by sender and receiver const senderEvents = userEvents.get(event.senderId) || []; senderEvents.push(event); userEvents.set(event.senderId, senderEvents); if (event.senderId !== event.receiverId) { const receiverEvents = userEvents.get(event.receiverId) || []; receiverEvents.push(event); userEvents.set(event.receiverId, receiverEvents); } } for (const [userId, userEventList] of userEvents) { this.userAnalytics.set(userId, this.analyzeUser(userId, userEventList)); } } /** * Analyze a single user's transport patterns */ private analyzeUser(userId: string, events: TransportEvent[]): TransportAnalysis { const methodBreakdown: Record = {} as any; const eventTypeBreakdown: Record = {} as any; let totalDistance = 0; let totalCarbon = 0; for (const event of events) { totalDistance += event.distanceKm; totalCarbon += event.carbonFootprintKg; // Method breakdown if (!methodBreakdown[event.transportMethod]) { methodBreakdown[event.transportMethod] = { count: 0, distance: 0, carbon: 0 }; } methodBreakdown[event.transportMethod].count++; methodBreakdown[event.transportMethod].distance += event.distanceKm; methodBreakdown[event.transportMethod].carbon += event.carbonFootprintKg; // Event type breakdown eventTypeBreakdown[event.eventType] = (eventTypeBreakdown[event.eventType] || 0) + 1; } // Find most used method let mostUsedMethod: TransportMethod = 'walking'; let maxCount = 0; for (const [method, data] of Object.entries(methodBreakdown)) { if (data.count > maxCount) { maxCount = data.count; mostUsedMethod = method as TransportMethod; } } // Calculate efficiency rating const carbonPerKm = totalDistance > 0 ? totalCarbon / totalDistance : 0; let efficiency: TransportAnalysis['efficiency']; if (carbonPerKm < 0.01) efficiency = 'excellent'; else if (carbonPerKm < 0.05) efficiency = 'good'; else if (carbonPerKm < 0.1) efficiency = 'average'; else efficiency = 'poor'; // Generate recommendations const recommendations = this.generateRecommendations(methodBreakdown, carbonPerKm, events); return { userId, totalEvents: events.length, totalDistanceKm: Math.round(totalDistance * 100) / 100, totalCarbonKg: Math.round(totalCarbon * 100) / 100, carbonPerKm: Math.round(carbonPerKm * 1000) / 1000, mostUsedMethod, methodBreakdown, eventTypeBreakdown, efficiency, recommendations }; } /** * Generate personalized recommendations */ private generateRecommendations( methodBreakdown: Record, carbonPerKm: number, events: TransportEvent[] ): string[] { const recommendations: string[] = []; // High-carbon transport suggestions if (methodBreakdown['gasoline_vehicle']?.count > 5) { recommendations.push('Consider switching to electric vehicle for short-distance transport'); } if (methodBreakdown['air_freight']?.count > 0) { recommendations.push('Air freight has 50x the carbon footprint of rail - consider alternatives'); } if (methodBreakdown['refrigerated_truck']?.count > 3) { recommendations.push('Batch refrigerated shipments together to reduce trips'); } // Distance-based suggestions const avgDistance = events.length > 0 ? events.reduce((sum, e) => sum + e.distanceKm, 0) / events.length : 0; if (avgDistance < 5 && !methodBreakdown['bicycle'] && !methodBreakdown['walking']) { recommendations.push('Short distances detected - bicycle or walking would eliminate carbon impact'); } // General efficiency if (carbonPerKm > 0.1) { recommendations.push('Overall carbon efficiency is low - prioritize green transport methods'); } return recommendations.slice(0, 5); // Max 5 recommendations } /** * Detect inefficient transport patterns */ private detectInefficiencies(events: TransportEvent[]): TransportPattern[] { const patterns: TransportPattern[] = []; // Group events by plant/batch for journey analysis const journeys = new Map(); for (const event of events) { // Use a simplified grouping const key = event.id.split('-')[0]; const journey = journeys.get(key) || []; journey.push(event); journeys.set(key, journey); } // Check for excessive handling for (const [key, journey] of journeys) { if (journey.length > 5) { patterns.push({ type: 'excessive_handling', description: `${journey.length} transport events detected - consider streamlining logistics`, affectedEvents: journey.map(e => e.id), potentialSavingsKg: journey.reduce((sum, e) => sum + e.carbonFootprintKg * 0.2, 0), severity: journey.length > 10 ? 'high' : 'medium' }); } } // Check for high-carbon single events const highCarbonEvents = events.filter(e => e.carbonFootprintKg > 10); for (const event of highCarbonEvents) { const alternativeCarbon = CARBON_FACTORS['rail'] * event.distanceKm * 5; // Estimate 5kg cargo const savings = event.carbonFootprintKg - alternativeCarbon; if (savings > 5) { patterns.push({ type: 'high_carbon', description: `High carbon event using ${event.transportMethod} - alternative transport could save ${savings.toFixed(1)}kg CO2`, affectedEvents: [event.id], potentialSavingsKg: savings, severity: savings > 20 ? 'high' : 'medium' }); } } // Check for cold chain breaks (temperature-sensitive transport) const coldChainEvents = events.filter(e => e.transportMethod === 'refrigerated_truck' || (e as any).temperatureControlled === true ); // Simple check: refrigerated followed by non-refrigerated for (let i = 0; i < coldChainEvents.length - 1; i++) { const current = coldChainEvents[i]; const next = events.find(e => new Date(e.timestamp) > new Date(current.timestamp) && e.transportMethod !== 'refrigerated_truck' ); if (next) { patterns.push({ type: 'cold_chain_break', description: 'Potential cold chain break detected - verify temperature maintenance', affectedEvents: [current.id, next.id], potentialSavingsKg: 0, // Savings in spoilage, not carbon severity: 'medium' }); break; // Only report once } } return patterns; } /** * Check for achievement milestones */ private checkMilestones(): void { if (!this.networkStats) return; // Carbon milestones const carbonMilestones = [100, 500, 1000, 5000, 10000]; for (const milestone of carbonMilestones) { if (this.networkStats.totalCarbonKg > milestone * 0.95 && this.networkStats.totalCarbonKg < milestone * 1.05) { this.createAlert('info', `Approaching ${milestone}kg CO2 Network Total`, `Network has recorded ${this.networkStats.totalCarbonKg.toFixed(1)}kg total carbon footprint`, { relatedEntityType: 'network' } ); } } // Green transport milestones if (this.networkStats.greenTransportPercentage >= 75 && this.networkStats.totalEvents > 100) { this.createAlert('info', 'Green Transport Achievement', `${this.networkStats.greenTransportPercentage}% of transport uses green methods!`, { relatedEntityType: 'network' } ); } } /** * Get user analysis */ getUserAnalysis(userId: string): TransportAnalysis | null { return this.userAnalytics.get(userId) || null; } /** * Get network statistics */ getNetworkStats(): NetworkTransportStats | null { return this.networkStats; } /** * Get detected patterns */ getPatterns(): TransportPattern[] { return this.detectedPatterns; } /** * Calculate carbon savings vs conventional */ calculateSavingsVsConventional(): { localGreenCarbon: number; conventionalCarbon: number; savedKg: number; savedPercentage: number; } { if (!this.networkStats) { return { localGreenCarbon: 0, conventionalCarbon: 0, savedKg: 0, savedPercentage: 0 }; } // Conventional: assume 1500 miles avg, 0.2 kg CO2 per mile const conventionalCarbon = this.networkStats.totalEvents * 1500 * 1.6 * 0.2; const savedKg = Math.max(0, conventionalCarbon - this.networkStats.totalCarbonKg); const savedPercentage = conventionalCarbon > 0 ? Math.round((1 - this.networkStats.totalCarbonKg / conventionalCarbon) * 100) : 0; return { localGreenCarbon: this.networkStats.totalCarbonKg, conventionalCarbon: Math.round(conventionalCarbon * 100) / 100, savedKg: Math.round(savedKg * 100) / 100, savedPercentage }; } } // Singleton instance let transportAgentInstance: TransportTrackerAgent | null = null; export function getTransportTrackerAgent(): TransportTrackerAgent { if (!transportAgentInstance) { transportAgentInstance = new TransportTrackerAgent(); } return transportAgentInstance; }