/** * SustainabilityAgent * Monitors and reports on environmental impact across the network * * Responsibilities: * - Calculate network-wide carbon footprint * - Track food miles reduction vs conventional * - Monitor water usage in vertical farms * - Generate sustainability reports * - Identify improvement opportunities */ import { BaseAgent } from './BaseAgent'; import { AgentConfig, AgentTask, SustainabilityReport } from './types'; import { getTransportChain } from '../transport/tracker'; import { getBlockchain } from '../blockchain/manager'; interface CarbonMetrics { totalEmissionsKg: number; emissionsPerKgProduce: number; savedVsConventionalKg: number; percentageReduction: number; byTransportMethod: Record; byEventType: Record; trend: 'improving' | 'stable' | 'worsening'; } interface FoodMilesMetrics { totalMiles: number; avgMilesPerDelivery: number; localDeliveryPercent: number; // < 50km regionalDeliveryPercent: number; // 50-200km longDistancePercent: number; // > 200km conventionalComparison: number; // avg miles saved } interface WaterMetrics { totalUsageLiters: number; verticalFarmUsage: number; traditionalUsage: number; savedLiters: number; recyclingRate: number; efficiencyScore: number; } interface WasteMetrics { totalWasteKg: number; spoilageKg: number; compostedKg: number; wasteReductionPercent: number; spoilageRate: number; } interface SustainabilityScore { overall: number; carbon: number; water: number; waste: number; localFood: number; biodiversity: number; } interface ImprovementOpportunity { id: string; category: 'carbon' | 'water' | 'waste' | 'transport' | 'energy'; title: string; description: string; potentialImpact: string; difficulty: 'easy' | 'moderate' | 'challenging'; estimatedSavings: { value: number; unit: string }; priority: 'low' | 'medium' | 'high'; } export class SustainabilityAgent extends BaseAgent { private carbonMetrics: CarbonMetrics | null = null; private foodMilesMetrics: FoodMilesMetrics | null = null; private waterMetrics: WaterMetrics | null = null; private wasteMetrics: WasteMetrics | null = null; private sustainabilityScore: SustainabilityScore | null = null; private opportunities: ImprovementOpportunity[] = []; private historicalScores: { date: string; score: number }[] = []; constructor() { const config: AgentConfig = { id: 'sustainability-agent', name: 'Sustainability Agent', description: 'Monitors environmental impact and sustainability metrics', enabled: true, intervalMs: 300000, // Run every 5 minutes priority: 'medium', maxRetries: 3, timeoutMs: 60000 }; super(config); } /** * Main execution cycle */ async runOnce(): Promise { // Calculate all metrics this.carbonMetrics = this.calculateCarbonMetrics(); this.foodMilesMetrics = this.calculateFoodMilesMetrics(); this.waterMetrics = this.calculateWaterMetrics(); this.wasteMetrics = this.calculateWasteMetrics(); // Calculate overall sustainability score this.sustainabilityScore = this.calculateSustainabilityScore(); // Track historical scores this.historicalScores.push({ date: new Date().toISOString(), score: this.sustainabilityScore.overall }); if (this.historicalScores.length > 365) { this.historicalScores = this.historicalScores.slice(-365); } // Identify improvement opportunities this.opportunities = this.identifyOpportunities(); // Generate alerts for concerning metrics this.checkMetricAlerts(); // Generate milestone alerts this.checkMilestones(); return this.createTaskResult('sustainability_analysis', 'completed', { overallScore: this.sustainabilityScore.overall, carbonSavedKg: this.carbonMetrics.savedVsConventionalKg, foodMilesSaved: this.foodMilesMetrics?.conventionalComparison || 0, opportunitiesIdentified: this.opportunities.length }); } /** * Calculate carbon metrics */ private calculateCarbonMetrics(): CarbonMetrics { const transportChain = getTransportChain(); const events = transportChain.chain.slice(1).map(b => b.transportEvent); let totalEmissions = 0; let totalWeightKg = 0; const byMethod: Record = {}; const byEventType: Record = {}; for (const event of events) { totalEmissions += event.carbonFootprintKg; totalWeightKg += 5; // Estimate avg weight byMethod[event.transportMethod] = (byMethod[event.transportMethod] || 0) + event.carbonFootprintKg; byEventType[event.eventType] = (byEventType[event.eventType] || 0) + event.carbonFootprintKg; } // Conventional comparison: 2.5 kg CO2 per kg produce avg const conventionalEmissions = totalWeightKg * 2.5; const saved = Math.max(0, conventionalEmissions - totalEmissions); // Determine trend const recentScores = this.historicalScores.slice(-10); let trend: 'improving' | 'stable' | 'worsening' = 'stable'; if (recentScores.length >= 5) { const firstHalf = recentScores.slice(0, 5).reduce((s, e) => s + e.score, 0) / 5; const secondHalf = recentScores.slice(-5).reduce((s, e) => s + e.score, 0) / 5; if (secondHalf > firstHalf + 2) trend = 'improving'; else if (secondHalf < firstHalf - 2) trend = 'worsening'; } return { totalEmissionsKg: Math.round(totalEmissions * 100) / 100, emissionsPerKgProduce: totalWeightKg > 0 ? Math.round((totalEmissions / totalWeightKg) * 1000) / 1000 : 0, savedVsConventionalKg: Math.round(saved * 100) / 100, percentageReduction: conventionalEmissions > 0 ? Math.round((1 - totalEmissions / conventionalEmissions) * 100) : 0, byTransportMethod: byMethod, byEventType, trend }; } /** * Calculate food miles metrics */ private calculateFoodMilesMetrics(): FoodMilesMetrics { const transportChain = getTransportChain(); const events = transportChain.chain.slice(1).map(b => b.transportEvent); let totalMiles = 0; let localCount = 0; let regionalCount = 0; let longDistanceCount = 0; for (const event of events) { const km = event.distanceKm; totalMiles += km; if (km < 50) localCount++; else if (km < 200) regionalCount++; else longDistanceCount++; } const totalDeliveries = events.length; // Conventional avg: 1500 miles per item const conventionalMiles = totalDeliveries * 1500; return { totalMiles: Math.round(totalMiles), avgMilesPerDelivery: totalDeliveries > 0 ? Math.round(totalMiles / totalDeliveries) : 0, localDeliveryPercent: totalDeliveries > 0 ? Math.round((localCount / totalDeliveries) * 100) : 0, regionalDeliveryPercent: totalDeliveries > 0 ? Math.round((regionalCount / totalDeliveries) * 100) : 0, longDistancePercent: totalDeliveries > 0 ? Math.round((longDistanceCount / totalDeliveries) * 100) : 0, conventionalComparison: Math.round(conventionalMiles - totalMiles) }; } /** * Calculate water metrics (simulated for demo) */ private calculateWaterMetrics(): WaterMetrics { const blockchain = getBlockchain(); const plantCount = blockchain.getChain().length - 1; // Simulate water usage based on plant count // Vertical farms use ~10% of traditional water const traditionalUsagePerPlant = 500; // liters const verticalFarmUsagePerPlant = 50; // liters const verticalFarmRatio = 0.3; // 30% in vertical farms const verticalFarmPlants = Math.floor(plantCount * verticalFarmRatio); const traditionalPlants = plantCount - verticalFarmPlants; const verticalUsage = verticalFarmPlants * verticalFarmUsagePerPlant; const traditionalUsage = traditionalPlants * traditionalUsagePerPlant; const totalUsage = verticalUsage + traditionalUsage; const conventionalUsage = plantCount * traditionalUsagePerPlant; const saved = conventionalUsage - totalUsage; return { totalUsageLiters: totalUsage, verticalFarmUsage: verticalUsage, traditionalUsage: traditionalUsage, savedLiters: Math.max(0, saved), recyclingRate: 85, // 85% water recycling in vertical farms efficiencyScore: Math.round((saved / conventionalUsage) * 100) }; } /** * Calculate waste metrics (simulated for demo) */ private calculateWasteMetrics(): WasteMetrics { const blockchain = getBlockchain(); const plants = blockchain.getChain().slice(1); const deceasedPlants = plants.filter(p => p.plant.status === 'deceased').length; const totalPlants = plants.length; // Conventional spoilage: 30-40% const conventionalSpoilageRate = 0.35; const localSpoilageRate = totalPlants > 0 ? deceasedPlants / totalPlants : 0; const totalProduceKg = totalPlants * 2; // Estimate 2kg per plant const spoilageKg = totalProduceKg * localSpoilageRate; const compostedKg = spoilageKg * 0.8; // 80% composted return { totalWasteKg: Math.round(spoilageKg * 10) / 10, spoilageKg: Math.round(spoilageKg * 10) / 10, compostedKg: Math.round(compostedKg * 10) / 10, wasteReductionPercent: Math.round((conventionalSpoilageRate - localSpoilageRate) / conventionalSpoilageRate * 100), spoilageRate: Math.round(localSpoilageRate * 100) }; } /** * Calculate overall sustainability score */ private calculateSustainabilityScore(): SustainabilityScore { const carbon = this.carbonMetrics ? Math.min(100, Math.max(0, this.carbonMetrics.percentageReduction + 20)) : 50; const water = this.waterMetrics ? Math.min(100, this.waterMetrics.efficiencyScore + 30) : 50; const waste = this.wasteMetrics ? Math.min(100, 100 - this.wasteMetrics.spoilageRate * 2) : 50; const localFood = this.foodMilesMetrics ? Math.min(100, this.foodMilesMetrics.localDeliveryPercent + this.foodMilesMetrics.regionalDeliveryPercent) : 50; // Biodiversity: based on plant variety const blockchain = getBlockchain(); const plants = blockchain.getChain().slice(1); const uniqueSpecies = new Set(plants.map(p => p.plant.commonName)).size; const biodiversity = Math.min(100, 30 + uniqueSpecies * 5); const overall = Math.round( (carbon * 0.25 + water * 0.2 + waste * 0.15 + localFood * 0.25 + biodiversity * 0.15) ); return { overall, carbon: Math.round(carbon), water: Math.round(water), waste: Math.round(waste), localFood: Math.round(localFood), biodiversity: Math.round(biodiversity) }; } /** * Identify improvement opportunities */ private identifyOpportunities(): ImprovementOpportunity[] { const opportunities: ImprovementOpportunity[] = []; // Carbon opportunities if (this.carbonMetrics) { if (this.carbonMetrics.byTransportMethod['gasoline_vehicle'] > 10) { opportunities.push({ id: `opp-${Date.now()}-ev`, category: 'transport', title: 'Switch to Electric Vehicles', description: 'Replace gasoline vehicles with EVs for local deliveries', potentialImpact: 'Reduce transport emissions by 60-80%', difficulty: 'moderate', estimatedSavings: { value: this.carbonMetrics.byTransportMethod['gasoline_vehicle'] * 0.7, unit: 'kg CO2' }, priority: 'high' }); } if (this.carbonMetrics.byTransportMethod['air_freight'] > 0) { opportunities.push({ id: `opp-${Date.now()}-air`, category: 'transport', title: 'Eliminate Air Freight', description: 'Replace air freight with rail or local sourcing', potentialImpact: 'Eliminate highest-carbon transport method', difficulty: 'challenging', estimatedSavings: { value: this.carbonMetrics.byTransportMethod['air_freight'], unit: 'kg CO2' }, priority: 'high' }); } } // Food miles opportunities if (this.foodMilesMetrics && this.foodMilesMetrics.longDistancePercent > 10) { opportunities.push({ id: `opp-${Date.now()}-local`, category: 'transport', title: 'Increase Local Sourcing', description: 'Partner with more local growers to reduce food miles', potentialImpact: `Reduce ${this.foodMilesMetrics.longDistancePercent}% long-distance deliveries`, difficulty: 'moderate', estimatedSavings: { value: Math.round(this.foodMilesMetrics.totalMiles * 0.3), unit: 'miles' }, priority: 'medium' }); } // Water opportunities if (this.waterMetrics && this.waterMetrics.recyclingRate < 90) { opportunities.push({ id: `opp-${Date.now()}-water`, category: 'water', title: 'Improve Water Recycling', description: 'Upgrade water recycling systems in vertical farms', potentialImpact: 'Increase water recycling from 85% to 95%', difficulty: 'moderate', estimatedSavings: { value: Math.round(this.waterMetrics.totalUsageLiters * 0.1), unit: 'liters' }, priority: 'medium' }); } // Waste opportunities if (this.wasteMetrics && this.wasteMetrics.spoilageRate > 10) { opportunities.push({ id: `opp-${Date.now()}-waste`, category: 'waste', title: 'Reduce Spoilage Rate', description: 'Implement better cold chain and demand forecasting', potentialImpact: 'Reduce spoilage from ' + this.wasteMetrics.spoilageRate + '% to 5%', difficulty: 'moderate', estimatedSavings: { value: Math.round(this.wasteMetrics.spoilageKg * 0.5), unit: 'kg produce' }, priority: 'high' }); } return opportunities.sort((a, b) => a.priority === 'high' ? -1 : b.priority === 'high' ? 1 : 0 ); } /** * Check metric alerts */ private checkMetricAlerts(): void { if (this.carbonMetrics && this.carbonMetrics.trend === 'worsening') { this.createAlert('warning', 'Carbon Footprint Increasing', 'Network carbon emissions trending upward over the past week', { actionRequired: 'Review transport methods and route efficiency' } ); } if (this.wasteMetrics && this.wasteMetrics.spoilageRate > 15) { this.createAlert('warning', 'High Spoilage Rate', `Current spoilage rate of ${this.wasteMetrics.spoilageRate}% exceeds target of 10%`, { actionRequired: 'Improve cold chain or demand matching' } ); } if (this.foodMilesMetrics && this.foodMilesMetrics.localDeliveryPercent < 30) { this.createAlert('info', 'Low Local Delivery Rate', `Only ${this.foodMilesMetrics.localDeliveryPercent}% of deliveries are local (<50km)`, { actionRequired: 'Expand local grower network' } ); } } /** * Check for milestone achievements */ private checkMilestones(): void { const milestones = [ { carbon: 100, message: '100 kg CO2 saved!' }, { carbon: 500, message: '500 kg CO2 saved!' }, { carbon: 1000, message: '1 tonne CO2 saved!' }, { carbon: 5000, message: '5 tonnes CO2 saved!' } ]; if (this.carbonMetrics) { for (const milestone of milestones) { const saved = this.carbonMetrics.savedVsConventionalKg; if (saved >= milestone.carbon * 0.98 && saved <= milestone.carbon * 1.02) { this.createAlert('info', 'Sustainability Milestone', milestone.message, { relatedEntityType: 'network' } ); } } } } /** * Generate sustainability report */ generateReport(): SustainabilityReport { const now = new Date(); const weekAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000); return { periodStart: weekAgo.toISOString(), periodEnd: now.toISOString(), totalCarbonSavedKg: this.carbonMetrics?.savedVsConventionalKg || 0, totalFoodMilesSaved: this.foodMilesMetrics?.conventionalComparison || 0, localProductionPercentage: this.foodMilesMetrics?.localDeliveryPercent || 0, wasteReductionPercentage: this.wasteMetrics?.wasteReductionPercent || 0, waterSavedLiters: this.waterMetrics?.savedLiters || 0, recommendations: this.opportunities.slice(0, 3).map(o => o.title) }; } /** * Get carbon metrics */ getCarbonMetrics(): CarbonMetrics | null { return this.carbonMetrics; } /** * Get food miles metrics */ getFoodMilesMetrics(): FoodMilesMetrics | null { return this.foodMilesMetrics; } /** * Get water metrics */ getWaterMetrics(): WaterMetrics | null { return this.waterMetrics; } /** * Get waste metrics */ getWasteMetrics(): WasteMetrics | null { return this.wasteMetrics; } /** * Get sustainability score */ getSustainabilityScore(): SustainabilityScore | null { return this.sustainabilityScore; } /** * Get improvement opportunities */ getOpportunities(): ImprovementOpportunity[] { return this.opportunities; } /** * Get historical scores */ getHistoricalScores(): { date: string; score: number }[] { return this.historicalScores; } } // Singleton instance let sustainabilityAgentInstance: SustainabilityAgent | null = null; export function getSustainabilityAgent(): SustainabilityAgent { if (!sustainabilityAgentInstance) { sustainabilityAgentInstance = new SustainabilityAgent(); } return sustainabilityAgentInstance; }