elanuk commited on
Commit
630a124
·
verified ·
1 Parent(s): 410b4b5

Update server.py

Browse files
Files changed (1) hide show
  1. server.py +321 -33
server.py CHANGED
@@ -454,74 +454,157 @@ def get_current_season(month: int):
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
@@ -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():