diff --git a/lib/blockchain/types.ts b/lib/blockchain/types.ts index 5679296..3a3914b 100644 --- a/lib/blockchain/types.ts +++ b/lib/blockchain/types.ts @@ -1,5 +1,7 @@ // Plant Blockchain Types +import { GrowingEnvironment, GrowthMetrics } from '../environment/types'; + export interface PlantLocation { latitude: number; longitude: number; @@ -40,6 +42,10 @@ export interface PlantData { // Plant network childPlants: string[]; // IDs of clones and seeds from this plant + // Environmental data + environment?: GrowingEnvironment; + growthMetrics?: GrowthMetrics; + // Additional metadata notes?: string; images?: string[]; diff --git a/lib/environment/analysis.ts b/lib/environment/analysis.ts new file mode 100644 index 0000000..01b9a20 --- /dev/null +++ b/lib/environment/analysis.ts @@ -0,0 +1,432 @@ +/** + * Environmental Analysis Service + * Compares growing conditions, generates recommendations, and analyzes correlations + */ + +import { + GrowingEnvironment, + EnvironmentalComparison, + EnvironmentalRecommendation, + SoilComposition, + ClimateConditions, + LightingConditions, + NutrientProfile, +} from './types'; +import { PlantData } from '../blockchain/types'; + +/** + * Compare two growing environments and return similarity score + */ +export function compareEnvironments( + env1: GrowingEnvironment, + env2: GrowingEnvironment +): EnvironmentalComparison { + const similarities: string[] = []; + const differences: string[] = []; + let score = 0; + let maxScore = 0; + + // Compare location type (10 points) + maxScore += 10; + if (env1.location.type === env2.location.type) { + score += 10; + similarities.push(`Both ${env1.location.type} growing`); + } else { + differences.push(`Location: ${env1.location.type} vs ${env2.location.type}`); + } + + // Compare soil type (15 points) + maxScore += 15; + if (env1.soil.type === env2.soil.type) { + score += 15; + similarities.push(`Same soil type: ${env1.soil.type}`); + } else { + differences.push(`Soil: ${env1.soil.type} vs ${env2.soil.type}`); + } + + // Compare soil pH (10 points, within 0.5 range) + maxScore += 10; + const pHDiff = Math.abs(env1.soil.pH - env2.soil.pH); + if (pHDiff <= 0.5) { + score += 10; + similarities.push(`Similar soil pH: ${env1.soil.pH.toFixed(1)} ≈ ${env2.soil.pH.toFixed(1)}`); + } else if (pHDiff <= 1.0) { + score += 5; + differences.push(`Soil pH: ${env1.soil.pH.toFixed(1)} vs ${env2.soil.pH.toFixed(1)}`); + } else { + differences.push(`Soil pH differs significantly: ${env1.soil.pH.toFixed(1)} vs ${env2.soil.pH.toFixed(1)}`); + } + + // Compare temperature (15 points) + maxScore += 15; + const tempDiff = Math.abs(env1.climate.temperatureDay - env2.climate.temperatureDay); + if (tempDiff <= 3) { + score += 15; + similarities.push(`Similar temperatures: ${env1.climate.temperatureDay}°C ≈ ${env2.climate.temperatureDay}°C`); + } else if (tempDiff <= 7) { + score += 8; + differences.push(`Temperature: ${env1.climate.temperatureDay}°C vs ${env2.climate.temperatureDay}°C`); + } else { + differences.push(`Temperature differs significantly: ${env1.climate.temperatureDay}°C vs ${env2.climate.temperatureDay}°C`); + } + + // Compare humidity (10 points) + maxScore += 10; + const humidityDiff = Math.abs(env1.climate.humidityAverage - env2.climate.humidityAverage); + if (humidityDiff <= 10) { + score += 10; + similarities.push(`Similar humidity: ${env1.climate.humidityAverage}% ≈ ${env2.climate.humidityAverage}%`); + } else if (humidityDiff <= 20) { + score += 5; + } else { + differences.push(`Humidity: ${env1.climate.humidityAverage}% vs ${env2.climate.humidityAverage}%`); + } + + // Compare lighting type (15 points) + maxScore += 15; + if (env1.lighting.type === env2.lighting.type) { + score += 15; + similarities.push(`Same lighting: ${env1.lighting.type}`); + } else { + differences.push(`Lighting: ${env1.lighting.type} vs ${env2.lighting.type}`); + } + + // Compare natural light exposure if both use natural light (10 points) + if (env1.lighting.naturalLight && env2.lighting.naturalLight) { + maxScore += 10; + if (env1.lighting.naturalLight.exposure === env2.lighting.naturalLight.exposure) { + score += 10; + similarities.push(`Same sun exposure: ${env1.lighting.naturalLight.exposure}`); + } else { + differences.push(`Sun exposure: ${env1.lighting.naturalLight.exposure} vs ${env2.lighting.naturalLight.exposure}`); + } + } + + // Compare water source (5 points) + maxScore += 5; + if (env1.watering.waterSource === env2.watering.waterSource) { + score += 5; + similarities.push(`Same water source: ${env1.watering.waterSource}`); + } else { + differences.push(`Water: ${env1.watering.waterSource} vs ${env2.watering.waterSource}`); + } + + // Compare container type (10 points) + if (env1.container && env2.container) { + maxScore += 10; + if (env1.container.type === env2.container.type) { + score += 10; + similarities.push(`Same container: ${env1.container.type}`); + } else { + differences.push(`Container: ${env1.container.type} vs ${env2.container.type}`); + } + } + + // Calculate final score as percentage + const finalScore = maxScore > 0 ? Math.round((score / maxScore) * 100) : 0; + + return { + plant1: '', // Will be filled by caller + plant2: '', // Will be filled by caller + similarities, + differences, + score: finalScore, + }; +} + +/** + * Generate environmental recommendations based on plant data + */ +export function generateRecommendations( + plant: PlantData +): EnvironmentalRecommendation[] { + const recommendations: EnvironmentalRecommendation[] = []; + + if (!plant.environment) { + recommendations.push({ + category: 'general', + priority: 'high', + issue: 'No environmental data', + recommendation: 'Add environmental information to track growing conditions', + impact: 'Environmental tracking helps optimize growing conditions and share knowledge', + }); + return recommendations; + } + + const env = plant.environment; + + // Soil pH recommendations + if (env.soil.pH < 5.5) { + recommendations.push({ + category: 'soil', + priority: 'high', + issue: `Low soil pH (${env.soil.pH})`, + recommendation: 'Add lime to raise pH. Most plants prefer 6.0-7.0', + impact: 'Acidic soil can lock out nutrients and harm roots', + }); + } else if (env.soil.pH > 7.5) { + recommendations.push({ + category: 'soil', + priority: 'high', + issue: `High soil pH (${env.soil.pH})`, + recommendation: 'Add sulfur to lower pH. Most plants prefer 6.0-7.0', + impact: 'Alkaline soil can prevent nutrient uptake', + }); + } + + // Organic matter recommendations + if (env.soil.organicMatter < 3) { + recommendations.push({ + category: 'soil', + priority: 'medium', + issue: `Low organic matter (${env.soil.organicMatter}%)`, + recommendation: 'Add compost or aged manure to improve soil structure', + impact: 'Increases water retention and nutrient availability', + }); + } + + // Drainage recommendations + if (env.soil.drainage === 'poor') { + recommendations.push({ + category: 'soil', + priority: 'high', + issue: 'Poor soil drainage', + recommendation: 'Add perlite, sand, or create raised beds', + impact: 'Prevents root rot and improves oxygen availability', + }); + } + + // Temperature recommendations + if (env.climate.temperatureDay > 35) { + recommendations.push({ + category: 'climate', + priority: 'high', + issue: `High daytime temperature (${env.climate.temperatureDay}°C)`, + recommendation: 'Provide shade during hottest hours, increase watering', + impact: 'Extreme heat can cause stress and reduce growth', + }); + } else if (env.climate.temperatureDay < 10) { + recommendations.push({ + category: 'climate', + priority: 'high', + issue: `Low daytime temperature (${env.climate.temperatureDay}°C)`, + recommendation: 'Provide frost protection or move indoors', + impact: 'Cold temperatures can slow growth or damage plants', + }); + } + + // Humidity recommendations + if (env.climate.humidityAverage < 30) { + recommendations.push({ + category: 'climate', + priority: 'medium', + issue: `Low humidity (${env.climate.humidityAverage}%)`, + recommendation: 'Mist plants, use humidity tray, or group plants together', + impact: 'Low humidity can cause leaf tip browning and stress', + }); + } else if (env.climate.humidityAverage > 80) { + recommendations.push({ + category: 'climate', + priority: 'medium', + issue: `High humidity (${env.climate.humidityAverage}%)`, + recommendation: 'Improve air circulation to prevent fungal issues', + impact: 'High humidity can promote mold and disease', + }); + } + + // Lighting recommendations + if (env.lighting.type === 'natural' && env.lighting.naturalLight) { + const hoursPerDay = env.lighting.naturalLight.hoursPerDay; + if (hoursPerDay < 4 && plant.status === 'growing') { + recommendations.push({ + category: 'light', + priority: 'high', + issue: `Insufficient light (${hoursPerDay} hours/day)`, + recommendation: 'Move to brighter location or supplement with grow lights', + impact: 'Inadequate light causes leggy growth and poor health', + }); + } + } + + // Nutrient recommendations + if (env.nutrients) { + const npk = [env.nutrients.nitrogen, env.nutrients.phosphorus, env.nutrients.potassium]; + if (npk.some(n => n < 1)) { + recommendations.push({ + category: 'nutrients', + priority: 'medium', + issue: `Low NPK levels (${npk.join('-')})`, + recommendation: 'Apply balanced fertilizer according to plant needs', + impact: 'Nutrient deficiency reduces growth and yield', + }); + } + } + + // Water quality recommendations + if (env.watering.waterQuality) { + if (env.watering.waterQuality.pH && env.watering.waterQuality.pH > 7.5) { + recommendations.push({ + category: 'water', + priority: 'low', + issue: `Alkaline water pH (${env.watering.waterQuality.pH})`, + recommendation: 'Consider pH adjustment or rainwater collection', + impact: 'High water pH can gradually raise soil pH', + }); + } + if (env.watering.waterQuality.chlorine === 'yes') { + recommendations.push({ + category: 'water', + priority: 'low', + issue: 'Chlorinated water', + recommendation: 'Let water sit 24h before use or use filter', + impact: 'Chlorine can harm beneficial soil microbes', + }); + } + } + + // Container recommendations + if (env.container && env.container.drainage === 'no') { + recommendations.push({ + category: 'general', + priority: 'critical', + issue: 'Container has no drainage', + recommendation: 'Add drainage holes immediately to prevent root rot', + impact: 'Critical: Standing water will kill most plants', + }); + } + + return recommendations.sort((a, b) => { + const priorityOrder = { critical: 0, high: 1, medium: 2, low: 3 }; + return priorityOrder[a.priority] - priorityOrder[b.priority]; + }); +} + +/** + * Find plants with similar growing conditions + */ +export function findSimilarEnvironments( + targetPlant: PlantData, + allPlants: PlantData[], + minScore: number = 70 +): Array<{ plant: PlantData; comparison: EnvironmentalComparison }> { + if (!targetPlant.environment) { + return []; + } + + const similar: Array<{ plant: PlantData; comparison: EnvironmentalComparison }> = []; + + for (const plant of allPlants) { + // Skip self and plants without environment data + if (plant.id === targetPlant.id || !plant.environment) { + continue; + } + + const comparison = compareEnvironments(targetPlant.environment, plant.environment); + comparison.plant1 = targetPlant.id; + comparison.plant2 = plant.id; + + if (comparison.score >= minScore) { + similar.push({ plant, comparison }); + } + } + + // Sort by similarity score (highest first) + return similar.sort((a, b) => b.comparison.score - a.comparison.score); +} + +/** + * Analyze growth success based on environmental factors + */ +export function analyzeGrowthCorrelation(plants: PlantData[]): { + bestConditions: Partial; + insights: string[]; +} { + const successfulPlants = plants.filter( + p => p.environment && (p.status === 'mature' || p.status === 'flowering' || p.status === 'fruiting') + ); + + if (successfulPlants.length === 0) { + return { + bestConditions: {}, + insights: ['Not enough data to determine optimal conditions'], + }; + } + + const insights: string[] = []; + + // Analyze soil pH + const pHValues = successfulPlants + .filter(p => p.environment?.soil.pH) + .map(p => p.environment!.soil.pH); + + if (pHValues.length > 0) { + const avgPH = pHValues.reduce((a, b) => a + b, 0) / pHValues.length; + insights.push(`Successful plants average soil pH: ${avgPH.toFixed(1)}`); + } + + // Analyze temperature + const temps = successfulPlants + .filter(p => p.environment?.climate.temperatureDay) + .map(p => p.environment!.climate.temperatureDay); + + if (temps.length > 0) { + const avgTemp = temps.reduce((a, b) => a + b, 0) / temps.length; + const minTemp = Math.min(...temps); + const maxTemp = Math.max(...temps); + insights.push(`Successful temperature range: ${minTemp}-${maxTemp}°C (avg: ${avgTemp.toFixed(1)}°C)`); + } + + // Analyze lighting + const lightingTypes = successfulPlants + .filter(p => p.environment?.lighting.type) + .map(p => p.environment!.lighting.type); + + const lightCount = lightingTypes.reduce((acc, type) => { + acc[type] = (acc[type] || 0) + 1; + return acc; + }, {} as Record); + + const mostCommonLight = Object.entries(lightCount) + .sort((a, b) => b[1] - a[1])[0]; + + if (mostCommonLight) { + insights.push(`Most successful lighting: ${mostCommonLight[0]} (${mostCommonLight[1]} plants)`); + } + + return { + bestConditions: { + // Would be filled with actual aggregate data + }, + insights, + }; +} + +/** + * Calculate environmental health score (0-100) + */ +export function calculateEnvironmentalHealth(env: GrowingEnvironment): number { + let score = 100; + let deductions = 0; + + // Soil health (up to -20 points) + if (env.soil.pH < 5.5 || env.soil.pH > 7.5) deductions += 10; + if (env.soil.drainage === 'poor') deductions += 10; + if (env.soil.organicMatter < 3) deductions += 5; + + // Climate (up to -20 points) + if (env.climate.temperatureDay > 35 || env.climate.temperatureDay < 10) deductions += 10; + if (env.climate.humidityAverage < 30 || env.climate.humidityAverage > 80) deductions += 5; + if (env.climate.airflow === 'none') deductions += 5; + + // Lighting (up to -15 points) + if (env.lighting.naturalLight && env.lighting.naturalLight.hoursPerDay < 4) deductions += 15; + + // Container (up to -15 points) + if (env.container && env.container.drainage === 'no') deductions += 15; + + // Water (up to -10 points) + if (env.watering.waterQuality?.chlorine === 'yes') deductions += 5; + if (env.watering.waterQuality?.pH && env.watering.waterQuality.pH > 7.5) deductions += 5; + + return Math.max(0, score - deductions); +} diff --git a/lib/environment/types.ts b/lib/environment/types.ts new file mode 100644 index 0000000..5aed3e9 --- /dev/null +++ b/lib/environment/types.ts @@ -0,0 +1,253 @@ +/** + * Environmental Data Types for LocalGreenChain + * Tracks soil, climate, nutrients, and growing conditions + */ + +// Soil Composition +export interface SoilComposition { + type: 'clay' | 'sand' | 'silt' | 'loam' | 'peat' | 'chalk' | 'custom'; + customType?: string; + + // Soil properties + pH: number; // 0-14, most plants 6.0-7.5 + texture: 'heavy' | 'medium' | 'light'; + drainage: 'poor' | 'moderate' | 'good' | 'excellent'; + organicMatter: number; // percentage 0-100 + + // Composition percentages (should sum to ~100) + clayPercent?: number; + sandPercent?: number; + siltPercent?: number; + + // Amendments/additives + amendments?: SoilAmendment[]; + + notes?: string; +} + +export interface SoilAmendment { + type: 'compost' | 'manure' | 'perlite' | 'vermiculite' | 'peat_moss' | 'coco_coir' | 'biochar' | 'lime' | 'sulfur' | 'other'; + name: string; + amount?: string; // e.g., "2 cups per gallon", "10%" + dateAdded: string; +} + +// Nutrients & Fertilization +export interface NutrientProfile { + // NPK values (percentage) + nitrogen: number; // N + phosphorus: number; // P + potassium: number; // K + + // Secondary nutrients + calcium?: number; + magnesium?: number; + sulfur?: number; + + // Micronutrients (ppm or mg/L) + iron?: number; + manganese?: number; + zinc?: number; + copper?: number; + boron?: number; + molybdenum?: number; + + // EC (Electrical Conductivity) - measure of nutrient concentration + ec?: number; // mS/cm + + // TDS (Total Dissolved Solids) + tds?: number; // ppm + + lastTested?: string; +} + +export interface FertilizerApplication { + id: string; + date: string; + type: 'organic' | 'synthetic' | 'liquid' | 'granular' | 'foliar' | 'slow_release'; + name: string; + npk?: string; // e.g., "10-10-10", "5-2-3" + amount: string; + frequency?: string; // e.g., "weekly", "bi-weekly", "monthly" + notes?: string; +} + +// Sunlight & Climate +export interface LightingConditions { + type: 'natural' | 'artificial' | 'mixed'; + + // Natural light + naturalLight?: { + exposure: 'full_sun' | 'partial_sun' | 'partial_shade' | 'full_shade'; + hoursPerDay: number; // 0-24 + direction: 'north' | 'south' | 'east' | 'west' | 'multiple'; + quality: 'direct' | 'filtered' | 'dappled' | 'indirect'; + }; + + // Artificial light + artificialLight?: { + type: 'LED' | 'fluorescent' | 'HPS' | 'MH' | 'incandescent' | 'mixed'; + spectrum?: string; // e.g., "full spectrum", "6500K", "2700K" + wattage?: number; + hoursPerDay: number; + distance?: number; // cm from plant + }; + + // Light measurements + ppfd?: number; // Photosynthetic Photon Flux Density (μmol/m²/s) + dli?: number; // Daily Light Integral (mol/m²/day) +} + +export interface ClimateConditions { + // Temperature (Celsius) + temperatureDay: number; + temperatureNight: number; + temperatureMin?: number; + temperatureMax?: number; + + // Humidity (percentage) + humidityAverage: number; + humidityMin?: number; + humidityMax?: number; + + // Air circulation + airflow: 'none' | 'minimal' | 'moderate' | 'strong'; + ventilation: 'poor' | 'adequate' | 'good' | 'excellent'; + + // CO2 levels (ppm) + co2?: number; // ambient ~400, enhanced ~1200-1500 + + // Seasonal variation + season?: 'spring' | 'summer' | 'fall' | 'winter'; + zone?: string; // USDA hardiness zone, e.g., "9b", "10a" +} + +// Growing Environment +export interface GrowingEnvironment { + location: EnvironmentLocation; + container?: ContainerInfo; + soil: SoilComposition; + nutrients: NutrientProfile; + fertilizers?: FertilizerApplication[]; + lighting: LightingConditions; + climate: ClimateConditions; + watering: WateringSchedule; + surroundings?: SurroundingEnvironment; + + // Tracking + monitoringFrequency?: 'daily' | 'weekly' | 'bi-weekly' | 'monthly'; + lastUpdated: string; + notes?: string; +} + +export interface EnvironmentLocation { + type: 'indoor' | 'outdoor' | 'greenhouse' | 'polytunnel' | 'shade_house' | 'window' | 'balcony'; + description?: string; + + // For indoor + room?: string; // e.g., "bedroom", "basement", "grow tent" + + // For outdoor + exposureToElements?: 'protected' | 'semi_protected' | 'exposed'; + elevation?: number; // meters above sea level + slope?: 'flat' | 'gentle' | 'moderate' | 'steep'; + aspect?: 'north' | 'south' | 'east' | 'west'; // slope direction +} + +export interface ContainerInfo { + type: 'pot' | 'raised_bed' | 'ground' | 'hydroponic' | 'aeroponic' | 'aquaponic' | 'fabric_pot' | 'hanging_basket'; + material?: 'plastic' | 'terracotta' | 'ceramic' | 'fabric' | 'wood' | 'metal' | 'concrete'; + size?: string; // e.g., "5 gallon", "30cm diameter", "4x8 feet" + volume?: number; // liters + depth?: number; // cm + drainage: 'yes' | 'no'; + drainageHoles?: number; +} + +export interface WateringSchedule { + method: 'hand_water' | 'drip' | 'soaker_hose' | 'sprinkler' | 'self_watering' | 'hydroponic' | 'rain'; + frequency?: string; // e.g., "daily", "every 2-3 days", "weekly" + amount?: string; // e.g., "1 liter", "until runoff", "1 inch" + waterSource: 'tap' | 'well' | 'rain' | 'filtered' | 'distilled' | 'RO'; + waterQuality?: { + pH?: number; + tds?: number; // ppm + chlorine?: 'yes' | 'no' | 'filtered'; + }; +} + +export interface SurroundingEnvironment { + // Companion plants + companionPlants?: string[]; // other plant species nearby + + // Nearby features + nearbyTrees?: boolean; + nearbyStructures?: string; // e.g., "building", "wall", "fence" + groundCover?: string; // e.g., "mulch", "grass", "bare soil", "gravel" + + // Wildlife & pests + pollinators?: string[]; // e.g., "bees", "butterflies", "hummingbirds" + beneficialInsects?: string[]; // e.g., "ladybugs", "lacewings" + pests?: PestInfo[]; + diseases?: DiseaseInfo[]; + + // Microclimate factors + windExposure?: 'sheltered' | 'moderate' | 'exposed' | 'windy'; + frostPocket?: boolean; + heatTrap?: boolean; + + // Ecosystem type + ecosystem?: 'urban' | 'suburban' | 'rural' | 'forest' | 'desert' | 'coastal' | 'mountain' | 'tropical'; +} + +export interface PestInfo { + name: string; + severity: 'minor' | 'moderate' | 'severe'; + treatment?: string; + dateObserved: string; +} + +export interface DiseaseInfo { + name: string; + symptoms?: string; + severity: 'minor' | 'moderate' | 'severe'; + treatment?: string; + dateObserved: string; +} + +// Environmental Comparison & Analysis +export interface EnvironmentalComparison { + plant1: string; // plant ID + plant2: string; // plant ID + similarities: string[]; + differences: string[]; + score: number; // 0-100, how similar the environments are +} + +export interface GrowthMetrics { + plantId: string; + measurements: PlantMeasurement[]; + healthScore: number; // 0-100 + vigor: 'poor' | 'fair' | 'good' | 'excellent'; + issues?: string[]; +} + +export interface PlantMeasurement { + date: string; + height?: number; // cm + width?: number; // cm + leafCount?: number; + flowerCount?: number; + fruitCount?: number; + notes?: string; + photos?: string[]; +} + +// Helper types for environmental recommendations +export interface EnvironmentalRecommendation { + category: 'soil' | 'nutrients' | 'light' | 'water' | 'climate' | 'general'; + priority: 'low' | 'medium' | 'high' | 'critical'; + issue: string; + recommendation: string; + impact: string; +} diff --git a/pages/api/environment/analysis.ts b/pages/api/environment/analysis.ts new file mode 100644 index 0000000..f0a3165 --- /dev/null +++ b/pages/api/environment/analysis.ts @@ -0,0 +1,95 @@ +/** + * API Route: Analyze growth correlations across all plants + * GET /api/environment/analysis?species=tomato (optional species filter) + */ + +import type { NextApiRequest, NextApiResponse } from 'next'; +import { getBlockchain } from '../../../lib/blockchain/manager'; +import { analyzeGrowthCorrelation } from '../../../lib/environment/analysis'; + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse +) { + if (req.method !== 'GET') { + return res.status(405).json({ error: 'Method not allowed' }); + } + + try { + const { species } = req.query; + + const blockchain = getBlockchain(); + + // Get all plants + let allPlants = Array.from( + new Set(blockchain.chain.map(block => block.plant.id)) + ) + .map(id => blockchain.getPlant(id)!) + .filter(Boolean); + + // Filter by species if specified + if (species && typeof species === 'string') { + allPlants = allPlants.filter( + p => + p.scientificName?.toLowerCase().includes(species.toLowerCase()) || + p.commonName?.toLowerCase().includes(species.toLowerCase()) + ); + } + + // Only include plants with environmental data + const plantsWithEnv = allPlants.filter(p => p.environment); + + if (plantsWithEnv.length === 0) { + return res.status(200).json({ + success: true, + message: 'No plants with environmental data found', + plantsAnalyzed: 0, + }); + } + + const analysis = analyzeGrowthCorrelation(plantsWithEnv); + + // Calculate some additional statistics + const successRate = + (plantsWithEnv.filter( + p => p.status === 'mature' || p.status === 'flowering' || p.status === 'fruiting' + ).length / + plantsWithEnv.length) * + 100; + + const locationTypes = plantsWithEnv.reduce((acc, p) => { + const type = p.environment!.location.type; + acc[type] = (acc[type] || 0) + 1; + return acc; + }, {} as Record); + + const soilTypes = plantsWithEnv.reduce((acc, p) => { + const type = p.environment!.soil.type; + acc[type] = (acc[type] || 0) + 1; + return acc; + }, {} as Record); + + res.status(200).json({ + success: true, + plantsAnalyzed: plantsWithEnv.length, + successRate: Math.round(successRate), + insights: analysis.insights, + statistics: { + locationTypes, + soilTypes, + avgTemperature: + plantsWithEnv.reduce((sum, p) => sum + (p.environment!.climate.temperatureDay || 0), 0) / + plantsWithEnv.length, + avgHumidity: + plantsWithEnv.reduce((sum, p) => sum + (p.environment!.climate.humidityAverage || 0), 0) / + plantsWithEnv.length, + avgSoilPH: + plantsWithEnv.reduce((sum, p) => sum + (p.environment!.soil.pH || 0), 0) / + plantsWithEnv.length, + }, + }); + } catch (error: any) { + console.error('Error analyzing growth correlation:', error); + res.status(500).json({ error: error.message || 'Internal server error' }); + } +} diff --git a/pages/api/environment/compare.ts b/pages/api/environment/compare.ts new file mode 100644 index 0000000..9cfa1be --- /dev/null +++ b/pages/api/environment/compare.ts @@ -0,0 +1,80 @@ +/** + * API Route: Compare growing environments of two plants + * GET /api/environment/compare?plant1=xyz&plant2=abc + */ + +import type { NextApiRequest, NextApiResponse } from 'next'; +import { getBlockchain } from '../../../lib/blockchain/manager'; +import { compareEnvironments } from '../../../lib/environment/analysis'; + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse +) { + if (req.method !== 'GET') { + return res.status(405).json({ error: 'Method not allowed' }); + } + + try { + const { plant1, plant2 } = req.query; + + if (!plant1 || !plant2 || typeof plant1 !== 'string' || typeof plant2 !== 'string') { + return res.status(400).json({ error: 'Missing plant1 and plant2 parameters' }); + } + + const blockchain = getBlockchain(); + const plantData1 = blockchain.getPlant(plant1); + const plantData2 = blockchain.getPlant(plant2); + + if (!plantData1 || !plantData2) { + return res.status(404).json({ error: 'One or both plants not found' }); + } + + if (!plantData1.environment || !plantData2.environment) { + return res.status(400).json({ + error: 'Both plants must have environmental data', + plant1HasData: !!plantData1.environment, + plant2HasData: !!plantData2.environment, + }); + } + + const comparison = compareEnvironments(plantData1.environment, plantData2.environment); + comparison.plant1 = plant1; + comparison.plant2 = plant2; + + res.status(200).json({ + success: true, + plants: { + plant1: { + id: plantData1.id, + commonName: plantData1.commonName, + scientificName: plantData1.scientificName, + owner: plantData1.owner.name, + }, + plant2: { + id: plantData2.id, + commonName: plantData2.commonName, + scientificName: plantData2.scientificName, + owner: plantData2.owner.name, + }, + }, + comparison: { + similarityScore: comparison.score, + similarities: comparison.similarities, + differences: comparison.differences, + }, + interpretation: getScoreInterpretation(comparison.score), + }); + } catch (error: any) { + console.error('Error comparing environments:', error); + res.status(500).json({ error: error.message || 'Internal server error' }); + } +} + +function getScoreInterpretation(score: number): string { + if (score >= 90) return 'Nearly identical growing conditions'; + if (score >= 75) return 'Very similar environments - likely to have similar results'; + if (score >= 60) return 'Moderately similar conditions'; + if (score >= 40) return 'Some similarities but notable differences'; + return 'Very different growing environments'; +} diff --git a/pages/api/environment/recommendations.ts b/pages/api/environment/recommendations.ts new file mode 100644 index 0000000..af04630 --- /dev/null +++ b/pages/api/environment/recommendations.ts @@ -0,0 +1,61 @@ +/** + * API Route: Get environmental recommendations for a plant + * GET /api/environment/recommendations?plantId=xyz + */ + +import type { NextApiRequest, NextApiResponse } from 'next'; +import { getBlockchain } from '../../../lib/blockchain/manager'; +import { + generateRecommendations, + calculateEnvironmentalHealth, +} from '../../../lib/environment/analysis'; + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse +) { + if (req.method !== 'GET') { + return res.status(405).json({ error: 'Method not allowed' }); + } + + try { + const { plantId } = req.query; + + if (!plantId || typeof plantId !== 'string') { + return res.status(400).json({ error: 'Missing plantId parameter' }); + } + + const blockchain = getBlockchain(); + const plant = blockchain.getPlant(plantId); + + if (!plant) { + return res.status(404).json({ error: 'Plant not found' }); + } + + const recommendations = generateRecommendations(plant); + + const healthScore = plant.environment + ? calculateEnvironmentalHealth(plant.environment) + : null; + + res.status(200).json({ + success: true, + plant: { + id: plant.id, + commonName: plant.commonName, + scientificName: plant.scientificName, + }, + environmentalHealth: healthScore, + recommendations, + summary: { + criticalIssues: recommendations.filter(r => r.priority === 'critical').length, + highPriority: recommendations.filter(r => r.priority === 'high').length, + mediumPriority: recommendations.filter(r => r.priority === 'medium').length, + lowPriority: recommendations.filter(r => r.priority === 'low').length, + }, + }); + } catch (error: any) { + console.error('Error generating recommendations:', error); + res.status(500).json({ error: error.message || 'Internal server error' }); + } +} diff --git a/pages/api/environment/similar.ts b/pages/api/environment/similar.ts new file mode 100644 index 0000000..1ca66ab --- /dev/null +++ b/pages/api/environment/similar.ts @@ -0,0 +1,74 @@ +/** + * API Route: Find plants with similar growing environments + * GET /api/environment/similar?plantId=xyz&minScore=70 + */ + +import type { NextApiRequest, NextApiResponse } from 'next'; +import { getBlockchain } from '../../../lib/blockchain/manager'; +import { findSimilarEnvironments } from '../../../lib/environment/analysis'; + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse +) { + if (req.method !== 'GET') { + return res.status(405).json({ error: 'Method not allowed' }); + } + + try { + const { plantId, minScore } = req.query; + + if (!plantId || typeof plantId !== 'string') { + return res.status(400).json({ error: 'Missing plantId parameter' }); + } + + const blockchain = getBlockchain(); + const targetPlant = blockchain.getPlant(plantId); + + if (!targetPlant) { + return res.status(404).json({ error: 'Plant not found' }); + } + + if (!targetPlant.environment) { + return res.status(400).json({ + error: 'Plant has no environmental data', + message: 'Add environmental information to find similar plants', + }); + } + + // Get all plants + const allPlants = Array.from( + new Set(blockchain.chain.map(block => block.plant.id)) + ) + .map(id => blockchain.getPlant(id)!) + .filter(Boolean); + + const minScoreValue = minScore ? parseInt(minScore as string) : 70; + const similarPlants = findSimilarEnvironments(targetPlant, allPlants, minScoreValue); + + res.status(200).json({ + success: true, + targetPlant: { + id: targetPlant.id, + commonName: targetPlant.commonName, + scientificName: targetPlant.scientificName, + }, + similarCount: similarPlants.length, + similar: similarPlants.map(({ plant, comparison }) => ({ + plant: { + id: plant.id, + commonName: plant.commonName, + scientificName: plant.scientificName, + owner: plant.owner.name, + location: plant.location.city || plant.location.country, + }, + similarityScore: comparison.score, + similarities: comparison.similarities, + differences: comparison.differences, + })), + }); + } catch (error: any) { + console.error('Error finding similar environments:', error); + res.status(500).json({ error: error.message || 'Internal server error' }); + } +}