localgreenchain/lib/agents/EnvironmentAnalysisAgent.ts
Claude 507df5912f
Deploy GrowerAdvisoryAgent (Agent 10) and fix type errors
- Add GrowerAdvisoryAgent test file
- Fix PlantChain constructor initialization order (plantIndex before genesis block)
- Fix blockchain.getChain() calls to use blockchain.chain property
- Add PropagationType export to blockchain types
- Fix SoilComposition.type property references (was soilType)
- Fix ClimateConditions.temperatureDay property references (was avgTemperature)
- Fix ClimateConditions.humidityAverage property references (was avgHumidity)
- Fix LightingConditions.naturalLight.hoursPerDay nested access
- Add 'critical' severity to QualityReport issues
- Add 'sqm' unit to PlantingRecommendation.quantityUnit
- Fix GrowerAdvisoryAgent growthMetrics property access
- Update TypeScript to v5 for react-hook-form compatibility
- Enable downlevelIteration in tsconfig for Map iteration
- Fix crypto Buffer type issues in anonymity.ts
- Fix zones.tsx status type comparison
- Fix next.config.js images.domains filter
- Rename [[...slug]].tsx to [...slug].tsx to resolve routing conflict
2025-11-23 00:44:58 +00:00

694 lines
21 KiB
TypeScript

/**
* 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<GrowingEnvironment>;
successMetric: 'growth_rate' | 'health' | 'yield' | 'survival';
successValue: number;
sampleSize: number;
confidence: number;
}
export class EnvironmentAnalysisAgent extends BaseAgent {
private speciesProfiles: Map<string, EnvironmentProfile> = new Map();
private plantScores: Map<string, PlantEnvironmentScore> = new Map();
private successPatterns: SuccessPattern[] = [];
private comparisonCache: Map<string, EnvironmentComparison> = 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<AgentTask | null> {
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<string, PlantBlock[]> {
const groups = new Map<string, PlantBlock[]>();
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<string, PlantBlock[]>();
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<string, number>();
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;
}