Agents created: 1. PlantLineageAgent - Monitors plant ancestry and lineage integrity 2. TransportTrackerAgent - Tracks transport events and carbon footprint 3. DemandForecastAgent - Predicts consumer demand and market trends 4. VerticalFarmAgent - Manages vertical farm operations and optimization 5. EnvironmentAnalysisAgent - Analyzes growing conditions and recommendations 6. MarketMatchingAgent - Connects grower supply with consumer demand 7. SustainabilityAgent - Monitors environmental impact and sustainability 8. NetworkDiscoveryAgent - Maps geographic distribution and network analysis 9. QualityAssuranceAgent - Verifies blockchain integrity and data quality 10. GrowerAdvisoryAgent - Provides personalized growing recommendations Also includes: - BaseAgent abstract class for common functionality - AgentOrchestrator for centralized agent management - Comprehensive type definitions - Full documentation in docs/AGENTS.md
452 lines
15 KiB
TypeScript
452 lines
15 KiB
TypeScript
/**
|
|
* 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<TransportMethod, { count: number; distance: number; carbon: number }>;
|
|
eventTypeBreakdown: Record<TransportEventType, number>;
|
|
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<TransportMethod, number>;
|
|
dailyTrends: { date: string; events: number; carbon: number }[];
|
|
}
|
|
|
|
export class TransportTrackerAgent extends BaseAgent {
|
|
private userAnalytics: Map<string, TransportAnalysis> = 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<AgentTask | null> {
|
|
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<TransportMethod, number> = {} as any;
|
|
const dailyMap = new Map<string, { events: number; carbon: number }>();
|
|
|
|
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<string, TransportEvent[]>();
|
|
|
|
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<TransportMethod, { count: number; distance: number; carbon: number }> = {} as any;
|
|
const eventTypeBreakdown: Record<TransportEventType, number> = {} 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<TransportMethod, { count: number; distance: number; carbon: number }>,
|
|
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<string, TransportEvent[]>();
|
|
|
|
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;
|
|
}
|