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
668 lines
20 KiB
TypeScript
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;
|
|
}
|