Add comprehensive environmental tracking for plants

Implements detailed tracking of soil composition, nutrients, climate,
lighting, and surrounding environment to help understand plant success
and optimize growing conditions.

Environmental Data Types (lib/environment/types.ts):
- Soil composition (type, pH, texture, drainage, organic matter)
- Soil amendments (compost, perlite, amendments tracking)
- Nutrient profiles (NPK, secondary nutrients, micronutrients, EC, TDS)
- Fertilizer applications (type, schedule, NPK values)
- Lighting conditions (natural/artificial, hours, spectrum, PPFD/DLI)
- Climate tracking (temperature, humidity, airflow, CO2, USDA zones)
- Growing location (indoor/outdoor/greenhouse with details)
- Container information (type, material, size, drainage)
- Watering schedule (method, frequency, water quality/pH)
- Surrounding environment (companion plants, pests, diseases, ecosystem)
- Growth metrics (measurements, health scores, vigor tracking)

Environmental Analysis (lib/environment/analysis.ts):
- Compare environments with similarity scoring (0-100)
- Generate personalized growing recommendations
- Calculate environmental health scores
- Find plants with similar conditions
- Analyze growth correlations across network
- Identify optimal growing conditions

API Endpoints:
- /api/environment/recommendations - Get plant-specific advice
- /api/environment/similar - Find plants with similar conditions
- /api/environment/compare - Compare two plants environments
- /api/environment/analysis - Network-wide growth correlation

Features:
- Comprehensive soil tracking (pH, texture, drainage, amendments)
- Full NPK and micronutrient monitoring
- Sunlight exposure and artificial light tracking
- Temperature and humidity ranges
- Water quality monitoring (pH, TDS, chlorine)
- Pest and disease tracking
- Companion planting recommendations
- Environmental health scoring with priority-based recommendations
- Growth success analysis across similar environments

Integration:
- Updated PlantData type to include environment and growthMetrics
- Compatible with existing blockchain structure
- Optional environmental data (backward compatible)

Use Cases:
- Track what works for your plants
- Learn from successful growers with similar conditions
- Get personalized recommendations based on your setup
- Compare your environment with thriving plants
- Optimize conditions for better yield/health
- Share growing knowledge with the community
- Research optimal conditions by species

This enables growers to:
- Understand why plants thrive or struggle
- Replicate successful growing conditions
- Make data-driven decisions
- Learn from the collective experience
- Improve propagation success rates
This commit is contained in:
Claude 2025-11-16 16:24:38 +00:00
parent ccea9535d4
commit 85591fc348
No known key found for this signature in database
7 changed files with 1001 additions and 0 deletions

View file

@ -1,5 +1,7 @@
// Plant Blockchain Types // Plant Blockchain Types
import { GrowingEnvironment, GrowthMetrics } from '../environment/types';
export interface PlantLocation { export interface PlantLocation {
latitude: number; latitude: number;
longitude: number; longitude: number;
@ -40,6 +42,10 @@ export interface PlantData {
// Plant network // Plant network
childPlants: string[]; // IDs of clones and seeds from this plant childPlants: string[]; // IDs of clones and seeds from this plant
// Environmental data
environment?: GrowingEnvironment;
growthMetrics?: GrowthMetrics;
// Additional metadata // Additional metadata
notes?: string; notes?: string;
images?: string[]; images?: string[];

432
lib/environment/analysis.ts Normal file
View file

@ -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<GrowingEnvironment>;
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<string, number>);
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);
}

253
lib/environment/types.ts Normal file
View file

@ -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;
}

View file

@ -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<string, number>);
const soilTypes = plantsWithEnv.reduce((acc, p) => {
const type = p.environment!.soil.type;
acc[type] = (acc[type] || 0) + 1;
return acc;
}, {} as Record<string, number>);
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' });
}
}

View file

@ -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';
}

View file

@ -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' });
}
}

View file

@ -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' });
}
}