localgreenchain/lib/agents/SustainabilityAgent.ts
Claude 4235e17f60
Add comprehensive 10-agent autonomous system for LocalGreenChain
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
2025-11-22 21:24:40 +00:00

556 lines
18 KiB
TypeScript

/**
* 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<string, number>;
byEventType: Record<string, number>;
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<AgentTask | null> {
// 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<string, number> = {};
const byEventType: Record<string, number> = {};
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;
}