/** * EnvironmentAnalysisAgent * Analyzes growing conditions and provides optimization recommendations * * Responsibilities: * - Compare growing environments across plants * - Identify optimal conditions for each species * - Generate environment improvement recommendations * - Track environmental impacts on plant health * - Learn from successful growing patterns */ import { BaseAgent } from './BaseAgent'; import { AgentConfig, AgentTask } from './types'; import { getBlockchain } from '../blockchain/manager'; import { PlantBlock, GrowingEnvironment, GrowthMetrics } from '../blockchain/types'; interface EnvironmentProfile { species: string; sampleSize: number; optimalConditions: { soilPH: { min: number; max: number; optimal: number }; temperature: { min: number; max: number; optimal: number }; humidity: { min: number; max: number; optimal: number }; lightHours: { min: number; max: number; optimal: number }; wateringFrequency: string; preferredSoilType: string[]; }; successRate: number; commonIssues: string[]; } interface EnvironmentComparison { plant1Id: string; plant2Id: string; similarityScore: number; matchingFactors: string[]; differingFactors: { factor: string; plant1Value: any; plant2Value: any }[]; recommendation: string; } interface PlantEnvironmentScore { plantId: string; species: string; overallScore: number; categoryScores: { soil: number; lighting: number; watering: number; climate: number; nutrients: number; }; improvements: EnvironmentImprovement[]; } interface EnvironmentImprovement { category: string; currentState: string; recommendedState: string; priority: 'low' | 'medium' | 'high'; expectedImpact: string; difficulty: 'easy' | 'moderate' | 'difficult'; } interface SuccessPattern { patternId: string; species: string; conditions: Partial; successMetric: 'growth_rate' | 'health' | 'yield' | 'survival'; successValue: number; sampleSize: number; confidence: number; } export class EnvironmentAnalysisAgent extends BaseAgent { private speciesProfiles: Map = new Map(); private plantScores: Map = new Map(); private successPatterns: SuccessPattern[] = []; private comparisonCache: Map = new Map(); constructor() { const config: AgentConfig = { id: 'environment-analysis-agent', name: 'Environment Analysis Agent', description: 'Analyzes and optimizes growing conditions', enabled: true, intervalMs: 180000, // Run every 3 minutes priority: 'medium', maxRetries: 3, timeoutMs: 45000 }; super(config); // Initialize with known species profiles this.initializeKnownProfiles(); } /** * Initialize profiles for common species */ private initializeKnownProfiles(): void { const commonProfiles: EnvironmentProfile[] = [ { species: 'Tomato', sampleSize: 0, optimalConditions: { soilPH: { min: 6.0, max: 6.8, optimal: 6.5 }, temperature: { min: 18, max: 29, optimal: 24 }, humidity: { min: 50, max: 70, optimal: 60 }, lightHours: { min: 8, max: 12, optimal: 10 }, wateringFrequency: 'daily', preferredSoilType: ['loamy', 'sandy_loam'] }, successRate: 85, commonIssues: ['blossom_end_rot', 'early_blight', 'aphids'] }, { species: 'Lettuce', sampleSize: 0, optimalConditions: { soilPH: { min: 6.0, max: 7.0, optimal: 6.5 }, temperature: { min: 10, max: 21, optimal: 16 }, humidity: { min: 50, max: 70, optimal: 60 }, lightHours: { min: 10, max: 14, optimal: 12 }, wateringFrequency: 'daily', preferredSoilType: ['loamy', 'clay_loam'] }, successRate: 90, commonIssues: ['bolting', 'tip_burn', 'slugs'] }, { species: 'Basil', sampleSize: 0, optimalConditions: { soilPH: { min: 6.0, max: 7.0, optimal: 6.5 }, temperature: { min: 18, max: 29, optimal: 24 }, humidity: { min: 40, max: 60, optimal: 50 }, lightHours: { min: 6, max: 8, optimal: 7 }, wateringFrequency: 'every_2_days', preferredSoilType: ['loamy', 'sandy_loam'] }, successRate: 88, commonIssues: ['downy_mildew', 'fusarium_wilt', 'aphids'] }, { species: 'Pepper', sampleSize: 0, optimalConditions: { soilPH: { min: 6.0, max: 6.8, optimal: 6.4 }, temperature: { min: 18, max: 32, optimal: 26 }, humidity: { min: 50, max: 70, optimal: 60 }, lightHours: { min: 8, max: 12, optimal: 10 }, wateringFrequency: 'every_2_days', preferredSoilType: ['loamy', 'sandy_loam'] }, successRate: 82, commonIssues: ['blossom_drop', 'bacterial_spot', 'aphids'] } ]; for (const profile of commonProfiles) { this.speciesProfiles.set(profile.species.toLowerCase(), profile); } } /** * Main execution cycle */ async runOnce(): Promise { const blockchain = getBlockchain(); const chain = blockchain.chain; const plants = chain.slice(1); // Skip genesis let profilesUpdated = 0; let scoresCalculated = 0; let patternsIdentified = 0; // Group plants by species for analysis const speciesGroups = this.groupBySpecies(plants); // Update species profiles based on actual data for (const [species, speciesPlants] of speciesGroups) { this.updateSpeciesProfile(species, speciesPlants); profilesUpdated++; } // Calculate environment scores for each plant for (const block of plants) { const score = this.calculateEnvironmentScore(block); this.plantScores.set(block.plant.id, score); scoresCalculated++; // Alert for plants with poor environment scores if (score.overallScore < 50) { this.createAlert('warning', `Poor Growing Conditions: ${block.plant.commonName}`, `Plant ${block.plant.id} has environment score of ${score.overallScore}`, { actionRequired: score.improvements[0]?.recommendedState || 'Review growing conditions', relatedEntityId: block.plant.id, relatedEntityType: 'plant' } ); } } // Identify success patterns const newPatterns = this.identifySuccessPatterns(plants); this.successPatterns = [...this.successPatterns, ...newPatterns].slice(-100); patternsIdentified = newPatterns.length; // Clean comparison cache if (this.comparisonCache.size > 1000) { this.comparisonCache.clear(); } return this.createTaskResult('environment_analysis', 'completed', { plantsAnalyzed: plants.length, profilesUpdated, scoresCalculated, patternsIdentified, avgEnvironmentScore: this.calculateAverageScore() }); } /** * Group plants by species */ private groupBySpecies(plants: PlantBlock[]): Map { const groups = new Map(); for (const block of plants) { const species = block.plant.commonName?.toLowerCase() || 'unknown'; const group = groups.get(species) || []; group.push(block); groups.set(species, group); } return groups; } /** * Update species profile based on actual data */ private updateSpeciesProfile(species: string, plants: PlantBlock[]): void { const existing = this.speciesProfiles.get(species); const plantsWithEnv = plants.filter(p => p.plant.environment); if (plantsWithEnv.length < 3) { // Not enough data to update profile if (existing) { existing.sampleSize = plantsWithEnv.length; } return; } // Calculate statistics from actual data const pHValues: number[] = []; const tempValues: number[] = []; const humidityValues: number[] = []; const lightValues: number[] = []; const healthyPlants = plantsWithEnv.filter(p => p.plant.status === 'growing' || p.plant.status === 'mature' || p.plant.status === 'flowering' ); for (const block of healthyPlants) { const env = block.plant.environment; if (env?.soil?.pH) pHValues.push(env.soil.pH); if (env?.climate?.temperatureDay) tempValues.push(env.climate.temperatureDay); if (env?.climate?.humidityAverage) humidityValues.push(env.climate.humidityAverage); if (env?.lighting?.naturalLight?.hoursPerDay) lightValues.push(env.lighting.naturalLight.hoursPerDay); } const profile: EnvironmentProfile = existing || { species, sampleSize: 0, optimalConditions: { soilPH: { min: 6.0, max: 7.0, optimal: 6.5 }, temperature: { min: 15, max: 30, optimal: 22 }, humidity: { min: 40, max: 80, optimal: 60 }, lightHours: { min: 6, max: 12, optimal: 8 }, wateringFrequency: 'as_needed', preferredSoilType: [] }, successRate: 0, commonIssues: [] }; // Update with statistical analysis if (pHValues.length > 0) { profile.optimalConditions.soilPH = this.calculateOptimalRange(pHValues); } if (tempValues.length > 0) { profile.optimalConditions.temperature = this.calculateOptimalRange(tempValues); } if (humidityValues.length > 0) { profile.optimalConditions.humidity = this.calculateOptimalRange(humidityValues); } if (lightValues.length > 0) { profile.optimalConditions.lightHours = this.calculateOptimalRange(lightValues); } profile.sampleSize = plantsWithEnv.length; profile.successRate = (healthyPlants.length / plantsWithEnv.length) * 100; this.speciesProfiles.set(species, profile); } /** * Calculate optimal range from values */ private calculateOptimalRange(values: number[]): { min: number; max: number; optimal: number } { const sorted = [...values].sort((a, b) => a - b); const n = sorted.length; return { min: sorted[Math.floor(n * 0.1)] || sorted[0], max: sorted[Math.floor(n * 0.9)] || sorted[n - 1], optimal: sorted[Math.floor(n * 0.5)] // Median }; } /** * Calculate environment score for a plant */ private calculateEnvironmentScore(block: PlantBlock): PlantEnvironmentScore { const plant = block.plant; const env = plant.environment; const species = plant.commonName?.toLowerCase() || 'unknown'; const profile = this.speciesProfiles.get(species); const improvements: EnvironmentImprovement[] = []; let soilScore = 50; let lightingScore = 50; let wateringScore = 50; let climateScore = 50; let nutrientsScore = 50; if (env && profile) { // Soil analysis if (env.soil) { const pHDiff = env.soil.pH ? Math.abs(env.soil.pH - profile.optimalConditions.soilPH.optimal) : 1; soilScore = Math.max(0, 100 - pHDiff * 20); if (pHDiff > 0.5) { improvements.push({ category: 'soil', currentState: `pH ${env.soil.pH?.toFixed(1) || 'unknown'}`, recommendedState: `pH ${profile.optimalConditions.soilPH.optimal}`, priority: pHDiff > 1 ? 'high' : 'medium', expectedImpact: 'Improved nutrient uptake', difficulty: 'moderate' }); } } // Lighting analysis if (env.lighting) { const lightHours = env.lighting.naturalLight?.hoursPerDay || env.lighting.artificialLight?.hoursPerDay; const lightDiff = lightHours ? Math.abs(lightHours - profile.optimalConditions.lightHours.optimal) : 2; lightingScore = Math.max(0, 100 - lightDiff * 15); if (lightDiff > 2) { improvements.push({ category: 'lighting', currentState: `${lightHours || 'unknown'} hours/day`, recommendedState: `${profile.optimalConditions.lightHours.optimal} hours/day`, priority: lightDiff > 4 ? 'high' : 'medium', expectedImpact: 'Better photosynthesis and growth', difficulty: env.lighting.type === 'artificial' ? 'easy' : 'difficult' }); } } // Climate analysis if (env.climate) { const tempDiff = env.climate.temperatureDay ? Math.abs(env.climate.temperatureDay - profile.optimalConditions.temperature.optimal) : 5; const humDiff = env.climate.humidityAverage ? Math.abs(env.climate.humidityAverage - profile.optimalConditions.humidity.optimal) : 10; climateScore = Math.max(0, 100 - tempDiff * 5 - humDiff * 1); if (tempDiff > 3) { improvements.push({ category: 'climate', currentState: `${env.climate.temperatureDay?.toFixed(1) || 'unknown'}°C`, recommendedState: `${profile.optimalConditions.temperature.optimal}°C`, priority: tempDiff > 6 ? 'high' : 'medium', expectedImpact: 'Reduced stress and improved growth', difficulty: 'moderate' }); } } // Watering analysis if (env.watering) { wateringScore = 70; // Base score if watering data exists if (env.watering.frequency === profile.optimalConditions.wateringFrequency) { wateringScore = 90; } } // Nutrients analysis if (env.nutrients) { nutrientsScore = 75; // Base score if nutrient data exists // Bonus for complete NPK profile if (env.nutrients.nitrogen && env.nutrients.phosphorus && env.nutrients.potassium) { nutrientsScore = 90; } } } const overallScore = Math.round( (soilScore + lightingScore + wateringScore + climateScore + nutrientsScore) / 5 ); return { plantId: plant.id, species: plant.commonName || 'Unknown', overallScore, categoryScores: { soil: Math.round(soilScore), lighting: Math.round(lightingScore), watering: Math.round(wateringScore), climate: Math.round(climateScore), nutrients: Math.round(nutrientsScore) }, improvements: improvements.sort((a, b) => a.priority === 'high' ? -1 : b.priority === 'high' ? 1 : 0 ) }; } /** * Identify success patterns from plant data */ private identifySuccessPatterns(plants: PlantBlock[]): SuccessPattern[] { const patterns: SuccessPattern[] = []; // Group healthy plants by species const healthyBySpecies = new Map(); for (const block of plants) { if (block.plant.status === 'mature' || block.plant.status === 'flowering' || block.plant.status === 'fruiting') { const species = block.plant.commonName?.toLowerCase() || 'unknown'; const group = healthyBySpecies.get(species) || []; group.push(block); healthyBySpecies.set(species, group); } } // Identify common conditions among successful plants for (const [species, successPlants] of healthyBySpecies) { if (successPlants.length < 2) continue; const plantsWithEnv = successPlants.filter(p => p.plant.environment); if (plantsWithEnv.length < 2) continue; // Find common soil types const soilTypes = plantsWithEnv .map(p => p.plant.environment?.soil?.type) .filter(Boolean); const commonSoilType = this.findMostCommon(soilTypes as string[]); if (commonSoilType) { patterns.push({ patternId: `pattern-${Date.now()}-${Math.random().toString(36).substr(2, 5)}`, species, conditions: { soil: { type: commonSoilType } } as any, successMetric: 'health', successValue: 85, sampleSize: plantsWithEnv.length, confidence: Math.min(95, 50 + plantsWithEnv.length * 10) }); } } return patterns; } /** * Find most common value in array */ private findMostCommon(arr: string[]): string | null { if (arr.length === 0) return null; const counts = new Map(); for (const item of arr) { counts.set(item, (counts.get(item) || 0) + 1); } let maxCount = 0; let mostCommon: string | null = null; for (const [item, count] of counts) { if (count > maxCount) { maxCount = count; mostCommon = item; } } return mostCommon; } /** * Calculate average environment score */ private calculateAverageScore(): number { const scores = Array.from(this.plantScores.values()); if (scores.length === 0) return 0; return Math.round( scores.reduce((sum, s) => sum + s.overallScore, 0) / scores.length ); } /** * Compare two plant environments */ compareEnvironments(plant1Id: string, plant2Id: string): EnvironmentComparison | null { const cacheKey = [plant1Id, plant2Id].sort().join('-'); const cached = this.comparisonCache.get(cacheKey); if (cached) return cached; const blockchain = getBlockchain(); const chain = blockchain.chain; const block1 = chain.find(b => b.plant.id === plant1Id); const block2 = chain.find(b => b.plant.id === plant2Id); if (!block1 || !block2) return null; const env1 = block1.plant.environment; const env2 = block2.plant.environment; const matchingFactors: string[] = []; const differingFactors: { factor: string; plant1Value: any; plant2Value: any }[] = []; let matchScore = 0; let totalFactors = 0; // Compare soil if (env1?.soil && env2?.soil) { totalFactors++; if (env1.soil.type === env2.soil.type) { matchingFactors.push('Soil type'); matchScore++; } else { differingFactors.push({ factor: 'Soil type', plant1Value: env1.soil.type, plant2Value: env2.soil.type }); } totalFactors++; if (Math.abs((env1.soil.pH || 0) - (env2.soil.pH || 0)) < 0.5) { matchingFactors.push('Soil pH'); matchScore++; } else { differingFactors.push({ factor: 'Soil pH', plant1Value: env1.soil.pH, plant2Value: env2.soil.pH }); } } // Compare lighting if (env1?.lighting && env2?.lighting) { totalFactors++; if (env1.lighting.type === env2.lighting.type) { matchingFactors.push('Light type'); matchScore++; } else { differingFactors.push({ factor: 'Light type', plant1Value: env1.lighting.type, plant2Value: env2.lighting.type }); } } // Compare climate if (env1?.climate && env2?.climate) { totalFactors++; const tempDiff = Math.abs( (env1.climate.temperatureDay || 0) - (env2.climate.temperatureDay || 0) ); if (tempDiff < 3) { matchingFactors.push('Temperature'); matchScore++; } else { differingFactors.push({ factor: 'Temperature', plant1Value: env1.climate.temperatureDay, plant2Value: env2.climate.temperatureDay }); } } const similarityScore = totalFactors > 0 ? Math.round((matchScore / totalFactors) * 100) : 50; let recommendation = ''; if (similarityScore > 80) { recommendation = 'Very similar environments - good candidates for companion planting'; } else if (similarityScore > 60) { recommendation = 'Moderately similar - consider adjusting differing factors'; } else { recommendation = 'Different environments - may require separate growing areas'; } const comparison: EnvironmentComparison = { plant1Id, plant2Id, similarityScore, matchingFactors, differingFactors, recommendation }; this.comparisonCache.set(cacheKey, comparison); return comparison; } /** * Get species profile */ getSpeciesProfile(species: string): EnvironmentProfile | null { return this.speciesProfiles.get(species.toLowerCase()) || null; } /** * Get all species profiles */ getAllProfiles(): EnvironmentProfile[] { return Array.from(this.speciesProfiles.values()); } /** * Get plant environment score */ getPlantScore(plantId: string): PlantEnvironmentScore | null { return this.plantScores.get(plantId) || null; } /** * Get success patterns */ getSuccessPatterns(): SuccessPattern[] { return this.successPatterns; } /** * Get recommendations for a species */ getSpeciesRecommendations(species: string): { profile: EnvironmentProfile | null; patterns: SuccessPattern[]; tips: string[]; } { const profile = this.speciesProfiles.get(species.toLowerCase()); const patterns = this.successPatterns.filter(p => p.species === species.toLowerCase()); const tips: string[] = []; if (profile) { tips.push(`Maintain soil pH between ${profile.optimalConditions.soilPH.min} and ${profile.optimalConditions.soilPH.max}`); tips.push(`Keep temperature between ${profile.optimalConditions.temperature.min}°C and ${profile.optimalConditions.temperature.max}°C`); tips.push(`Provide ${profile.optimalConditions.lightHours.optimal} hours of light daily`); if (profile.commonIssues.length > 0) { tips.push(`Watch for common issues: ${profile.commonIssues.join(', ')}`); } } return { profile, patterns, tips }; } } // Singleton instance let envAgentInstance: EnvironmentAnalysisAgent | null = null; export function getEnvironmentAnalysisAgent(): EnvironmentAnalysisAgent { if (!envAgentInstance) { envAgentInstance = new EnvironmentAnalysisAgent(); } return envAgentInstance; }