/** * 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 = new Map(); private batches: Map = new Map(); private yieldPredictions: Map = new Map(); private recommendations: OptimizationRecommendation[] = []; private environmentHistory: Map = 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 { 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 = { '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; }