Spaces:
Running
Running
| /** | |
| * Soil Health Analyzer | |
| * Comprehensive soil quality assessment and agricultural insights | |
| */ | |
| export class SoilHealthAnalyzer { | |
| constructor() { | |
| this.soilParameters = { | |
| ph: { | |
| optimal: [6.0, 7.5], | |
| acceptable: [5.5, 8.0], | |
| critical: [4.0, 9.0], | |
| unit: '', | |
| name: 'pH Level' | |
| }, | |
| nitrogen: { | |
| optimal: [20, 40], | |
| acceptable: [10, 60], | |
| critical: [0, 100], | |
| unit: 'ppm', | |
| name: 'Nitrogen (N)' | |
| }, | |
| phosphorus: { | |
| optimal: [30, 60], | |
| acceptable: [15, 80], | |
| critical: [0, 120], | |
| unit: 'ppm', | |
| name: 'Phosphorus (P)' | |
| }, | |
| potassium: { | |
| optimal: [150, 300], | |
| acceptable: [100, 400], | |
| critical: [0, 600], | |
| unit: 'ppm', | |
| name: 'Potassium (K)' | |
| }, | |
| organicMatter: { | |
| optimal: [3.0, 6.0], | |
| acceptable: [2.0, 8.0], | |
| critical: [0.5, 12.0], | |
| unit: '%', | |
| name: 'Organic Matter' | |
| }, | |
| moisture: { | |
| optimal: [40, 60], | |
| acceptable: [25, 75], | |
| critical: [10, 90], | |
| unit: '%', | |
| name: 'Soil Moisture' | |
| }, | |
| temperature: { | |
| optimal: [15, 25], | |
| acceptable: [10, 30], | |
| critical: [0, 40], | |
| unit: '°C', | |
| name: 'Soil Temperature' | |
| }, | |
| salinity: { | |
| optimal: [0, 2], | |
| acceptable: [0, 4], | |
| critical: [0, 8], | |
| unit: 'dS/m', | |
| name: 'Electrical Conductivity' | |
| } | |
| }; | |
| this.cropRequirements = { | |
| tomatoes: { ph: [6.0, 6.8], nitrogen: 'high', phosphorus: 'medium', potassium: 'high' }, | |
| corn: { ph: [6.0, 6.8], nitrogen: 'high', phosphorus: 'high', potassium: 'medium' }, | |
| wheat: { ph: [6.0, 7.0], nitrogen: 'medium', phosphorus: 'medium', potassium: 'medium' }, | |
| soybeans: { ph: [6.0, 7.0], nitrogen: 'low', phosphorus: 'medium', potassium: 'high' }, | |
| lettuce: { ph: [6.0, 7.0], nitrogen: 'high', phosphorus: 'medium', potassium: 'high' }, | |
| carrots: { ph: [5.5, 6.5], nitrogen: 'medium', phosphorus: 'medium', potassium: 'high' }, | |
| potatoes: { ph: [5.0, 6.0], nitrogen: 'medium', phosphorus: 'high', potassium: 'high' }, | |
| beans: { ph: [6.0, 7.0], nitrogen: 'low', phosphorus: 'medium', potassium: 'medium' } | |
| }; | |
| this.soilTypes = { | |
| clay: { drainage: 'poor', fertility: 'high', workability: 'difficult' }, | |
| loam: { drainage: 'good', fertility: 'high', workability: 'excellent' }, | |
| sand: { drainage: 'excellent', fertility: 'low', workability: 'easy' }, | |
| silt: { drainage: 'moderate', fertility: 'medium', workability: 'good' }, | |
| peat: { drainage: 'poor', fertility: 'variable', workability: 'difficult' }, | |
| chalk: { drainage: 'good', fertility: 'low', workability: 'good' } | |
| }; | |
| } | |
| /** | |
| * Analyze soil health from test data or image | |
| */ | |
| async analyzeSoilHealth(data, location = null, cropType = null) { | |
| try { | |
| console.log('🌱 Starting soil health analysis...'); | |
| let soilData; | |
| if (typeof data === 'object' && data.ph !== undefined) { | |
| // Direct test data | |
| soilData = data; | |
| } else if (typeof data === 'string' || data instanceof Blob) { | |
| // Image analysis | |
| soilData = await this.analyzeImageForSoilData(data); | |
| } else { | |
| // Generate mock data for demonstration | |
| soilData = this.generateMockSoilData(location); | |
| } | |
| // Analyze each parameter | |
| const parameterAnalysis = {}; | |
| Object.keys(this.soilParameters).forEach(param => { | |
| if (soilData[param] !== undefined) { | |
| parameterAnalysis[param] = this.analyzeParameter(param, soilData[param]); | |
| } | |
| }); | |
| // Calculate overall soil health | |
| const overallHealth = this.calculateOverallHealth(parameterAnalysis); | |
| // Generate crop-specific recommendations | |
| const cropRecommendations = cropType ? | |
| this.generateCropRecommendations(parameterAnalysis, cropType) : | |
| this.generateGeneralRecommendations(parameterAnalysis); | |
| // Detect soil type | |
| const soilType = this.detectSoilType(soilData); | |
| // Calculate fertility index | |
| const fertilityIndex = this.calculateFertilityIndex(parameterAnalysis); | |
| // Generate improvement plan | |
| const improvementPlan = this.generateImprovementPlan(parameterAnalysis, soilType); | |
| // Create comprehensive analysis | |
| const analysis = { | |
| timestamp: new Date().toISOString(), | |
| location: location, | |
| cropType: cropType, | |
| soilData: soilData, | |
| parameterAnalysis: parameterAnalysis, | |
| overallHealth: overallHealth, | |
| soilType: soilType, | |
| fertilityIndex: fertilityIndex, | |
| cropRecommendations: cropRecommendations, | |
| improvementPlan: improvementPlan, | |
| seasonalAdvice: this.generateSeasonalAdvice(), | |
| confidence: this.calculateConfidence(soilData), | |
| dataSource: soilData.source || 'Analysis', | |
| nextTestDate: this.calculateNextTestDate() | |
| }; | |
| console.log('✅ Soil health analysis completed'); | |
| return analysis; | |
| } catch (error) { | |
| console.error('Soil health analysis failed:', error); | |
| throw new Error(`Soil health analysis failed: ${error.message}`); | |
| } | |
| } | |
| /** | |
| * Analyze soil image for basic parameters | |
| */ | |
| async analyzeImageForSoilData(imageSource) { | |
| return new Promise((resolve, reject) => { | |
| const img = new Image(); | |
| img.onload = () => { | |
| try { | |
| const canvas = document.createElement('canvas'); | |
| const ctx = canvas.getContext('2d'); | |
| canvas.width = img.width; | |
| canvas.height = img.height; | |
| ctx.drawImage(img, 0, 0); | |
| // Analyze image colors and texture | |
| const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); | |
| const soilData = this.extractSoilDataFromImage(imageData); | |
| resolve(soilData); | |
| } catch (error) { | |
| reject(error); | |
| } | |
| }; | |
| img.onerror = reject; | |
| img.src = typeof imageSource === 'string' ? imageSource : URL.createObjectURL(imageSource); | |
| }); | |
| } | |
| /** | |
| * Extract soil data from image analysis | |
| */ | |
| extractSoilDataFromImage(imageData) { | |
| const data = imageData.data; | |
| let totalR = 0, totalG = 0, totalB = 0; | |
| let pixelCount = 0; | |
| // Calculate average color | |
| for (let i = 0; i < data.length; i += 4) { | |
| totalR += data[i]; | |
| totalG += data[i + 1]; | |
| totalB += data[i + 2]; | |
| pixelCount++; | |
| } | |
| const avgR = totalR / pixelCount; | |
| const avgG = totalG / pixelCount; | |
| const avgB = totalB / pixelCount; | |
| // Estimate soil parameters based on color analysis | |
| // This is a simplified approach - real implementation would use ML models | |
| const darkness = (avgR + avgG + avgB) / 3; | |
| const organicMatter = Math.max(1, 8 - (darkness / 32)); // Darker soil = more organic matter | |
| const redness = avgR - avgG; | |
| const ph = 6.5 + (redness / 50); // Reddish soil tends to be more acidic | |
| const moisture = Math.max(10, 70 - (darkness / 4)); // Darker soil often indicates moisture | |
| return { | |
| ph: Math.max(4.0, Math.min(9.0, ph)), | |
| organicMatter: Math.max(0.5, Math.min(12.0, organicMatter)), | |
| moisture: Math.max(10, Math.min(90, moisture)), | |
| nitrogen: 15 + Math.random() * 20, | |
| phosphorus: 25 + Math.random() * 30, | |
| potassium: 120 + Math.random() * 150, | |
| temperature: 18 + Math.random() * 8, | |
| salinity: Math.random() * 3, | |
| source: 'Image Analysis', | |
| colorProfile: { r: Math.round(avgR), g: Math.round(avgG), b: Math.round(avgB) } | |
| }; | |
| } | |
| /** | |
| * Generate mock soil data for demonstration | |
| */ | |
| generateMockSoilData(location = null) { | |
| const baseData = { | |
| ph: 6.5 + (Math.random() - 0.5) * 2, | |
| nitrogen: 20 + Math.random() * 30, | |
| phosphorus: 35 + Math.random() * 40, | |
| potassium: 180 + Math.random() * 200, | |
| organicMatter: 3.5 + Math.random() * 3, | |
| moisture: 45 + Math.random() * 20, | |
| temperature: 18 + Math.random() * 10, | |
| salinity: Math.random() * 4 | |
| }; | |
| // Adjust based on location if provided | |
| if (location) { | |
| const locationLower = location.toLowerCase(); | |
| if (locationLower.includes('desert') || locationLower.includes('arid')) { | |
| baseData.moisture *= 0.3; | |
| baseData.organicMatter *= 0.5; | |
| baseData.salinity *= 2; | |
| } else if (locationLower.includes('forest') || locationLower.includes('woodland')) { | |
| baseData.organicMatter *= 1.5; | |
| baseData.ph -= 0.5; // Forest soils tend to be more acidic | |
| baseData.nitrogen *= 1.3; | |
| } else if (locationLower.includes('agricultural') || locationLower.includes('farm')) { | |
| baseData.nitrogen *= 1.2; | |
| baseData.phosphorus *= 1.1; | |
| baseData.potassium *= 1.1; | |
| } | |
| } | |
| // Add seasonal variation | |
| const month = new Date().getMonth(); | |
| if (month >= 5 && month <= 8) { // Summer | |
| baseData.temperature += 5; | |
| baseData.moisture *= 0.8; | |
| } else if (month >= 11 || month <= 2) { // Winter | |
| baseData.temperature -= 8; | |
| baseData.moisture *= 1.2; | |
| } | |
| return { | |
| ...baseData, | |
| source: 'Simulated Data', | |
| timestamp: new Date().toISOString() | |
| }; | |
| } | |
| /** | |
| * Analyze individual parameter | |
| */ | |
| analyzeParameter(paramName, value) { | |
| const param = this.soilParameters[paramName]; | |
| if (!param) return null; | |
| let status, recommendation; | |
| if (value >= param.optimal[0] && value <= param.optimal[1]) { | |
| status = 'optimal'; | |
| recommendation = `${param.name} is in the optimal range.`; | |
| } else if (value >= param.acceptable[0] && value <= param.acceptable[1]) { | |
| status = 'acceptable'; | |
| if (value < param.optimal[0]) { | |
| recommendation = `${param.name} is slightly low. Consider gradual improvement.`; | |
| } else { | |
| recommendation = `${param.name} is slightly high. Monitor and adjust if needed.`; | |
| } | |
| } else if (value >= param.critical[0] && value <= param.critical[1]) { | |
| status = 'critical'; | |
| if (value < param.acceptable[0]) { | |
| recommendation = `${param.name} is critically low. Immediate action required.`; | |
| } else { | |
| recommendation = `${param.name} is critically high. Immediate correction needed.`; | |
| } | |
| } else { | |
| status = 'extreme'; | |
| recommendation = `${param.name} is at extreme levels. Professional consultation recommended.`; | |
| } | |
| return { | |
| value: Math.round(value * 100) / 100, | |
| status: status, | |
| recommendation: recommendation, | |
| optimal: param.optimal, | |
| unit: param.unit, | |
| name: param.name | |
| }; | |
| } | |
| /** | |
| * Calculate overall soil health | |
| */ | |
| calculateOverallHealth(parameterAnalysis) { | |
| const statusScores = { | |
| optimal: 100, | |
| acceptable: 75, | |
| critical: 40, | |
| extreme: 10 | |
| }; | |
| let totalScore = 0; | |
| let paramCount = 0; | |
| Object.values(parameterAnalysis).forEach(analysis => { | |
| if (analysis && analysis.status) { | |
| totalScore += statusScores[analysis.status] || 0; | |
| paramCount++; | |
| } | |
| }); | |
| const averageScore = paramCount > 0 ? totalScore / paramCount : 0; | |
| let healthCategory; | |
| if (averageScore >= 90) healthCategory = 'Excellent'; | |
| else if (averageScore >= 75) healthCategory = 'Good'; | |
| else if (averageScore >= 60) healthCategory = 'Fair'; | |
| else if (averageScore >= 40) healthCategory = 'Poor'; | |
| else healthCategory = 'Critical'; | |
| return { | |
| score: Math.round(averageScore), | |
| category: healthCategory, | |
| color: this.getHealthColor(healthCategory) | |
| }; | |
| } | |
| getHealthColor(category) { | |
| const colors = { | |
| 'Excellent': '#4CAF50', | |
| 'Good': '#8BC34A', | |
| 'Fair': '#FF9800', | |
| 'Poor': '#FF5722', | |
| 'Critical': '#F44336' | |
| }; | |
| return colors[category] || '#666666'; | |
| } | |
| /** | |
| * Detect soil type based on parameters | |
| */ | |
| detectSoilType(soilData) { | |
| // Simplified soil type detection based on available parameters | |
| // Real implementation would use particle size analysis | |
| let soilType = 'loam'; // Default | |
| let confidence = 60; | |
| // Use organic matter and drainage characteristics to estimate | |
| if (soilData.organicMatter > 8) { | |
| soilType = 'peat'; | |
| confidence = 70; | |
| } else if (soilData.moisture < 30 && soilData.salinity < 1) { | |
| soilType = 'sand'; | |
| confidence = 65; | |
| } else if (soilData.moisture > 70 && soilData.organicMatter < 2) { | |
| soilType = 'clay'; | |
| confidence = 65; | |
| } else if (soilData.ph > 7.5 && soilData.salinity < 1) { | |
| soilType = 'chalk'; | |
| confidence = 60; | |
| } | |
| return { | |
| type: soilType, | |
| confidence: confidence, | |
| characteristics: this.soilTypes[soilType] || {}, | |
| description: this.getSoilTypeDescription(soilType) | |
| }; | |
| } | |
| getSoilTypeDescription(soilType) { | |
| const descriptions = { | |
| clay: 'Heavy soil that retains water and nutrients well but can be difficult to work.', | |
| loam: 'Ideal garden soil with good drainage, fertility, and workability.', | |
| sand: 'Light, well-draining soil that warms quickly but requires frequent watering and fertilizing.', | |
| silt: 'Smooth, fertile soil with good water retention but can compact easily.', | |
| peat: 'Organic-rich soil with excellent water retention but may be acidic.', | |
| chalk: 'Alkaline soil that drains well but may lack nutrients and organic matter.' | |
| }; | |
| return descriptions[soilType] || 'Mixed soil type with variable characteristics.'; | |
| } | |
| /** | |
| * Calculate fertility index | |
| */ | |
| calculateFertilityIndex(parameterAnalysis) { | |
| const keyNutrients = ['nitrogen', 'phosphorus', 'potassium', 'organicMatter']; | |
| let fertilityScore = 0; | |
| let nutrientCount = 0; | |
| keyNutrients.forEach(nutrient => { | |
| const analysis = parameterAnalysis[nutrient]; | |
| if (analysis) { | |
| const statusScores = { optimal: 100, acceptable: 75, critical: 40, extreme: 10 }; | |
| fertilityScore += statusScores[analysis.status] || 0; | |
| nutrientCount++; | |
| } | |
| }); | |
| const index = nutrientCount > 0 ? fertilityScore / nutrientCount : 0; | |
| let category; | |
| if (index >= 90) category = 'Very High'; | |
| else if (index >= 75) category = 'High'; | |
| else if (index >= 60) category = 'Medium'; | |
| else if (index >= 40) category = 'Low'; | |
| else category = 'Very Low'; | |
| return { | |
| score: Math.round(index), | |
| category: category, | |
| description: this.getFertilityDescription(category) | |
| }; | |
| } | |
| getFertilityDescription(category) { | |
| const descriptions = { | |
| 'Very High': 'Excellent nutrient levels support vigorous plant growth.', | |
| 'High': 'Good nutrient availability for most crops.', | |
| 'Medium': 'Adequate nutrients with room for improvement.', | |
| 'Low': 'Limited nutrients may restrict plant growth.', | |
| 'Very Low': 'Significant nutrient deficiencies require immediate attention.' | |
| }; | |
| return descriptions[category] || 'Nutrient status unclear.'; | |
| } | |
| /** | |
| * Generate crop-specific recommendations | |
| */ | |
| generateCropRecommendations(parameterAnalysis, cropType) { | |
| const cropReqs = this.cropRequirements[cropType.toLowerCase()]; | |
| if (!cropReqs) { | |
| return this.generateGeneralRecommendations(parameterAnalysis); | |
| } | |
| const recommendations = []; | |
| // Check pH requirements | |
| const phAnalysis = parameterAnalysis.ph; | |
| if (phAnalysis) { | |
| const [minPh, maxPh] = cropReqs.ph; | |
| if (phAnalysis.value < minPh) { | |
| recommendations.push({ | |
| priority: 'high', | |
| category: 'pH', | |
| issue: `pH too low for ${cropType}`, | |
| action: 'Add lime to raise pH', | |
| target: `${minPh}-${maxPh}`, | |
| timeframe: '2-4 weeks' | |
| }); | |
| } else if (phAnalysis.value > maxPh) { | |
| recommendations.push({ | |
| priority: 'high', | |
| category: 'pH', | |
| issue: `pH too high for ${cropType}`, | |
| action: 'Add sulfur or organic matter to lower pH', | |
| target: `${minPh}-${maxPh}`, | |
| timeframe: '4-8 weeks' | |
| }); | |
| } | |
| } | |
| // Check nutrient requirements | |
| const nutrientReqs = { | |
| nitrogen: cropReqs.nitrogen, | |
| phosphorus: cropReqs.phosphorus, | |
| potassium: cropReqs.potassium | |
| }; | |
| Object.keys(nutrientReqs).forEach(nutrient => { | |
| const analysis = parameterAnalysis[nutrient]; | |
| const requirement = nutrientReqs[nutrient]; | |
| if (analysis && requirement) { | |
| if (requirement === 'high' && analysis.status !== 'optimal') { | |
| recommendations.push({ | |
| priority: 'medium', | |
| category: 'nutrition', | |
| issue: `${cropType} requires high ${nutrient}`, | |
| action: this.getNutrientAction(nutrient, 'increase'), | |
| target: 'Optimal range', | |
| timeframe: '1-2 weeks' | |
| }); | |
| } else if (requirement === 'low' && analysis.status === 'critical' && analysis.value > analysis.optimal[1]) { | |
| recommendations.push({ | |
| priority: 'low', | |
| category: 'nutrition', | |
| issue: `${nutrient} may be too high for ${cropType}`, | |
| action: 'Reduce fertilizer application', | |
| target: 'Moderate levels', | |
| timeframe: '2-4 weeks' | |
| }); | |
| } | |
| } | |
| }); | |
| return recommendations; | |
| } | |
| /** | |
| * Generate general recommendations | |
| */ | |
| generateGeneralRecommendations(parameterAnalysis) { | |
| const recommendations = []; | |
| Object.keys(parameterAnalysis).forEach(param => { | |
| const analysis = parameterAnalysis[param]; | |
| if (analysis && (analysis.status === 'critical' || analysis.status === 'extreme')) { | |
| recommendations.push({ | |
| priority: analysis.status === 'extreme' ? 'high' : 'medium', | |
| category: param, | |
| issue: analysis.recommendation, | |
| action: this.getParameterAction(param, analysis), | |
| timeframe: this.getActionTimeframe(param) | |
| }); | |
| } | |
| }); | |
| return recommendations; | |
| } | |
| getNutrientAction(nutrient, direction) { | |
| const actions = { | |
| nitrogen: { | |
| increase: 'Apply nitrogen-rich fertilizer or compost', | |
| decrease: 'Reduce nitrogen fertilizer, plant nitrogen-fixing crops' | |
| }, | |
| phosphorus: { | |
| increase: 'Add bone meal or phosphorus fertilizer', | |
| decrease: 'Avoid phosphorus fertilizers, improve drainage' | |
| }, | |
| potassium: { | |
| increase: 'Apply potash or wood ash', | |
| decrease: 'Improve soil drainage, avoid potassium fertilizers' | |
| } | |
| }; | |
| return actions[nutrient]?.[direction] || `Adjust ${nutrient} levels`; | |
| } | |
| getParameterAction(param, analysis) { | |
| const actions = { | |
| ph: analysis.value < analysis.optimal[0] ? 'Add lime to raise pH' : 'Add sulfur to lower pH', | |
| organicMatter: 'Add compost, manure, or organic mulch', | |
| moisture: analysis.value < analysis.optimal[0] ? 'Improve irrigation' : 'Improve drainage', | |
| salinity: 'Improve drainage, leach salts with fresh water', | |
| temperature: 'Use mulch to moderate soil temperature' | |
| }; | |
| return actions[param] || 'Consult soil specialist'; | |
| } | |
| getActionTimeframe(param) { | |
| const timeframes = { | |
| ph: '4-8 weeks', | |
| nitrogen: '1-2 weeks', | |
| phosphorus: '2-4 weeks', | |
| potassium: '2-4 weeks', | |
| organicMatter: '2-6 months', | |
| moisture: '1-2 weeks', | |
| salinity: '4-12 weeks', | |
| temperature: '1-4 weeks' | |
| }; | |
| return timeframes[param] || '2-4 weeks'; | |
| } | |
| /** | |
| * Generate improvement plan | |
| */ | |
| generateImprovementPlan(parameterAnalysis, soilType) { | |
| const plan = { | |
| immediate: [], // 0-2 weeks | |
| shortTerm: [], // 2-8 weeks | |
| longTerm: [], // 2+ months | |
| seasonal: [] | |
| }; | |
| // Immediate actions | |
| Object.keys(parameterAnalysis).forEach(param => { | |
| const analysis = parameterAnalysis[param]; | |
| if (analysis && analysis.status === 'extreme') { | |
| plan.immediate.push({ | |
| action: `Address critical ${param} levels`, | |
| description: analysis.recommendation, | |
| priority: 'critical' | |
| }); | |
| } | |
| }); | |
| // Short-term actions | |
| if (parameterAnalysis.ph && parameterAnalysis.ph.status !== 'optimal') { | |
| plan.shortTerm.push({ | |
| action: 'Adjust soil pH', | |
| description: parameterAnalysis.ph.recommendation, | |
| priority: 'high' | |
| }); | |
| } | |
| // Long-term actions | |
| if (parameterAnalysis.organicMatter && parameterAnalysis.organicMatter.status !== 'optimal') { | |
| plan.longTerm.push({ | |
| action: 'Improve organic matter content', | |
| description: 'Regular addition of compost and organic materials', | |
| priority: 'medium' | |
| }); | |
| } | |
| // Seasonal recommendations | |
| plan.seasonal = this.generateSeasonalAdvice(); | |
| return plan; | |
| } | |
| /** | |
| * Generate seasonal advice | |
| */ | |
| generateSeasonalAdvice() { | |
| const month = new Date().getMonth(); | |
| const season = month >= 2 && month <= 4 ? 'spring' : | |
| month >= 5 && month <= 7 ? 'summer' : | |
| month >= 8 && month <= 10 ? 'fall' : 'winter'; | |
| const advice = { | |
| spring: [ | |
| 'Test soil before planting season', | |
| 'Add compost and organic matter', | |
| 'Apply pre-plant fertilizers', | |
| 'Improve drainage if needed' | |
| ], | |
| summer: [ | |
| 'Monitor soil moisture regularly', | |
| 'Apply mulch to conserve water', | |
| 'Side-dress crops with nutrients', | |
| 'Watch for nutrient deficiencies' | |
| ], | |
| fall: [ | |
| 'Conduct end-of-season soil test', | |
| 'Add lime if pH adjustment needed', | |
| 'Plant cover crops', | |
| 'Add organic matter for winter decomposition' | |
| ], | |
| winter: [ | |
| 'Plan next year\'s soil improvements', | |
| 'Order soil amendments', | |
| 'Protect soil from erosion', | |
| 'Review and analyze soil test results' | |
| ] | |
| }; | |
| return { | |
| currentSeason: season, | |
| recommendations: advice[season] || [] | |
| }; | |
| } | |
| /** | |
| * Calculate confidence in analysis | |
| */ | |
| calculateConfidence(soilData) { | |
| let confidence = 85; // Base confidence | |
| // Increase confidence for more complete data | |
| const parameterCount = Object.keys(soilData).filter(key => | |
| typeof soilData[key] === 'number' && key !== 'timestamp' | |
| ).length; | |
| if (parameterCount >= 6) confidence += 10; | |
| else if (parameterCount >= 4) confidence += 5; | |
| else if (parameterCount < 3) confidence -= 15; | |
| // Adjust based on data source | |
| if (soilData.source === 'Lab Analysis') confidence += 10; | |
| else if (soilData.source === 'Image Analysis') confidence -= 10; | |
| return Math.max(60, Math.min(98, confidence)); | |
| } | |
| /** | |
| * Calculate next test date | |
| */ | |
| calculateNextTestDate() { | |
| const nextTest = new Date(); | |
| nextTest.setMonth(nextTest.getMonth() + 6); // Test every 6 months | |
| return nextTest.toISOString().split('T')[0]; | |
| } | |
| } | |
| // Create singleton instance | |
| export const soilHealthAnalyzer = new SoilHealthAnalyzer(); | |
| // Export main analysis function | |
| export const analyzeSoilHealth = async (data, location, cropType) => { | |
| try { | |
| return await soilHealthAnalyzer.analyzeSoilHealth(data, location, cropType); | |
| } catch (error) { | |
| console.error('Soil health analysis error:', error); | |
| throw error; | |
| } | |
| }; | |
| export default soilHealthAnalyzer; |