Spaces:
Sleeping
Sleeping
Upload 31 files
Browse files- .gitattributes +7 -0
- models/ai +0 -0
- models/au +0 -0
- models/central_coordinator.py +204 -0
- models/enhanced_pest_predictor.py +419 -0
- models/enhanced_weather_analyst.py +363 -0
- models/farmer_advisor.py +100 -0
- models/farmer_advisor_model.pkl +0 -0
- models/farmer_encoders.pkl +0 -0
- models/market_Researcher.py +114 -0
- models/market_encoder_corn.pkl +0 -0
- models/market_encoder_rice.pkl +0 -0
- models/market_encoder_soybean.pkl +0 -0
- models/market_encoder_wheat.pkl +0 -0
- models/market_model_corn.pkl +3 -0
- models/market_model_rice.pkl +3 -0
- models/market_model_soybean.pkl +3 -0
- models/market_model_wheat.pkl +3 -0
- models/market_scaler_corn.pkl +0 -0
- models/market_scaler_rice.pkl +0 -0
- models/market_scaler_soybean.pkl +0 -0
- models/market_scaler_wheat.pkl +0 -0
- models/pest_disease_predictor.py +46 -0
- models/rain_model.pkl +3 -0
- models/speech_interface.py +487 -0
- models/sustainability_Expert.py +74 -0
- models/sustainable_farming.db +3 -0
- models/temp_model.pkl +3 -0
- models/test_recommendation.py +29 -0
- models/weather_Analyst.py +112 -0
- models/weather_api.py +52 -0
- models/weather_scaler.pkl +0 -0
.gitattributes
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
models/market_model_corn.pkl filter=lfs diff=lfs merge=lfs -text
|
| 2 |
+
models/market_model_rice.pkl filter=lfs diff=lfs merge=lfs -text
|
| 3 |
+
models/market_model_soybean.pkl filter=lfs diff=lfs merge=lfs -text
|
| 4 |
+
models/market_model_wheat.pkl filter=lfs diff=lfs merge=lfs -text
|
| 5 |
+
models/rain_model.pkl filter=lfs diff=lfs merge=lfs -text
|
| 6 |
+
models/sustainable_farming.db filter=lfs diff=lfs merge=lfs -text
|
| 7 |
+
models/temp_model.pkl filter=lfs diff=lfs merge=lfs -text
|
models/ai
ADDED
|
File without changes
|
models/au
ADDED
|
File without changes
|
models/central_coordinator.py
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import joblib
|
| 2 |
+
from Models.farmer_advisor import FarmerAdvisor
|
| 3 |
+
from Models.market_Researcher import MarketResearcher
|
| 4 |
+
from Models.weather_Analyst import WeatherAnalyst
|
| 5 |
+
from Models.sustainability_Expert import SustainabilityExpert
|
| 6 |
+
import matplotlib.pyplot as plt
|
| 7 |
+
from Models import weather_api
|
| 8 |
+
from Models.pest_disease_predictor import PestDiseasePredictor
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
class CentralCoordinator:
|
| 12 |
+
def __init__(self):
|
| 13 |
+
db_path = "database/sustainable_farming.db"
|
| 14 |
+
self.farmer_advisor = FarmerAdvisor(db_path=db_path)
|
| 15 |
+
self.market_researcher = MarketResearcher(db_path=db_path)
|
| 16 |
+
self.weather_analyst = WeatherAnalyst(db_path=db_path)
|
| 17 |
+
self.sustainability_expert = SustainabilityExpert(db_path=db_path)
|
| 18 |
+
self.pest_predictor = PestDiseasePredictor()
|
| 19 |
+
|
| 20 |
+
def generate_recommendation(self, soil_ph, soil_moisture, temperature, rainfall,
|
| 21 |
+
fertilizer, pesticide, crop_yield, city_name=None):
|
| 22 |
+
warnings = []
|
| 23 |
+
# If city_name is provided, fetch real-time weather
|
| 24 |
+
if city_name:
|
| 25 |
+
try:
|
| 26 |
+
weather = weather_api.get_current_weather(city_name)
|
| 27 |
+
temperature = weather['temperature']
|
| 28 |
+
rainfall = weather['rainfall']
|
| 29 |
+
except Exception as e:
|
| 30 |
+
warnings.append(f"Weather API error: {e}")
|
| 31 |
+
|
| 32 |
+
# 1. Recommend crop using FarmerAdvisor
|
| 33 |
+
crop = self.farmer_advisor.recommend(
|
| 34 |
+
soil_ph, soil_moisture, temperature, rainfall,
|
| 35 |
+
fertilizer, pesticide, crop_yield
|
| 36 |
+
)
|
| 37 |
+
|
| 38 |
+
# Pest/Disease prediction
|
| 39 |
+
pest_advice = self.pest_predictor.predict(
|
| 40 |
+
crop_type=crop,
|
| 41 |
+
soil_ph=soil_ph,
|
| 42 |
+
soil_moisture=soil_moisture,
|
| 43 |
+
temperature=temperature,
|
| 44 |
+
rainfall=rainfall
|
| 45 |
+
)
|
| 46 |
+
|
| 47 |
+
# 2. Prepare dummy input for MarketResearcher
|
| 48 |
+
market_features = {
|
| 49 |
+
'Demand_Index': 0.5,
|
| 50 |
+
'Supply_Index': 0.5,
|
| 51 |
+
'Competitor_Price_per_ton': 1000.0,
|
| 52 |
+
'Economic_Indicator': 0.8,
|
| 53 |
+
'Weather_Impact_Score': 0.7,
|
| 54 |
+
'Seasonal_Factor': 'Medium',
|
| 55 |
+
'Consumer_Trend_Index': 0.6
|
| 56 |
+
}
|
| 57 |
+
|
| 58 |
+
# 3. Market forecast for recommended crop
|
| 59 |
+
market_forecast = self.market_researcher.forecast(product=crop, input_features=market_features)
|
| 60 |
+
market_score = market_forecast[0] / 1000 # Normalize
|
| 61 |
+
|
| 62 |
+
# 4. Weather forecast
|
| 63 |
+
weather_forecast = self.weather_analyst.forecast(
|
| 64 |
+
soil_ph=soil_ph,
|
| 65 |
+
soil_moisture=soil_moisture,
|
| 66 |
+
fertilizer=fertilizer,
|
| 67 |
+
pesticide=pesticide
|
| 68 |
+
)
|
| 69 |
+
predicted_temp = weather_forecast['temperature'][0]
|
| 70 |
+
predicted_rain = weather_forecast['rainfall'][0]
|
| 71 |
+
|
| 72 |
+
# 5. Weather suitability score
|
| 73 |
+
weather_score = 1 - abs(predicted_temp - temperature) / 50 - abs(predicted_rain - rainfall) / 100
|
| 74 |
+
weather_score = max(0, round(weather_score, 2))
|
| 75 |
+
|
| 76 |
+
# 6. Get sustainability scores
|
| 77 |
+
scores = self.sustainability_expert.evaluate(
|
| 78 |
+
[crop],
|
| 79 |
+
soil_ph=soil_ph,
|
| 80 |
+
soil_moisture=soil_moisture,
|
| 81 |
+
rainfall=rainfall,
|
| 82 |
+
fertilizer=fertilizer,
|
| 83 |
+
pesticide=pesticide,
|
| 84 |
+
crop_yield=crop_yield
|
| 85 |
+
)
|
| 86 |
+
|
| 87 |
+
# Get the scores dictionary from the tuple returned by evaluate
|
| 88 |
+
sustainability_scores = scores[1] # Dictionary with all scores
|
| 89 |
+
|
| 90 |
+
# 7. Final weighted score
|
| 91 |
+
final_score = (
|
| 92 |
+
0.25 * market_score +
|
| 93 |
+
0.20 * weather_score +
|
| 94 |
+
0.20 * sustainability_scores['sustainability'] +
|
| 95 |
+
0.15 * sustainability_scores['carbon'] +
|
| 96 |
+
0.10 * sustainability_scores['water'] +
|
| 97 |
+
0.10 * sustainability_scores['erosion']
|
| 98 |
+
)
|
| 99 |
+
|
| 100 |
+
# 8. Enhanced Weather Warnings
|
| 101 |
+
if city_name:
|
| 102 |
+
# General weather hazards
|
| 103 |
+
if temperature > 40:
|
| 104 |
+
warnings.append("Warning: High temperature detected! Crop stress and yield loss possible.")
|
| 105 |
+
if rainfall > 50:
|
| 106 |
+
warnings.append("Warning: Heavy rainfall detected! Risk of flooding and waterlogging.")
|
| 107 |
+
if temperature < 5:
|
| 108 |
+
warnings.append("Warning: Low temperature detected! Frost risk and stunted growth possible.")
|
| 109 |
+
if rainfall < 5:
|
| 110 |
+
warnings.append("Warning: Very low rainfall detected! Drought risk and irrigation needed.")
|
| 111 |
+
# Crop-specific suitability (example ranges, can be refined per crop)
|
| 112 |
+
crop_temp_ranges = {
|
| 113 |
+
'Wheat': (10, 25),
|
| 114 |
+
'Rice': (20, 35),
|
| 115 |
+
'Corn': (15, 35),
|
| 116 |
+
'Soybeans': (15, 30),
|
| 117 |
+
'Cotton': (20, 35)
|
| 118 |
+
}
|
| 119 |
+
crop_rain_ranges = {
|
| 120 |
+
'Wheat': (30, 90),
|
| 121 |
+
'Rice': (100, 200),
|
| 122 |
+
'Corn': (50, 120),
|
| 123 |
+
'Soybeans': (50, 100),
|
| 124 |
+
'Cotton': (50, 100)
|
| 125 |
+
}
|
| 126 |
+
temp_range = crop_temp_ranges.get(crop)
|
| 127 |
+
rain_range = crop_rain_ranges.get(crop)
|
| 128 |
+
if temp_range:
|
| 129 |
+
if not (temp_range[0] <= temperature <= temp_range[1]):
|
| 130 |
+
warnings.append(f"Warning: Real-time temperature ({temperature}°C) is outside the optimal range for {crop} ({temp_range[0]}–{temp_range[1]}°C).")
|
| 131 |
+
if rain_range:
|
| 132 |
+
if not (rain_range[0] <= rainfall <= rain_range[1]):
|
| 133 |
+
warnings.append(f"Warning: Real-time rainfall ({rainfall} mm) is outside the optimal range for {crop} ({rain_range[0]}–{rain_range[1]} mm).")
|
| 134 |
+
# Severe weather
|
| 135 |
+
if temperature > 45:
|
| 136 |
+
warnings.append("Severe Alert: Extreme heat! Crop failure likely.")
|
| 137 |
+
if temperature < 0:
|
| 138 |
+
warnings.append("Severe Alert: Freezing conditions! Crop loss likely.")
|
| 139 |
+
if rainfall > 100:
|
| 140 |
+
warnings.append("Severe Alert: Torrential rain! Flooding and root rot risk.")
|
| 141 |
+
|
| 142 |
+
result = {
|
| 143 |
+
'Recommended Crop': crop,
|
| 144 |
+
'Market Score': round(market_score, 2),
|
| 145 |
+
'Weather Suitability Score': weather_score,
|
| 146 |
+
'Sustainability Score': round(sustainability_scores['sustainability'], 2),
|
| 147 |
+
'Carbon Footprint Score': round(sustainability_scores['carbon'], 2),
|
| 148 |
+
'Water Score': round(sustainability_scores['water'], 2),
|
| 149 |
+
'Erosion Score': round(sustainability_scores['erosion'], 2),
|
| 150 |
+
'Final Score': round(final_score, 2),
|
| 151 |
+
'Predicted Temperature': round(predicted_temp, 2),
|
| 152 |
+
'Predicted Rainfall': round(predicted_rain, 2),
|
| 153 |
+
'Real-Time Temperature': round(temperature, 2) if city_name else None,
|
| 154 |
+
'Real-Time Rainfall': round(rainfall, 2) if city_name else None,
|
| 155 |
+
'Warnings': warnings,
|
| 156 |
+
'Pest/Disease Advice': pest_advice
|
| 157 |
+
}
|
| 158 |
+
return result
|
| 159 |
+
|
| 160 |
+
@staticmethod
|
| 161 |
+
def plot_scores(result):
|
| 162 |
+
# Extract relevant numeric scores
|
| 163 |
+
labels = []
|
| 164 |
+
values = []
|
| 165 |
+
for key in ['Market Score', 'Weather Suitability Score', 'Sustainability Score',
|
| 166 |
+
'Carbon Footprint Score', 'Water Score', 'Erosion Score', 'Final Score']:
|
| 167 |
+
val = result.get(key)
|
| 168 |
+
if val is not None:
|
| 169 |
+
labels.append(key)
|
| 170 |
+
values.append(val)
|
| 171 |
+
|
| 172 |
+
# Plot
|
| 173 |
+
plt.figure(figsize=(10, 8))
|
| 174 |
+
colors = ['#4caf50', '#2196f3', '#ff9800', '#607d8b',
|
| 175 |
+
'#00bcd4', '#795548', '#e91e63']
|
| 176 |
+
|
| 177 |
+
# Create pie chart
|
| 178 |
+
plt.pie(values, labels=labels, colors=colors, autopct='%1.1f%%',
|
| 179 |
+
startangle=90, shadow=True)
|
| 180 |
+
plt.title('Crop Recommendation Score Distribution')
|
| 181 |
+
plt.axis('equal') # Equal aspect ratio ensures that pie is drawn as a circle
|
| 182 |
+
plt.tight_layout()
|
| 183 |
+
plt.show()
|
| 184 |
+
|
| 185 |
+
|
| 186 |
+
# Run it directly (for testing)
|
| 187 |
+
if __name__ == "__main__":
|
| 188 |
+
coordinator = CentralCoordinator()
|
| 189 |
+
result = coordinator.generate_recommendation(
|
| 190 |
+
soil_ph=6.5,
|
| 191 |
+
soil_moisture=35,
|
| 192 |
+
temperature=27,
|
| 193 |
+
rainfall=60,
|
| 194 |
+
fertilizer=20,
|
| 195 |
+
pesticide=5,
|
| 196 |
+
crop_yield=3.5,
|
| 197 |
+
city_name="New York"
|
| 198 |
+
)
|
| 199 |
+
|
| 200 |
+
print("\n --- Final Recommendation ---")
|
| 201 |
+
for k, v in result.items():
|
| 202 |
+
print(f"{k}: {v}")
|
| 203 |
+
|
| 204 |
+
CentralCoordinator.plot_scores(result)
|
models/enhanced_pest_predictor.py
ADDED
|
@@ -0,0 +1,419 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import logging
|
| 3 |
+
from typing import Dict, List, Optional, Tuple
|
| 4 |
+
from datetime import datetime, timedelta
|
| 5 |
+
import pandas as pd
|
| 6 |
+
from dotenv import load_dotenv
|
| 7 |
+
from transformers import AutoTokenizer, AutoModelForCausalLM
|
| 8 |
+
import torch
|
| 9 |
+
import json
|
| 10 |
+
|
| 11 |
+
# Load environment variables
|
| 12 |
+
load_dotenv()
|
| 13 |
+
|
| 14 |
+
# Set up logging
|
| 15 |
+
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
| 16 |
+
logger = logging.getLogger(__name__)
|
| 17 |
+
|
| 18 |
+
class EnhancedPestDiseasePredictor:
|
| 19 |
+
"""
|
| 20 |
+
Enhanced pest and disease predictor using Llama 2 for intelligent analysis
|
| 21 |
+
"""
|
| 22 |
+
|
| 23 |
+
def __init__(self):
|
| 24 |
+
# Initialize Llama 2 model for pest/disease analysis
|
| 25 |
+
self.device = "cuda" if torch.cuda.is_available() else "cpu"
|
| 26 |
+
self.model_name = "meta-llama/Llama-2-7b-chat-hf"
|
| 27 |
+
|
| 28 |
+
try:
|
| 29 |
+
self.tokenizer = AutoTokenizer.from_pretrained(self.model_name)
|
| 30 |
+
self.model = AutoModelForCausalLM.from_pretrained(
|
| 31 |
+
self.model_name,
|
| 32 |
+
torch_dtype=torch.float16 if self.device == "cuda" else torch.float32,
|
| 33 |
+
device_map="auto" if self.device == "cuda" else None
|
| 34 |
+
)
|
| 35 |
+
logger.info(f"Loaded Llama 2 model for pest prediction on {self.device}")
|
| 36 |
+
except Exception as e:
|
| 37 |
+
logger.warning(f"Could not load Llama 2 model: {e}. Using rule-based fallback.")
|
| 38 |
+
self.model = None
|
| 39 |
+
self.tokenizer = None
|
| 40 |
+
|
| 41 |
+
# Pest and disease database
|
| 42 |
+
self.pest_database = self._load_pest_database()
|
| 43 |
+
self.disease_database = self._load_disease_database()
|
| 44 |
+
|
| 45 |
+
def _load_pest_database(self) -> Dict:
|
| 46 |
+
"""Load comprehensive pest database"""
|
| 47 |
+
return {
|
| 48 |
+
'wheat': {
|
| 49 |
+
'aphids': {
|
| 50 |
+
'conditions': {'temp_range': (15, 25), 'humidity_range': (40, 80), 'rainfall_range': (20, 100)},
|
| 51 |
+
'symptoms': 'Yellowing leaves, sticky honeydew, stunted growth',
|
| 52 |
+
'treatment': 'Neem oil spray, beneficial insects, resistant varieties',
|
| 53 |
+
'risk_factors': ['high_nitrogen', 'dense_planting', 'poor_air_circulation']
|
| 54 |
+
},
|
| 55 |
+
'rust': {
|
| 56 |
+
'conditions': {'temp_range': (15, 25), 'humidity_range': (70, 95), 'rainfall_range': (50, 150)},
|
| 57 |
+
'symptoms': 'Orange or brown pustules on leaves and stems',
|
| 58 |
+
'treatment': 'Fungicide application, crop rotation, resistant varieties',
|
| 59 |
+
'risk_factors': ['high_humidity', 'warm_temperatures', 'previous_infestation']
|
| 60 |
+
},
|
| 61 |
+
'armyworm': {
|
| 62 |
+
'conditions': {'temp_range': (20, 30), 'humidity_range': (50, 90), 'rainfall_range': (30, 120)},
|
| 63 |
+
'symptoms': 'Chewed leaves, defoliation, larvae visible',
|
| 64 |
+
'treatment': 'Bacillus thuringiensis, pheromone traps, natural predators',
|
| 65 |
+
'risk_factors': ['warm_weather', 'high_humidity', 'dense_vegetation']
|
| 66 |
+
}
|
| 67 |
+
},
|
| 68 |
+
'rice': {
|
| 69 |
+
'brown_planthopper': {
|
| 70 |
+
'conditions': {'temp_range': (25, 35), 'humidity_range': (70, 95), 'rainfall_range': (100, 300)},
|
| 71 |
+
'symptoms': 'Yellowing and wilting, honeydew secretion, sooty mold',
|
| 72 |
+
'treatment': 'Systemic insecticides, resistant varieties, proper water management',
|
| 73 |
+
'risk_factors': ['high_nitrogen', 'excessive_irrigation', 'dense_planting']
|
| 74 |
+
},
|
| 75 |
+
'bacterial_leaf_blight': {
|
| 76 |
+
'conditions': {'temp_range': (25, 35), 'humidity_range': (80, 95), 'rainfall_range': (150, 400)},
|
| 77 |
+
'symptoms': 'Yellow stripes on leaves, wilting, plant death',
|
| 78 |
+
'treatment': 'Copper-based fungicides, resistant varieties, proper drainage',
|
| 79 |
+
'risk_factors': ['high_humidity', 'warm_temperatures', 'poor_drainage']
|
| 80 |
+
},
|
| 81 |
+
'rice_blast': {
|
| 82 |
+
'conditions': {'temp_range': (20, 30), 'humidity_range': (85, 95), 'rainfall_range': (100, 300)},
|
| 83 |
+
'symptoms': 'Diamond-shaped lesions on leaves, neck rot, yield loss',
|
| 84 |
+
'treatment': 'Fungicide application, resistant varieties, proper spacing',
|
| 85 |
+
'risk_factors': ['high_humidity', 'cool_temperatures', 'excessive_nitrogen']
|
| 86 |
+
}
|
| 87 |
+
},
|
| 88 |
+
'corn': {
|
| 89 |
+
'corn_borer': {
|
| 90 |
+
'conditions': {'temp_range': (20, 30), 'humidity_range': (50, 80), 'rainfall_range': (40, 120)},
|
| 91 |
+
'symptoms': 'Holes in stalks, ear damage, reduced yield',
|
| 92 |
+
'treatment': 'Bt corn varieties, beneficial insects, crop rotation',
|
| 93 |
+
'risk_factors': ['warm_weather', 'dense_planting', 'previous_infestation']
|
| 94 |
+
},
|
| 95 |
+
'corn_earworm': {
|
| 96 |
+
'conditions': {'temp_range': (25, 35), 'humidity_range': (60, 85), 'rainfall_range': (50, 150)},
|
| 97 |
+
'symptoms': 'Damage to ears, frass in silk, reduced quality',
|
| 98 |
+
'treatment': 'Insecticides, Bt varieties, pheromone traps',
|
| 99 |
+
'risk_factors': ['warm_weather', 'high_humidity', 'late_planting']
|
| 100 |
+
},
|
| 101 |
+
'gray_leaf_spot': {
|
| 102 |
+
'conditions': {'temp_range': (20, 30), 'humidity_range': (80, 95), 'rainfall_range': (80, 200)},
|
| 103 |
+
'symptoms': 'Gray lesions on leaves, premature death',
|
| 104 |
+
'treatment': 'Fungicide application, resistant varieties, crop rotation',
|
| 105 |
+
'risk_factors': ['high_humidity', 'warm_temperatures', 'continuous_corn']
|
| 106 |
+
}
|
| 107 |
+
},
|
| 108 |
+
'tomato': {
|
| 109 |
+
'aphids': {
|
| 110 |
+
'conditions': {'temp_range': (20, 30), 'humidity_range': (50, 80), 'rainfall_range': (30, 100)},
|
| 111 |
+
'symptoms': 'Curled leaves, honeydew, virus transmission',
|
| 112 |
+
'treatment': 'Neem oil, beneficial insects, reflective mulches',
|
| 113 |
+
'risk_factors': ['high_nitrogen', 'dense_planting', 'poor_air_circulation']
|
| 114 |
+
},
|
| 115 |
+
'late_blight': {
|
| 116 |
+
'conditions': {'temp_range': (15, 25), 'humidity_range': (85, 95), 'rainfall_range': (50, 150)},
|
| 117 |
+
'symptoms': 'Water-soaked lesions, white mold, rapid spread',
|
| 118 |
+
'treatment': 'Copper fungicides, resistant varieties, proper spacing',
|
| 119 |
+
'risk_factors': ['high_humidity', 'cool_temperatures', 'poor_air_circulation']
|
| 120 |
+
},
|
| 121 |
+
'whitefly': {
|
| 122 |
+
'conditions': {'temp_range': (25, 35), 'humidity_range': (40, 70), 'rainfall_range': (20, 80)},
|
| 123 |
+
'symptoms': 'Yellowing leaves, honeydew, sooty mold',
|
| 124 |
+
'treatment': 'Yellow sticky traps, beneficial insects, reflective mulches',
|
| 125 |
+
'risk_factors': ['warm_weather', 'low_humidity', 'dense_planting']
|
| 126 |
+
}
|
| 127 |
+
}
|
| 128 |
+
}
|
| 129 |
+
|
| 130 |
+
def _load_disease_database(self) -> Dict:
|
| 131 |
+
"""Load comprehensive disease database"""
|
| 132 |
+
return {
|
| 133 |
+
'fungal_diseases': {
|
| 134 |
+
'powdery_mildew': {
|
| 135 |
+
'conditions': {'temp_range': (15, 25), 'humidity_range': (70, 90), 'rainfall_range': (20, 100)},
|
| 136 |
+
'symptoms': 'White powdery coating on leaves',
|
| 137 |
+
'treatment': 'Sulfur fungicides, resistant varieties, proper spacing',
|
| 138 |
+
'affected_crops': ['wheat', 'tomato', 'cucumber', 'grape']
|
| 139 |
+
},
|
| 140 |
+
'downy_mildew': {
|
| 141 |
+
'conditions': {'temp_range': (10, 20), 'humidity_range': (85, 95), 'rainfall_range': (50, 200)},
|
| 142 |
+
'symptoms': 'Yellow spots on upper leaves, white mold underneath',
|
| 143 |
+
'treatment': 'Copper fungicides, resistant varieties, proper drainage',
|
| 144 |
+
'affected_crops': ['lettuce', 'cucumber', 'grape', 'onion']
|
| 145 |
+
}
|
| 146 |
+
},
|
| 147 |
+
'bacterial_diseases': {
|
| 148 |
+
'bacterial_wilt': {
|
| 149 |
+
'conditions': {'temp_range': (25, 35), 'humidity_range': (70, 90), 'rainfall_range': (50, 200)},
|
| 150 |
+
'symptoms': 'Wilting, yellowing, plant death',
|
| 151 |
+
'treatment': 'Copper-based bactericides, resistant varieties, crop rotation',
|
| 152 |
+
'affected_crops': ['tomato', 'pepper', 'cucumber', 'eggplant']
|
| 153 |
+
}
|
| 154 |
+
},
|
| 155 |
+
'viral_diseases': {
|
| 156 |
+
'mosaic_viruses': {
|
| 157 |
+
'conditions': {'temp_range': (20, 30), 'humidity_range': (50, 80), 'rainfall_range': (30, 120)},
|
| 158 |
+
'symptoms': 'Mottled leaves, stunted growth, reduced yield',
|
| 159 |
+
'treatment': 'Virus-free seeds, vector control, resistant varieties',
|
| 160 |
+
'affected_crops': ['tomato', 'cucumber', 'tobacco', 'pepper']
|
| 161 |
+
}
|
| 162 |
+
}
|
| 163 |
+
}
|
| 164 |
+
|
| 165 |
+
def predict_with_llama(self, crop_type: str, weather_data: Dict, soil_data: Dict) -> str:
|
| 166 |
+
"""
|
| 167 |
+
Use Llama 2 to predict pest and disease risks
|
| 168 |
+
"""
|
| 169 |
+
if not self.model or not self.tokenizer:
|
| 170 |
+
return self._rule_based_prediction(crop_type, weather_data, soil_data)
|
| 171 |
+
|
| 172 |
+
try:
|
| 173 |
+
# Prepare context for Llama 2
|
| 174 |
+
context = f"""
|
| 175 |
+
As an agricultural pest and disease expert, analyze the following conditions for {crop_type} farming:
|
| 176 |
+
|
| 177 |
+
Weather Conditions:
|
| 178 |
+
- Temperature: {weather_data.get('temperature', 'N/A')}°C
|
| 179 |
+
- Humidity: {weather_data.get('humidity', 'N/A')}%
|
| 180 |
+
- Rainfall: {weather_data.get('rainfall', 'N/A')} mm
|
| 181 |
+
- Wind Speed: {weather_data.get('wind_speed', 'N/A')} m/s
|
| 182 |
+
- Description: {weather_data.get('description', 'N/A')}
|
| 183 |
+
|
| 184 |
+
Soil Conditions:
|
| 185 |
+
- pH: {soil_data.get('ph', 'N/A')}
|
| 186 |
+
- Moisture: {soil_data.get('moisture', 'N/A')}%
|
| 187 |
+
- Type: {soil_data.get('type', 'N/A')}
|
| 188 |
+
|
| 189 |
+
Provide a comprehensive pest and disease risk assessment for {crop_type} including:
|
| 190 |
+
1. Most likely pests and diseases based on current conditions
|
| 191 |
+
2. Risk level (Low/Medium/High) for each threat
|
| 192 |
+
3. Specific symptoms to watch for
|
| 193 |
+
4. Recommended preventive measures
|
| 194 |
+
5. Treatment options if infestation occurs
|
| 195 |
+
6. Timing for monitoring and intervention
|
| 196 |
+
|
| 197 |
+
Focus on practical, actionable advice for farmers.
|
| 198 |
+
"""
|
| 199 |
+
|
| 200 |
+
# Tokenize and generate response
|
| 201 |
+
inputs = self.tokenizer.encode(context, return_tensors="pt").to(self.device)
|
| 202 |
+
|
| 203 |
+
with torch.no_grad():
|
| 204 |
+
outputs = self.model.generate(
|
| 205 |
+
inputs,
|
| 206 |
+
max_length=inputs.shape[1] + 300,
|
| 207 |
+
num_return_sequences=1,
|
| 208 |
+
temperature=0.7,
|
| 209 |
+
do_sample=True,
|
| 210 |
+
pad_token_id=self.tokenizer.eos_token_id
|
| 211 |
+
)
|
| 212 |
+
|
| 213 |
+
response = self.tokenizer.decode(outputs[0], skip_special_tokens=True)
|
| 214 |
+
|
| 215 |
+
# Extract the generated part
|
| 216 |
+
generated_text = response[len(context):].strip()
|
| 217 |
+
|
| 218 |
+
return generated_text if generated_text else self._rule_based_prediction(crop_type, weather_data, soil_data)
|
| 219 |
+
|
| 220 |
+
except Exception as e:
|
| 221 |
+
logger.error(f"Error in Llama 2 pest prediction: {e}")
|
| 222 |
+
return self._rule_based_prediction(crop_type, weather_data, soil_data)
|
| 223 |
+
|
| 224 |
+
def predict(self, crop_type: str, soil_ph: float, soil_moisture: float,
|
| 225 |
+
temperature: float, rainfall: float, additional_data: Dict = None) -> str:
|
| 226 |
+
"""
|
| 227 |
+
Main prediction method that combines Llama 2 analysis with rule-based fallback
|
| 228 |
+
"""
|
| 229 |
+
try:
|
| 230 |
+
# Prepare weather and soil data
|
| 231 |
+
weather_data = {
|
| 232 |
+
'temperature': temperature,
|
| 233 |
+
'humidity': 65.0, # Default if not provided
|
| 234 |
+
'rainfall': rainfall,
|
| 235 |
+
'wind_speed': 3.0, # Default if not provided
|
| 236 |
+
'description': 'partly cloudy' # Default if not provided
|
| 237 |
+
}
|
| 238 |
+
|
| 239 |
+
soil_data = {
|
| 240 |
+
'ph': soil_ph,
|
| 241 |
+
'moisture': soil_moisture,
|
| 242 |
+
'type': 'loamy' # Default if not provided
|
| 243 |
+
}
|
| 244 |
+
|
| 245 |
+
# Update with additional data if provided
|
| 246 |
+
if additional_data:
|
| 247 |
+
weather_data.update(additional_data.get('weather', {}))
|
| 248 |
+
soil_data.update(additional_data.get('soil', {}))
|
| 249 |
+
|
| 250 |
+
# Get Llama 2 prediction
|
| 251 |
+
llama_prediction = self.predict_with_llama(crop_type, weather_data, soil_data)
|
| 252 |
+
|
| 253 |
+
# Get rule-based risk assessment
|
| 254 |
+
risk_assessment = self._assess_risks(crop_type, weather_data, soil_data)
|
| 255 |
+
|
| 256 |
+
# Combine predictions
|
| 257 |
+
combined_prediction = self._combine_predictions(llama_prediction, risk_assessment, crop_type)
|
| 258 |
+
|
| 259 |
+
return combined_prediction
|
| 260 |
+
|
| 261 |
+
except Exception as e:
|
| 262 |
+
logger.error(f"Error in pest prediction: {e}")
|
| 263 |
+
return self._rule_based_prediction(crop_type, weather_data, soil_data)
|
| 264 |
+
|
| 265 |
+
def _assess_risks(self, crop_type: str, weather_data: Dict, soil_data: Dict) -> Dict:
|
| 266 |
+
"""Assess pest and disease risks using rule-based approach"""
|
| 267 |
+
risks = {
|
| 268 |
+
'pests': [],
|
| 269 |
+
'diseases': [],
|
| 270 |
+
'overall_risk': 'low',
|
| 271 |
+
'recommendations': []
|
| 272 |
+
}
|
| 273 |
+
|
| 274 |
+
temp = weather_data.get('temperature', 25)
|
| 275 |
+
humidity = weather_data.get('humidity', 65)
|
| 276 |
+
rainfall = weather_data.get('rainfall', 50)
|
| 277 |
+
soil_ph = soil_data.get('ph', 6.5)
|
| 278 |
+
soil_moisture = soil_data.get('moisture', 50)
|
| 279 |
+
|
| 280 |
+
crop_lower = crop_type.lower()
|
| 281 |
+
|
| 282 |
+
# Check for specific pests based on crop type
|
| 283 |
+
if crop_lower in self.pest_database:
|
| 284 |
+
for pest_name, pest_info in self.pest_database[crop_lower].items():
|
| 285 |
+
conditions = pest_info['conditions']
|
| 286 |
+
|
| 287 |
+
# Check temperature conditions
|
| 288 |
+
temp_ok = conditions['temp_range'][0] <= temp <= conditions['temp_range'][1]
|
| 289 |
+
humidity_ok = conditions['humidity_range'][0] <= humidity <= conditions['humidity_range'][1]
|
| 290 |
+
rainfall_ok = conditions['rainfall_range'][0] <= rainfall <= conditions['rainfall_range'][1]
|
| 291 |
+
|
| 292 |
+
if temp_ok and humidity_ok and rainfall_ok:
|
| 293 |
+
risks['pests'].append({
|
| 294 |
+
'name': pest_name,
|
| 295 |
+
'risk_level': 'high',
|
| 296 |
+
'symptoms': pest_info['symptoms'],
|
| 297 |
+
'treatment': pest_info['treatment']
|
| 298 |
+
})
|
| 299 |
+
elif any([temp_ok, humidity_ok, rainfall_ok]):
|
| 300 |
+
risks['pests'].append({
|
| 301 |
+
'name': pest_name,
|
| 302 |
+
'risk_level': 'medium',
|
| 303 |
+
'symptoms': pest_info['symptoms'],
|
| 304 |
+
'treatment': pest_info['treatment']
|
| 305 |
+
})
|
| 306 |
+
|
| 307 |
+
# Check for diseases
|
| 308 |
+
for disease_category, diseases in self.disease_database.items():
|
| 309 |
+
for disease_name, disease_info in diseases.items():
|
| 310 |
+
if crop_lower in disease_info.get('affected_crops', []):
|
| 311 |
+
conditions = disease_info['conditions']
|
| 312 |
+
|
| 313 |
+
temp_ok = conditions['temp_range'][0] <= temp <= conditions['temp_range'][1]
|
| 314 |
+
humidity_ok = conditions['humidity_range'][0] <= humidity <= conditions['humidity_range'][1]
|
| 315 |
+
rainfall_ok = conditions['rainfall_range'][0] <= rainfall <= conditions['rainfall_range'][1]
|
| 316 |
+
|
| 317 |
+
if temp_ok and humidity_ok and rainfall_ok:
|
| 318 |
+
risks['diseases'].append({
|
| 319 |
+
'name': disease_name,
|
| 320 |
+
'category': disease_category,
|
| 321 |
+
'risk_level': 'high',
|
| 322 |
+
'symptoms': disease_info['symptoms'],
|
| 323 |
+
'treatment': disease_info['treatment']
|
| 324 |
+
})
|
| 325 |
+
|
| 326 |
+
# Determine overall risk level
|
| 327 |
+
high_risks = len([p for p in risks['pests'] if p['risk_level'] == 'high']) + \
|
| 328 |
+
len([d for d in risks['diseases'] if d['risk_level'] == 'high'])
|
| 329 |
+
|
| 330 |
+
if high_risks > 0:
|
| 331 |
+
risks['overall_risk'] = 'high'
|
| 332 |
+
elif len(risks['pests']) + len(risks['diseases']) > 0:
|
| 333 |
+
risks['overall_risk'] = 'medium'
|
| 334 |
+
|
| 335 |
+
# Generate recommendations
|
| 336 |
+
risks['recommendations'] = self._generate_recommendations(risks, crop_type)
|
| 337 |
+
|
| 338 |
+
return risks
|
| 339 |
+
|
| 340 |
+
def _generate_recommendations(self, risks: Dict, crop_type: str) -> List[str]:
|
| 341 |
+
"""Generate specific recommendations based on risk assessment"""
|
| 342 |
+
recommendations = []
|
| 343 |
+
|
| 344 |
+
if risks['overall_risk'] == 'high':
|
| 345 |
+
recommendations.append("🚨 HIGH RISK: Immediate action required. Monitor crops daily.")
|
| 346 |
+
elif risks['overall_risk'] == 'medium':
|
| 347 |
+
recommendations.append("⚠️ MEDIUM RISK: Increased monitoring recommended.")
|
| 348 |
+
else:
|
| 349 |
+
recommendations.append("✅ LOW RISK: Continue regular monitoring.")
|
| 350 |
+
|
| 351 |
+
# Add specific recommendations for pests
|
| 352 |
+
for pest in risks['pests']:
|
| 353 |
+
if pest['risk_level'] == 'high':
|
| 354 |
+
recommendations.append(f"🐛 High risk of {pest['name']}: {pest['treatment']}")
|
| 355 |
+
|
| 356 |
+
# Add specific recommendations for diseases
|
| 357 |
+
for disease in risks['diseases']:
|
| 358 |
+
if disease['risk_level'] == 'high':
|
| 359 |
+
recommendations.append(f"🦠 High risk of {disease['name']}: {disease['treatment']}")
|
| 360 |
+
|
| 361 |
+
# General recommendations
|
| 362 |
+
recommendations.append("📅 Monitor crops every 2-3 days during high-risk periods.")
|
| 363 |
+
recommendations.append("🔍 Look for early symptoms and take preventive action.")
|
| 364 |
+
recommendations.append("🌱 Consider resistant varieties for future plantings.")
|
| 365 |
+
|
| 366 |
+
return recommendations
|
| 367 |
+
|
| 368 |
+
def _combine_predictions(self, llama_prediction: str, risk_assessment: Dict, crop_type: str) -> str:
|
| 369 |
+
"""Combine Llama 2 prediction with rule-based assessment"""
|
| 370 |
+
combined = f"🤖 AI Analysis for {crop_type}:\n\n"
|
| 371 |
+
combined += llama_prediction + "\n\n"
|
| 372 |
+
|
| 373 |
+
combined += "📊 Risk Assessment:\n"
|
| 374 |
+
combined += f"Overall Risk Level: {risk_assessment['overall_risk'].upper()}\n\n"
|
| 375 |
+
|
| 376 |
+
if risk_assessment['pests']:
|
| 377 |
+
combined += "🐛 Pest Risks:\n"
|
| 378 |
+
for pest in risk_assessment['pests']:
|
| 379 |
+
combined += f"- {pest['name']} ({pest['risk_level']} risk)\n"
|
| 380 |
+
combined += "\n"
|
| 381 |
+
|
| 382 |
+
if risk_assessment['diseases']:
|
| 383 |
+
combined += "🦠 Disease Risks:\n"
|
| 384 |
+
for disease in risk_assessment['diseases']:
|
| 385 |
+
combined += f"- {disease['name']} ({disease['risk_level']} risk)\n"
|
| 386 |
+
combined += "\n"
|
| 387 |
+
|
| 388 |
+
combined += "💡 Recommendations:\n"
|
| 389 |
+
for rec in risk_assessment['recommendations']:
|
| 390 |
+
combined += f"- {rec}\n"
|
| 391 |
+
|
| 392 |
+
return combined
|
| 393 |
+
|
| 394 |
+
def _rule_based_prediction(self, crop_type: str, weather_data: Dict, soil_data: Dict) -> str:
|
| 395 |
+
"""Fallback rule-based prediction when Llama 2 is not available"""
|
| 396 |
+
temp = weather_data.get('temperature', 25)
|
| 397 |
+
humidity = weather_data.get('humidity', 65)
|
| 398 |
+
rainfall = weather_data.get('rainfall', 50)
|
| 399 |
+
|
| 400 |
+
prediction = f"Pest and Disease Risk Assessment for {crop_type}:\n\n"
|
| 401 |
+
|
| 402 |
+
# Basic risk assessment
|
| 403 |
+
if temp > 30 and humidity > 80:
|
| 404 |
+
prediction += "⚠️ High risk of fungal diseases due to hot, humid conditions.\n"
|
| 405 |
+
elif temp < 15 and humidity > 85:
|
| 406 |
+
prediction += "⚠️ High risk of bacterial diseases due to cool, wet conditions.\n"
|
| 407 |
+
elif temp > 25 and humidity < 40:
|
| 408 |
+
prediction += "⚠️ High risk of pest infestation due to hot, dry conditions.\n"
|
| 409 |
+
else:
|
| 410 |
+
prediction += "✅ Conditions appear favorable with low disease risk.\n"
|
| 411 |
+
|
| 412 |
+
# General recommendations
|
| 413 |
+
prediction += "\nGeneral Recommendations:\n"
|
| 414 |
+
prediction += "- Monitor crops regularly for early signs of problems\n"
|
| 415 |
+
prediction += "- Maintain proper spacing for air circulation\n"
|
| 416 |
+
prediction += "- Use disease-resistant varieties when possible\n"
|
| 417 |
+
prediction += "- Practice crop rotation to break pest cycles\n"
|
| 418 |
+
|
| 419 |
+
return prediction
|
models/enhanced_weather_analyst.py
ADDED
|
@@ -0,0 +1,363 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import requests
|
| 3 |
+
import json
|
| 4 |
+
import logging
|
| 5 |
+
from datetime import datetime, timedelta
|
| 6 |
+
from typing import Dict, List, Optional
|
| 7 |
+
import pandas as pd
|
| 8 |
+
from dotenv import load_dotenv
|
| 9 |
+
from transformers import AutoTokenizer, AutoModelForCausalLM
|
| 10 |
+
import torch
|
| 11 |
+
|
| 12 |
+
# Load environment variables
|
| 13 |
+
load_dotenv()
|
| 14 |
+
|
| 15 |
+
# Set up logging
|
| 16 |
+
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
| 17 |
+
logger = logging.getLogger(__name__)
|
| 18 |
+
|
| 19 |
+
class EnhancedWeatherAnalyst:
|
| 20 |
+
"""
|
| 21 |
+
Enhanced weather analyst that uses real weather APIs and Llama 2 for intelligent forecasting
|
| 22 |
+
"""
|
| 23 |
+
|
| 24 |
+
def __init__(self, openweather_api_key: Optional[str] = None):
|
| 25 |
+
self.openweather_api_key = openweather_api_key or os.getenv('OPENWEATHER_API_KEY')
|
| 26 |
+
self.base_url = "https://api.openweathermap.org/data/2.5"
|
| 27 |
+
|
| 28 |
+
# Initialize Llama 2 model for weather analysis
|
| 29 |
+
self.device = "cuda" if torch.cuda.is_available() else "cpu"
|
| 30 |
+
self.model_name = "meta-llama/Llama-2-7b-chat-hf"
|
| 31 |
+
|
| 32 |
+
try:
|
| 33 |
+
self.tokenizer = AutoTokenizer.from_pretrained(self.model_name)
|
| 34 |
+
self.model = AutoModelForCausalLM.from_pretrained(
|
| 35 |
+
self.model_name,
|
| 36 |
+
torch_dtype=torch.float16 if self.device == "cuda" else torch.float32,
|
| 37 |
+
device_map="auto" if self.device == "cuda" else None
|
| 38 |
+
)
|
| 39 |
+
logger.info(f"Loaded Llama 2 model on {self.device}")
|
| 40 |
+
except Exception as e:
|
| 41 |
+
logger.warning(f"Could not load Llama 2 model: {e}. Using fallback analysis.")
|
| 42 |
+
self.model = None
|
| 43 |
+
self.tokenizer = None
|
| 44 |
+
|
| 45 |
+
def get_current_weather(self, lat: float = 12.9716, lon: float = 77.5946) -> Dict:
|
| 46 |
+
"""
|
| 47 |
+
Get current weather data from OpenWeatherMap API
|
| 48 |
+
Default coordinates are for Bangalore, India
|
| 49 |
+
"""
|
| 50 |
+
if not self.openweather_api_key:
|
| 51 |
+
logger.warning("No OpenWeatherMap API key provided. Using simulated data.")
|
| 52 |
+
return self._get_simulated_weather()
|
| 53 |
+
|
| 54 |
+
try:
|
| 55 |
+
url = f"{self.base_url}/weather"
|
| 56 |
+
params = {
|
| 57 |
+
'lat': lat,
|
| 58 |
+
'lon': lon,
|
| 59 |
+
'appid': self.openweather_api_key,
|
| 60 |
+
'units': 'metric'
|
| 61 |
+
}
|
| 62 |
+
|
| 63 |
+
response = requests.get(url, params=params, timeout=10)
|
| 64 |
+
response.raise_for_status()
|
| 65 |
+
|
| 66 |
+
data = response.json()
|
| 67 |
+
|
| 68 |
+
return {
|
| 69 |
+
'temperature': data['main']['temp'],
|
| 70 |
+
'humidity': data['main']['humidity'],
|
| 71 |
+
'pressure': data['main']['pressure'],
|
| 72 |
+
'wind_speed': data['wind']['speed'],
|
| 73 |
+
'wind_direction': data['wind'].get('deg', 0),
|
| 74 |
+
'description': data['weather'][0]['description'],
|
| 75 |
+
'visibility': data.get('visibility', 10000) / 1000, # Convert to km
|
| 76 |
+
'clouds': data['clouds']['all'],
|
| 77 |
+
'timestamp': datetime.now().isoformat()
|
| 78 |
+
}
|
| 79 |
+
|
| 80 |
+
except Exception as e:
|
| 81 |
+
logger.error(f"Error fetching current weather: {e}")
|
| 82 |
+
return self._get_simulated_weather()
|
| 83 |
+
|
| 84 |
+
def get_weather_forecast(self, lat: float = 12.9716, lon: float = 77.5946, days: int = 5) -> List[Dict]:
|
| 85 |
+
"""
|
| 86 |
+
Get 5-day weather forecast from OpenWeatherMap API
|
| 87 |
+
"""
|
| 88 |
+
if not self.openweather_api_key:
|
| 89 |
+
logger.warning("No OpenWeatherMap API key provided. Using simulated data.")
|
| 90 |
+
return self._get_simulated_forecast(days)
|
| 91 |
+
|
| 92 |
+
try:
|
| 93 |
+
url = f"{self.base_url}/forecast"
|
| 94 |
+
params = {
|
| 95 |
+
'lat': lat,
|
| 96 |
+
'lon': lon,
|
| 97 |
+
'appid': self.openweather_api_key,
|
| 98 |
+
'units': 'metric'
|
| 99 |
+
}
|
| 100 |
+
|
| 101 |
+
response = requests.get(url, params=params, timeout=10)
|
| 102 |
+
response.raise_for_status()
|
| 103 |
+
|
| 104 |
+
data = response.json()
|
| 105 |
+
forecasts = []
|
| 106 |
+
|
| 107 |
+
for item in data['list'][:days * 8]: # 8 forecasts per day (3-hour intervals)
|
| 108 |
+
forecasts.append({
|
| 109 |
+
'datetime': item['dt_txt'],
|
| 110 |
+
'temperature': item['main']['temp'],
|
| 111 |
+
'humidity': item['main']['humidity'],
|
| 112 |
+
'pressure': item['main']['pressure'],
|
| 113 |
+
'wind_speed': item['wind']['speed'],
|
| 114 |
+
'wind_direction': item['wind'].get('deg', 0),
|
| 115 |
+
'description': item['weather'][0]['description'],
|
| 116 |
+
'rain_probability': item.get('pop', 0) * 100, # Probability of precipitation
|
| 117 |
+
'rain_volume': item.get('rain', {}).get('3h', 0), # Rain volume for 3h
|
| 118 |
+
'clouds': item['clouds']['all']
|
| 119 |
+
})
|
| 120 |
+
|
| 121 |
+
return forecasts
|
| 122 |
+
|
| 123 |
+
except Exception as e:
|
| 124 |
+
logger.error(f"Error fetching weather forecast: {e}")
|
| 125 |
+
return self._get_simulated_forecast(days)
|
| 126 |
+
|
| 127 |
+
def analyze_weather_with_llama(self, weather_data: Dict, crop_type: str = "wheat") -> str:
|
| 128 |
+
"""
|
| 129 |
+
Use Llama 2 to analyze weather data and provide agricultural insights
|
| 130 |
+
"""
|
| 131 |
+
if not self.model or not self.tokenizer:
|
| 132 |
+
return self._get_fallback_analysis(weather_data, crop_type)
|
| 133 |
+
|
| 134 |
+
try:
|
| 135 |
+
# Prepare context for Llama 2
|
| 136 |
+
context = f"""
|
| 137 |
+
As an agricultural weather expert, analyze the following weather data for {crop_type} farming:
|
| 138 |
+
|
| 139 |
+
Current Weather:
|
| 140 |
+
- Temperature: {weather_data.get('temperature', 'N/A')}°C
|
| 141 |
+
- Humidity: {weather_data.get('humidity', 'N/A')}%
|
| 142 |
+
- Pressure: {weather_data.get('pressure', 'N/A')} hPa
|
| 143 |
+
- Wind Speed: {weather_data.get('wind_speed', 'N/A')} m/s
|
| 144 |
+
- Description: {weather_data.get('description', 'N/A')}
|
| 145 |
+
- Visibility: {weather_data.get('visibility', 'N/A')} km
|
| 146 |
+
- Cloud Cover: {weather_data.get('clouds', 'N/A')}%
|
| 147 |
+
|
| 148 |
+
Provide specific agricultural recommendations for {crop_type} farming based on these conditions.
|
| 149 |
+
Focus on:
|
| 150 |
+
1. Crop growth implications
|
| 151 |
+
2. Irrigation needs
|
| 152 |
+
3. Pest and disease risks
|
| 153 |
+
4. Harvest timing considerations
|
| 154 |
+
5. Any weather warnings or alerts
|
| 155 |
+
|
| 156 |
+
Keep the response concise and actionable for farmers.
|
| 157 |
+
"""
|
| 158 |
+
|
| 159 |
+
# Tokenize and generate response
|
| 160 |
+
inputs = self.tokenizer.encode(context, return_tensors="pt").to(self.device)
|
| 161 |
+
|
| 162 |
+
with torch.no_grad():
|
| 163 |
+
outputs = self.model.generate(
|
| 164 |
+
inputs,
|
| 165 |
+
max_length=inputs.shape[1] + 200,
|
| 166 |
+
num_return_sequences=1,
|
| 167 |
+
temperature=0.7,
|
| 168 |
+
do_sample=True,
|
| 169 |
+
pad_token_id=self.tokenizer.eos_token_id
|
| 170 |
+
)
|
| 171 |
+
|
| 172 |
+
response = self.tokenizer.decode(outputs[0], skip_special_tokens=True)
|
| 173 |
+
|
| 174 |
+
# Extract the generated part (remove the input context)
|
| 175 |
+
generated_text = response[len(context):].strip()
|
| 176 |
+
|
| 177 |
+
return generated_text if generated_text else self._get_fallback_analysis(weather_data, crop_type)
|
| 178 |
+
|
| 179 |
+
except Exception as e:
|
| 180 |
+
logger.error(f"Error in Llama 2 analysis: {e}")
|
| 181 |
+
return self._get_fallback_analysis(weather_data, crop_type)
|
| 182 |
+
|
| 183 |
+
def forecast_agricultural_conditions(self, lat: float = 12.9716, lon: float = 77.5946,
|
| 184 |
+
crop_type: str = "wheat") -> Dict:
|
| 185 |
+
"""
|
| 186 |
+
Main method to get comprehensive weather forecast with agricultural insights
|
| 187 |
+
"""
|
| 188 |
+
try:
|
| 189 |
+
# Get current weather
|
| 190 |
+
current_weather = self.get_current_weather(lat, lon)
|
| 191 |
+
|
| 192 |
+
# Get forecast
|
| 193 |
+
forecast_data = self.get_weather_forecast(lat, lon, 5)
|
| 194 |
+
|
| 195 |
+
# Analyze with Llama 2
|
| 196 |
+
analysis = self.analyze_weather_with_llama(current_weather, crop_type)
|
| 197 |
+
|
| 198 |
+
# Calculate agricultural metrics
|
| 199 |
+
avg_temp = sum([day['temperature'] for day in forecast_data[:8]]) / min(8, len(forecast_data))
|
| 200 |
+
total_rainfall = sum([day['rain_volume'] for day in forecast_data[:8]])
|
| 201 |
+
avg_humidity = sum([day['humidity'] for day in forecast_data[:8]]) / min(8, len(forecast_data))
|
| 202 |
+
|
| 203 |
+
# Determine agricultural conditions
|
| 204 |
+
conditions = self._assess_agricultural_conditions(avg_temp, total_rainfall, avg_humidity, crop_type)
|
| 205 |
+
|
| 206 |
+
return {
|
| 207 |
+
'current_weather': current_weather,
|
| 208 |
+
'forecast': forecast_data,
|
| 209 |
+
'analysis': analysis,
|
| 210 |
+
'agricultural_conditions': conditions,
|
| 211 |
+
'metrics': {
|
| 212 |
+
'avg_temperature': round(avg_temp, 1),
|
| 213 |
+
'total_rainfall': round(total_rainfall, 1),
|
| 214 |
+
'avg_humidity': round(avg_humidity, 1)
|
| 215 |
+
},
|
| 216 |
+
'recommendations': self._generate_recommendations(conditions, crop_type)
|
| 217 |
+
}
|
| 218 |
+
|
| 219 |
+
except Exception as e:
|
| 220 |
+
logger.error(f"Error in agricultural weather forecast: {e}")
|
| 221 |
+
return self._get_fallback_forecast(crop_type)
|
| 222 |
+
|
| 223 |
+
def _assess_agricultural_conditions(self, temp: float, rainfall: float, humidity: float, crop_type: str) -> Dict:
|
| 224 |
+
"""Assess agricultural conditions based on weather metrics"""
|
| 225 |
+
conditions = {
|
| 226 |
+
'temperature_status': 'optimal',
|
| 227 |
+
'rainfall_status': 'adequate',
|
| 228 |
+
'humidity_status': 'normal',
|
| 229 |
+
'overall_risk': 'low'
|
| 230 |
+
}
|
| 231 |
+
|
| 232 |
+
# Temperature assessment
|
| 233 |
+
if temp < 10 or temp > 35:
|
| 234 |
+
conditions['temperature_status'] = 'extreme'
|
| 235 |
+
conditions['overall_risk'] = 'high'
|
| 236 |
+
elif temp < 15 or temp > 30:
|
| 237 |
+
conditions['temperature_status'] = 'suboptimal'
|
| 238 |
+
if conditions['overall_risk'] == 'low':
|
| 239 |
+
conditions['overall_risk'] = 'medium'
|
| 240 |
+
|
| 241 |
+
# Rainfall assessment
|
| 242 |
+
if rainfall < 10:
|
| 243 |
+
conditions['rainfall_status'] = 'insufficient'
|
| 244 |
+
if conditions['overall_risk'] == 'low':
|
| 245 |
+
conditions['overall_risk'] = 'medium'
|
| 246 |
+
elif rainfall > 50:
|
| 247 |
+
conditions['rainfall_status'] = 'excessive'
|
| 248 |
+
if conditions['overall_risk'] == 'low':
|
| 249 |
+
conditions['overall_risk'] = 'medium'
|
| 250 |
+
|
| 251 |
+
# Humidity assessment
|
| 252 |
+
if humidity < 30:
|
| 253 |
+
conditions['humidity_status'] = 'low'
|
| 254 |
+
elif humidity > 80:
|
| 255 |
+
conditions['humidity_status'] = 'high'
|
| 256 |
+
if conditions['overall_risk'] == 'low':
|
| 257 |
+
conditions['overall_risk'] = 'medium'
|
| 258 |
+
|
| 259 |
+
return conditions
|
| 260 |
+
|
| 261 |
+
def _generate_recommendations(self, conditions: Dict, crop_type: str) -> List[str]:
|
| 262 |
+
"""Generate specific recommendations based on conditions"""
|
| 263 |
+
recommendations = []
|
| 264 |
+
|
| 265 |
+
if conditions['temperature_status'] == 'extreme':
|
| 266 |
+
if conditions['temperature_status'] == 'extreme':
|
| 267 |
+
recommendations.append("⚠️ Extreme temperature detected. Consider protective measures like shade nets or heating systems.")
|
| 268 |
+
|
| 269 |
+
if conditions['rainfall_status'] == 'insufficient':
|
| 270 |
+
recommendations.append("💧 Insufficient rainfall. Irrigation may be necessary.")
|
| 271 |
+
elif conditions['rainfall_status'] == 'excessive':
|
| 272 |
+
recommendations.append("🌧️ Excessive rainfall expected. Ensure proper drainage to prevent waterlogging.")
|
| 273 |
+
|
| 274 |
+
if conditions['humidity_status'] == 'high':
|
| 275 |
+
recommendations.append("🌫️ High humidity conditions. Monitor for fungal diseases and ensure good air circulation.")
|
| 276 |
+
elif conditions['humidity_status'] == 'low':
|
| 277 |
+
recommendations.append("🏜️ Low humidity conditions. Consider misting or increased irrigation frequency.")
|
| 278 |
+
|
| 279 |
+
if conditions['overall_risk'] == 'high':
|
| 280 |
+
recommendations.append("🚨 High risk conditions detected. Consult with agricultural experts before proceeding.")
|
| 281 |
+
|
| 282 |
+
return recommendations
|
| 283 |
+
|
| 284 |
+
def _get_simulated_weather(self) -> Dict:
|
| 285 |
+
"""Fallback simulated weather data"""
|
| 286 |
+
return {
|
| 287 |
+
'temperature': 25.0,
|
| 288 |
+
'humidity': 65.0,
|
| 289 |
+
'pressure': 1013.25,
|
| 290 |
+
'wind_speed': 3.5,
|
| 291 |
+
'wind_direction': 180,
|
| 292 |
+
'description': 'partly cloudy',
|
| 293 |
+
'visibility': 10.0,
|
| 294 |
+
'clouds': 40,
|
| 295 |
+
'timestamp': datetime.now().isoformat()
|
| 296 |
+
}
|
| 297 |
+
|
| 298 |
+
def _get_simulated_forecast(self, days: int) -> List[Dict]:
|
| 299 |
+
"""Fallback simulated forecast data"""
|
| 300 |
+
forecasts = []
|
| 301 |
+
base_temp = 25.0
|
| 302 |
+
|
| 303 |
+
for i in range(days * 8):
|
| 304 |
+
temp = base_temp + (i % 3 - 1) * 2 # Slight variation
|
| 305 |
+
forecasts.append({
|
| 306 |
+
'datetime': (datetime.now() + timedelta(hours=i*3)).strftime('%Y-%m-%d %H:%M:%S'),
|
| 307 |
+
'temperature': temp,
|
| 308 |
+
'humidity': 60 + (i % 5) * 2,
|
| 309 |
+
'pressure': 1013 + (i % 3 - 1) * 2,
|
| 310 |
+
'wind_speed': 2 + (i % 4),
|
| 311 |
+
'wind_direction': (i * 45) % 360,
|
| 312 |
+
'description': 'partly cloudy',
|
| 313 |
+
'rain_probability': 20 + (i % 3) * 10,
|
| 314 |
+
'rain_volume': (i % 7) * 0.5,
|
| 315 |
+
'clouds': 30 + (i % 4) * 10
|
| 316 |
+
})
|
| 317 |
+
|
| 318 |
+
return forecasts
|
| 319 |
+
|
| 320 |
+
def _get_fallback_analysis(self, weather_data: Dict, crop_type: str) -> str:
|
| 321 |
+
"""Fallback analysis when Llama 2 is not available"""
|
| 322 |
+
temp = weather_data.get('temperature', 25)
|
| 323 |
+
humidity = weather_data.get('humidity', 65)
|
| 324 |
+
|
| 325 |
+
analysis = f"Weather analysis for {crop_type} farming:\n"
|
| 326 |
+
analysis += f"Current temperature: {temp}°C - "
|
| 327 |
+
|
| 328 |
+
if temp < 15:
|
| 329 |
+
analysis += "Cool conditions may slow growth.\n"
|
| 330 |
+
elif temp > 30:
|
| 331 |
+
analysis += "Hot conditions may stress plants.\n"
|
| 332 |
+
else:
|
| 333 |
+
analysis += "Optimal temperature range.\n"
|
| 334 |
+
|
| 335 |
+
analysis += f"Humidity: {humidity}% - "
|
| 336 |
+
if humidity > 80:
|
| 337 |
+
analysis += "High humidity increases disease risk.\n"
|
| 338 |
+
elif humidity < 30:
|
| 339 |
+
analysis += "Low humidity may require more irrigation.\n"
|
| 340 |
+
else:
|
| 341 |
+
analysis += "Normal humidity levels.\n"
|
| 342 |
+
|
| 343 |
+
return analysis
|
| 344 |
+
|
| 345 |
+
def _get_fallback_forecast(self, crop_type: str) -> Dict:
|
| 346 |
+
"""Fallback forecast when API fails"""
|
| 347 |
+
return {
|
| 348 |
+
'current_weather': self._get_simulated_weather(),
|
| 349 |
+
'forecast': self._get_simulated_forecast(5),
|
| 350 |
+
'analysis': self._get_fallback_analysis(self._get_simulated_weather(), crop_type),
|
| 351 |
+
'agricultural_conditions': {
|
| 352 |
+
'temperature_status': 'optimal',
|
| 353 |
+
'rainfall_status': 'adequate',
|
| 354 |
+
'humidity_status': 'normal',
|
| 355 |
+
'overall_risk': 'low'
|
| 356 |
+
},
|
| 357 |
+
'metrics': {
|
| 358 |
+
'avg_temperature': 25.0,
|
| 359 |
+
'total_rainfall': 15.0,
|
| 360 |
+
'avg_humidity': 65.0
|
| 361 |
+
},
|
| 362 |
+
'recommendations': ["Monitor weather conditions regularly."]
|
| 363 |
+
}
|
models/farmer_advisor.py
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import sqlite3
|
| 2 |
+
import pandas as pd
|
| 3 |
+
import joblib
|
| 4 |
+
from sklearn.tree import DecisionTreeClassifier, export_text
|
| 5 |
+
from sklearn.preprocessing import LabelEncoder
|
| 6 |
+
from sklearn.model_selection import train_test_split
|
| 7 |
+
from sklearn.metrics import accuracy_score
|
| 8 |
+
import warnings
|
| 9 |
+
import os
|
| 10 |
+
|
| 11 |
+
class FarmerAdvisor:
|
| 12 |
+
def __init__(self, db_path='Models/database/sustainable_farming.db'):
|
| 13 |
+
self.db_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'database', 'sustainable_farming.db'))
|
| 14 |
+
self.model = None
|
| 15 |
+
self.encoders = {}
|
| 16 |
+
model_path = 'models/farmer_advisor_model.pkl'
|
| 17 |
+
encoder_path = 'models/farmer_encoders.pkl'
|
| 18 |
+
if os.path.exists(model_path) and os.path.exists(encoder_path):
|
| 19 |
+
self.model = joblib.load(model_path)
|
| 20 |
+
self.encoders = joblib.load(encoder_path)
|
| 21 |
+
else:
|
| 22 |
+
self._load_data()
|
| 23 |
+
self._preprocess()
|
| 24 |
+
self._train_model()
|
| 25 |
+
|
| 26 |
+
def _load_data(self):
|
| 27 |
+
with sqlite3.connect(self.db_path) as conn:
|
| 28 |
+
self.df = pd.read_sql("""
|
| 29 |
+
SELECT Soil_pH, Soil_Moisture, Temperature_C, Rainfall_mm,
|
| 30 |
+
Fertilizer_Usage_kg, Pesticide_Usage_kg, Crop_Yield_ton,
|
| 31 |
+
Crop_Type, Sustainability_Score
|
| 32 |
+
FROM farmer_advisor
|
| 33 |
+
""", conn)
|
| 34 |
+
|
| 35 |
+
# Calculate Carbon Footprint, Water, and Erosion Scores
|
| 36 |
+
self.df["Carbon_Footprint_Score"] = 100 - self.df["Fertilizer_Usage_kg"].fillna(0) * 0.6
|
| 37 |
+
self.df["Water_Score"] = 100 - self.df["Soil_Moisture"].fillna(0) * 0.7
|
| 38 |
+
self.df["Erosion_Score"] = 100 - self.df["Rainfall_mm"].fillna(0) * 0.5
|
| 39 |
+
|
| 40 |
+
# Clip the scores to be within 0 to 100
|
| 41 |
+
self.df["Carbon_Footprint_Score"] = self.df["Carbon_Footprint_Score"].clip(0, 100)
|
| 42 |
+
self.df["Water_Score"] = self.df["Water_Score"].clip(0, 100)
|
| 43 |
+
self.df["Erosion_Score"] = self.df["Erosion_Score"].clip(0, 100)
|
| 44 |
+
|
| 45 |
+
def _preprocess(self):
|
| 46 |
+
# Drop rows where Crop_Type is missing
|
| 47 |
+
self.df.dropna(subset=['Crop_Type'], inplace=True)
|
| 48 |
+
# Initialize and fit the label encoder
|
| 49 |
+
self.encoders['crop'] = LabelEncoder()
|
| 50 |
+
self.df['Crop_encoded'] = self.encoders['crop'].fit_transform(self.df['Crop_Type'].astype(str))
|
| 51 |
+
|
| 52 |
+
def _train_model(self):
|
| 53 |
+
# Define feature columns
|
| 54 |
+
feature_cols = [
|
| 55 |
+
'Soil_pH', 'Soil_Moisture', 'Temperature_C', 'Rainfall_mm',
|
| 56 |
+
'Fertilizer_Usage_kg', 'Pesticide_Usage_kg', 'Crop_Yield_ton'
|
| 57 |
+
]
|
| 58 |
+
# Prepare features and target
|
| 59 |
+
X = self.df[feature_cols].fillna(0)
|
| 60 |
+
y = self.df['Crop_encoded']
|
| 61 |
+
X_train, X_test, y_train, y_test = train_test_split(
|
| 62 |
+
X, y, test_size=0.2, stratify=y, random_state=42
|
| 63 |
+
)
|
| 64 |
+
self.model = DecisionTreeClassifier(
|
| 65 |
+
max_depth=8,
|
| 66 |
+
min_samples_split=6,
|
| 67 |
+
random_state=42
|
| 68 |
+
)
|
| 69 |
+
self.model.fit(X_train, y_train)
|
| 70 |
+
os.makedirs('models', exist_ok=True)
|
| 71 |
+
joblib.dump(self.model, 'models/farmer_advisor_model.pkl')
|
| 72 |
+
joblib.dump(self.encoders, 'models/farmer_encoders.pkl')
|
| 73 |
+
# Only print accuracy and rules if running as a script (not imported)
|
| 74 |
+
if __name__ == "__main__":
|
| 75 |
+
y_pred = self.model.predict(X_test)
|
| 76 |
+
acc = accuracy_score(y_test, y_pred)
|
| 77 |
+
print(f"\nFarmerAdvisor Model Accuracy: {acc:.2f}")
|
| 78 |
+
with warnings.catch_warnings():
|
| 79 |
+
warnings.simplefilter("ignore")
|
| 80 |
+
rules = export_text(self.model, feature_names=feature_cols)
|
| 81 |
+
print("\nDecision Tree Rules for Crop Recommendation:\n")
|
| 82 |
+
print(rules)
|
| 83 |
+
|
| 84 |
+
def recommend(self, soil_ph, soil_moisture, temp, rainfall, fertilizer, pesticide, crop_yield,
|
| 85 |
+
carbon_score=None, water_score=None, erosion_score=None):
|
| 86 |
+
if self.model is None:
|
| 87 |
+
self.model = joblib.load('models/farmer_advisor_model.pkl')
|
| 88 |
+
if not self.encoders:
|
| 89 |
+
self.encoders = joblib.load('models/farmer_encoders.pkl')
|
| 90 |
+
|
| 91 |
+
input_df = pd.DataFrame([[
|
| 92 |
+
soil_ph, soil_moisture, temp, rainfall,
|
| 93 |
+
fertilizer, pesticide, crop_yield
|
| 94 |
+
]], columns=[
|
| 95 |
+
'Soil_pH', 'Soil_Moisture', 'Temperature_C', 'Rainfall_mm',
|
| 96 |
+
'Fertilizer_Usage_kg', 'Pesticide_Usage_kg', 'Crop_Yield_ton'
|
| 97 |
+
])
|
| 98 |
+
|
| 99 |
+
crop_code = self.model.predict(input_df)[0]
|
| 100 |
+
return self.encoders['crop'].inverse_transform([crop_code])[0]
|
models/farmer_advisor_model.pkl
ADDED
|
Binary file (18.5 kB). View file
|
|
|
models/farmer_encoders.pkl
ADDED
|
Binary file (515 Bytes). View file
|
|
|
models/market_Researcher.py
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import sqlite3
|
| 2 |
+
import pandas as pd
|
| 3 |
+
import joblib
|
| 4 |
+
import os
|
| 5 |
+
from sklearn.ensemble import RandomForestRegressor
|
| 6 |
+
from sklearn.model_selection import train_test_split
|
| 7 |
+
from sklearn.preprocessing import LabelEncoder, StandardScaler
|
| 8 |
+
|
| 9 |
+
class MarketResearcher:
|
| 10 |
+
def __init__(self, db_path="Models/database/sustainable_farming.db"):
|
| 11 |
+
# Prefer explicitly provided db_path if it exists; else fall back to project default
|
| 12 |
+
provided_path = db_path if db_path else ""
|
| 13 |
+
default_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'database', 'sustainable_farming.db'))
|
| 14 |
+
resolved_path = os.path.abspath(provided_path)
|
| 15 |
+
self.db_path = resolved_path if os.path.exists(resolved_path) else default_path
|
| 16 |
+
self.models = {}
|
| 17 |
+
self.encoders = {}
|
| 18 |
+
self.scalers = {}
|
| 19 |
+
# Only train if any model/encoder/scaler file is missing for any product
|
| 20 |
+
base_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'models'))
|
| 21 |
+
with sqlite3.connect(self.db_path) as conn:
|
| 22 |
+
products = pd.read_sql(
|
| 23 |
+
"SELECT DISTINCT Product FROM market_researcher", conn
|
| 24 |
+
)['Product'].dropna().unique().tolist()
|
| 25 |
+
missing = False
|
| 26 |
+
for product in products:
|
| 27 |
+
model_name = product.strip().lower().replace(" ", "_")
|
| 28 |
+
if not (os.path.exists(os.path.join(base_dir, f"market_model_{model_name}.pkl")) and \
|
| 29 |
+
os.path.exists(os.path.join(base_dir, f"market_encoder_{model_name}.pkl")) and \
|
| 30 |
+
os.path.exists(os.path.join(base_dir, f"market_scaler_{model_name}.pkl"))):
|
| 31 |
+
missing = True
|
| 32 |
+
break
|
| 33 |
+
if missing:
|
| 34 |
+
self._train_all_models()
|
| 35 |
+
|
| 36 |
+
def _train_all_models(self):
|
| 37 |
+
with sqlite3.connect(self.db_path) as conn:
|
| 38 |
+
products = pd.read_sql(
|
| 39 |
+
"SELECT DISTINCT Product FROM market_researcher", conn
|
| 40 |
+
)['Product'].dropna().unique().tolist()
|
| 41 |
+
|
| 42 |
+
for product in products:
|
| 43 |
+
model_name = product.strip().lower().replace(" ", "_")
|
| 44 |
+
base_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'models'))
|
| 45 |
+
model_path = os.path.join(base_dir, f"market_model_{model_name}.pkl")
|
| 46 |
+
encoder_path = os.path.join(base_dir, f"market_encoder_{model_name}.pkl")
|
| 47 |
+
scaler_path = os.path.join(base_dir, f"market_scaler_{model_name}.pkl")
|
| 48 |
+
if os.path.exists(model_path) and os.path.exists(encoder_path) and os.path.exists(scaler_path):
|
| 49 |
+
continue
|
| 50 |
+
df = pd.read_sql("""
|
| 51 |
+
SELECT Product, Market_Price_per_ton, Demand_Index, Supply_Index,
|
| 52 |
+
Competitor_Price_per_ton, Economic_Indicator,
|
| 53 |
+
Weather_Impact_Score, Seasonal_Factor, Consumer_Trend_Index
|
| 54 |
+
FROM market_researcher
|
| 55 |
+
WHERE Product = ?
|
| 56 |
+
""", conn, params=(product,))
|
| 57 |
+
if len(df) < 10:
|
| 58 |
+
continue
|
| 59 |
+
df['Seasonal_Factor'] = df['Seasonal_Factor'].fillna('None')
|
| 60 |
+
le = LabelEncoder()
|
| 61 |
+
df['Seasonal_Factor_Encoded'] = le.fit_transform(df['Seasonal_Factor'])
|
| 62 |
+
self.encoders[product] = le
|
| 63 |
+
features = ['Demand_Index', 'Supply_Index', 'Competitor_Price_per_ton',
|
| 64 |
+
'Economic_Indicator', 'Weather_Impact_Score',
|
| 65 |
+
'Seasonal_Factor_Encoded', 'Consumer_Trend_Index']
|
| 66 |
+
X = df[features]
|
| 67 |
+
y = df['Market_Price_per_ton']
|
| 68 |
+
scaler = StandardScaler()
|
| 69 |
+
X_scaled = scaler.fit_transform(X)
|
| 70 |
+
self.scalers[product] = scaler
|
| 71 |
+
X_train, _, y_train, _ = train_test_split(X_scaled, y, test_size=0.2, random_state=42)
|
| 72 |
+
model = RandomForestRegressor(n_estimators=100, random_state=42)
|
| 73 |
+
model.fit(X_train, y_train)
|
| 74 |
+
os.makedirs(base_dir, exist_ok=True)
|
| 75 |
+
joblib.dump(model, model_path)
|
| 76 |
+
joblib.dump(le, encoder_path)
|
| 77 |
+
joblib.dump(scaler, scaler_path)
|
| 78 |
+
|
| 79 |
+
def forecast(self, product, input_features):
|
| 80 |
+
model_name = product.strip().lower().replace(" ", "_")
|
| 81 |
+
model_path = f"models/market_model_{model_name}.pkl"
|
| 82 |
+
encoder_path = f"models/market_encoder_{model_name}.pkl"
|
| 83 |
+
scaler_path = f"models/market_scaler_{model_name}.pkl"
|
| 84 |
+
|
| 85 |
+
if not os.path.exists(model_path) or not os.path.exists(encoder_path) or not os.path.exists(scaler_path):
|
| 86 |
+
raise ValueError(f"No trained model found for product: {product}")
|
| 87 |
+
|
| 88 |
+
model = joblib.load(model_path)
|
| 89 |
+
le = joblib.load(encoder_path)
|
| 90 |
+
scaler = joblib.load(scaler_path)
|
| 91 |
+
|
| 92 |
+
sf = input_features.get('Seasonal_Factor', 'None')
|
| 93 |
+
if sf not in le.classes_:
|
| 94 |
+
sf = le.classes_[0]
|
| 95 |
+
|
| 96 |
+
sf_encoded = le.transform([sf])[0]
|
| 97 |
+
|
| 98 |
+
input_df = pd.DataFrame([[
|
| 99 |
+
input_features.get('Demand_Index', 0),
|
| 100 |
+
input_features.get('Supply_Index', 0),
|
| 101 |
+
input_features.get('Competitor_Price_per_ton', 0),
|
| 102 |
+
input_features.get('Economic_Indicator', 0),
|
| 103 |
+
input_features.get('Weather_Impact_Score', 0),
|
| 104 |
+
sf_encoded,
|
| 105 |
+
input_features.get('Consumer_Trend_Index', 0)
|
| 106 |
+
]], columns=[
|
| 107 |
+
'Demand_Index', 'Supply_Index', 'Competitor_Price_per_ton',
|
| 108 |
+
'Economic_Indicator', 'Weather_Impact_Score',
|
| 109 |
+
'Seasonal_Factor_Encoded', 'Consumer_Trend_Index'
|
| 110 |
+
])
|
| 111 |
+
|
| 112 |
+
input_scaled = scaler.transform(input_df)
|
| 113 |
+
prediction = model.predict(input_scaled)
|
| 114 |
+
return prediction.tolist()
|
models/market_encoder_corn.pkl
ADDED
|
Binary file (495 Bytes). View file
|
|
|
models/market_encoder_rice.pkl
ADDED
|
Binary file (495 Bytes). View file
|
|
|
models/market_encoder_soybean.pkl
ADDED
|
Binary file (495 Bytes). View file
|
|
|
models/market_encoder_wheat.pkl
ADDED
|
Binary file (495 Bytes). View file
|
|
|
models/market_model_corn.pkl
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:b70f05f98895fe52e199e193be035af378cd2889d79879257ce0f9b22617db69
|
| 3 |
+
size 17862433
|
models/market_model_rice.pkl
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:d6c5c89d70d15b43c43298a2f7c7b455c07c9b8fdb4ba7cd8a0e0f174e82192c
|
| 3 |
+
size 18663073
|
models/market_model_soybean.pkl
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:381882ee9739adaf56c99433be9cb22ea5218e204482253bcf8970ba52bde543
|
| 3 |
+
size 18323953
|
models/market_model_wheat.pkl
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:b948159a30a1767989ca39f95d29cd555c504efb882e6560c015c499f030bb9f
|
| 3 |
+
size 18040417
|
models/market_scaler_corn.pkl
ADDED
|
Binary file (1.17 kB). View file
|
|
|
models/market_scaler_rice.pkl
ADDED
|
Binary file (1.17 kB). View file
|
|
|
models/market_scaler_soybean.pkl
ADDED
|
Binary file (1.17 kB). View file
|
|
|
models/market_scaler_wheat.pkl
ADDED
|
Binary file (1.17 kB). View file
|
|
|
models/pest_disease_predictor.py
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
class PestDiseasePredictor:
|
| 2 |
+
def predict(self, crop_type, soil_ph, soil_moisture, temperature, rainfall):
|
| 3 |
+
# Example rules (customize as needed)
|
| 4 |
+
crop = crop_type.lower()
|
| 5 |
+
if crop == "tomato":
|
| 6 |
+
if temperature > 28 and rainfall > 60:
|
| 7 |
+
return "High risk of aphids in tomatoes; consider neem oil treatment."
|
| 8 |
+
elif temperature < 15:
|
| 9 |
+
return "Risk of late blight in tomatoes; monitor for leaf spots."
|
| 10 |
+
elif crop == "wheat":
|
| 11 |
+
if rainfall > 80:
|
| 12 |
+
return "High risk of rust disease in wheat; consider fungicide spray."
|
| 13 |
+
elif crop == "rice":
|
| 14 |
+
if temperature > 30 and rainfall > 100:
|
| 15 |
+
return "High risk of bacterial leaf blight in rice; ensure proper drainage."
|
| 16 |
+
elif crop == "corn":
|
| 17 |
+
if temperature > 32 and rainfall < 40:
|
| 18 |
+
return "Risk of corn borer infestation; monitor for larvae."
|
| 19 |
+
elif crop == "soybeans":
|
| 20 |
+
if soil_moisture > 35:
|
| 21 |
+
return "Risk of root rot in soybeans; avoid over-irrigation."
|
| 22 |
+
# Add more rules for other crops as needed
|
| 23 |
+
return "No significant pest or disease risk detected."
|
| 24 |
+
class PestDiseasePredictor:
|
| 25 |
+
def predict(self, crop_type, soil_ph, soil_moisture, temperature, rainfall):
|
| 26 |
+
# Example rules (customize as needed)
|
| 27 |
+
crop = crop_type.lower()
|
| 28 |
+
if crop == "tomato":
|
| 29 |
+
if temperature > 28 and rainfall > 60:
|
| 30 |
+
return "High risk of aphids in tomatoes; consider neem oil treatment."
|
| 31 |
+
elif temperature < 15:
|
| 32 |
+
return "Risk of late blight in tomatoes; monitor for leaf spots."
|
| 33 |
+
elif crop == "wheat":
|
| 34 |
+
if rainfall > 80:
|
| 35 |
+
return "High risk of rust disease in wheat; consider fungicide spray."
|
| 36 |
+
elif crop == "rice":
|
| 37 |
+
if temperature > 30 and rainfall > 100:
|
| 38 |
+
return "High risk of bacterial leaf blight in rice; ensure proper drainage."
|
| 39 |
+
elif crop == "corn":
|
| 40 |
+
if temperature > 32 and rainfall < 40:
|
| 41 |
+
return "Risk of corn borer infestation; monitor for larvae."
|
| 42 |
+
elif crop == "soybeans":
|
| 43 |
+
if soil_moisture > 35:
|
| 44 |
+
return "Risk of root rot in soybeans; avoid over-irrigation."
|
| 45 |
+
# Add more rules for other crops as needed
|
| 46 |
+
return "No significant pest or disease risk detected."
|
models/rain_model.pkl
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:5f442fe0bc96a49622c85a4bcf216b4b56ecc62c081a805d9973b6dd8d44db34
|
| 3 |
+
size 91073185
|
models/speech_interface.py
ADDED
|
@@ -0,0 +1,487 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import streamlit as st
|
| 2 |
+
import speech_recognition as sr
|
| 3 |
+
import pyttsx3
|
| 4 |
+
from gtts import gTTS
|
| 5 |
+
import io
|
| 6 |
+
import tempfile
|
| 7 |
+
import os
|
| 8 |
+
import threading
|
| 9 |
+
import time
|
| 10 |
+
from typing import Optional, Dict, List
|
| 11 |
+
import logging
|
| 12 |
+
|
| 13 |
+
# Set up logging
|
| 14 |
+
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
| 15 |
+
logger = logging.getLogger(__name__)
|
| 16 |
+
|
| 17 |
+
class SpeechInterface:
|
| 18 |
+
"""
|
| 19 |
+
Comprehensive speech interface supporting multiple languages for farmers
|
| 20 |
+
"""
|
| 21 |
+
|
| 22 |
+
def __init__(self):
|
| 23 |
+
self.recognizer = sr.Recognizer()
|
| 24 |
+
self.microphone = None
|
| 25 |
+
self.pyaudio_available = False
|
| 26 |
+
|
| 27 |
+
# Try to initialize microphone with error handling
|
| 28 |
+
try:
|
| 29 |
+
self.microphone = sr.Microphone()
|
| 30 |
+
self.pyaudio_available = True
|
| 31 |
+
logger.info("PyAudio and microphone initialized successfully")
|
| 32 |
+
except AttributeError as e:
|
| 33 |
+
if "PyAudio" in str(e):
|
| 34 |
+
logger.warning("PyAudio not available. Voice input will be disabled.")
|
| 35 |
+
self.pyaudio_available = False
|
| 36 |
+
else:
|
| 37 |
+
logger.error(f"Microphone initialization error: {e}")
|
| 38 |
+
self.pyaudio_available = False
|
| 39 |
+
except Exception as e:
|
| 40 |
+
logger.error(f"Unexpected error initializing microphone: {e}")
|
| 41 |
+
self.pyaudio_available = False
|
| 42 |
+
|
| 43 |
+
# Language mapping for speech recognition and synthesis
|
| 44 |
+
self.language_codes = {
|
| 45 |
+
'English': 'en',
|
| 46 |
+
'Hindi': 'hi',
|
| 47 |
+
'Telugu': 'te',
|
| 48 |
+
'Kannada': 'kn',
|
| 49 |
+
'Tamil': 'ta',
|
| 50 |
+
'Malayalam': 'ml',
|
| 51 |
+
'Marathi': 'mr',
|
| 52 |
+
'Bengali': 'bn',
|
| 53 |
+
'Gujarati': 'gu',
|
| 54 |
+
'Punjabi': 'pa',
|
| 55 |
+
'Urdu': 'ur',
|
| 56 |
+
'French': 'fr',
|
| 57 |
+
'Spanish': 'es'
|
| 58 |
+
}
|
| 59 |
+
|
| 60 |
+
# Initialize text-to-speech engine
|
| 61 |
+
try:
|
| 62 |
+
self.tts_engine = pyttsx3.init()
|
| 63 |
+
self.tts_engine.setProperty('rate', 150) # Speed of speech
|
| 64 |
+
self.tts_engine.setProperty('volume', 0.9) # Volume level
|
| 65 |
+
except Exception as e:
|
| 66 |
+
logger.warning(f"Could not initialize TTS engine: {e}")
|
| 67 |
+
self.tts_engine = None
|
| 68 |
+
|
| 69 |
+
def speech_to_text(self, language: str = 'English', timeout: int = 5) -> Optional[str]:
|
| 70 |
+
"""
|
| 71 |
+
Convert speech to text using microphone input
|
| 72 |
+
"""
|
| 73 |
+
if not self.pyaudio_available or self.microphone is None:
|
| 74 |
+
st.error("❌ Microphone not available. PyAudio is not installed or not working properly.")
|
| 75 |
+
st.info("💡 **To fix this:**\n"
|
| 76 |
+
"1. Install PyAudio: `pip install pyaudio`\n"
|
| 77 |
+
"2. On Windows, you might need: `pip install pipwin` then `pipwin install pyaudio`\n"
|
| 78 |
+
"3. Or use conda: `conda install pyaudio`")
|
| 79 |
+
return None
|
| 80 |
+
|
| 81 |
+
try:
|
| 82 |
+
with self.microphone as source:
|
| 83 |
+
# Adjust for ambient noise
|
| 84 |
+
self.recognizer.adjust_for_ambient_noise(source, duration=0.5)
|
| 85 |
+
|
| 86 |
+
# Show listening indicator
|
| 87 |
+
st.info("🎤 Listening... Speak now!")
|
| 88 |
+
|
| 89 |
+
# Listen for audio
|
| 90 |
+
audio = self.recognizer.listen(source, timeout=timeout, phrase_time_limit=10)
|
| 91 |
+
|
| 92 |
+
# Show processing indicator
|
| 93 |
+
st.info("🔄 Processing your speech...")
|
| 94 |
+
|
| 95 |
+
# Convert speech to text
|
| 96 |
+
language_code = self.language_codes.get(language, 'en')
|
| 97 |
+
text = self.recognizer.recognize_google(audio, language=language_code)
|
| 98 |
+
|
| 99 |
+
st.success(f"✅ Heard: {text}")
|
| 100 |
+
return text
|
| 101 |
+
|
| 102 |
+
except sr.WaitTimeoutError:
|
| 103 |
+
st.warning("⏰ No speech detected. Please try again.")
|
| 104 |
+
return None
|
| 105 |
+
except sr.UnknownValueError:
|
| 106 |
+
st.error("❌ Could not understand the speech. Please speak clearly.")
|
| 107 |
+
return None
|
| 108 |
+
except sr.RequestError as e:
|
| 109 |
+
st.error(f"❌ Speech recognition service error: {e}")
|
| 110 |
+
return None
|
| 111 |
+
except Exception as e:
|
| 112 |
+
st.error(f"❌ Unexpected error: {e}")
|
| 113 |
+
return None
|
| 114 |
+
|
| 115 |
+
def text_to_speech(self, text: str, language: str = 'English', use_gtts: bool = True) -> bool:
|
| 116 |
+
"""
|
| 117 |
+
Convert text to speech using either gTTS (online) or pyttsx3 (offline)
|
| 118 |
+
"""
|
| 119 |
+
try:
|
| 120 |
+
if use_gtts and text.strip():
|
| 121 |
+
# Use Google Text-to-Speech (online, better quality, supports more languages)
|
| 122 |
+
language_code = self.language_codes.get(language, 'en')
|
| 123 |
+
tts = gTTS(text=text, lang=language_code, slow=False)
|
| 124 |
+
|
| 125 |
+
# Create temporary file with better handling
|
| 126 |
+
import uuid
|
| 127 |
+
temp_filename = f"tts_{uuid.uuid4().hex}.mp3"
|
| 128 |
+
temp_path = os.path.join(tempfile.gettempdir(), temp_filename)
|
| 129 |
+
|
| 130 |
+
try:
|
| 131 |
+
tts.save(temp_path)
|
| 132 |
+
|
| 133 |
+
# Play audio in Streamlit
|
| 134 |
+
with open(temp_path, 'rb') as audio_file:
|
| 135 |
+
audio_bytes = audio_file.read()
|
| 136 |
+
st.audio(audio_bytes, format='audio/mp3')
|
| 137 |
+
st.info(f"🔊 Audio generated! If you don't hear anything, check your speakers/headphones and browser audio settings.")
|
| 138 |
+
|
| 139 |
+
# Provide download option as fallback
|
| 140 |
+
st.download_button(
|
| 141 |
+
label="⬇️ Download Audio File",
|
| 142 |
+
data=audio_bytes,
|
| 143 |
+
file_name=f"recommendation_audio_{uuid.uuid4().hex[:8]}.mp3",
|
| 144 |
+
mime="audio/mp3",
|
| 145 |
+
help="Download the audio file to play it in your preferred audio player"
|
| 146 |
+
)
|
| 147 |
+
|
| 148 |
+
# Clean up with retry mechanism
|
| 149 |
+
try:
|
| 150 |
+
os.unlink(temp_path)
|
| 151 |
+
except PermissionError:
|
| 152 |
+
# File might still be in use, try again after a short delay
|
| 153 |
+
import time
|
| 154 |
+
time.sleep(0.5)
|
| 155 |
+
try:
|
| 156 |
+
os.unlink(temp_path)
|
| 157 |
+
except:
|
| 158 |
+
# If still can't delete, just leave it - temp files are cleaned up by system
|
| 159 |
+
pass
|
| 160 |
+
|
| 161 |
+
return True
|
| 162 |
+
|
| 163 |
+
except Exception as e:
|
| 164 |
+
logger.error(f"Error with gTTS file handling: {e}")
|
| 165 |
+
# Fallback to pyttsx3
|
| 166 |
+
if self.tts_engine:
|
| 167 |
+
self.tts_engine.say(text)
|
| 168 |
+
self.tts_engine.runAndWait()
|
| 169 |
+
return True
|
| 170 |
+
return False
|
| 171 |
+
|
| 172 |
+
elif self.tts_engine and text.strip():
|
| 173 |
+
# Use pyttsx3 (offline, limited language support)
|
| 174 |
+
try:
|
| 175 |
+
self.tts_engine.say(text)
|
| 176 |
+
self.tts_engine.runAndWait()
|
| 177 |
+
st.info("🔊 Playing audio using offline TTS engine...")
|
| 178 |
+
return True
|
| 179 |
+
except Exception as e:
|
| 180 |
+
logger.error(f"Error with pyttsx3: {e}")
|
| 181 |
+
st.error(f"❌ Offline TTS error: {e}")
|
| 182 |
+
return False
|
| 183 |
+
|
| 184 |
+
else:
|
| 185 |
+
if not text.strip():
|
| 186 |
+
st.warning("⚠️ No text provided to speak")
|
| 187 |
+
else:
|
| 188 |
+
st.warning("⚠️ No TTS engine available. Please check your internet connection for online TTS or install offline TTS dependencies.")
|
| 189 |
+
return False
|
| 190 |
+
|
| 191 |
+
except Exception as e:
|
| 192 |
+
logger.error(f"Error in text-to-speech: {e}")
|
| 193 |
+
st.error(f"❌ Text-to-speech error: {e}")
|
| 194 |
+
return False
|
| 195 |
+
|
| 196 |
+
def create_voice_input_widget(self, label: str, language: str = 'English',
|
| 197 |
+
key: str = None, help_text: str = None) -> Optional[str]:
|
| 198 |
+
"""
|
| 199 |
+
Create a voice input widget for Streamlit
|
| 200 |
+
"""
|
| 201 |
+
col1, col2 = st.columns([3, 1])
|
| 202 |
+
|
| 203 |
+
# Get the current value from session state
|
| 204 |
+
text_key = f"{key}_text" if key else "text_input"
|
| 205 |
+
voice_result_key = f"{key}_voice_result" if key else "voice_result"
|
| 206 |
+
|
| 207 |
+
# Initialize session state for voice result
|
| 208 |
+
if voice_result_key not in st.session_state:
|
| 209 |
+
st.session_state[voice_result_key] = ""
|
| 210 |
+
|
| 211 |
+
with col1:
|
| 212 |
+
text_input = st.text_input(
|
| 213 |
+
label,
|
| 214 |
+
key=text_key,
|
| 215 |
+
help=help_text
|
| 216 |
+
)
|
| 217 |
+
|
| 218 |
+
with col2:
|
| 219 |
+
if st.button("🎤 Voice", key=f"{key}_voice_btn" if key else None, help="Click to speak"):
|
| 220 |
+
if not self.pyaudio_available:
|
| 221 |
+
st.error("❌ Microphone not available")
|
| 222 |
+
return text_input
|
| 223 |
+
|
| 224 |
+
with st.spinner("Preparing microphone..."):
|
| 225 |
+
time.sleep(1) # Give time for microphone to initialize
|
| 226 |
+
|
| 227 |
+
try:
|
| 228 |
+
voice_text = self.speech_to_text(language)
|
| 229 |
+
if voice_text:
|
| 230 |
+
# Store voice input in session state
|
| 231 |
+
st.session_state[voice_result_key] = voice_text
|
| 232 |
+
st.success(f"✅ Voice input: {voice_text}")
|
| 233 |
+
# Show the voice input in a separate display
|
| 234 |
+
st.info(f"🎤 Voice input captured: **{voice_text}**")
|
| 235 |
+
st.info("💡 Copy this text and paste it into the input field above")
|
| 236 |
+
# Force a rerun to update the text input
|
| 237 |
+
st.rerun()
|
| 238 |
+
else:
|
| 239 |
+
st.warning("⚠️ No voice input detected")
|
| 240 |
+
except Exception as e:
|
| 241 |
+
st.error(f"❌ Voice input error: {e}")
|
| 242 |
+
logger.error(f"Voice input error: {e}")
|
| 243 |
+
|
| 244 |
+
# Show voice result if available
|
| 245 |
+
if st.session_state.get(voice_result_key):
|
| 246 |
+
st.info(f"🎤 Last voice input: **{st.session_state[voice_result_key]}**")
|
| 247 |
+
|
| 248 |
+
return text_input
|
| 249 |
+
|
| 250 |
+
def create_voice_output_button(self, text: str, language: str = 'English',
|
| 251 |
+
button_text: str = "🔊 Listen", key: str = None):
|
| 252 |
+
"""
|
| 253 |
+
Create a voice output button for Streamlit
|
| 254 |
+
"""
|
| 255 |
+
if st.button(button_text, key=f"{key}_speak" if key else None, help="Click to hear the text"):
|
| 256 |
+
if not text or not text.strip():
|
| 257 |
+
st.warning("⚠️ No text to speak")
|
| 258 |
+
return
|
| 259 |
+
|
| 260 |
+
with st.spinner("Generating speech..."):
|
| 261 |
+
success = self.text_to_speech(text, language)
|
| 262 |
+
if success:
|
| 263 |
+
st.success("✅ Audio generated successfully!")
|
| 264 |
+
else:
|
| 265 |
+
st.error("❌ Failed to generate audio. Please try again.")
|
| 266 |
+
|
| 267 |
+
def create_voice_interface_for_sustainability(self, language: str = 'English') -> Dict:
|
| 268 |
+
"""
|
| 269 |
+
Create voice interface specifically for sustainability tracker
|
| 270 |
+
"""
|
| 271 |
+
st.markdown("### 🎤 Voice Input for Sustainability Data")
|
| 272 |
+
|
| 273 |
+
# Voice input for water usage
|
| 274 |
+
water_usage_text = self.create_voice_input_widget(
|
| 275 |
+
"💧 Water Usage (ML/ha) - Voice Input",
|
| 276 |
+
language=language,
|
| 277 |
+
key="water_voice",
|
| 278 |
+
help_text="Speak the water usage amount"
|
| 279 |
+
)
|
| 280 |
+
|
| 281 |
+
# Voice input for fertilizer usage
|
| 282 |
+
fertilizer_usage_text = self.create_voice_input_widget(
|
| 283 |
+
"🧪 Fertilizer Usage (tons/ha) - Voice Input",
|
| 284 |
+
language=language,
|
| 285 |
+
key="fertilizer_voice",
|
| 286 |
+
help_text="Speak the fertilizer usage amount"
|
| 287 |
+
)
|
| 288 |
+
|
| 289 |
+
# Voice input for crop rotation
|
| 290 |
+
rotation_text = self.create_voice_input_widget(
|
| 291 |
+
"🔄 Crop Rotation (Yes/No) - Voice Input",
|
| 292 |
+
language=language,
|
| 293 |
+
key="rotation_voice",
|
| 294 |
+
help_text="Say 'Yes' or 'No' for crop rotation"
|
| 295 |
+
)
|
| 296 |
+
|
| 297 |
+
# Get voice results from session state
|
| 298 |
+
water_voice = st.session_state.get("water_voice_voice_result", "")
|
| 299 |
+
fertilizer_voice = st.session_state.get("fertilizer_voice_voice_result", "")
|
| 300 |
+
rotation_voice = st.session_state.get("rotation_voice_voice_result", "")
|
| 301 |
+
|
| 302 |
+
# Use voice input if available, otherwise use text input
|
| 303 |
+
water_usage_text = water_voice if water_voice else water_usage_text
|
| 304 |
+
fertilizer_usage_text = fertilizer_voice if fertilizer_voice else fertilizer_usage_text
|
| 305 |
+
rotation_text = rotation_voice if rotation_voice else rotation_text
|
| 306 |
+
|
| 307 |
+
# Process voice inputs
|
| 308 |
+
data = {}
|
| 309 |
+
|
| 310 |
+
if water_usage_text:
|
| 311 |
+
try:
|
| 312 |
+
# Extract numbers from voice input
|
| 313 |
+
import re
|
| 314 |
+
numbers = re.findall(r'\d+\.?\d*', water_usage_text)
|
| 315 |
+
if numbers:
|
| 316 |
+
data['water_score'] = float(numbers[0])
|
| 317 |
+
except:
|
| 318 |
+
st.warning("Could not parse water usage from voice input")
|
| 319 |
+
|
| 320 |
+
if fertilizer_usage_text:
|
| 321 |
+
try:
|
| 322 |
+
import re
|
| 323 |
+
numbers = re.findall(r'\d+\.?\d*', fertilizer_usage_text)
|
| 324 |
+
if numbers:
|
| 325 |
+
data['fertilizer_use'] = float(numbers[0])
|
| 326 |
+
except:
|
| 327 |
+
st.warning("Could not parse fertilizer usage from voice input")
|
| 328 |
+
|
| 329 |
+
if rotation_text:
|
| 330 |
+
rotation_lower = rotation_text.lower()
|
| 331 |
+
if any(word in rotation_lower for word in ['yes', 'haan', 'ಹೌದು', 'అవును', 'ஆம்', 'അതെ', 'हाँ', 'oui', 'sí']):
|
| 332 |
+
data['rotation'] = True
|
| 333 |
+
elif any(word in rotation_lower for word in ['no', 'nahi', 'ಇಲ್ಲ', 'లేదు', 'இல்லை', 'ഇല്ല', 'नहीं', 'non', 'no']):
|
| 334 |
+
data['rotation'] = False
|
| 335 |
+
|
| 336 |
+
return data
|
| 337 |
+
|
| 338 |
+
def create_voice_interface_for_farm_details(self, language: str = 'English') -> Dict:
|
| 339 |
+
"""
|
| 340 |
+
Create voice interface for farm details input
|
| 341 |
+
"""
|
| 342 |
+
st.markdown("### 🎤 Voice Input for Farm Details")
|
| 343 |
+
|
| 344 |
+
# Voice input for farm size
|
| 345 |
+
farm_size_text = self.create_voice_input_widget(
|
| 346 |
+
"🌾 Farm Size (hectares) - Voice Input",
|
| 347 |
+
language=language,
|
| 348 |
+
key="farm_size_voice",
|
| 349 |
+
help_text="Speak the farm size in hectares"
|
| 350 |
+
)
|
| 351 |
+
|
| 352 |
+
# Voice input for crop preference
|
| 353 |
+
crop_preference_text = self.create_voice_input_widget(
|
| 354 |
+
"🌱 Crop Preference - Voice Input",
|
| 355 |
+
language=language,
|
| 356 |
+
key="crop_preference_voice",
|
| 357 |
+
help_text="Speak your crop preference (Grains, Vegetables, Fruits)"
|
| 358 |
+
)
|
| 359 |
+
|
| 360 |
+
# Voice input for soil type
|
| 361 |
+
soil_type_text = self.create_voice_input_widget(
|
| 362 |
+
"🗺️ Soil Type - Voice Input",
|
| 363 |
+
language=language,
|
| 364 |
+
key="soil_type_voice",
|
| 365 |
+
help_text="Speak the soil type (Loamy, Sandy, Clay)"
|
| 366 |
+
)
|
| 367 |
+
|
| 368 |
+
# Get voice results from session state
|
| 369 |
+
farm_size_voice = st.session_state.get("farm_size_voice_voice_result", "")
|
| 370 |
+
crop_preference_voice = st.session_state.get("crop_preference_voice_voice_result", "")
|
| 371 |
+
soil_type_voice = st.session_state.get("soil_type_voice_voice_result", "")
|
| 372 |
+
|
| 373 |
+
# Use voice input if available, otherwise use text input
|
| 374 |
+
farm_size_text = farm_size_voice if farm_size_voice else farm_size_text
|
| 375 |
+
crop_preference_text = crop_preference_voice if crop_preference_voice else crop_preference_text
|
| 376 |
+
soil_type_text = soil_type_voice if soil_type_voice else soil_type_text
|
| 377 |
+
|
| 378 |
+
# Process voice inputs
|
| 379 |
+
data = {}
|
| 380 |
+
|
| 381 |
+
# Debug: Show what was captured
|
| 382 |
+
if farm_size_text or crop_preference_text or soil_type_text:
|
| 383 |
+
st.info(f"🎤 Voice inputs captured: Farm Size='{farm_size_text}', Crop='{crop_preference_text}', Soil='{soil_type_text}'")
|
| 384 |
+
|
| 385 |
+
if farm_size_text:
|
| 386 |
+
try:
|
| 387 |
+
import re
|
| 388 |
+
numbers = re.findall(r'\d+', farm_size_text)
|
| 389 |
+
if numbers:
|
| 390 |
+
data['land_size'] = int(numbers[0])
|
| 391 |
+
st.success(f"✅ Parsed land size: {data['land_size']} hectares")
|
| 392 |
+
else:
|
| 393 |
+
st.warning("Could not find numbers in farm size voice input")
|
| 394 |
+
except Exception as e:
|
| 395 |
+
st.warning(f"Could not parse farm size from voice input: {e}")
|
| 396 |
+
|
| 397 |
+
if crop_preference_text:
|
| 398 |
+
crop_lower = crop_preference_text.lower()
|
| 399 |
+
if any(word in crop_lower for word in ['grain', 'grains', 'अनाज', 'ಧಾನ್ಯ', 'ధాన్యం', 'தானியம்', 'ധാന്യം']):
|
| 400 |
+
data['crop_preference'] = 'Grains'
|
| 401 |
+
st.success(f"✅ Parsed crop preference: {data['crop_preference']}")
|
| 402 |
+
elif any(word in crop_lower for word in ['vegetable', 'vegetables', 'सब्जी', 'ತರಕಾರಿ', 'కూరగాయలు', 'காய்கறி', 'പച്ചക്കറി']):
|
| 403 |
+
data['crop_preference'] = 'Vegetables'
|
| 404 |
+
st.success(f"✅ Parsed crop preference: {data['crop_preference']}")
|
| 405 |
+
elif any(word in crop_lower for word in ['fruit', 'fruits', 'फल', 'ಹಣ್ಣು', 'పండు', 'பழம்', 'പഴം']):
|
| 406 |
+
data['crop_preference'] = 'Fruits'
|
| 407 |
+
st.success(f"✅ Parsed crop preference: {data['crop_preference']}")
|
| 408 |
+
else:
|
| 409 |
+
st.warning(f"Could not recognize crop preference from: '{crop_preference_text}'")
|
| 410 |
+
|
| 411 |
+
if soil_type_text:
|
| 412 |
+
soil_lower = soil_type_text.lower()
|
| 413 |
+
if any(word in soil_lower for word in ['loamy', 'loam', 'दोमट', 'ಲೋಮಿ', 'లోమి', 'லோமி', 'ലോമി']):
|
| 414 |
+
data['soil_type'] = 'Loamy'
|
| 415 |
+
st.success(f"✅ Parsed soil type: {data['soil_type']}")
|
| 416 |
+
elif any(word in soil_lower for word in ['sandy', 'sand', 'बालू', 'ಮರಳು', 'ఇసుక', 'மணல்', 'മണൽ']):
|
| 417 |
+
data['soil_type'] = 'Sandy'
|
| 418 |
+
st.success(f"✅ Parsed soil type: {data['soil_type']}")
|
| 419 |
+
elif any(word in soil_lower for word in ['clay', 'चिकनी', 'ಕ್ಲೇ', 'క్లే', 'களிமண்', 'കളിമണ്ണ്']):
|
| 420 |
+
data['soil_type'] = 'Clay'
|
| 421 |
+
st.success(f"✅ Parsed soil type: {data['soil_type']}")
|
| 422 |
+
else:
|
| 423 |
+
st.warning(f"Could not recognize soil type from: '{soil_type_text}'")
|
| 424 |
+
|
| 425 |
+
return data
|
| 426 |
+
|
| 427 |
+
def create_voice_help_system(self, language: str = 'English'):
|
| 428 |
+
"""
|
| 429 |
+
Create a voice help system for farmers
|
| 430 |
+
"""
|
| 431 |
+
st.markdown("### 🎤 Voice Help System")
|
| 432 |
+
|
| 433 |
+
help_texts = {
|
| 434 |
+
'English': {
|
| 435 |
+
'welcome': "Welcome to the Sustainable Farming AI Platform. You can use voice commands to interact with the system.",
|
| 436 |
+
'farm_details': "To enter farm details, speak your farm size, crop preference, and soil type.",
|
| 437 |
+
'sustainability': "To log sustainability data, speak your water usage, fertilizer usage, and whether you practice crop rotation.",
|
| 438 |
+
'recommendations': "Click the generate recommendation button to get AI-powered farming advice based on your inputs."
|
| 439 |
+
},
|
| 440 |
+
'Hindi': {
|
| 441 |
+
'welcome': "सस्टेनेबल फार्मिंग AI प्लेटफॉर्म में आपका स्वागत है। आप सिस्टम के साथ बातचीत करने के लिए आवाज कमांड का उपयोग कर सकते हैं।",
|
| 442 |
+
'farm_details': "फार्म विवरण दर्ज करने के लिए, अपने फार्म का आकार, फसल पसंद और मिट्टी का प्रकार बोलें।",
|
| 443 |
+
'sustainability': "सस्टेनेबलिटी डेटा लॉग करने के लिए, अपने पानी के उपयोग, उर्वरक के उपयोग और क्या आप फसल चक्रण का अभ्यास करते हैं, बोलें।",
|
| 444 |
+
'recommendations': "अपने इनपुट के आधार पर AI-संचालित खेती सलाह प्राप्त करने के लिए सिफारिश बटन पर क्लिक करें।"
|
| 445 |
+
},
|
| 446 |
+
'Telugu': {
|
| 447 |
+
'welcome': "సస్టైనబుల్ ఫార్మింగ్ AI ప్లాట్ఫారమ్కు స్వాగతం. మీరు సిస్టమ్తో ఇంటరాక్ట్ చేయడానికి వాయిస్ కమాండ్లను ఉపయోగించవచ్చు.",
|
| 448 |
+
'farm_details': "ఫార్మ్ వివరాలను నమోదు చేయడానికి, మీ ఫార్మ్ పరిమాణం, పంట ప్రాధాన్యత మరియు నేల రకాన్ని మాట్లాడండి.",
|
| 449 |
+
'sustainability': "సస్టైనబిలిటీ డేటాను లాగ్ చేయడానికి, మీ నీటి వినియోగం, ఎరువు వినియోగం మరియు మీరు పంట మార్పిడిని అభ్యసిస్తున్నారా అని మాట్లాడండి.",
|
| 450 |
+
'recommendations': "మీ ఇన్పుట్ల ఆధారంగా AI-ఆధారిత వ్యవసాయ సలహా పొందడానికి సిఫారసు బటన్పై క్లిక్ చేయండి."
|
| 451 |
+
}
|
| 452 |
+
}
|
| 453 |
+
|
| 454 |
+
help_data = help_texts.get(language, help_texts['English'])
|
| 455 |
+
|
| 456 |
+
col1, col2 = st.columns(2)
|
| 457 |
+
|
| 458 |
+
with col1:
|
| 459 |
+
if st.button("🔊 Listen to Welcome", key="help_welcome"):
|
| 460 |
+
self.text_to_speech(help_data['welcome'], language)
|
| 461 |
+
|
| 462 |
+
with col2:
|
| 463 |
+
if st.button("🔊 Listen to Farm Details Help", key="help_farm"):
|
| 464 |
+
self.text_to_speech(help_data['farm_details'], language)
|
| 465 |
+
|
| 466 |
+
col3, col4 = st.columns(2)
|
| 467 |
+
|
| 468 |
+
with col3:
|
| 469 |
+
if st.button("🔊 Listen to Sustainability Help", key="help_sustainability"):
|
| 470 |
+
self.text_to_speech(help_data['sustainability'], language)
|
| 471 |
+
|
| 472 |
+
with col4:
|
| 473 |
+
if st.button("🔊 Listen to Recommendations Help", key="help_recommendations"):
|
| 474 |
+
self.text_to_speech(help_data['recommendations'], language)
|
| 475 |
+
|
| 476 |
+
def get_supported_languages(self) -> List[str]:
|
| 477 |
+
"""
|
| 478 |
+
Get list of supported languages
|
| 479 |
+
"""
|
| 480 |
+
return list(self.language_codes.keys())
|
| 481 |
+
|
| 482 |
+
def is_voice_available(self) -> bool:
|
| 483 |
+
"""
|
| 484 |
+
Check if voice functionality is available
|
| 485 |
+
"""
|
| 486 |
+
return self.pyaudio_available and self.microphone is not None
|
| 487 |
+
|
models/sustainability_Expert.py
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import sqlite3
|
| 2 |
+
import pandas as pd
|
| 3 |
+
import numpy as np
|
| 4 |
+
import os
|
| 5 |
+
|
| 6 |
+
class SustainabilityExpert:
|
| 7 |
+
def __init__(self, db_path="Models/database/sustainable_farming.db"):
|
| 8 |
+
self.db_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'database', 'sustainable_farming.db'))
|
| 9 |
+
self.sustainability_db = self._load_sustainability_data()
|
| 10 |
+
|
| 11 |
+
def _load_sustainability_data(self):
|
| 12 |
+
with sqlite3.connect(self.db_path) as conn:
|
| 13 |
+
df = pd.read_sql("""
|
| 14 |
+
SELECT Crop_Type,
|
| 15 |
+
AVG(Sustainability_Score) AS avg_score,
|
| 16 |
+
AVG(Fertilizer_Usage_kg) AS avg_fertilizer,
|
| 17 |
+
AVG(Soil_Moisture) AS avg_moisture,
|
| 18 |
+
AVG(Pesticide_Usage_kg) AS avg_pesticide,
|
| 19 |
+
AVG(Crop_Yield_ton) AS avg_yield
|
| 20 |
+
FROM farmer_advisor
|
| 21 |
+
GROUP BY Crop_Type
|
| 22 |
+
""", conn)
|
| 23 |
+
return df.set_index('Crop_Type').to_dict('index')
|
| 24 |
+
|
| 25 |
+
def calculate_carbon_score(self, fertilizer, pesticide, crop_yield):
|
| 26 |
+
# Carbon footprint score based on fertilizer and pesticide usage
|
| 27 |
+
# Lower usage and higher yield means better score
|
| 28 |
+
fertilizer_impact = 1 - (fertilizer / 100) # Normalize to 0-1
|
| 29 |
+
pesticide_impact = 1 - (pesticide / 20) # Normalize to 0-1
|
| 30 |
+
yield_impact = min(crop_yield / 5, 1) # Normalize to 0-1
|
| 31 |
+
|
| 32 |
+
return (0.4 * fertilizer_impact + 0.3 * pesticide_impact + 0.3 * yield_impact)
|
| 33 |
+
|
| 34 |
+
def calculate_water_score(self, soil_moisture, rainfall):
|
| 35 |
+
# Water score based on soil moisture and rainfall
|
| 36 |
+
# Optimal moisture is between 30-40%
|
| 37 |
+
moisture_score = 1 - abs(soil_moisture - 35) / 35
|
| 38 |
+
rainfall_score = min(rainfall / 100, 1)
|
| 39 |
+
|
| 40 |
+
return (0.6 * moisture_score + 0.4 * rainfall_score)
|
| 41 |
+
|
| 42 |
+
def calculate_erosion_score(self, soil_ph, soil_moisture):
|
| 43 |
+
# Erosion score based on soil pH and moisture
|
| 44 |
+
# Optimal pH is between 6-7
|
| 45 |
+
ph_score = 1 - abs(soil_ph - 6.5) / 3.5
|
| 46 |
+
moisture_score = 1 - abs(soil_moisture - 35) / 35
|
| 47 |
+
|
| 48 |
+
return (0.5 * ph_score + 0.5 * moisture_score)
|
| 49 |
+
|
| 50 |
+
def evaluate(self, crops, soil_ph, soil_moisture, rainfall, fertilizer, pesticide, crop_yield):
|
| 51 |
+
"""Evaluate sustainability of crops based on environmental factors"""
|
| 52 |
+
results = {}
|
| 53 |
+
for crop in crops:
|
| 54 |
+
# Calculate individual scores
|
| 55 |
+
carbon_score = self.calculate_carbon_score(fertilizer, pesticide, crop_yield)
|
| 56 |
+
water_score = self.calculate_water_score(soil_moisture, rainfall)
|
| 57 |
+
erosion_score = self.calculate_erosion_score(soil_ph, soil_moisture)
|
| 58 |
+
|
| 59 |
+
# Calculate overall sustainability score
|
| 60 |
+
sustainability_score = (
|
| 61 |
+
0.4 * carbon_score +
|
| 62 |
+
0.3 * water_score +
|
| 63 |
+
0.3 * erosion_score
|
| 64 |
+
)
|
| 65 |
+
|
| 66 |
+
results[crop] = {
|
| 67 |
+
'sustainability': sustainability_score,
|
| 68 |
+
'carbon': carbon_score,
|
| 69 |
+
'water': water_score,
|
| 70 |
+
'erosion': erosion_score
|
| 71 |
+
}
|
| 72 |
+
|
| 73 |
+
# Return the first crop's results since we're only evaluating one crop at a time
|
| 74 |
+
return crops[0], results[crops[0]]
|
models/sustainable_farming.db
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:808edbd5def2a5dc97c70c955c1a96666afd71aa0ce844b93d032714189ae4c8
|
| 3 |
+
size 3477504
|
models/temp_model.pkl
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:5373e356705070954f5db3543e1d00088ecc78cd6afe57ee81a49daf4ae7e513
|
| 3 |
+
size 91073329
|
models/test_recommendation.py
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from Models.central_coordinator import CentralCoordinator
|
| 2 |
+
|
| 3 |
+
# Initialize the CentralCoordinator
|
| 4 |
+
coordinator = CentralCoordinator()
|
| 5 |
+
|
| 6 |
+
# Generate recommendation with sample input values and real-time weather for a city
|
| 7 |
+
result = coordinator.generate_recommendation(
|
| 8 |
+
soil_ph=4.0,
|
| 9 |
+
soil_moisture=10,
|
| 10 |
+
temperature=32,
|
| 11 |
+
rainfall=35,
|
| 12 |
+
fertilizer=0.5,
|
| 13 |
+
pesticide=0.3,
|
| 14 |
+
crop_yield=15,
|
| 15 |
+
city_name="bangalore" # Use real-time weather for Delhi
|
| 16 |
+
)
|
| 17 |
+
|
| 18 |
+
# Print recommendation results
|
| 19 |
+
print("\n--- Final Crop Recommendation ---")
|
| 20 |
+
for key, value in result.items():
|
| 21 |
+
if key == "Warnings" and value:
|
| 22 |
+
print("Warnings:")
|
| 23 |
+
for warning in value:
|
| 24 |
+
print(f" - {warning}")
|
| 25 |
+
else:
|
| 26 |
+
print(f"{key}: {value}")
|
| 27 |
+
|
| 28 |
+
# Automatically plot the result scores
|
| 29 |
+
CentralCoordinator.plot_scores(result)
|
models/weather_Analyst.py
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import sqlite3
|
| 2 |
+
import pandas as pd
|
| 3 |
+
import joblib
|
| 4 |
+
import os
|
| 5 |
+
import logging
|
| 6 |
+
from sklearn.ensemble import RandomForestRegressor
|
| 7 |
+
from sklearn.preprocessing import StandardScaler
|
| 8 |
+
from sklearn.model_selection import train_test_split
|
| 9 |
+
|
| 10 |
+
# Set up logging
|
| 11 |
+
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
| 12 |
+
logger = logging.getLogger(__name__)
|
| 13 |
+
|
| 14 |
+
class WeatherAnalyst:
|
| 15 |
+
|
| 16 |
+
def __init__(self, db_path=None):
|
| 17 |
+
if db_path is None:
|
| 18 |
+
db_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'database', 'sustainable_farming.db'))
|
| 19 |
+
self.db_path = db_path
|
| 20 |
+
# Always use absolute path for models_dir
|
| 21 |
+
self.models_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'models'))
|
| 22 |
+
|
| 23 |
+
if not os.path.exists(self.models_dir):
|
| 24 |
+
os.makedirs(self.models_dir)
|
| 25 |
+
logger.info(f"Created models directory at: {self.models_dir}")
|
| 26 |
+
|
| 27 |
+
self.scaler = StandardScaler()
|
| 28 |
+
self.df = None
|
| 29 |
+
temp_model_path = os.path.join(self.models_dir, 'temp_model.pkl')
|
| 30 |
+
rain_model_path = os.path.join(self.models_dir, 'rain_model.pkl')
|
| 31 |
+
scaler_path = os.path.join(self.models_dir, 'weather_scaler.pkl')
|
| 32 |
+
if os.path.exists(temp_model_path) and os.path.exists(rain_model_path) and os.path.exists(scaler_path):
|
| 33 |
+
# Models already exist, do not retrain
|
| 34 |
+
pass
|
| 35 |
+
else:
|
| 36 |
+
self._prepare_data()
|
| 37 |
+
self._train_models()
|
| 38 |
+
|
| 39 |
+
def _prepare_data(self):
|
| 40 |
+
try:
|
| 41 |
+
with sqlite3.connect(self.db_path) as conn:
|
| 42 |
+
self.df = pd.read_sql("""
|
| 43 |
+
SELECT Soil_pH, Soil_Moisture, Temperature_C, Rainfall_mm,
|
| 44 |
+
Fertilizer_Usage_kg, Pesticide_Usage_kg
|
| 45 |
+
FROM farmer_advisor
|
| 46 |
+
""", conn)
|
| 47 |
+
if self.df.empty:
|
| 48 |
+
logger.warning("No data found in farmer_advisor table.")
|
| 49 |
+
raise ValueError("The farmer_advisor table is empty. Please populate it with data.")
|
| 50 |
+
logger.info(f"Loaded {len(self.df)} rows from farmer_advisor table.")
|
| 51 |
+
except sqlite3.Error as e:
|
| 52 |
+
logger.error(f"Database error: {str(e)}")
|
| 53 |
+
raise sqlite3.Error(f"Failed to connect to database or query data: {str(e)}")
|
| 54 |
+
except Exception as e:
|
| 55 |
+
logger.error(f"Error preparing data: {str(e)}")
|
| 56 |
+
raise
|
| 57 |
+
|
| 58 |
+
def _train_models(self):
|
| 59 |
+
try:
|
| 60 |
+
features = ['Soil_pH', 'Soil_Moisture', 'Fertilizer_Usage_kg', 'Pesticide_Usage_kg']
|
| 61 |
+
X = self.df[features]
|
| 62 |
+
X_scaled = self.scaler.fit_transform(X)
|
| 63 |
+
|
| 64 |
+
y_temp = self.df['Temperature_C']
|
| 65 |
+
y_rain = self.df['Rainfall_mm']
|
| 66 |
+
|
| 67 |
+
temp_model = RandomForestRegressor(n_estimators=100, random_state=42)
|
| 68 |
+
temp_model.fit(X_scaled, y_temp)
|
| 69 |
+
|
| 70 |
+
rain_model = RandomForestRegressor(n_estimators=100, random_state=42)
|
| 71 |
+
rain_model.fit(X_scaled, y_rain)
|
| 72 |
+
|
| 73 |
+
temp_model_path = os.path.join(self.models_dir, 'temp_model.pkl')
|
| 74 |
+
rain_model_path = os.path.join(self.models_dir, 'rain_model.pkl')
|
| 75 |
+
scaler_path = os.path.join(self.models_dir, 'weather_scaler.pkl')
|
| 76 |
+
|
| 77 |
+
# Remove files if they exist to avoid OneDrive/Windows file lock issues
|
| 78 |
+
for path in [temp_model_path, rain_model_path, scaler_path]:
|
| 79 |
+
try:
|
| 80 |
+
if os.path.exists(path):
|
| 81 |
+
os.remove(path)
|
| 82 |
+
except Exception as e:
|
| 83 |
+
logger.warning(f"Could not remove existing model file {path}: {e}")
|
| 84 |
+
|
| 85 |
+
joblib.dump(temp_model, temp_model_path)
|
| 86 |
+
logger.info(f"Saved temperature model at {temp_model_path}")
|
| 87 |
+
|
| 88 |
+
joblib.dump(rain_model, rain_model_path)
|
| 89 |
+
logger.info(f"Saved rainfall model at {rain_model_path}")
|
| 90 |
+
|
| 91 |
+
joblib.dump(self.scaler, scaler_path)
|
| 92 |
+
logger.info(f"Saved scaler at {scaler_path}")
|
| 93 |
+
except Exception as e:
|
| 94 |
+
logger.error(f"Error training models: {str(e)}")
|
| 95 |
+
raise
|
| 96 |
+
|
| 97 |
+
def forecast(self, soil_ph, soil_moisture, fertilizer, pesticide):
|
| 98 |
+
try:
|
| 99 |
+
temp_model = joblib.load(os.path.join(self.models_dir, 'temp_model.pkl'))
|
| 100 |
+
rain_model = joblib.load(os.path.join(self.models_dir, 'rain_model.pkl'))
|
| 101 |
+
scaler = joblib.load(os.path.join(self.models_dir, 'weather_scaler.pkl'))
|
| 102 |
+
|
| 103 |
+
input_df = pd.DataFrame([[soil_ph, soil_moisture, fertilizer, pesticide]],
|
| 104 |
+
columns=['Soil_pH', 'Soil_Moisture', 'Fertilizer_Usage_kg', 'Pesticide_Usage_kg'])
|
| 105 |
+
input_scaled = scaler.transform(input_df)
|
| 106 |
+
|
| 107 |
+
predicted_temp = temp_model.predict(input_scaled)[0]
|
| 108 |
+
predicted_rain = rain_model.predict(input_scaled)[0]
|
| 109 |
+
return {'temperature': [predicted_temp], 'rainfall': [predicted_rain]}
|
| 110 |
+
except Exception as e:
|
| 111 |
+
logger.error(f"Error during forecasting: {str(e)}")
|
| 112 |
+
raise
|
models/weather_api.py
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import requests
|
| 2 |
+
|
| 3 |
+
API_KEY = "8acd7401e3d1478a87596f7e00e76226"
|
| 4 |
+
BASE_URL = "https://api.openweathermap.org/data/2.5/weather"
|
| 5 |
+
|
| 6 |
+
def get_current_weather(city_name):
|
| 7 |
+
"""
|
| 8 |
+
Fetch current weather data for a given city using OpenWeatherMap API.
|
| 9 |
+
Returns a dictionary with temperature (Celsius) and rainfall (mm, if available).
|
| 10 |
+
"""
|
| 11 |
+
params = {
|
| 12 |
+
'q': city_name,
|
| 13 |
+
'appid': API_KEY,
|
| 14 |
+
'units': 'metric'
|
| 15 |
+
}
|
| 16 |
+
response = requests.get(BASE_URL, params=params)
|
| 17 |
+
data = response.json()
|
| 18 |
+
if response.status_code != 200:
|
| 19 |
+
raise Exception(f"Weather API error: {data.get('message', 'Unknown error')}")
|
| 20 |
+
temp = data['main']['temp']
|
| 21 |
+
# Rainfall may not always be present
|
| 22 |
+
rain = data.get('rain', {}).get('1h', 0.0)
|
| 23 |
+
return {
|
| 24 |
+
'temperature': temp,
|
| 25 |
+
'rainfall': rain
|
| 26 |
+
}
|
| 27 |
+
import requests
|
| 28 |
+
|
| 29 |
+
API_KEY = "8acd7401e3d1478a87596f7e00e76226"
|
| 30 |
+
BASE_URL = "https://api.openweathermap.org/data/2.5/weather"
|
| 31 |
+
|
| 32 |
+
def get_current_weather(city_name):
|
| 33 |
+
"""
|
| 34 |
+
Fetch current weather data for a given city using OpenWeatherMap API.
|
| 35 |
+
Returns a dictionary with temperature (Celsius) and rainfall (mm, if available).
|
| 36 |
+
"""
|
| 37 |
+
params = {
|
| 38 |
+
'q': city_name,
|
| 39 |
+
'appid': API_KEY,
|
| 40 |
+
'units': 'metric'
|
| 41 |
+
}
|
| 42 |
+
response = requests.get(BASE_URL, params=params)
|
| 43 |
+
data = response.json()
|
| 44 |
+
if response.status_code != 200:
|
| 45 |
+
raise Exception(f"Weather API error: {data.get('message', 'Unknown error')}")
|
| 46 |
+
temp = data['main']['temp']
|
| 47 |
+
# Rainfall may not always be present
|
| 48 |
+
rain = data.get('rain', {}).get('1h', 0.0)
|
| 49 |
+
return {
|
| 50 |
+
'temperature': temp,
|
| 51 |
+
'rainfall': rain
|
| 52 |
+
}
|
models/weather_scaler.pkl
ADDED
|
Binary file (1.1 kB). View file
|
|
|