Update server.py
Browse files
server.py
CHANGED
@@ -454,74 +454,157 @@ def get_current_season(month: int):
|
|
454 |
else: # April, May
|
455 |
return 'zaid'
|
456 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
457 |
|
458 |
-
def
|
459 |
-
"""
|
460 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
461 |
|
|
|
|
|
|
|
|
|
|
|
|
|
462 |
current_year = datetime.now().year
|
463 |
|
464 |
try:
|
465 |
-
if 'june
|
466 |
-
if current_month >= 6
|
467 |
-
|
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)
|
474 |
elif current_month <= 4:
|
475 |
-
return date(current_year - 1, 11, 15)
|
476 |
else:
|
477 |
-
return date(current_year, 11, 15)
|
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
|
501 |
-
|
502 |
if not stages:
|
503 |
return 'Growing'
|
504 |
|
505 |
-
# Month-based stage mapping for common crops
|
506 |
stage_mappings = {
|
507 |
-
'rice': {
|
508 |
-
|
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)
|
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
|
@@ -565,6 +648,211 @@ def get_current_crop_stage_static(crop: str):
|
|
565 |
|
566 |
return 'Growing'
|
567 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
568 |
|
569 |
@app.get("/")
|
570 |
async def root():
|
|
|
454 |
else: # April, May
|
455 |
return 'zaid'
|
456 |
|
457 |
+
async def get_regional_crop_for_area(district: str, state: str):
|
458 |
+
"""Get typical crop for the region based on season and district - now fully dynamic"""
|
459 |
+
|
460 |
+
if state.lower() != 'bihar':
|
461 |
+
return 'rice' # fallback for other states
|
462 |
+
|
463 |
+
current_month = datetime.now().month
|
464 |
+
current_season = get_current_season(current_month)
|
465 |
+
|
466 |
+
# Get crops that are currently in season using your crop calendar tools
|
467 |
+
try:
|
468 |
+
seasonal_crops_data = await crop_calendar_tools.get_prominent_crops('bihar', current_season)
|
469 |
+
if "error" not in seasonal_crops_data:
|
470 |
+
seasonal_crops = seasonal_crops_data.get('crops', [])
|
471 |
+
else:
|
472 |
+
seasonal_crops = []
|
473 |
+
except Exception as e:
|
474 |
+
logger.warning(f"Failed to get seasonal crops: {e}")
|
475 |
+
seasonal_crops = []
|
476 |
+
|
477 |
+
# District-specific crop preferences
|
478 |
+
district_crop_preferences = {
|
479 |
+
'patna': {'primary': ['rice', 'wheat', 'potato'], 'secondary': ['mustard', 'gram', 'barley'], 'specialty': ['sugarcane']},
|
480 |
+
'gaya': {'primary': ['wheat', 'rice', 'gram'], 'secondary': ['barley', 'lentil', 'mustard'], 'specialty': ['arhar']},
|
481 |
+
'bhagalpur': {'primary': ['rice', 'maize', 'wheat'], 'secondary': ['jute', 'urd', 'moong'], 'specialty': ['groundnut']},
|
482 |
+
'muzaffarpur': {'primary': ['sugarcane', 'rice', 'wheat'], 'secondary': ['potato', 'mustard'], 'specialty': ['lentil']},
|
483 |
+
'darbhanga': {'primary': ['rice', 'wheat', 'maize'], 'secondary': ['gram', 'arhar'], 'specialty': ['bajra']},
|
484 |
+
'siwan': {'primary': ['rice', 'wheat'], 'secondary': ['gram', 'lentil', 'pea'], 'specialty': ['mustard']},
|
485 |
+
'begusarai': {'primary': ['rice', 'wheat'], 'secondary': ['jute', 'mustard'], 'specialty': ['moong', 'urd']},
|
486 |
+
'katihar': {'primary': ['maize', 'rice'], 'secondary': ['jute', 'urd', 'moong'], 'specialty': ['jowar', 'bajra']}
|
487 |
+
}
|
488 |
+
|
489 |
+
district_prefs = district_crop_preferences.get(district.lower(), {'primary': ['rice', 'wheat'], 'secondary': ['gram', 'mustard'], 'specialty': ['maize']})
|
490 |
+
|
491 |
+
all_district_crops = (district_prefs.get('primary', []) + district_prefs.get('secondary', []) + district_prefs.get('specialty', []))
|
492 |
+
|
493 |
+
# Find crops that are both seasonal AND grown in this district
|
494 |
+
suitable_crops = []
|
495 |
+
if seasonal_crops:
|
496 |
+
suitable_crops = [crop for crop in all_district_crops if crop in seasonal_crops]
|
497 |
+
|
498 |
+
# If no seasonal match, use season-based fallback
|
499 |
+
if not suitable_crops:
|
500 |
+
if current_season == 'kharif':
|
501 |
+
kharif_crops = ['rice', 'maize', 'arhar', 'moong', 'urd', 'jowar', 'bajra', 'groundnut', 'soybean']
|
502 |
+
suitable_crops = [crop for crop in all_district_crops if crop in kharif_crops]
|
503 |
+
elif current_season == 'rabi':
|
504 |
+
rabi_crops = ['wheat', 'barley', 'gram', 'lentil', 'pea', 'mustard', 'linseed', 'potato']
|
505 |
+
suitable_crops = [crop for crop in all_district_crops if crop in rabi_crops]
|
506 |
+
elif current_season == 'zaid':
|
507 |
+
zaid_crops = ['maize', 'moong', 'urd', 'watermelon', 'cucumber']
|
508 |
+
suitable_crops = [crop for crop in all_district_crops if crop in zaid_crops]
|
509 |
+
|
510 |
+
if not suitable_crops:
|
511 |
+
suitable_crops = district_prefs.get('primary', ['rice'])
|
512 |
+
|
513 |
+
# Weight selection based on crop category
|
514 |
+
weighted_crops = []
|
515 |
+
for crop in suitable_crops:
|
516 |
+
if crop in district_prefs.get('primary', []):
|
517 |
+
weighted_crops.extend([crop] * 5) # 5x weight for primary crops
|
518 |
+
elif crop in district_prefs.get('secondary', []):
|
519 |
+
weighted_crops.extend([crop] * 3) # 3x weight for secondary crops
|
520 |
+
else:
|
521 |
+
weighted_crops.extend([crop] * 1) # 1x weight for specialty crops
|
522 |
+
|
523 |
+
selected_crop = random.choice(weighted_crops) if weighted_crops else 'rice'
|
524 |
+
logger.info(f"Selected crop: {selected_crop} for {district} in {current_season} season")
|
525 |
+
|
526 |
+
return selected_crop
|
527 |
|
528 |
+
async def get_current_crop_stage_dynamic(crop: str, district: str = None):
|
529 |
+
"""Determine crop stage based on current date and crop calendar"""
|
530 |
+
try:
|
531 |
+
crop_info = await crop_calendar_tools.get_crop_calendar('bihar', crop)
|
532 |
+
|
533 |
+
if "error" in crop_info:
|
534 |
+
return get_current_crop_stage_static(crop)
|
535 |
+
|
536 |
+
stages = crop_info.get('stages', [])
|
537 |
+
planting_period = crop_info.get('planting', '')
|
538 |
+
current_month = datetime.now().month
|
539 |
+
current_date = date.today()
|
540 |
+
|
541 |
+
estimated_plant_date = estimate_planting_date(crop, planting_period, current_month)
|
542 |
+
|
543 |
+
if estimated_plant_date:
|
544 |
+
try:
|
545 |
+
stage_data = await crop_calendar_tools.estimate_crop_stage(
|
546 |
+
crop, estimated_plant_date.isoformat(), current_date.isoformat()
|
547 |
+
)
|
548 |
+
|
549 |
+
if "error" not in stage_data:
|
550 |
+
return stage_data.get('stage', stages[0] if stages else 'Growing')
|
551 |
+
except Exception as e:
|
552 |
+
logger.warning(f"Error in dynamic stage calculation: {e}")
|
553 |
+
|
554 |
+
return estimate_stage_by_month(crop, current_month, stages)
|
555 |
|
556 |
+
except Exception as e:
|
557 |
+
logger.error(f"Error in dynamic crop stage calculation: {e}")
|
558 |
+
return get_current_crop_stage_static(crop)
|
559 |
+
|
560 |
+
def estimate_planting_date(crop: str, planting_period: str, current_month: int):
|
561 |
+
"""Estimate when the crop was likely planted"""
|
562 |
current_year = datetime.now().year
|
563 |
|
564 |
try:
|
565 |
+
if 'june' in planting_period.lower():
|
566 |
+
return date(current_year, 6, 15) if current_month >= 6 else date(current_year - 1, 6, 15)
|
567 |
+
elif 'november' in planting_period.lower():
|
|
|
|
|
|
|
|
|
568 |
if current_month >= 11:
|
569 |
+
return date(current_year, 11, 15)
|
570 |
elif current_month <= 4:
|
571 |
+
return date(current_year - 1, 11, 15)
|
572 |
else:
|
573 |
+
return date(current_year, 11, 15)
|
574 |
+
elif 'october' in planting_period.lower():
|
|
|
575 |
if current_month >= 10:
|
576 |
return date(current_year, 10, 15)
|
577 |
elif current_month <= 4:
|
578 |
return date(current_year - 1, 10, 15)
|
579 |
else:
|
580 |
return date(current_year, 10, 15)
|
581 |
+
elif 'march' in planting_period.lower():
|
|
|
582 |
if current_month >= 3 and current_month <= 8:
|
583 |
return date(current_year, 3, 15)
|
584 |
else:
|
585 |
return date(current_year - 1, 3, 15)
|
|
|
586 |
except Exception as e:
|
587 |
logger.warning(f"Error estimating planting date: {e}")
|
588 |
|
589 |
return None
|
590 |
|
|
|
591 |
def estimate_stage_by_month(crop: str, current_month: int, stages: list):
|
592 |
+
"""Estimate crop stage based on current month"""
|
|
|
593 |
if not stages:
|
594 |
return 'Growing'
|
595 |
|
|
|
596 |
stage_mappings = {
|
597 |
+
'rice': {6: 0, 7: 1, 8: 2, 9: 3, 10: 4, 11: 5, 12: 6, 1: 7, 2: 8},
|
598 |
+
'wheat': {11: 0, 12: 1, 1: 2, 2: 3, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8},
|
599 |
+
'maize': {6: 0, 7: 1, 8: 2, 9: 3, 10: 4, 11: 5, 12: 6, 1: 7, 2: 7, 3: 0, 4: 1, 5: 2}
|
|
|
|
|
|
|
|
|
|
|
|
|
600 |
}
|
601 |
|
602 |
crop_mapping = stage_mappings.get(crop, {})
|
603 |
+
stage_index = crop_mapping.get(current_month, 2)
|
604 |
stage_index = min(stage_index, len(stages) - 1)
|
605 |
|
606 |
return stages[stage_index] if stage_index < len(stages) else stages[-1]
|
607 |
|
|
|
608 |
def get_current_crop_stage_static(crop: str):
|
609 |
"""Original static crop stage function as fallback"""
|
610 |
current_month = datetime.now().month
|
|
|
648 |
|
649 |
return 'Growing'
|
650 |
|
651 |
+
#
|
652 |
+
|
653 |
+
async def generate_dynamic_alert(district: str, state: str):
|
654 |
+
"""Generate AI-powered dynamic alert data using real weather and crop intelligence"""
|
655 |
+
|
656 |
+
try:
|
657 |
+
# Step 1: Get villages for the district
|
658 |
+
villages_data = await geographic_tools.list_villages(state, district)
|
659 |
+
|
660 |
+
if "error" in villages_data:
|
661 |
+
raise Exception(f"District '{district}' not found in {state}")
|
662 |
+
|
663 |
+
# Step 2: Pick a random village from the actual list
|
664 |
+
available_villages = villages_data.get("villages", [])
|
665 |
+
if not available_villages:
|
666 |
+
raise Exception(f"No villages found for {district}")
|
667 |
+
|
668 |
+
selected_village = random.choice(available_villages)
|
669 |
+
logger.info(f"Selected village: {selected_village} from {len(available_villages)} villages")
|
670 |
+
|
671 |
+
# Step 3: Get coordinates for the selected village/district
|
672 |
+
location_coords = None
|
673 |
+
location_source = ""
|
674 |
+
|
675 |
+
# Try village coordinates first
|
676 |
+
try:
|
677 |
+
village_location = await geographic_tools.reverse_geocode(selected_village)
|
678 |
+
if "error" not in village_location and "lat" in village_location:
|
679 |
+
location_coords = [village_location["lat"], village_location["lng"]]
|
680 |
+
location_source = f"village_{selected_village}"
|
681 |
+
logger.info(f"Using village coordinates for {selected_village}: {location_coords}")
|
682 |
+
except Exception as e:
|
683 |
+
logger.warning(f"Village geocoding failed for {selected_village}: {e}")
|
684 |
+
|
685 |
+
# Fallback to district coordinates if village lookup failed
|
686 |
+
if not location_coords:
|
687 |
+
try:
|
688 |
+
district_location = await geographic_tools.reverse_geocode(district)
|
689 |
+
if "error" not in district_location and "lat" in district_location:
|
690 |
+
location_coords = [district_location["lat"], district_location["lng"]]
|
691 |
+
location_source = f"district_{district}"
|
692 |
+
logger.info(f"Using district coordinates for {district}: {location_coords}")
|
693 |
+
except Exception as e:
|
694 |
+
logger.warning(f"District geocoding failed for {district}: {e}")
|
695 |
+
|
696 |
+
# Final fallback
|
697 |
+
if not location_coords:
|
698 |
+
logger.warning(f"No coordinates found for {selected_village} or {district}, using default")
|
699 |
+
location_coords = [25.5941, 85.1376] # Patna fallback
|
700 |
+
location_source = "fallback_patna"
|
701 |
+
|
702 |
+
# Step 4: Generate dynamic crop selection and stage
|
703 |
+
regional_crop = await get_regional_crop_for_area(district, state)
|
704 |
+
crop_stage = await get_current_crop_stage_dynamic(regional_crop, district)
|
705 |
+
|
706 |
+
# Step 5: GET AI-POWERED WEATHER ALERT using your alert_generation_tools
|
707 |
+
try:
|
708 |
+
logger.info(f"Generating AI-powered alert for coordinates: {location_coords} (source: {location_source})")
|
709 |
+
|
710 |
+
# Get the API key
|
711 |
+
api_key = config.get("OPENAI_API_KEY")
|
712 |
+
if not api_key:
|
713 |
+
raise Exception("OpenAI API key not found")
|
714 |
+
|
715 |
+
# Use your AI prediction tool
|
716 |
+
ai_alert = await alert_generation_tools.predict_weather_alert(
|
717 |
+
latitude=location_coords[0],
|
718 |
+
longitude=location_coords[1],
|
719 |
+
api_key=api_key
|
720 |
+
)
|
721 |
+
|
722 |
+
logger.info(f"AI alert generated successfully for {selected_village}, {district}")
|
723 |
+
|
724 |
+
# Also get basic weather data for additional context
|
725 |
+
try:
|
726 |
+
current_weather_data = await open_meteo.get_current_weather(
|
727 |
+
latitude=location_coords[0],
|
728 |
+
longitude=location_coords[1]
|
729 |
+
)
|
730 |
+
|
731 |
+
forecast_data = await open_meteo.get_weather_forecast(
|
732 |
+
latitude=location_coords[0],
|
733 |
+
longitude=location_coords[1],
|
734 |
+
days=7
|
735 |
+
)
|
736 |
+
|
737 |
+
current_weather = current_weather_data.get('current_weather', {})
|
738 |
+
daily_forecast = forecast_data.get('daily', {})
|
739 |
+
|
740 |
+
current_temp = current_weather.get('temperature', 25)
|
741 |
+
current_windspeed = current_weather.get('windspeed', 10)
|
742 |
+
|
743 |
+
precipitation_list = daily_forecast.get('precipitation_sum', [0, 0, 0])
|
744 |
+
next_3_days_rain = sum(precipitation_list[:3]) if precipitation_list else 0
|
745 |
+
|
746 |
+
rain_probability = min(90, max(10, int(next_3_days_rain * 10))) if next_3_days_rain > 0 else 10
|
747 |
+
estimated_humidity = min(95, max(40, 60 + int(next_3_days_rain * 2)))
|
748 |
+
|
749 |
+
weather_context = {
|
750 |
+
"forecast_days": 7,
|
751 |
+
"rain_probability": rain_probability,
|
752 |
+
"expected_rainfall": f"{next_3_days_rain:.1f}mm",
|
753 |
+
"temperature": f"{current_temp:.1f}°C",
|
754 |
+
"humidity": f"{estimated_humidity}%",
|
755 |
+
"wind_speed": f"{current_windspeed:.1f} km/h",
|
756 |
+
"coordinates_source": location_source
|
757 |
+
}
|
758 |
+
|
759 |
+
except Exception as weather_error:
|
760 |
+
logger.warning(f"Could not get basic weather data: {weather_error}")
|
761 |
+
weather_context = {
|
762 |
+
"forecast_days": 7,
|
763 |
+
"coordinates_source": location_source,
|
764 |
+
"note": "Weather context limited due to API error"
|
765 |
+
}
|
766 |
+
|
767 |
+
# Extract AI analysis
|
768 |
+
alert_description = ai_alert.get('alert', 'Weather update for agricultural activities')
|
769 |
+
impact_description = ai_alert.get('impact', 'Monitor crops regularly')
|
770 |
+
recommendations = ai_alert.get('recommendations', 'Continue routine farming activities')
|
771 |
+
|
772 |
+
# Create comprehensive alert message combining AI insights
|
773 |
+
alert_message = f"🤖 AI Weather Alert for {selected_village}, {district}: {alert_description}"
|
774 |
+
if impact_description and impact_description.lower() not in ['none', 'n/a', '']:
|
775 |
+
alert_message += f" 🌾 Crop Impact: {impact_description}"
|
776 |
+
|
777 |
+
# Determine urgency and type based on AI response content
|
778 |
+
urgency = "low"
|
779 |
+
alert_type = "weather_update"
|
780 |
+
|
781 |
+
alert_lower = alert_description.lower()
|
782 |
+
impact_lower = impact_description.lower()
|
783 |
+
recommendations_lower = recommendations.lower()
|
784 |
+
|
785 |
+
# High urgency keywords
|
786 |
+
if any(word in alert_lower + impact_lower for word in ['urgent', 'severe', 'critical', 'danger', 'emergency', 'immediate']):
|
787 |
+
urgency = "high"
|
788 |
+
alert_type = "severe_weather_warning"
|
789 |
+
# Medium urgency keywords
|
790 |
+
elif any(word in alert_lower + impact_lower for word in ['warning', 'caution', 'alert', 'risk', 'damage', 'loss', 'stress', 'threat']):
|
791 |
+
urgency = "medium"
|
792 |
+
alert_type = "weather_warning"
|
793 |
+
# Check recommendations for urgency indicators
|
794 |
+
elif any(word in recommendations_lower for word in ['immediate', 'urgent', 'quickly', 'soon', 'now']):
|
795 |
+
urgency = "medium"
|
796 |
+
alert_type = "crop_risk_alert"
|
797 |
+
|
798 |
+
# Parse recommendations into actionable items
|
799 |
+
action_items = []
|
800 |
+
if recommendations:
|
801 |
+
# Split recommendations by common delimiters and clean up
|
802 |
+
items = recommendations.replace('.', '|').replace(',', '|').replace(';', '|').replace(' and ', '|').split('|')
|
803 |
+
action_items = [item.strip().lower().replace(' ', '_') for item in items if item.strip() and len(item.strip()) > 3]
|
804 |
+
# Limit to 5 most important items and ensure they're actionable
|
805 |
+
action_items = [item for item in action_items[:5] if any(verb in item for verb in ['monitor', 'check', 'apply', 'water', 'harvest', 'plant', 'protect', 'cover', 'drain', 'spray', 'fertilize'])]
|
806 |
+
|
807 |
+
if not action_items:
|
808 |
+
action_items = ["monitor_crops", "follow_weather_updates", "maintain_irrigation"]
|
809 |
+
|
810 |
+
logger.info(f"AI-powered alert processed: Type={alert_type}, Urgency={urgency}, Actions={len(action_items)}")
|
811 |
+
|
812 |
+
except Exception as ai_error:
|
813 |
+
logger.error(f"Failed to get AI weather alert for {selected_village}, {district}: {ai_error}")
|
814 |
+
raise Exception(f"Unable to generate AI weather alert: {str(ai_error)}")
|
815 |
+
|
816 |
+
# Generate unique alert ID with timestamp
|
817 |
+
alert_id = f"{state.upper()[:2]}_{district.upper()[:3]}_{selected_village.upper()[:3]}_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
|
818 |
+
|
819 |
+
return {
|
820 |
+
"alert_id": alert_id,
|
821 |
+
"timestamp": datetime.now().isoformat() + "Z",
|
822 |
+
"location": {
|
823 |
+
"village": selected_village,
|
824 |
+
"district": district,
|
825 |
+
"state": state.capitalize(),
|
826 |
+
"coordinates": location_coords,
|
827 |
+
"coordinates_source": location_source,
|
828 |
+
"total_villages_in_district": len(available_villages)
|
829 |
+
},
|
830 |
+
"crop": {
|
831 |
+
"name": regional_crop,
|
832 |
+
"stage": crop_stage,
|
833 |
+
"season": get_current_season(datetime.now().month),
|
834 |
+
"planted_estimate": "2025-06-15" # Could make this dynamic based on crop calendar
|
835 |
+
},
|
836 |
+
"alert": {
|
837 |
+
"type": alert_type,
|
838 |
+
"urgency": urgency,
|
839 |
+
"message": alert_message,
|
840 |
+
"action_items": action_items,
|
841 |
+
"valid_until": (datetime.now() + timedelta(days=3)).isoformat() + "Z",
|
842 |
+
"ai_generated": True
|
843 |
+
},
|
844 |
+
"ai_analysis": {
|
845 |
+
"alert": alert_description,
|
846 |
+
"impact": impact_description,
|
847 |
+
"recommendations": recommendations
|
848 |
+
},
|
849 |
+
"weather": weather_context,
|
850 |
+
"data_source": "ai_powered_openai_gpt4_with_open_meteo"
|
851 |
+
}
|
852 |
+
|
853 |
+
except Exception as e:
|
854 |
+
logger.error(f"Error generating AI-powered alert for {district}, {state}: {e}")
|
855 |
+
raise Exception(f"Failed to generate AI weather alert for {district}: {str(e)}")
|
856 |
|
857 |
@app.get("/")
|
858 |
async def root():
|