Update server.py
Browse files
server.py
CHANGED
@@ -108,36 +108,59 @@ async def generate_dynamic_alert(district: str, state: str):
|
|
108 |
"""Generate dynamic alert data using geographic functions and REAL weather data"""
|
109 |
|
110 |
try:
|
111 |
-
# Get villages for the district
|
112 |
villages_data = await geographic_tools.list_villages(state, district)
|
113 |
|
114 |
-
|
115 |
-
|
116 |
-
if "villages" in villages_data and villages_data["villages"]:
|
117 |
-
village_name = random.choice(villages_data["villages"])
|
118 |
-
# Avoid village name being same as district
|
119 |
-
if village_name.lower() == district.lower() and len(villages_data["villages"]) > 1:
|
120 |
-
other_villages = [v for v in villages_data["villages"] if v.lower() != district.lower()]
|
121 |
-
if other_villages:
|
122 |
-
village_name = random.choice(other_villages)
|
123 |
|
124 |
-
#
|
125 |
-
|
|
|
|
|
126 |
|
127 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
128 |
try:
|
129 |
-
|
130 |
-
if "error" not in
|
131 |
-
location_coords = [
|
132 |
-
|
133 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
134 |
|
135 |
-
#
|
136 |
-
|
137 |
-
|
|
|
|
|
138 |
|
139 |
-
#
|
|
|
|
|
|
|
|
|
140 |
try:
|
|
|
|
|
141 |
current_weather_data = await open_meteo.get_current_weather(
|
142 |
latitude=location_coords[0],
|
143 |
longitude=location_coords[1]
|
@@ -169,50 +192,63 @@ async def generate_dynamic_alert(district: str, state: str):
|
|
169 |
"expected_rainfall": f"{next_3_days_rain:.1f}mm",
|
170 |
"temperature": f"{current_temp:.1f}°C",
|
171 |
"humidity": f"{estimated_humidity}%",
|
172 |
-
"wind_speed": f"{current_windspeed:.1f} km/h"
|
|
|
173 |
}
|
174 |
|
175 |
-
# Generate alert message based on weather conditions
|
176 |
if next_3_days_rain > 25:
|
177 |
alert_type = "heavy_rain_warning"
|
178 |
urgency = "high"
|
179 |
-
alert_message = f"Heavy rainfall ({next_3_days_rain:.1f}mm) expected in next 3 days
|
180 |
action_items = ["delay_fertilizer", "check_drainage", "monitor_crops", "prepare_harvest_protection"]
|
181 |
elif next_3_days_rain > 10:
|
182 |
alert_type = "moderate_rain_warning"
|
183 |
urgency = "medium"
|
184 |
-
alert_message = f"Moderate rainfall ({next_3_days_rain:.1f}mm) expected in next 3 days
|
185 |
action_items = ["monitor_soil", "check_drainage", "adjust_irrigation"]
|
186 |
elif next_3_days_rain < 2 and current_temp > 35:
|
187 |
alert_type = "heat_drought_warning"
|
188 |
urgency = "high"
|
189 |
-
alert_message = f"High temperature ({current_temp:.1f}°C) with minimal rainfall expected
|
190 |
action_items = ["increase_irrigation", "mulch_crops", "monitor_plant_stress"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
191 |
else:
|
192 |
alert_type = "weather_update"
|
193 |
urgency = "low"
|
194 |
-
alert_message = f"Normal weather conditions expected
|
195 |
action_items = ["routine_monitoring", "maintain_irrigation"]
|
196 |
|
197 |
-
logger.info(f"Real weather data retrieved for {district}: {current_temp}°C, {next_3_days_rain:.1f}mm rain")
|
198 |
|
199 |
except Exception as weather_error:
|
200 |
-
logger.error(f"Failed to get real weather data for {district}: {weather_error}")
|
201 |
-
raise Exception(f"Unable to retrieve current weather conditions for {district}")
|
202 |
|
203 |
return {
|
204 |
-
"alert_id": f"{state.upper()[:2]}_{district.upper()[:3]}
|
205 |
"timestamp": datetime.now().isoformat() + "Z",
|
206 |
"location": {
|
207 |
-
"village":
|
208 |
"district": district,
|
209 |
"state": state.capitalize(),
|
210 |
-
"coordinates": location_coords
|
|
|
|
|
211 |
},
|
212 |
"crop": {
|
213 |
"name": regional_crop,
|
214 |
"stage": crop_stage,
|
215 |
-
"planted_estimate": "2025-06-15"
|
216 |
},
|
217 |
"alert": {
|
218 |
"type": alert_type,
|
@@ -222,7 +258,7 @@ async def generate_dynamic_alert(district: str, state: str):
|
|
222 |
"valid_until": (datetime.now() + timedelta(days=3)).isoformat() + "Z"
|
223 |
},
|
224 |
"weather": real_weather,
|
225 |
-
"data_source": "
|
226 |
}
|
227 |
|
228 |
except Exception as e:
|
@@ -230,6 +266,306 @@ async def generate_dynamic_alert(district: str, state: str):
|
|
230 |
raise Exception(f"Failed to generate weather alert for {district}: {str(e)}")
|
231 |
|
232 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
233 |
@app.get("/")
|
234 |
async def root():
|
235 |
return {"message": "MCP Weather Server is running"}
|
|
|
108 |
"""Generate dynamic alert data using geographic functions and REAL weather data"""
|
109 |
|
110 |
try:
|
111 |
+
# Step 1: Get villages for the district using your geographic tools
|
112 |
villages_data = await geographic_tools.list_villages(state, district)
|
113 |
|
114 |
+
if "error" in villages_data:
|
115 |
+
raise Exception(f"District '{district}' not found in {state}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
116 |
|
117 |
+
# Step 2: Pick a random village from the actual list
|
118 |
+
available_villages = villages_data.get("villages", [])
|
119 |
+
if not available_villages:
|
120 |
+
raise Exception(f"No villages found for {district}")
|
121 |
|
122 |
+
selected_village = random.choice(available_villages)
|
123 |
+
logger.info(f"Selected village: {selected_village} from {len(available_villages)} villages")
|
124 |
+
|
125 |
+
# Step 3: Try to get coordinates for the selected village first, then district
|
126 |
+
location_coords = None
|
127 |
+
location_source = ""
|
128 |
+
|
129 |
+
# Try village coordinates first
|
130 |
try:
|
131 |
+
village_location = await geographic_tools.reverse_geocode(selected_village)
|
132 |
+
if "error" not in village_location and "lat" in village_location:
|
133 |
+
location_coords = [village_location["lat"], village_location["lng"]]
|
134 |
+
location_source = f"village_{selected_village}"
|
135 |
+
logger.info(f"Using village coordinates for {selected_village}: {location_coords}")
|
136 |
+
except Exception as e:
|
137 |
+
logger.warning(f"Village geocoding failed for {selected_village}: {e}")
|
138 |
+
|
139 |
+
# Fallback to district coordinates if village lookup failed
|
140 |
+
if not location_coords:
|
141 |
+
try:
|
142 |
+
district_location = await geographic_tools.reverse_geocode(district)
|
143 |
+
if "error" not in district_location and "lat" in district_location:
|
144 |
+
location_coords = [district_location["lat"], district_location["lng"]]
|
145 |
+
location_source = f"district_{district}"
|
146 |
+
logger.info(f"Using district coordinates for {district}: {location_coords}")
|
147 |
+
except Exception as e:
|
148 |
+
logger.warning(f"District geocoding failed for {district}: {e}")
|
149 |
|
150 |
+
# Final fallback - but this should rarely happen now
|
151 |
+
if not location_coords:
|
152 |
+
logger.warning(f"No coordinates found for {selected_village} or {district}, using default")
|
153 |
+
location_coords = [25.5941, 85.1376] # Patna fallback
|
154 |
+
location_source = "fallback_patna"
|
155 |
|
156 |
+
# Step 4: Generate regional crop and stage using crop calendar data
|
157 |
+
regional_crop = await get_regional_crop_for_area(district, state)
|
158 |
+
crop_stage = await get_current_crop_stage_dynamic(regional_crop, district)
|
159 |
+
|
160 |
+
# Step 5: GET REAL WEATHER DATA using the actual coordinates
|
161 |
try:
|
162 |
+
logger.info(f"Fetching weather for coordinates: {location_coords} (source: {location_source})")
|
163 |
+
|
164 |
current_weather_data = await open_meteo.get_current_weather(
|
165 |
latitude=location_coords[0],
|
166 |
longitude=location_coords[1]
|
|
|
192 |
"expected_rainfall": f"{next_3_days_rain:.1f}mm",
|
193 |
"temperature": f"{current_temp:.1f}°C",
|
194 |
"humidity": f"{estimated_humidity}%",
|
195 |
+
"wind_speed": f"{current_windspeed:.1f} km/h",
|
196 |
+
"coordinates_source": location_source # Track where coords came from
|
197 |
}
|
198 |
|
199 |
+
# Step 6: Generate alert message based on actual weather conditions
|
200 |
if next_3_days_rain > 25:
|
201 |
alert_type = "heavy_rain_warning"
|
202 |
urgency = "high"
|
203 |
+
alert_message = f"Heavy rainfall ({next_3_days_rain:.1f}mm) expected in next 3 days near {selected_village}, {district}. Delay fertilizer application. Ensure proper drainage."
|
204 |
action_items = ["delay_fertilizer", "check_drainage", "monitor_crops", "prepare_harvest_protection"]
|
205 |
elif next_3_days_rain > 10:
|
206 |
alert_type = "moderate_rain_warning"
|
207 |
urgency = "medium"
|
208 |
+
alert_message = f"Moderate rainfall ({next_3_days_rain:.1f}mm) expected in next 3 days near {selected_village}, {district}. Monitor soil moisture levels."
|
209 |
action_items = ["monitor_soil", "check_drainage", "adjust_irrigation"]
|
210 |
elif next_3_days_rain < 2 and current_temp > 35:
|
211 |
alert_type = "heat_drought_warning"
|
212 |
urgency = "high"
|
213 |
+
alert_message = f"High temperature ({current_temp:.1f}°C) with minimal rainfall expected near {selected_village}, {district}. Increase irrigation frequency."
|
214 |
action_items = ["increase_irrigation", "mulch_crops", "monitor_plant_stress"]
|
215 |
+
elif current_temp < 10:
|
216 |
+
alert_type = "cold_warning"
|
217 |
+
urgency = "medium"
|
218 |
+
alert_message = f"Low temperature ({current_temp:.1f}°C) expected near {selected_village}, {district}. Protect crops from cold damage."
|
219 |
+
action_items = ["protect_crops", "cover_seedlings", "adjust_irrigation_timing"]
|
220 |
+
elif current_windspeed > 30:
|
221 |
+
alert_type = "high_wind_warning"
|
222 |
+
urgency = "medium"
|
223 |
+
alert_message = f"High winds ({current_windspeed:.1f} km/h) expected near {selected_village}, {district}. Secure crop supports and structures."
|
224 |
+
action_items = ["secure_supports", "check_structures", "monitor_damage"]
|
225 |
else:
|
226 |
alert_type = "weather_update"
|
227 |
urgency = "low"
|
228 |
+
alert_message = f"Normal weather conditions expected near {selected_village}, {district}. Temperature {current_temp:.1f}°C, rainfall {next_3_days_rain:.1f}mm."
|
229 |
action_items = ["routine_monitoring", "maintain_irrigation"]
|
230 |
|
231 |
+
logger.info(f"Real weather data retrieved for {selected_village}, {district}: {current_temp}°C, {next_3_days_rain:.1f}mm rain (coords: {location_coords})")
|
232 |
|
233 |
except Exception as weather_error:
|
234 |
+
logger.error(f"Failed to get real weather data for {selected_village}, {district}: {weather_error}")
|
235 |
+
raise Exception(f"Unable to retrieve current weather conditions for {selected_village}, {district}")
|
236 |
|
237 |
return {
|
238 |
+
"alert_id": f"{state.upper()[:2]}_{district.upper()[:3]}_{selected_village.upper()[:3]}_{datetime.now().strftime('%Y%m%d_%H%M')}",
|
239 |
"timestamp": datetime.now().isoformat() + "Z",
|
240 |
"location": {
|
241 |
+
"village": selected_village,
|
242 |
"district": district,
|
243 |
"state": state.capitalize(),
|
244 |
+
"coordinates": location_coords,
|
245 |
+
"coordinates_source": location_source,
|
246 |
+
"total_villages_in_district": len(available_villages)
|
247 |
},
|
248 |
"crop": {
|
249 |
"name": regional_crop,
|
250 |
"stage": crop_stage,
|
251 |
+
"planted_estimate": "2025-06-15" # You could make this dynamic too
|
252 |
},
|
253 |
"alert": {
|
254 |
"type": alert_type,
|
|
|
258 |
"valid_until": (datetime.now() + timedelta(days=3)).isoformat() + "Z"
|
259 |
},
|
260 |
"weather": real_weather,
|
261 |
+
"data_source": "open_meteo_api_with_dynamic_location"
|
262 |
}
|
263 |
|
264 |
except Exception as e:
|
|
|
266 |
raise Exception(f"Failed to generate weather alert for {district}: {str(e)}")
|
267 |
|
268 |
|
269 |
+
|
270 |
+
|
271 |
+
import random
|
272 |
+
from datetime import datetime, date
|
273 |
+
|
274 |
+
# Enhanced crop selection function using your crop calendar data
|
275 |
+
async def get_regional_crop_for_area(district: str, state: str):
|
276 |
+
"""Get typical crop for the region based on season and district - now fully dynamic"""
|
277 |
+
|
278 |
+
if state.lower() != 'bihar':
|
279 |
+
return 'rice' # fallback for other states
|
280 |
+
|
281 |
+
current_month = datetime.now().month
|
282 |
+
current_season = get_current_season(current_month)
|
283 |
+
|
284 |
+
# Get crops that are currently in season using your crop calendar tools
|
285 |
+
try:
|
286 |
+
seasonal_crops_data = await crop_calendar_tools.get_prominent_crops('bihar', current_season)
|
287 |
+
if "error" not in seasonal_crops_data:
|
288 |
+
seasonal_crops = seasonal_crops_data.get('crops', [])
|
289 |
+
else:
|
290 |
+
seasonal_crops = []
|
291 |
+
except Exception as e:
|
292 |
+
logger.warning(f"Failed to get seasonal crops: {e}")
|
293 |
+
seasonal_crops = []
|
294 |
+
|
295 |
+
# District-specific crop preferences (what's commonly grown in each district)
|
296 |
+
district_crop_preferences = {
|
297 |
+
'patna': {
|
298 |
+
'primary': ['rice', 'wheat', 'potato'],
|
299 |
+
'secondary': ['mustard', 'gram', 'barley'],
|
300 |
+
'specialty': ['sugarcane']
|
301 |
+
},
|
302 |
+
'gaya': {
|
303 |
+
'primary': ['wheat', 'rice', 'gram'],
|
304 |
+
'secondary': ['barley', 'lentil', 'mustard'],
|
305 |
+
'specialty': ['arhar']
|
306 |
+
},
|
307 |
+
'bhagalpur': {
|
308 |
+
'primary': ['rice', 'maize', 'wheat'],
|
309 |
+
'secondary': ['jute', 'urd', 'moong'],
|
310 |
+
'specialty': ['groundnut']
|
311 |
+
},
|
312 |
+
'muzaffarpur': {
|
313 |
+
'primary': ['sugarcane', 'rice', 'wheat'],
|
314 |
+
'secondary': ['potato', 'mustard'],
|
315 |
+
'specialty': ['lentil']
|
316 |
+
},
|
317 |
+
'darbhanga': {
|
318 |
+
'primary': ['rice', 'wheat', 'maize'],
|
319 |
+
'secondary': ['gram', 'arhar'],
|
320 |
+
'specialty': ['bajra']
|
321 |
+
},
|
322 |
+
'siwan': {
|
323 |
+
'primary': ['rice', 'wheat'],
|
324 |
+
'secondary': ['gram', 'lentil', 'pea'],
|
325 |
+
'specialty': ['mustard']
|
326 |
+
},
|
327 |
+
'begusarai': {
|
328 |
+
'primary': ['rice', 'wheat'],
|
329 |
+
'secondary': ['jute', 'mustard'],
|
330 |
+
'specialty': ['moong', 'urd']
|
331 |
+
},
|
332 |
+
'katihar': {
|
333 |
+
'primary': ['maize', 'rice'],
|
334 |
+
'secondary': ['jute', 'urd', 'moong'],
|
335 |
+
'specialty': ['jowar', 'bajra']
|
336 |
+
},
|
337 |
+
'vaishali': {
|
338 |
+
'primary': ['rice', 'wheat', 'sugarcane'],
|
339 |
+
'secondary': ['potato', 'gram'],
|
340 |
+
'specialty': ['mustard']
|
341 |
+
},
|
342 |
+
'madhubani': {
|
343 |
+
'primary': ['rice', 'wheat', 'maize'],
|
344 |
+
'secondary': ['gram', 'lentil'],
|
345 |
+
'specialty': ['arhar']
|
346 |
+
}
|
347 |
+
}
|
348 |
+
|
349 |
+
# Get district preferences or use default
|
350 |
+
district_prefs = district_crop_preferences.get(district.lower(), {
|
351 |
+
'primary': ['rice', 'wheat'],
|
352 |
+
'secondary': ['gram', 'mustard'],
|
353 |
+
'specialty': ['maize']
|
354 |
+
})
|
355 |
+
|
356 |
+
# Combine all possible crops for this district
|
357 |
+
all_district_crops = (district_prefs.get('primary', []) +
|
358 |
+
district_prefs.get('secondary', []) +
|
359 |
+
district_prefs.get('specialty', []))
|
360 |
+
|
361 |
+
# Find crops that are both seasonal AND grown in this district
|
362 |
+
suitable_crops = []
|
363 |
+
if seasonal_crops:
|
364 |
+
suitable_crops = [crop for crop in all_district_crops if crop in seasonal_crops]
|
365 |
+
|
366 |
+
# If no seasonal match, use district preferences with seasonal weighting
|
367 |
+
if not suitable_crops:
|
368 |
+
if current_season == 'kharif':
|
369 |
+
# Monsoon crops preference
|
370 |
+
kharif_crops = ['rice', 'maize', 'arhar', 'moong', 'urd', 'jowar', 'bajra', 'groundnut', 'soybean']
|
371 |
+
suitable_crops = [crop for crop in all_district_crops if crop in kharif_crops]
|
372 |
+
elif current_season == 'rabi':
|
373 |
+
# Winter crops preference
|
374 |
+
rabi_crops = ['wheat', 'barley', 'gram', 'lentil', 'pea', 'mustard', 'linseed', 'potato']
|
375 |
+
suitable_crops = [crop for crop in all_district_crops if crop in rabi_crops]
|
376 |
+
elif current_season == 'zaid':
|
377 |
+
# Summer crops preference
|
378 |
+
zaid_crops = ['maize', 'moong', 'urd', 'watermelon', 'cucumber']
|
379 |
+
suitable_crops = [crop for crop in all_district_crops if crop in zaid_crops]
|
380 |
+
|
381 |
+
# If still no match, fall back to district primary crops
|
382 |
+
if not suitable_crops:
|
383 |
+
suitable_crops = district_prefs.get('primary', ['rice'])
|
384 |
+
|
385 |
+
# Weight selection based on crop category (primary crops more likely)
|
386 |
+
weighted_crops = []
|
387 |
+
for crop in suitable_crops:
|
388 |
+
if crop in district_prefs.get('primary', []):
|
389 |
+
weighted_crops.extend([crop] * 5) # 5x weight for primary crops
|
390 |
+
elif crop in district_prefs.get('secondary', []):
|
391 |
+
weighted_crops.extend([crop] * 3) # 3x weight for secondary crops
|
392 |
+
else:
|
393 |
+
weighted_crops.extend([crop] * 1) # 1x weight for specialty crops
|
394 |
+
|
395 |
+
selected_crop = random.choice(weighted_crops) if weighted_crops else 'rice'
|
396 |
+
|
397 |
+
logger.info(f"Selected crop: {selected_crop} for {district} in {current_season} season from options: {suitable_crops}")
|
398 |
+
|
399 |
+
return selected_crop
|
400 |
+
|
401 |
+
|
402 |
+
async def get_current_crop_stage_dynamic(crop: str, district: str = None):
|
403 |
+
"""Determine crop stage based on current date and crop calendar - now more accurate"""
|
404 |
+
|
405 |
+
try:
|
406 |
+
# Get crop calendar information
|
407 |
+
crop_info = await crop_calendar_tools.get_crop_calendar('bihar', crop)
|
408 |
+
|
409 |
+
if "error" in crop_info:
|
410 |
+
# Fallback to the old static method
|
411 |
+
return get_current_crop_stage_static(crop)
|
412 |
+
|
413 |
+
# Parse planting and harvesting periods
|
414 |
+
planting_period = crop_info.get('planting', '')
|
415 |
+
season = crop_info.get('season', '')
|
416 |
+
stages = crop_info.get('stages', [])
|
417 |
+
|
418 |
+
current_month = datetime.now().month
|
419 |
+
current_date = date.today()
|
420 |
+
|
421 |
+
# Estimate planting date based on season and current month
|
422 |
+
estimated_plant_date = estimate_planting_date(crop, season, planting_period, current_month)
|
423 |
+
|
424 |
+
if estimated_plant_date:
|
425 |
+
# Use the crop calendar function to estimate stage
|
426 |
+
try:
|
427 |
+
stage_data = await crop_calendar_tools.estimate_crop_stage(
|
428 |
+
crop,
|
429 |
+
estimated_plant_date.isoformat(),
|
430 |
+
current_date.isoformat()
|
431 |
+
)
|
432 |
+
|
433 |
+
if "error" not in stage_data:
|
434 |
+
stage = stage_data.get('stage', stages[0] if stages else 'Growing')
|
435 |
+
logger.info(f"Dynamic stage calculation for {crop}: {stage} (planted ~{estimated_plant_date})")
|
436 |
+
return stage
|
437 |
+
except Exception as e:
|
438 |
+
logger.warning(f"Error in dynamic stage calculation: {e}")
|
439 |
+
|
440 |
+
# Fallback to month-based estimation
|
441 |
+
return estimate_stage_by_month(crop, current_month, stages)
|
442 |
+
|
443 |
+
except Exception as e:
|
444 |
+
logger.error(f"Error in dynamic crop stage calculation: {e}")
|
445 |
+
return get_current_crop_stage_static(crop)
|
446 |
+
|
447 |
+
|
448 |
+
def get_current_season(month: int):
|
449 |
+
"""Determine current agricultural season"""
|
450 |
+
if month in [6, 7, 8, 9]: # June to September
|
451 |
+
return 'kharif'
|
452 |
+
elif month in [10, 11, 12, 1, 2, 3]: # October to March
|
453 |
+
return 'rabi'
|
454 |
+
else: # April, May
|
455 |
+
return 'zaid'
|
456 |
+
|
457 |
+
|
458 |
+
def estimate_planting_date(crop: str, season: str, planting_period: str, current_month: int):
|
459 |
+
"""Estimate when the crop was likely planted based on season and current month"""
|
460 |
+
from datetime import date, timedelta
|
461 |
+
|
462 |
+
current_year = datetime.now().year
|
463 |
+
|
464 |
+
try:
|
465 |
+
if 'june-july' in planting_period.lower() or 'june' in planting_period.lower():
|
466 |
+
if current_month >= 6:
|
467 |
+
return date(current_year, 6, 15) # Mid June this year
|
468 |
+
else:
|
469 |
+
return date(current_year - 1, 6, 15) # Mid June last year
|
470 |
+
|
471 |
+
elif 'november-december' in planting_period.lower() or 'november' in planting_period.lower():
|
472 |
+
if current_month >= 11:
|
473 |
+
return date(current_year, 11, 15) # Mid November this year
|
474 |
+
elif current_month <= 4:
|
475 |
+
return date(current_year - 1, 11, 15) # Mid November last year
|
476 |
+
else:
|
477 |
+
return date(current_year, 11, 15) # Will be planted this November
|
478 |
+
|
479 |
+
elif 'october-november' in planting_period.lower() or 'october' in planting_period.lower():
|
480 |
+
if current_month >= 10:
|
481 |
+
return date(current_year, 10, 15)
|
482 |
+
elif current_month <= 4:
|
483 |
+
return date(current_year - 1, 10, 15)
|
484 |
+
else:
|
485 |
+
return date(current_year, 10, 15)
|
486 |
+
|
487 |
+
elif 'march-april' in planting_period.lower() or 'march' in planting_period.lower():
|
488 |
+
if current_month >= 3 and current_month <= 8:
|
489 |
+
return date(current_year, 3, 15)
|
490 |
+
else:
|
491 |
+
return date(current_year - 1, 3, 15)
|
492 |
+
|
493 |
+
except Exception as e:
|
494 |
+
logger.warning(f"Error estimating planting date: {e}")
|
495 |
+
|
496 |
+
return None
|
497 |
+
|
498 |
+
|
499 |
+
def estimate_stage_by_month(crop: str, current_month: int, stages: list):
|
500 |
+
"""Estimate crop stage based on current month and crop type"""
|
501 |
+
|
502 |
+
if not stages:
|
503 |
+
return 'Growing'
|
504 |
+
|
505 |
+
# Month-based stage mapping for common crops
|
506 |
+
stage_mappings = {
|
507 |
+
'rice': {
|
508 |
+
6: 0, 7: 1, 8: 2, 9: 3, 10: 4, 11: 5, 12: 6, 1: 7, 2: 8, 3: 8, 4: 8, 5: 8
|
509 |
+
},
|
510 |
+
'wheat': {
|
511 |
+
11: 0, 12: 1, 1: 2, 2: 3, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 8, 9: 8, 10: 8
|
512 |
+
},
|
513 |
+
'maize': {
|
514 |
+
6: 0, 7: 1, 8: 2, 9: 3, 10: 4, 11: 5, 12: 6, 1: 7, 2: 7, 3: 0, 4: 1, 5: 2 # Dual season
|
515 |
+
}
|
516 |
+
}
|
517 |
+
|
518 |
+
crop_mapping = stage_mappings.get(crop, {})
|
519 |
+
stage_index = crop_mapping.get(current_month, 2) # Default to middle stage
|
520 |
+
stage_index = min(stage_index, len(stages) - 1)
|
521 |
+
|
522 |
+
return stages[stage_index] if stage_index < len(stages) else stages[-1]
|
523 |
+
|
524 |
+
|
525 |
+
def get_current_crop_stage_static(crop: str):
|
526 |
+
"""Original static crop stage function as fallback"""
|
527 |
+
current_month = datetime.now().month
|
528 |
+
|
529 |
+
if crop == 'rice':
|
530 |
+
if current_month in [6, 7]:
|
531 |
+
return 'Transplanting'
|
532 |
+
elif current_month in [8, 9]:
|
533 |
+
return 'Vegetative'
|
534 |
+
elif current_month in [10, 11]:
|
535 |
+
return 'Flowering'
|
536 |
+
else:
|
537 |
+
return 'Maturity'
|
538 |
+
elif crop == 'wheat':
|
539 |
+
if current_month in [11, 12]:
|
540 |
+
return 'Sowing'
|
541 |
+
elif current_month in [1, 2]:
|
542 |
+
return 'Tillering'
|
543 |
+
elif current_month in [3, 4]:
|
544 |
+
return 'Flowering'
|
545 |
+
else:
|
546 |
+
return 'Harvesting'
|
547 |
+
elif crop == 'sugarcane':
|
548 |
+
if current_month in [2, 3, 4]:
|
549 |
+
return 'Planting'
|
550 |
+
elif current_month in [5, 6, 7, 8]:
|
551 |
+
return 'Vegetative'
|
552 |
+
elif current_month in [9, 10, 11]:
|
553 |
+
return 'Maturity'
|
554 |
+
else:
|
555 |
+
return 'Harvesting'
|
556 |
+
elif crop == 'maize':
|
557 |
+
if current_month in [6, 7]:
|
558 |
+
return 'Sowing'
|
559 |
+
elif current_month in [8, 9]:
|
560 |
+
return 'Vegetative'
|
561 |
+
elif current_month in [10, 11]:
|
562 |
+
return 'Grain Filling'
|
563 |
+
else:
|
564 |
+
return 'Harvesting'
|
565 |
+
|
566 |
+
return 'Growing'
|
567 |
+
|
568 |
+
|
569 |
@app.get("/")
|
570 |
async def root():
|
571 |
return {"message": "MCP Weather Server is running"}
|