localgreenchain/lib/agents/VerticalFarmAgent.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

668 lines
20 KiB
TypeScript

/**
* VerticalFarmAgent
* Autonomous vertical farm monitoring and optimization
*
* Responsibilities:
* - Monitor environmental conditions in all zones
* - Detect anomalies and trigger alerts
* - Optimize growing parameters
* - Track crop batch progress
* - Generate yield predictions
* - Coordinate harvest scheduling
*/
import { BaseAgent } from './BaseAgent';
import { AgentConfig, AgentTask } from './types';
interface FarmEnvironment {
temperature: number;
humidity: number;
co2Level: number;
lightPPFD: number;
nutrientEC: number;
waterPH: number;
}
interface EnvironmentTarget {
tempMin: number;
tempMax: number;
humidityMin: number;
humidityMax: number;
co2Min: number;
co2Max: number;
ppfdMin: number;
ppfdMax: number;
}
interface ZoneStatus {
zoneId: string;
zoneName: string;
currentEnv: FarmEnvironment;
targetEnv: EnvironmentTarget;
deviations: EnvironmentDeviation[];
healthScore: number;
activeBatches: number;
}
interface EnvironmentDeviation {
parameter: 'temperature' | 'humidity' | 'co2' | 'light' | 'nutrients' | 'ph';
currentValue: number;
targetRange: { min: number; max: number };
severity: 'low' | 'medium' | 'high' | 'critical';
duration: number; // minutes
action: string;
}
interface BatchProgress {
batchId: string;
cropType: string;
zoneId: string;
startDate: string;
currentStage: string;
daysInStage: number;
expectedHarvestDate: string;
healthScore: number;
predictedYieldKg: number;
issues: string[];
}
interface YieldPrediction {
batchId: string;
cropType: string;
predictedYieldKg: number;
confidence: number;
harvestWindow: { start: string; optimal: string; end: string };
qualityPrediction: 'premium' | 'standard' | 'below_standard';
factors: { name: string; impact: number }[];
}
interface OptimizationRecommendation {
zoneId: string;
parameter: string;
currentValue: number;
recommendedValue: number;
expectedImprovement: string;
priority: 'low' | 'medium' | 'high';
autoApply: boolean;
}
export class VerticalFarmAgent extends BaseAgent {
private zones: Map<string, ZoneStatus> = new Map();
private batches: Map<string, BatchProgress> = new Map();
private yieldPredictions: Map<string, YieldPrediction> = new Map();
private recommendations: OptimizationRecommendation[] = [];
private environmentHistory: Map<string, FarmEnvironment[]> = new Map();
constructor() {
const config: AgentConfig = {
id: 'vertical-farm-agent',
name: 'Vertical Farm Agent',
description: 'Monitors and optimizes vertical farm operations',
enabled: true,
intervalMs: 30000, // Run every 30 seconds for real-time monitoring
priority: 'critical',
maxRetries: 5,
timeoutMs: 15000
};
super(config);
// Initialize with sample zone
this.initializeSampleFarm();
}
/**
* Initialize sample farm for demonstration
*/
private initializeSampleFarm(): void {
const sampleZone: ZoneStatus = {
zoneId: 'zone-1',
zoneName: 'Leafy Greens Zone A',
currentEnv: {
temperature: 22.5,
humidity: 65,
co2Level: 800,
lightPPFD: 250,
nutrientEC: 1.8,
waterPH: 6.2
},
targetEnv: {
tempMin: 20,
tempMax: 24,
humidityMin: 60,
humidityMax: 70,
co2Min: 600,
co2Max: 1000,
ppfdMin: 200,
ppfdMax: 400
},
deviations: [],
healthScore: 95,
activeBatches: 3
};
this.zones.set('zone-1', sampleZone);
// Add sample batch
const sampleBatch: BatchProgress = {
batchId: 'batch-001',
cropType: 'Butterhead Lettuce',
zoneId: 'zone-1',
startDate: new Date(Date.now() - 21 * 24 * 60 * 60 * 1000).toISOString(),
currentStage: 'vegetative',
daysInStage: 14,
expectedHarvestDate: new Date(Date.now() + 14 * 24 * 60 * 60 * 1000).toISOString(),
healthScore: 92,
predictedYieldKg: 45,
issues: []
};
this.batches.set('batch-001', sampleBatch);
}
/**
* Main execution cycle
*/
async runOnce(): Promise<AgentTask | null> {
const startTime = Date.now();
let alertsGenerated = 0;
let recommendationsGenerated = 0;
// Monitor each zone
for (const [zoneId, zone] of this.zones) {
// Simulate environment reading (in production, this would come from sensors)
this.simulateEnvironmentUpdate(zone);
// Record history
const history = this.environmentHistory.get(zoneId) || [];
history.push({ ...zone.currentEnv });
if (history.length > 1440) { // Keep 24 hours at 1-minute intervals
history.shift();
}
this.environmentHistory.set(zoneId, history);
// Check for deviations
const deviations = this.checkDeviations(zone);
zone.deviations = deviations;
zone.healthScore = this.calculateZoneHealth(zone);
// Generate alerts for critical deviations
for (const deviation of deviations) {
if (deviation.severity === 'critical' || deviation.severity === 'high') {
this.createAlert(
deviation.severity === 'critical' ? 'critical' : 'warning',
`${zone.zoneName}: ${deviation.parameter} deviation`,
`${deviation.parameter} at ${deviation.currentValue} (target: ${deviation.targetRange.min}-${deviation.targetRange.max})`,
{ actionRequired: deviation.action, relatedEntityId: zoneId, relatedEntityType: 'zone' }
);
alertsGenerated++;
}
}
// Generate optimization recommendations
const zoneRecs = this.generateOptimizations(zone);
this.recommendations = [...this.recommendations.filter(r => r.zoneId !== zoneId), ...zoneRecs];
recommendationsGenerated += zoneRecs.length;
}
// Update batch progress
for (const [batchId, batch] of this.batches) {
this.updateBatchProgress(batch);
this.updateYieldPrediction(batch);
// Alert for batches nearing harvest
const daysToHarvest = Math.floor(
(new Date(batch.expectedHarvestDate).getTime() - Date.now()) / (24 * 60 * 60 * 1000)
);
if (daysToHarvest <= 3 && daysToHarvest > 0) {
this.createAlert('info', `Harvest Approaching: ${batch.cropType}`,
`Batch ${batch.batchId} ready for harvest in ${daysToHarvest} days`,
{ relatedEntityId: batchId, relatedEntityType: 'batch' }
);
}
}
// Keep recommendations manageable
if (this.recommendations.length > 50) {
this.recommendations = this.recommendations.slice(-50);
}
return this.createTaskResult('farm_monitoring', 'completed', {
zonesMonitored: this.zones.size,
batchesTracked: this.batches.size,
alertsGenerated,
recommendationsGenerated,
executionTimeMs: Date.now() - startTime
});
}
/**
* Simulate environment updates (for demo purposes)
*/
private simulateEnvironmentUpdate(zone: ZoneStatus): void {
// Add small random variations
zone.currentEnv.temperature += (Math.random() - 0.5) * 0.5;
zone.currentEnv.humidity += (Math.random() - 0.5) * 2;
zone.currentEnv.co2Level += (Math.random() - 0.5) * 20;
zone.currentEnv.lightPPFD += (Math.random() - 0.5) * 10;
zone.currentEnv.nutrientEC += (Math.random() - 0.5) * 0.1;
zone.currentEnv.waterPH += (Math.random() - 0.5) * 0.1;
// Clamp values to reasonable ranges
zone.currentEnv.temperature = Math.max(15, Math.min(30, zone.currentEnv.temperature));
zone.currentEnv.humidity = Math.max(40, Math.min(90, zone.currentEnv.humidity));
zone.currentEnv.co2Level = Math.max(400, Math.min(1500, zone.currentEnv.co2Level));
zone.currentEnv.lightPPFD = Math.max(0, Math.min(600, zone.currentEnv.lightPPFD));
zone.currentEnv.nutrientEC = Math.max(0.5, Math.min(3.5, zone.currentEnv.nutrientEC));
zone.currentEnv.waterPH = Math.max(5.0, Math.min(7.5, zone.currentEnv.waterPH));
}
/**
* Check for environmental deviations
*/
private checkDeviations(zone: ZoneStatus): EnvironmentDeviation[] {
const deviations: EnvironmentDeviation[] = [];
const env = zone.currentEnv;
const target = zone.targetEnv;
// Temperature check
if (env.temperature < target.tempMin || env.temperature > target.tempMax) {
const severity = this.calculateDeviationSeverity(
env.temperature,
target.tempMin,
target.tempMax,
2, 5 // Warning at 2°C, critical at 5°C
);
deviations.push({
parameter: 'temperature',
currentValue: Math.round(env.temperature * 10) / 10,
targetRange: { min: target.tempMin, max: target.tempMax },
severity,
duration: 0,
action: env.temperature < target.tempMin
? 'Increase heating or reduce ventilation'
: 'Increase cooling or ventilation'
});
}
// Humidity check
if (env.humidity < target.humidityMin || env.humidity > target.humidityMax) {
const severity = this.calculateDeviationSeverity(
env.humidity,
target.humidityMin,
target.humidityMax,
10, 20
);
deviations.push({
parameter: 'humidity',
currentValue: Math.round(env.humidity),
targetRange: { min: target.humidityMin, max: target.humidityMax },
severity,
duration: 0,
action: env.humidity < target.humidityMin
? 'Increase misting or reduce dehumidification'
: 'Increase dehumidification or ventilation'
});
}
// CO2 check
if (env.co2Level < target.co2Min || env.co2Level > target.co2Max) {
const severity = this.calculateDeviationSeverity(
env.co2Level,
target.co2Min,
target.co2Max,
200, 400
);
deviations.push({
parameter: 'co2',
currentValue: Math.round(env.co2Level),
targetRange: { min: target.co2Min, max: target.co2Max },
severity,
duration: 0,
action: env.co2Level < target.co2Min
? 'Increase CO2 supplementation'
: 'Increase ventilation to reduce CO2'
});
}
// Light check
if (env.lightPPFD < target.ppfdMin || env.lightPPFD > target.ppfdMax) {
const severity = this.calculateDeviationSeverity(
env.lightPPFD,
target.ppfdMin,
target.ppfdMax,
50, 100
);
deviations.push({
parameter: 'light',
currentValue: Math.round(env.lightPPFD),
targetRange: { min: target.ppfdMin, max: target.ppfdMax },
severity,
duration: 0,
action: env.lightPPFD < target.ppfdMin
? 'Increase light intensity or duration'
: 'Reduce light intensity or increase plant density'
});
}
return deviations;
}
/**
* Calculate deviation severity
*/
private calculateDeviationSeverity(
current: number,
min: number,
max: number,
warningDelta: number,
criticalDelta: number
): 'low' | 'medium' | 'high' | 'critical' {
const deviation = current < min ? min - current : current - max;
if (deviation >= criticalDelta) return 'critical';
if (deviation >= warningDelta) return 'high';
if (deviation >= warningDelta / 2) return 'medium';
return 'low';
}
/**
* Calculate zone health score (0-100)
*/
private calculateZoneHealth(zone: ZoneStatus): number {
let score = 100;
for (const deviation of zone.deviations) {
switch (deviation.severity) {
case 'critical': score -= 25; break;
case 'high': score -= 15; break;
case 'medium': score -= 8; break;
case 'low': score -= 3; break;
}
}
return Math.max(0, score);
}
/**
* Generate optimization recommendations
*/
private generateOptimizations(zone: ZoneStatus): OptimizationRecommendation[] {
const recs: OptimizationRecommendation[] = [];
const env = zone.currentEnv;
const target = zone.targetEnv;
// Temperature optimization
const optimalTemp = (target.tempMin + target.tempMax) / 2;
if (Math.abs(env.temperature - optimalTemp) > 1) {
recs.push({
zoneId: zone.zoneId,
parameter: 'temperature',
currentValue: Math.round(env.temperature * 10) / 10,
recommendedValue: optimalTemp,
expectedImprovement: 'Improved growth rate and reduced stress',
priority: Math.abs(env.temperature - optimalTemp) > 2 ? 'high' : 'medium',
autoApply: false
});
}
// CO2 optimization during light hours
const hour = new Date().getHours();
const isLightPeriod = hour >= 6 && hour <= 22;
const optimalCO2 = isLightPeriod ? 900 : 600;
if (Math.abs(env.co2Level - optimalCO2) > 100) {
recs.push({
zoneId: zone.zoneId,
parameter: 'co2',
currentValue: Math.round(env.co2Level),
recommendedValue: optimalCO2,
expectedImprovement: isLightPeriod
? 'Enhanced photosynthesis during light hours'
: 'Reduced CO2 usage during dark period',
priority: 'low',
autoApply: true
});
}
return recs;
}
/**
* Update batch progress
*/
private updateBatchProgress(batch: BatchProgress): void {
const daysSinceStart = Math.floor(
(Date.now() - new Date(batch.startDate).getTime()) / (24 * 60 * 60 * 1000)
);
// Simple stage progression
if (daysSinceStart < 7) {
batch.currentStage = 'germination';
batch.daysInStage = daysSinceStart;
} else if (daysSinceStart < 21) {
batch.currentStage = 'vegetative';
batch.daysInStage = daysSinceStart - 7;
} else if (daysSinceStart < 35) {
batch.currentStage = 'finishing';
batch.daysInStage = daysSinceStart - 21;
} else {
batch.currentStage = 'harvest_ready';
batch.daysInStage = daysSinceStart - 35;
}
// Update health score based on zone conditions
const zone = this.zones.get(batch.zoneId);
if (zone) {
batch.healthScore = Math.round((batch.healthScore * 0.9 + zone.healthScore * 0.1));
}
// Update issues
batch.issues = [];
if (batch.healthScore < 80) {
batch.issues.push('Environmental stress detected');
}
if (batch.daysInStage > 20 && batch.currentStage === 'vegetative') {
batch.issues.push('Extended vegetative period - check nutrient levels');
}
}
/**
* Update yield prediction
*/
private updateYieldPrediction(batch: BatchProgress): void {
// Base yield based on crop type
const baseYields: Record<string, number> = {
'Butterhead Lettuce': 50,
'Romaine Lettuce': 45,
'Basil': 25,
'Microgreens': 15,
'Spinach': 30
};
const baseYield = baseYields[batch.cropType] || 40;
// Adjust based on health
const healthFactor = batch.healthScore / 100;
// Adjust based on stage
const stageFactor = batch.currentStage === 'harvest_ready' ? 1.0 :
batch.currentStage === 'finishing' ? 0.95 :
batch.currentStage === 'vegetative' ? 0.8 : 0.5;
const predictedYield = baseYield * healthFactor * stageFactor;
// Calculate harvest window
const optimalDate = new Date(batch.expectedHarvestDate);
const startDate = new Date(optimalDate.getTime() - 2 * 24 * 60 * 60 * 1000);
const endDate = new Date(optimalDate.getTime() + 5 * 24 * 60 * 60 * 1000);
const prediction: YieldPrediction = {
batchId: batch.batchId,
cropType: batch.cropType,
predictedYieldKg: Math.round(predictedYield * 10) / 10,
confidence: Math.min(95, 50 + batch.healthScore * 0.45),
harvestWindow: {
start: startDate.toISOString(),
optimal: optimalDate.toISOString(),
end: endDate.toISOString()
},
qualityPrediction: batch.healthScore >= 90 ? 'premium' :
batch.healthScore >= 70 ? 'standard' : 'below_standard',
factors: [
{ name: 'Health Score', impact: Math.round((healthFactor - 1) * 100) },
{ name: 'Growth Stage', impact: Math.round((stageFactor - 1) * 100) }
]
};
this.yieldPredictions.set(batch.batchId, prediction);
batch.predictedYieldKg = prediction.predictedYieldKg;
}
/**
* Register a new zone
*/
registerZone(zoneId: string, zoneName: string, targetEnv: EnvironmentTarget): void {
const zone: ZoneStatus = {
zoneId,
zoneName,
currentEnv: {
temperature: (targetEnv.tempMin + targetEnv.tempMax) / 2,
humidity: (targetEnv.humidityMin + targetEnv.humidityMax) / 2,
co2Level: (targetEnv.co2Min + targetEnv.co2Max) / 2,
lightPPFD: (targetEnv.ppfdMin + targetEnv.ppfdMax) / 2,
nutrientEC: 1.8,
waterPH: 6.0
},
targetEnv,
deviations: [],
healthScore: 100,
activeBatches: 0
};
this.zones.set(zoneId, zone);
}
/**
* Start a new batch
*/
startBatch(batchId: string, cropType: string, zoneId: string, expectedDays: number): BatchProgress | null {
const zone = this.zones.get(zoneId);
if (!zone) return null;
const batch: BatchProgress = {
batchId,
cropType,
zoneId,
startDate: new Date().toISOString(),
currentStage: 'germination',
daysInStage: 0,
expectedHarvestDate: new Date(Date.now() + expectedDays * 24 * 60 * 60 * 1000).toISOString(),
healthScore: 100,
predictedYieldKg: 0,
issues: []
};
this.batches.set(batchId, batch);
zone.activeBatches++;
return batch;
}
/**
* Get zone status
*/
getZoneStatus(zoneId: string): ZoneStatus | null {
return this.zones.get(zoneId) || null;
}
/**
* Get all zones
*/
getAllZones(): ZoneStatus[] {
return Array.from(this.zones.values());
}
/**
* Get batch progress
*/
getBatchProgress(batchId: string): BatchProgress | null {
return this.batches.get(batchId) || null;
}
/**
* Get all batches
*/
getAllBatches(): BatchProgress[] {
return Array.from(this.batches.values());
}
/**
* Get yield predictions
*/
getYieldPredictions(): YieldPrediction[] {
return Array.from(this.yieldPredictions.values());
}
/**
* Get optimization recommendations
*/
getRecommendations(): OptimizationRecommendation[] {
return this.recommendations;
}
/**
* Get farm summary
*/
getFarmSummary(): {
totalZones: number;
avgHealthScore: number;
activeBatches: number;
upcomingHarvests: number;
criticalAlerts: number;
predictedWeeklyYieldKg: number;
} {
const zones = Array.from(this.zones.values());
const batches = Array.from(this.batches.values());
const predictions = Array.from(this.yieldPredictions.values());
const avgHealth = zones.length > 0
? zones.reduce((sum, z) => sum + z.healthScore, 0) / zones.length
: 0;
const weekFromNow = Date.now() + 7 * 24 * 60 * 60 * 1000;
const upcomingHarvests = batches.filter(b =>
new Date(b.expectedHarvestDate).getTime() <= weekFromNow
).length;
const criticalDeviations = zones.reduce((count, z) =>
count + z.deviations.filter(d => d.severity === 'critical').length, 0
);
const weeklyYield = predictions
.filter(p => {
const harvestDate = new Date(p.harvestWindow.optimal).getTime();
return harvestDate <= weekFromNow;
})
.reduce((sum, p) => sum + p.predictedYieldKg, 0);
return {
totalZones: zones.length,
avgHealthScore: Math.round(avgHealth),
activeBatches: batches.length,
upcomingHarvests,
criticalAlerts: criticalDeviations,
predictedWeeklyYieldKg: Math.round(weeklyYield * 10) / 10
};
}
}
// Singleton instance
let farmAgentInstance: VerticalFarmAgent | null = null;
export function getVerticalFarmAgent(): VerticalFarmAgent {
if (!farmAgentInstance) {
farmAgentInstance = new VerticalFarmAgent();
}
return farmAgentInstance;
}