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
692 lines
21 KiB
TypeScript
692 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.getChain();
|
|
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?.avgTemperature) tempValues.push(env.climate.avgTemperature);
|
|
if (env?.climate?.avgHumidity) humidityValues.push(env.climate.avgHumidity);
|
|
if (env?.lighting?.hoursPerDay) lightValues.push(env.lighting.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 lightDiff = env.lighting.hoursPerDay
|
|
? Math.abs(env.lighting.hoursPerDay - profile.optimalConditions.lightHours.optimal)
|
|
: 2;
|
|
lightingScore = Math.max(0, 100 - lightDiff * 15);
|
|
|
|
if (lightDiff > 2) {
|
|
improvements.push({
|
|
category: 'lighting',
|
|
currentState: `${env.lighting.hoursPerDay || '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.avgTemperature
|
|
? Math.abs(env.climate.avgTemperature - profile.optimalConditions.temperature.optimal)
|
|
: 5;
|
|
const humDiff = env.climate.avgHumidity
|
|
? Math.abs(env.climate.avgHumidity - profile.optimalConditions.humidity.optimal)
|
|
: 10;
|
|
|
|
climateScore = Math.max(0, 100 - tempDiff * 5 - humDiff * 1);
|
|
|
|
if (tempDiff > 3) {
|
|
improvements.push({
|
|
category: 'climate',
|
|
currentState: `${env.climate.avgTemperature?.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
|
|
if (env.nutrients.fertilizer?.schedule === 'regular') {
|
|
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?.soilType)
|
|
.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: { soilType: 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.getChain();
|
|
|
|
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.soilType === env2.soil.soilType) {
|
|
matchingFactors.push('Soil type');
|
|
matchScore++;
|
|
} else {
|
|
differingFactors.push({
|
|
factor: 'Soil type',
|
|
plant1Value: env1.soil.soilType,
|
|
plant2Value: env2.soil.soilType
|
|
});
|
|
}
|
|
|
|
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.avgTemperature || 0) - (env2.climate.avgTemperature || 0)
|
|
);
|
|
if (tempDiff < 3) {
|
|
matchingFactors.push('Temperature');
|
|
matchScore++;
|
|
} else {
|
|
differingFactors.push({
|
|
factor: 'Temperature',
|
|
plant1Value: env1.climate.avgTemperature,
|
|
plant2Value: env2.climate.avgTemperature
|
|
});
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|