elanuk commited on
Commit
d2a1db5
·
1 Parent(s): 1b36082
.DS_Store ADDED
Binary file (6.15 kB). View file
 
a2a_agents/__init__.py ADDED
File without changes
a2a_agents/alert_agent.py ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import asyncio
2
+ from src.mcp_weather_server.tools import alert_generation_tools
3
+ from unittest.mock import patch
4
+
5
+ class AlertAgent:
6
+ async def generate_alert(self, crop, weather_data, growth_stage, latitude, longitude):
7
+ with patch('src.mcp_weather_server.tools.openai_llm.predict_weather_alert') as mock_predict_weather_alert:
8
+ # Mock the alert generation
9
+ mock_predict_weather_alert.return_value = {
10
+ "alert": "Heavy rainfall expected",
11
+ "impact": "High risk of waterlogging in fields.",
12
+ "recommendations": "Ensure proper drainage in fields."
13
+ }
14
+
15
+ print("Generating weather alert...")
16
+ api_key = "test_api_key" # This will be mocked, so the value doesn't matter
17
+ alert_response = await alert_generation_tools.generate_weather_alert(
18
+ crop=crop,
19
+ weather_data=weather_data,
20
+ growth_stage=growth_stage,
21
+ api_key=api_key,
22
+ latitude=latitude,
23
+ longitude=longitude
24
+ )
25
+ return alert_response
26
+
27
+ async def main():
28
+ agent = AlertAgent()
29
+ # Example usage
30
+ crop = "Wheat"
31
+ growth_stage = "Flowering"
32
+ lat = 25.6
33
+ lon = 85.1
34
+ # Mock weather data for the example
35
+ weather_data = {
36
+ "daily": {
37
+ "time": ["2024-02-15", "2024-02-16"],
38
+ "temperature_2m_max": [25, 26],
39
+ "temperature_2m_min": [12, 13],
40
+ "precipitation_sum": [0, 5]
41
+ }
42
+ }
43
+
44
+ alert = await agent.generate_alert(
45
+ crop=crop,
46
+ weather_data=weather_data,
47
+ growth_stage=growth_stage,
48
+ latitude=lat,
49
+ longitude=lon
50
+ )
51
+
52
+ if alert and "error" not in alert:
53
+ print("\n--- Generated Weather Alert ---")
54
+ print(f"Alert: {alert['alert']}")
55
+ print(f"Impact: {alert['impact']}")
56
+ print(f"Recommendations: {alert['recommendations']}")
57
+ print("-----------------------------")
58
+ elif alert and "error" in alert:
59
+ print(f"Error generating alert: {alert['error']}")
60
+
61
+
62
+ if __name__ == "__main__":
63
+ asyncio.run(main())
a2a_agents/crop_agent.py ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import asyncio
2
+ from src.mcp_weather_server.tools import crop_calendar_tools
3
+ import datetime
4
+
5
+ class CropAgent:
6
+ def get_current_season(self):
7
+ # This is a simplified way to determine the season.
8
+ # A more robust implementation would use a more accurate method.
9
+ month = datetime.datetime.now().month
10
+ if month >= 10 or month <= 3:
11
+ return "Rabi"
12
+ else:
13
+ return "Kharif"
14
+
15
+ async def get_crop_info(self, state, season, plant_date, current_date):
16
+ print(f"Finding prominent crops for {season} season in {state}...")
17
+ prominent_crops_response = await crop_calendar_tools.get_prominent_crops(region=state, season=season)
18
+ if "error" in prominent_crops_response:
19
+ print(f"Error: {prominent_crops_response['error']}")
20
+ return None
21
+
22
+ crop_info_list = []
23
+ for crop in prominent_crops_response["crops"]:
24
+ print(f"Estimating crop stage for {crop} planted on {plant_date}...")
25
+ crop_stage_response = await crop_calendar_tools.estimate_crop_stage(crop=crop, plant_date=plant_date, current_date=current_date)
26
+ if "error" in crop_stage_response:
27
+ print(f"Error: {crop_stage_response['error']}")
28
+ continue
29
+
30
+ crop_info_list.append({
31
+ "crop": crop,
32
+ "growth_stage": crop_stage_response["stage"]
33
+ })
34
+ return crop_info_list
35
+
36
+ async def main():
37
+ agent = CropAgent()
38
+ season = agent.get_current_season()
39
+ print(f"Current season: {season}")
40
+
41
+ # Example usage
42
+ plant_date = "2023-11-01"
43
+ current_date = "2024-02-15"
44
+ crop_info = await agent.get_crop_info(state="bihar", season=season, plant_date=plant_date, current_date=current_date)
45
+
46
+ if crop_info:
47
+ print("\n--- Crop Info ---")
48
+ for info in crop_info:
49
+ print(f"Crop: {info['crop']}, Growth Stage: {info['growth_stage']}")
50
+ print("-----------------")
51
+
52
+ if __name__ == "__main__":
53
+ asyncio.run(main())
a2a_agents/ivr_agent.py ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ def create_ivr_script(alert_json: dict) -> list[dict]:
2
+ """
3
+ Creates a voice script with timing from a structured alert JSON.
4
+ """
5
+
6
+ script = [
7
+ {"text": f"Namaste. Mausam ki chetavani {alert_json['location']['district']} ke liye.", "delay_after": 1},
8
+ {"text": f"Fasal: {alert_json['crop']['name']}.", "delay_after": 1},
9
+ {"text": f"Chetavani: {alert_json['alert']['message']}", "delay_after": 2},
10
+ {"text": "Salah ke liye, ek dabaye.", "delay_after": 0}
11
+ ]
12
+ return script
13
+
14
+ def get_ivr_submenu_script(alert_json: dict) -> list[dict]:
15
+ """
16
+ Returns a submenu script for the IVR.
17
+ """
18
+ actions = ". ".join([action.replace('_', ' ') for action in alert_json['alert']['action_items']])
19
+ script = [
20
+ {"text": f"Salah: {actions}", "delay_after": 2},
21
+ {"text": "Dhanyavad.", "delay_after": 0}
22
+ ]
23
+ return script
24
+
25
+ if __name__ == '__main__':
26
+ sample_alert = {
27
+ "alert_id": "BH_PAT_001_20250723",
28
+ "timestamp": "2025-07-23T06:00:00Z",
29
+ "location": {
30
+ "village": "Kumhrar",
31
+ "district": "Patna",
32
+ "state": "Bihar",
33
+ "coordinates": [25.5941, 85.1376]
34
+ },
35
+ "crop": {
36
+ "name": "rice",
37
+ "stage": "flowering",
38
+ "planted_estimate": "2025-06-15"
39
+ },
40
+ "alert": {
41
+ "type": "weather_warning",
42
+ "urgency": "high",
43
+ "message": "Heavy rainfall (40-60mm) expected in next 2 days. Delay fertilizer application. Ensure proper drainage.",
44
+ "action_items": ["delay_fertilizer", "check_drainage"],
45
+ "valid_until": "2025-07-25T18:00:00Z"
46
+ },
47
+ "weather": {
48
+ "forecast_days": 3,
49
+ "rain_probability": 85,
50
+ "expected_rainfall": "45mm"
51
+ }
52
+ }
53
+
54
+ main_script = create_ivr_script(sample_alert)
55
+ print("--- Main Script ---")
56
+ for line in main_script:
57
+ print(line)
58
+
59
+ submenu_script = get_ivr_submenu_script(sample_alert)
60
+ print("\n--- Submenu Script ---")
61
+ for line in submenu_script:
62
+ print(line)
a2a_agents/location_agent.py ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import asyncio
2
+ from src.mcp_weather_server.tools import geographic_tools
3
+
4
+ class LocationAgent:
5
+ async def get_locations(self, state, district):
6
+ print(f"Fetching villages for {district}, {state}...")
7
+ villages_response = await geographic_tools.list_villages(state=state, district=district)
8
+ if "error" in villages_response:
9
+ print(f"Error: {villages_response['error']}")
10
+ return None
11
+
12
+ locations = []
13
+ for village in villages_response["villages"]:
14
+ print(f"Getting coordinates for {village}...")
15
+ coordinates_response = await geographic_tools.reverse_geocode(location_name=village)
16
+ if "error" in coordinates_response:
17
+ print(f"Error: {coordinates_response['error']}")
18
+ continue
19
+
20
+ locations.append({
21
+ "village": village,
22
+ "latitude": coordinates_response["latitude"],
23
+ "longitude": coordinates_response["longitude"]
24
+ })
25
+ return locations
26
+
27
+ async def main():
28
+ agent = LocationAgent()
29
+ locations = await agent.get_locations(state="bihar", district="patna")
30
+ if locations:
31
+ print("\n--- Locations ---")
32
+ for loc in locations:
33
+ print(f"Village: {loc['village']}, Lat: {loc['latitude']}, Lon: {loc['longitude']}")
34
+ print("-----------------")
35
+
36
+ if __name__ == "__main__":
37
+ asyncio.run(main())
a2a_agents/sms_agent.py ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+
3
+ def to_hindi(text):
4
+ """
5
+ Mock function to "translate" text to Hindi.
6
+ In a real implementation, this would use a translation service.
7
+ """
8
+ translations = {
9
+ "Heavy rainfall": "भारी वर्षा",
10
+ "expected in next 2 days.": "अगले 2 दिनों में अपेक्षित।",
11
+ "Delay fertilizer application.": "उर्वरक आवेदन में देरी करें।",
12
+ "Ensure proper drainage.": "उचित जल निकासी सुनिश्चित करें।",
13
+ "rice": "चावल",
14
+ "flowering": "फूल",
15
+ "weather_warning": "मौसम की चेतावनी",
16
+ "high": "उच्च",
17
+ "Kumhrar": "कुम्हरार",
18
+ "Patna": "पटना",
19
+ "Bihar": "बिहार",
20
+ "Alert": "चेतावनी",
21
+ "Crop": "फसल",
22
+ "Stage": "चरण",
23
+ "Urgency": "तात्कालिकता",
24
+ "Action": "कार्य"
25
+ }
26
+ for en, hi in translations.items():
27
+ text = text.replace(en, hi)
28
+ return text
29
+
30
+ def create_sms_message(alert_json: dict) -> str:
31
+ """
32
+ Creates a 160-character SMS message from a structured alert JSON.
33
+ """
34
+
35
+ message = (
36
+ f"{to_hindi('Alert')}: {to_hindi(alert_json['alert']['type'])}, "
37
+ f"{to_hindi('Crop')}: {to_hindi(alert_json['crop']['name'])}, "
38
+ f"{to_hindi('Stage')}: {to_hindi(alert_json['crop']['stage'])}, "
39
+ f"{to_hindi('Urgency')}: {to_hindi(alert_json['alert']['urgency'])}. "
40
+ f"{to_hindi(alert_json['alert']['message'])}"
41
+ )
42
+
43
+ # Truncate to 160 characters
44
+ return message[:160]
45
+
46
+ if __name__ == '__main__':
47
+ sample_alert = {
48
+ "alert_id": "BH_PAT_001_20250723",
49
+ "timestamp": "2025-07-23T06:00:00Z",
50
+ "location": {
51
+ "village": "Kumhrar",
52
+ "district": "Patna",
53
+ "state": "Bihar",
54
+ "coordinates": [25.5941, 85.1376]
55
+ },
56
+ "crop": {
57
+ "name": "rice",
58
+ "stage": "flowering"
59
+ },
60
+ "alert": {
61
+ "type": "weather_warning",
62
+ "urgency": "high",
63
+ "message": "Heavy rainfall expected in next 2 days. Delay fertilizer application. Ensure proper drainage."
64
+ }
65
+ }
66
+
67
+ sms = create_sms_message(sample_alert)
68
+ print(sms)
69
+ print(f"Length: {len(sms)}")
a2a_agents/telegram_agent.py ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+
3
+ def create_telegram_message(alert_json: dict) -> dict:
4
+ """
5
+ Creates a Telegram message with an inline keyboard from a structured alert JSON.
6
+ """
7
+
8
+ message = (
9
+ f"🚨 *Weather Alert* 🚨\n\n"
10
+ f"📍 *Location:* {alert_json['location']['village']}, {alert_json['location']['district']}\n"
11
+ f"🌾 *Crop:* {alert_json['crop']['name'].capitalize()} ({alert_json['crop']['stage']})\n"
12
+ f"⚠️ *Urgency:* {alert_json['alert']['urgency'].upper()}\n\n"
13
+ f"📝 *Details:* {alert_json['alert']['message']}\n\n"
14
+ f"✅ *Recommended Actions:*\n"
15
+ )
16
+ for action in alert_json['alert']['action_items']:
17
+ message += f"- {action.replace('_', ' ').capitalize()}\n"
18
+
19
+ keyboard = {
20
+ "inline_keyboard": [
21
+ [
22
+ {"text": "Acknowledge", "callback_data": f"ack_{alert_json['alert_id']}"},
23
+ {"text": "More Info", "callback_data": f"info_{alert_json['alert_id']}"}
24
+ ]
25
+ ]
26
+ }
27
+
28
+ return {
29
+ "text": message,
30
+ "reply_markup": keyboard
31
+ }
32
+
33
+ if __name__ == '__main__':
34
+ sample_alert = {
35
+ "alert_id": "BH_PAT_001_20250723",
36
+ "timestamp": "2025-07-23T06:00:00Z",
37
+ "location": {
38
+ "village": "Kumhrar",
39
+ "district": "Patna",
40
+ "state": "Bihar",
41
+ "coordinates": [25.5941, 85.1376]
42
+ },
43
+ "crop": {
44
+ "name": "rice",
45
+ "stage": "flowering",
46
+ "planted_estimate": "2025-06-15"
47
+ },
48
+ "alert": {
49
+ "type": "weather_warning",
50
+ "urgency": "high",
51
+ "message": "Heavy rainfall (40-60mm) expected in next 2 days. Delay fertilizer application. Ensure proper drainage.",
52
+ "action_items": ["delay_fertilizer", "check_drainage"],
53
+ "valid_until": "2025-07-25T18:00:00Z"
54
+ },
55
+ "weather": {
56
+ "forecast_days": 3,
57
+ "rain_probability": 85,
58
+ "expected_rainfall": "45mm"
59
+ }
60
+ }
61
+
62
+ telegram_message = create_telegram_message(sample_alert)
63
+ print(json.dumps(telegram_message, indent=2))
a2a_agents/ussd_agent.py ADDED
@@ -0,0 +1,71 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ def create_ussd_menu(alert_json: dict) -> str:
2
+ """
3
+ Creates a USSD menu from a structured alert JSON.
4
+ """
5
+
6
+ menu = (
7
+ "Mausam ki jankari:\n"
8
+ f"1. {alert_json['crop']['name'].capitalize()} ki chetavani\n"
9
+ "2. Salah\n"
10
+ "3. Exit"
11
+ )
12
+ return menu
13
+
14
+ def get_ussd_submenu(alert_json: dict, choice: int) -> str:
15
+ """
16
+ Returns a submenu based on the user's choice.
17
+ """
18
+ if choice == 1:
19
+ return (
20
+ f"Chetavani: {alert_json['alert']['message']}\n"
21
+ "0. Back"
22
+ )
23
+ elif choice == 2:
24
+ actions = "\n".join([f"- {action.replace('_', ' ').capitalize()}" for action in alert_json['alert']['action_items']])
25
+ return (
26
+ f"Salah:\n{actions}\n"
27
+ "0. Back"
28
+ )
29
+ else:
30
+ return "Invalid choice. Please try again."
31
+
32
+ if __name__ == '__main__':
33
+ sample_alert = {
34
+ "alert_id": "BH_PAT_001_20250723",
35
+ "timestamp": "2025-07-23T06:00:00Z",
36
+ "location": {
37
+ "village": "Kumhrar",
38
+ "district": "Patna",
39
+ "state": "Bihar",
40
+ "coordinates": [25.5941, 85.1376]
41
+ },
42
+ "crop": {
43
+ "name": "rice",
44
+ "stage": "flowering",
45
+ "planted_estimate": "2025-06-15"
46
+ },
47
+ "alert": {
48
+ "type": "weather_warning",
49
+ "urgency": "high",
50
+ "message": "Heavy rainfall (40-60mm) expected in next 2 days. Delay fertilizer application. Ensure proper drainage.",
51
+ "action_items": ["delay_fertilizer", "check_drainage"],
52
+ "valid_until": "2025-07-25T18:00:00Z"
53
+ },
54
+ "weather": {
55
+ "forecast_days": 3,
56
+ "rain_probability": 85,
57
+ "expected_rainfall": "45mm"
58
+ }
59
+ }
60
+
61
+ main_menu = create_ussd_menu(sample_alert)
62
+ print("--- Main Menu ---")
63
+ print(main_menu)
64
+
65
+ submenu_1 = get_ussd_submenu(sample_alert, 1)
66
+ print("\n--- Submenu 1 ---")
67
+ print(submenu_1)
68
+
69
+ submenu_2 = get_ussd_submenu(sample_alert, 2)
70
+ print("\n--- Submenu 2 ---")
71
+ print(submenu_2)
a2a_agents/weather_agent.py ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import asyncio
2
+ from src.mcp_weather_server.tools import open_meteo
3
+
4
+ class WeatherAgent:
5
+ async def get_weather_forecast(self, latitude, longitude):
6
+ print("Fetching 7-day weather forecast...")
7
+ weather_forecast_response = await open_meteo.get_weather_forecast(latitude=latitude, longitude=longitude)
8
+ if "error" in weather_forecast_response:
9
+ print(f"Error: {weather_forecast_response['error']}")
10
+ return None
11
+ print("Weather forecast received.")
12
+ return weather_forecast_response
13
+
14
+ async def main():
15
+ agent = WeatherAgent()
16
+ # Example usage (Patna, Bihar)
17
+ lat = 25.6
18
+ lon = 85.1
19
+ forecast = await agent.get_weather_forecast(latitude=lat, longitude=lon)
20
+ if forecast:
21
+ print("\n--- Weather Forecast ---")
22
+ print(forecast)
23
+ print("----------------------")
24
+
25
+ if __name__ == "__main__":
26
+ asyncio.run(main())
a2a_agents/whatsapp_agent.py ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+
3
+ def create_whatsapp_message(alert_json: dict) -> dict:
4
+ """
5
+ Creates a rich formatted WhatsApp message from a structured alert JSON.
6
+ """
7
+
8
+ message = (
9
+ f"🚨 *Weather Alert* 🚨\n\n"
10
+ f"📍 *Location:* {alert_json['location']['village']}, {alert_json['location']['district']}\n"
11
+ f"🌾 *Crop:* {alert_json['crop']['name'].capitalize()} ({alert_json['crop']['stage']})\n"
12
+ f"⚠️ *Urgency:* {alert_json['alert']['urgency'].upper()}\n\n"
13
+ f"📝 *Details:* {alert_json['alert']['message']}\n\n"
14
+ f"✅ *Recommended Actions:*\n"
15
+ )
16
+ for action in alert_json['alert']['action_items']:
17
+ message += f"- {action.replace('_', ' ').capitalize()}\n"
18
+
19
+ return {
20
+ "text": message,
21
+ "buttons": [
22
+ {"title": "Acknowledge", "payload": f"ack_{alert_json['alert_id']}"},
23
+ {"title": "More Info", "payload": f"info_{alert_json['alert_id']}"}
24
+ ]
25
+ }
26
+
27
+ if __name__ == '__main__':
28
+ sample_alert = {
29
+ "alert_id": "BH_PAT_001_20250723",
30
+ "timestamp": "2025-07-23T06:00:00Z",
31
+ "location": {
32
+ "village": "Kumhrar",
33
+ "district": "Patna",
34
+ "state": "Bihar",
35
+ "coordinates": [25.5941, 85.1376]
36
+ },
37
+ "crop": {
38
+ "name": "rice",
39
+ "stage": "flowering",
40
+ "planted_estimate": "2025-06-15"
41
+ },
42
+ "alert": {
43
+ "type": "weather_warning",
44
+ "urgency": "high",
45
+ "message": "Heavy rainfall (40-60mm) expected in next 2 days. Delay fertilizer application. Ensure proper drainage.",
46
+ "action_items": ["delay_fertilizer", "check_drainage"],
47
+ "valid_until": "2025-07-25T18:00:00Z"
48
+ },
49
+ "weather": {
50
+ "forecast_days": 3,
51
+ "rain_probability": 85,
52
+ "expected_rainfall": "45mm"
53
+ }
54
+ }
55
+
56
+ whatsapp_message = create_whatsapp_message(sample_alert)
57
+ print(json.dumps(whatsapp_message, indent=2))
requirements.txt ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ fastapi
2
+ uvicorn
3
+ requests
4
+ python-dotenv
5
+ google-search-results
6
+ httpx
7
+ openai
server.py ADDED
@@ -0,0 +1,581 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import logging
3
+ import random
4
+ from fastapi import FastAPI, HTTPException
5
+ from fastapi.middleware.cors import CORSMiddleware
6
+ from pydantic import BaseModel
7
+ from dotenv import dotenv_values
8
+ import asyncio
9
+ from datetime import datetime, timedelta
10
+ import csv
11
+ from io import StringIO
12
+
13
+
14
+ from tools import open_meteo, tomorrow_io, google_weather, openweathermap, accuweather, openai_llm, geographic_tools, crop_calendar_tools, alert_generation_tools
15
+ from a2a_agents import sms_agent, whatsapp_agent, ussd_agent, ivr_agent, telegram_agent
16
+ from utils.weather_utils import get_tool_config
17
+
18
+
19
+ config = dotenv_values(".env")
20
+
21
+ LOG_LEVEL = os.getenv("LOG_LEVEL", "INFO")
22
+ logging.basicConfig(level=LOG_LEVEL)
23
+ logger = logging.getLogger(__name__)
24
+
25
+ app = FastAPI()
26
+
27
+ # CORS middleware for frontend
28
+ app.add_middleware(
29
+ CORSMiddleware,
30
+ allow_origins=["https://mcp-ui.vercel.app"],
31
+ allow_credentials=True,
32
+ allow_methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"],
33
+ allow_headers=["*"],
34
+ expose_headers=["*"]
35
+ )
36
+
37
+ class MCPRequest(BaseModel):
38
+ tool: str
39
+ parameters: dict
40
+
41
+ class AlertRequest(BaseModel):
42
+ alert_json: dict
43
+
44
+ class WorkflowRequest(BaseModel):
45
+ state: str
46
+ district: str
47
+
48
+ def get_regional_crop_for_area(district: str, state: str):
49
+ """Get typical crop for the region"""
50
+ if state.lower() == 'bihar':
51
+ district_crops = {
52
+ 'patna': 'rice',
53
+ 'gaya': 'wheat',
54
+ 'bhagalpur': 'rice',
55
+ 'muzaffarpur': 'sugarcane',
56
+ 'darbhanga': 'rice',
57
+ 'siwan': 'rice',
58
+ 'begusarai': 'rice',
59
+ 'katihar': 'maize',
60
+ }
61
+ return district_crops.get(district.lower(), 'rice')
62
+ return 'rice'
63
+
64
+ def get_current_crop_stage(crop: str):
65
+ """Determine crop stage based on current date"""
66
+ current_month = datetime.now().month
67
+
68
+ if crop == 'rice':
69
+ if current_month in [6, 7]:
70
+ return 'planting'
71
+ elif current_month in [8, 9]:
72
+ return 'growing'
73
+ elif current_month in [10, 11]:
74
+ return 'flowering'
75
+ else:
76
+ return 'harvesting'
77
+ elif crop == 'wheat':
78
+ if current_month in [11, 12]:
79
+ return 'planting'
80
+ elif current_month in [1, 2]:
81
+ return 'growing'
82
+ elif current_month in [3, 4]:
83
+ return 'flowering'
84
+ else:
85
+ return 'harvesting'
86
+ elif crop == 'sugarcane':
87
+ if current_month in [2, 3, 4]:
88
+ return 'planting'
89
+ elif current_month in [5, 6, 7, 8]:
90
+ return 'growing'
91
+ elif current_month in [9, 10, 11]:
92
+ return 'maturing'
93
+ else:
94
+ return 'harvesting'
95
+ elif crop == 'maize':
96
+ if current_month in [6, 7]:
97
+ return 'planting'
98
+ elif current_month in [8, 9]:
99
+ return 'growing'
100
+ elif current_month in [10, 11]:
101
+ return 'flowering'
102
+ else:
103
+ return 'harvesting'
104
+
105
+ return 'growing'
106
+
107
+ 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
+ # Pick a random village or use default
115
+ village_name = f"Village in {district}"
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
+ # Get coordinates for the district/village
125
+ location_coords = [25.5941, 85.1376] # Default to Patna
126
+
127
+ # Try to get coordinates for the district first
128
+ try:
129
+ district_location = await geographic_tools.reverse_geocode(district)
130
+ if "error" not in district_location and "lat" in district_location:
131
+ location_coords = [district_location["lat"], district_location["lng"]]
132
+ except:
133
+ pass # Keep default coordinates
134
+
135
+ # Generate regional crop and stage
136
+ regional_crop = get_regional_crop_for_area(district, state)
137
+ crop_stage = get_current_crop_stage(regional_crop)
138
+
139
+ # GET WEATHER DATA
140
+ try:
141
+ current_weather_data = await open_meteo.get_current_weather(
142
+ latitude=location_coords[0],
143
+ longitude=location_coords[1]
144
+ )
145
+
146
+ forecast_data = await open_meteo.get_weather_forecast(
147
+ latitude=location_coords[0],
148
+ longitude=location_coords[1],
149
+ days=7
150
+ )
151
+
152
+ current_weather = current_weather_data.get('current_weather', {})
153
+ daily_forecast = forecast_data.get('daily', {})
154
+
155
+ current_temp = current_weather.get('temperature', 25)
156
+ current_windspeed = current_weather.get('windspeed', 10)
157
+
158
+ precipitation_list = daily_forecast.get('precipitation_sum', [0, 0, 0])
159
+ next_3_days_rain = sum(precipitation_list[:3]) if precipitation_list else 0
160
+
161
+ rain_probability = min(90, max(10, int(next_3_days_rain * 10))) if next_3_days_rain > 0 else 10
162
+
163
+ # Higher precipitation = higher humidity estimate
164
+ estimated_humidity = min(95, max(40, 60 + int(next_3_days_rain * 2)))
165
+
166
+ real_weather = {
167
+ "forecast_days": 3,
168
+ "rain_probability": rain_probability,
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 in {district}. Delay fertilizer application. Ensure proper drainage."
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 in {district}. Monitor soil moisture levels."
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 in {district}. Increase irrigation frequency."
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 in {district}. Temperature {current_temp:.1f}°C, rainfall {next_3_days_rain:.1f}mm."
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]}_001_{datetime.now().strftime('%Y%m%d')}",
205
+ "timestamp": datetime.now().isoformat() + "Z",
206
+ "location": {
207
+ "village": village_name,
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,
219
+ "urgency": urgency,
220
+ "message": alert_message,
221
+ "action_items": action_items,
222
+ "valid_until": (datetime.now() + timedelta(days=3)).isoformat() + "Z"
223
+ },
224
+ "weather": real_weather,
225
+ "data_source": "open_meteo_api"
226
+ }
227
+
228
+ except Exception as e:
229
+ logger.error(f"Error generating dynamic alert for {district}, {state}: {e}")
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"}
236
+
237
+ @app.get("/api/health")
238
+ async def health_check():
239
+ return {"status": "healthy", "message": "API is working"}
240
+
241
+ # workflow endpoint for frontend
242
+ @app.post("/api/run-workflow")
243
+ async def run_workflow(request: WorkflowRequest):
244
+ logger.info(f"Received workflow request: {request.state}, {request.district}")
245
+
246
+ # Initialize variables
247
+ sample_alert = None
248
+ csv_content = ""
249
+
250
+ try:
251
+ # Create comprehensive workflow response
252
+ workflow_results = []
253
+
254
+ # Add workflow header
255
+ workflow_results.append(f"Workflow for {request.district}, {request.state}")
256
+ workflow_results.append("=" * 50)
257
+
258
+ # weather data collection
259
+ workflow_results.append("\n🌤️ Weather Data Collection")
260
+ workflow_results.append("-" * 30)
261
+ workflow_results.append("📡 Fetching real-time weather data...")
262
+
263
+ try:
264
+ sample_alert = await generate_dynamic_alert(request.district, request.state)
265
+
266
+ workflow_results.append("✅ Current weather data retrieved from Open-Meteo API")
267
+ workflow_results.append("✅ 7-day forecast collected")
268
+ workflow_results.append("✅ Agricultural indices calculated")
269
+
270
+ except Exception as weather_error:
271
+ logger.error(f"Weather data error: {weather_error}")
272
+ workflow_results.append(f"❌ Weather data collection failed: {str(weather_error)}")
273
+ return {
274
+ "message": "\n".join(workflow_results),
275
+ "status": "error",
276
+ "csv": "",
277
+ "error": f"Unable to retrieve weather data: {str(weather_error)}"
278
+ }
279
+
280
+ if not sample_alert:
281
+ return {
282
+ "message": "Failed to generate alert data",
283
+ "status": "error",
284
+ "csv": "",
285
+ "error": "Alert generation failed"
286
+ }
287
+
288
+ # Alert generation
289
+ workflow_results.append("\n🚨 Alert Generation")
290
+ workflow_results.append("-" * 30)
291
+ workflow_results.append("✅ Weather alerts generated")
292
+ workflow_results.append(f" - Data Source: {sample_alert.get('data_source', 'API')}")
293
+ workflow_results.append(f" - Alert Type: {sample_alert['alert']['type']}")
294
+ workflow_results.append(f" - Severity: {sample_alert['alert']['urgency']}")
295
+ workflow_results.append(f" - Village: {sample_alert['location']['village']}")
296
+ workflow_results.append(f" - Coordinates: {sample_alert['location']['coordinates']}")
297
+ workflow_results.append(f" - Crop: {sample_alert['crop']['name']} ({sample_alert['crop']['stage']})")
298
+ workflow_results.append(f" - Temperature: {sample_alert['weather']['temperature']}")
299
+ workflow_results.append(f" - Humidity: {sample_alert['weather']['humidity']}")
300
+ workflow_results.append(f" - Expected Rainfall: {sample_alert['weather']['expected_rainfall']}")
301
+ workflow_results.append(f" - Rain Probability: {sample_alert['weather']['rain_probability']}%")
302
+
303
+ # WhatsApp Agent Response
304
+ workflow_results.append("\n📱 WhatsApp Agent Response")
305
+ workflow_results.append("-" * 30)
306
+ try:
307
+ whatsapp_message = whatsapp_agent.create_whatsapp_message(sample_alert)
308
+ workflow_results.append(f"✅ Message created successfully")
309
+ workflow_results.append(f"Text: {whatsapp_message.get('text', 'N/A')}")
310
+ if 'buttons' in whatsapp_message:
311
+ workflow_results.append(f"Buttons: {len(whatsapp_message['buttons'])} button(s)")
312
+ except Exception as e:
313
+ workflow_results.append(f"❌ Error: {str(e)}")
314
+
315
+ # SMS Agent Response
316
+ workflow_results.append("\n📱 SMS Agent Response")
317
+ workflow_results.append("-" * 30)
318
+ try:
319
+ sms_message = sms_agent.create_sms_message(sample_alert)
320
+ workflow_results.append(f"✅ SMS created successfully")
321
+ workflow_results.append(f"Content: {str(sms_message)}")
322
+ except Exception as e:
323
+ workflow_results.append(f"❌ Error: {str(e)}")
324
+
325
+ # USSD Agent Response
326
+ workflow_results.append("\n📞 USSD Agent Response")
327
+ workflow_results.append("-" * 30)
328
+ try:
329
+ ussd_menu = ussd_agent.create_ussd_menu(sample_alert)
330
+ workflow_results.append(f"✅ USSD menu created successfully")
331
+ workflow_results.append(f"Menu: {str(ussd_menu)}")
332
+ except Exception as e:
333
+ workflow_results.append(f"❌ Error: {str(e)}")
334
+
335
+ # IVR Agent Response
336
+ workflow_results.append("\n🎙️ IVR Agent Response")
337
+ workflow_results.append("-" * 30)
338
+ try:
339
+ ivr_script = ivr_agent.create_ivr_script(sample_alert)
340
+ workflow_results.append(f"✅ IVR script created successfully")
341
+ workflow_results.append(f"Script: {str(ivr_script)}")
342
+ except Exception as e:
343
+ workflow_results.append(f"❌ Error: {str(e)}")
344
+
345
+ # Telegram Agent Response
346
+ workflow_results.append("\n🤖 Telegram Agent Response")
347
+ workflow_results.append("-" * 30)
348
+ try:
349
+ telegram_message = telegram_agent.create_telegram_message(sample_alert)
350
+ workflow_results.append(f"✅ Telegram message created successfully")
351
+ workflow_results.append(f"Content: {str(telegram_message)}")
352
+ except Exception as e:
353
+ workflow_results.append(f"❌ Error: {str(e)}")
354
+
355
+ # Summary
356
+ workflow_results.append("\n✅ Workflow Summary")
357
+ workflow_results.append("-" * 30)
358
+ workflow_results.append("Workflow execution completed with REAL weather data")
359
+ workflow_results.append(f"Location: {request.district}, {request.state}")
360
+ workflow_results.append(f"Weather Source: Open-Meteo API")
361
+ workflow_results.append(f"Timestamp: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
362
+
363
+ # Join all results into a single formatted string
364
+ formatted_output = "\n".join(workflow_results)
365
+
366
+ # Generate CSV
367
+ try:
368
+ csv_buffer = StringIO()
369
+ writer = csv.writer(csv_buffer)
370
+
371
+ # Write headers
372
+ headers = ["weather data", "whatsapp", "sms", "ussd", "ivr", "telegram"]
373
+ writer.writerow(headers)
374
+
375
+ # Prepare weather data as a single string with line breaks
376
+ weather_info = "\n".join([
377
+ f" - Data Source: {sample_alert.get('data_source', 'API')}",
378
+ f" - Alert Type: {sample_alert['alert']['type']}",
379
+ f" - Severity: {sample_alert['alert']['urgency']}",
380
+ f" - Village: {sample_alert['location']['village']}",
381
+ f" - Coordinates: {sample_alert['location']['coordinates']}",
382
+ f" - Crop: {sample_alert['crop']['name']} ({sample_alert['crop']['stage']})",
383
+ f" - Temperature: {sample_alert['weather']['temperature']}",
384
+ f" - Humidity: {sample_alert['weather']['humidity']}",
385
+ f" - Expected Rainfall: {sample_alert['weather']['expected_rainfall']}",
386
+ f" - Rain Probability: {sample_alert['weather']['rain_probability']}%"
387
+ ])
388
+
389
+ weather_data = [weather_info]
390
+
391
+ # Extract agent outputs only (no status messages)
392
+ whatsapp_data = []
393
+ sms_data = []
394
+ ussd_data = []
395
+ ivr_data = []
396
+ telegram_data = []
397
+
398
+ # Get WhatsApp message
399
+ try:
400
+ whatsapp_message = whatsapp_agent.create_whatsapp_message(sample_alert)
401
+ whatsapp_text = whatsapp_message.get('text', 'N/A')
402
+ whatsapp_data.append(whatsapp_text)
403
+ if 'buttons' in whatsapp_message and whatsapp_message['buttons']:
404
+ whatsapp_data.append(f"Buttons: {whatsapp_message['buttons']}")
405
+ except Exception as e:
406
+ whatsapp_data.append(f"Error: {str(e)}")
407
+
408
+ # Get SMS message
409
+ try:
410
+ sms_message = sms_agent.create_sms_message(sample_alert)
411
+ sms_data.append(str(sms_message))
412
+ except Exception as e:
413
+ sms_data.append(f"Error: {str(e)}")
414
+
415
+ # Get USSD menu
416
+ try:
417
+ ussd_menu = ussd_agent.create_ussd_menu(sample_alert)
418
+ ussd_data.append(str(ussd_menu))
419
+ except Exception as e:
420
+ ussd_data.append(f"Error: {str(e)}")
421
+
422
+ # Get IVR script
423
+ try:
424
+ ivr_script = ivr_agent.create_ivr_script(sample_alert)
425
+ ivr_data.append(str(ivr_script))
426
+ except Exception as e:
427
+ ivr_data.append(f"Error: {str(e)}")
428
+
429
+ # Get Telegram message
430
+ try:
431
+ telegram_message = telegram_agent.create_telegram_message(sample_alert)
432
+ telegram_data.append(str(telegram_message))
433
+ except Exception as e:
434
+ telegram_data.append(f"Error: {str(e)}")
435
+
436
+ # Find the maximum number of rows needed
437
+ max_rows = max(
438
+ len(weather_data),
439
+ len(whatsapp_data) if whatsapp_data else 1,
440
+ len(sms_data) if sms_data else 1,
441
+ len(ussd_data) if ussd_data else 1,
442
+ len(ivr_data) if ivr_data else 1,
443
+ len(telegram_data) if telegram_data else 1
444
+ )
445
+
446
+ # Write data rows
447
+ for i in range(max_rows):
448
+ row = [
449
+ weather_data[i] if i < len(weather_data) else "",
450
+ whatsapp_data[i] if i < len(whatsapp_data) else "",
451
+ sms_data[i] if i < len(sms_data) else "",
452
+ ussd_data[i] if i < len(ussd_data) else "",
453
+ ivr_data[i] if i < len(ivr_data) else "",
454
+ telegram_data[i] if i < len(telegram_data) else ""
455
+ ]
456
+ writer.writerow(row)
457
+
458
+ csv_content = csv_buffer.getvalue()
459
+ logger.info("CSV content generated successfully")
460
+
461
+ except Exception as csv_error:
462
+ logger.error(f"Error generating CSV: {csv_error}")
463
+ csv_content = f"Error generating CSV: {str(csv_error)}"
464
+
465
+ logger.info(f"Successfully completed workflow for {request.district}, {request.state}")
466
+ return {
467
+ "message": formatted_output,
468
+ "status": "success",
469
+ "csv": csv_content,
470
+ "raw_data": {
471
+ "state": request.state,
472
+ "district": request.district,
473
+ "alert_data": sample_alert
474
+ }
475
+ }
476
+
477
+ except Exception as e:
478
+ logger.exception(f"Error in workflow for {request.district}, {request.state}")
479
+ return {
480
+ "message": f"Error running workflow: {str(e)}",
481
+ "status": "error",
482
+ "csv": "",
483
+ "error": str(e)
484
+ }
485
+
486
+
487
+ @app.post("/mcp")
488
+ async def mcp_endpoint(request: MCPRequest):
489
+ logger.info(f"Received request for tool: {request.tool}")
490
+ tool_config = get_tool_config(request.tool)
491
+
492
+ if not tool_config:
493
+ logger.error(f"Tool not found: {request.tool}")
494
+ raise HTTPException(status_code=404, detail="Tool not found")
495
+
496
+ try:
497
+ if tool_config["module"] == "open_meteo":
498
+ result = await getattr(open_meteo, request.tool)(**request.parameters)
499
+ elif tool_config["module"] == "tomorrow_io":
500
+ api_key = config.get("TOMORROW_IO_API_KEY")
501
+ result = await getattr(tomorrow_io, request.tool)(**request.parameters, api_key=api_key)
502
+ elif tool_config["module"] == "google_weather":
503
+ api_key = config.get("GOOGLE_WEATHER_API_KEY")
504
+ result = await getattr(google_weather, request.tool)(**request.parameters, api_key=api_key)
505
+ elif tool_config["module"] == "openweathermap":
506
+ api_key = config.get("OPENWEATHERMAP_API_KEY")
507
+ result = await getattr(openweathermap, request.tool)(**request.parameters, api_key=api_key)
508
+ elif tool_config["module"] == "accuweather":
509
+ api_key = config.get("ACCUWEATHER_API_KEY")
510
+ result = await getattr(accuweather, request.tool)(**request.parameters, api_key=api_key)
511
+ elif tool_config["module"] == "openai_llm":
512
+ api_key = config.get("OPENAI_API_KEY")
513
+ result = await getattr(openai_llm, request.tool)(**request.parameters, api_key=api_key)
514
+ elif tool_config["module"] == "geographic_tools":
515
+ result = await getattr(geographic_tools, request.tool)(**request.parameters)
516
+ elif tool_config["module"] == "crop_calendar_tools":
517
+ result = await getattr(crop_calendar_tools, request.tool)(**request.parameters)
518
+ elif tool_config["module"] == "alert_generation_tools":
519
+ api_key = config.get("OPENAI_API_KEY")
520
+ result = await getattr(alert_generation_tools, request.tool)(**request.parameters, api_key=api_key)
521
+ else:
522
+ raise HTTPException(status_code=500, detail="Invalid tool module")
523
+
524
+ logger.info(f"Successfully executed tool: {request.tool}")
525
+ return result
526
+ except Exception as e:
527
+ logger.exception(f"Error executing tool: {request.tool}")
528
+ raise HTTPException(status_code=500, detail=str(e))
529
+
530
+ @app.post("/a2a/sms")
531
+ async def a2a_sms_endpoint(request: AlertRequest):
532
+ return {"message": sms_agent.create_sms_message(request.alert_json)}
533
+
534
+ @app.post("/a2a/whatsapp")
535
+ async def a2a_whatsapp_endpoint(request: AlertRequest):
536
+ return whatsapp_agent.create_whatsapp_message(request.alert_json)
537
+
538
+ @app.post("/a2a/ussd")
539
+ async def a2a_ussd_endpoint(request: AlertRequest):
540
+ return {"menu": ussd_agent.create_ussd_menu(request.alert_json)}
541
+
542
+ @app.post("/a2a/ivr")
543
+ async def a2a_ivr_endpoint(request: AlertRequest):
544
+ return {"script": ivr_agent.create_ivr_script(request.alert_json)}
545
+
546
+ @app.post("/a2a/telegram")
547
+ async def a2a_telegram_endpoint(request: AlertRequest):
548
+ return telegram_agent.create_telegram_message(request.alert_json)
549
+
550
+
551
+ # for smithery + context7
552
+
553
+ @app.post("/mcp")
554
+ async def mcp_rpc_handler(request: dict):
555
+ method = request.get("method")
556
+ params = request.get("params", {})
557
+ tool_name = params.get("tool_name")
558
+ arguments = params.get("arguments", {})
559
+ req_id = request.get("id")
560
+
561
+ # Handle run_workflow tool
562
+ if method == "call_tool" and tool_name == "run_workflow":
563
+ state = arguments.get("state")
564
+ district = arguments.get("district")
565
+ result = await run_workflow(WorkflowRequest(state=state, district=district))
566
+ return {"jsonrpc": "2.0", "result": result, "id": req_id}
567
+
568
+ # Handle other tools dynamically via your tool config
569
+ if method == "call_tool":
570
+ try:
571
+ result = await mcp_endpoint(MCPRequest(tool=tool_name, parameters=arguments))
572
+ return {"jsonrpc": "2.0", "result": result, "id": req_id}
573
+ except Exception as e:
574
+ return {"jsonrpc": "2.0", "error": {"code": -32000, "message": str(e)}, "id": req_id}
575
+
576
+ return {"jsonrpc": "2.0", "error": {"code": -32601, "message": "Unknown method"}, "id": req_id}
577
+
578
+
579
+ if __name__ == "__main__":
580
+ import uvicorn
581
+ uvicorn.run(app, host="0.0.0.0", port=8000)
tools/.DS_Store ADDED
Binary file (6.15 kB). View file
 
tools/__init__.py ADDED
File without changes
tools/accuweather.py ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import httpx
3
+ from fastapi import HTTPException
4
+
5
+ API_KEY = os.getenv("ACCUWEATHER_API_KEY")
6
+ BASE_URL = "http://dataservice.accuweather.com/currentconditions/v1"
7
+
8
+ async def get_accuweather_current_conditions(location_key: str, api_key: str):
9
+ if not api_key:
10
+ raise HTTPException(status_code=500, detail="AccuWeather API key not configured")
11
+
12
+ params = {
13
+ "apikey": api_key,
14
+ }
15
+
16
+ async with httpx.AsyncClient() as client:
17
+ response = await client.get(f"{BASE_URL}/{location_key}", params=params)
18
+
19
+ if response.status_code != 200:
20
+ raise HTTPException(status_code=response.status_code, detail="Error fetching data from AccuWeather")
21
+
22
+ return response.json()
tools/alert_generation_tools.py ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ This module contains tools for alert generation.
3
+ """
4
+ from . import openai_llm
5
+
6
+ async def generate_weather_alert(crop: str, weather_data: dict, growth_stage: str, api_key: str, latitude: float, longitude: float):
7
+ """
8
+ Generates a weather alert using an LLM.
9
+ """
10
+ prompt = f"Generate a weather alert for {crop} at the {growth_stage} stage. Weather data: {weather_data}"
11
+ response = await openai_llm.predict_weather_alert(latitude=latitude, longitude=longitude, api_key=api_key)
12
+ return response
13
+
14
+ async def prioritize_alerts(alerts_list: list, urgency_factors: dict):
15
+ """
16
+ Prioritizes a list of alerts based on urgency factors.
17
+ """
18
+ # Mock implementation
19
+ for alert in alerts_list:
20
+ alert["urgency"] = urgency_factors.get(alert["crop"], 0)
21
+ return sorted(alerts_list, key=lambda x: x["urgency"], reverse=True)
tools/crop_calendar_tools.py ADDED
@@ -0,0 +1,227 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ This module contains tools for crop calendar operations.
3
+ """
4
+ from datetime import date
5
+
6
+ CROP_CALENDAR = {
7
+ "rice": {
8
+ "season": "Kharif",
9
+ "planting": "June-July",
10
+ "harvesting": "October-November",
11
+ "stages": [
12
+ "Nursery/Seedling", "Transplanting", "Vegetative", "Tillering",
13
+ "Panicle Initiation", "Flowering", "Milk/Dough", "Maturity", "Harvesting"
14
+ ]
15
+ },
16
+ "wheat": {
17
+ "season": "Rabi",
18
+ "planting": "November-December",
19
+ "harvesting": "March-April",
20
+ "stages": [
21
+ "Sowing", "Germination", "Tillering", "Jointing", "Booting",
22
+ "Heading", "Flowering", "Grain Filling", "Maturity", "Harvesting"
23
+ ]
24
+ },
25
+ "maize": {
26
+ "season": "Kharif/Zaid",
27
+ "planting": "June-July / March-April",
28
+ "harvesting": "September-October / June",
29
+ "stages": [
30
+ "Sowing", "Emergence", "Vegetative", "Tasseling", "Silking",
31
+ "Grain Filling", "Maturity", "Harvesting"
32
+ ]
33
+ },
34
+ "barley": {
35
+ "season": "Rabi",
36
+ "planting": "November",
37
+ "harvesting": "March-April",
38
+ "stages": [
39
+ "Sowing", "Germination", "Tillering", "Jointing", "Booting",
40
+ "Heading", "Flowering", "Grain Filling", "Maturity", "Harvesting"
41
+ ]
42
+ },
43
+ "gram": {
44
+ "season": "Rabi",
45
+ "planting": "October-November",
46
+ "harvesting": "March-April",
47
+ "stages": [
48
+ "Sowing", "Germination", "Vegetative", "Flowering", "Pod Formation",
49
+ "Pod Filling", "Maturity", "Harvesting"
50
+ ]
51
+ },
52
+ "lentil": {
53
+ "season": "Rabi",
54
+ "planting": "October-November",
55
+ "harvesting": "March-April",
56
+ "stages": [
57
+ "Sowing", "Germination", "Vegetative", "Flowering", "Pod Formation",
58
+ "Pod Filling", "Maturity", "Harvesting"
59
+ ]
60
+ },
61
+ "pea": {
62
+ "season": "Rabi",
63
+ "planting": "October-November",
64
+ "harvesting": "February-March",
65
+ "stages": [
66
+ "Sowing", "Germination", "Vegetative", "Flowering", "Pod Formation",
67
+ "Pod Filling", "Maturity", "Harvesting"
68
+ ]
69
+ },
70
+ "mustard": {
71
+ "season": "Rabi",
72
+ "planting": "October-November",
73
+ "harvesting": "February-March",
74
+ "stages": [
75
+ "Sowing", "Germination", "Rosette", "Stem Elongation", "Flowering",
76
+ "Pod Formation", "Pod Filling", "Maturity", "Harvesting"
77
+ ]
78
+ },
79
+ "linseed": {
80
+ "season": "Rabi",
81
+ "planting": "October-November",
82
+ "harvesting": "March-April",
83
+ "stages": [
84
+ "Sowing", "Germination", "Vegetative", "Flowering", "Capsule Formation",
85
+ "Seed Filling", "Maturity", "Harvesting"
86
+ ]
87
+ },
88
+ "potato": {
89
+ "season": "Rabi",
90
+ "planting": "October-November",
91
+ "harvesting": "February-March",
92
+ "stages": [
93
+ "Planting", "Sprouting", "Vegetative", "Tuber Initiation", "Tuber Bulking",
94
+ "Maturity", "Harvesting"
95
+ ]
96
+ },
97
+ "arhar": {
98
+ "season": "Kharif",
99
+ "planting": "June-July",
100
+ "harvesting": "November-December",
101
+ "stages": [
102
+ "Sowing", "Germination", "Vegetative", "Flowering", "Pod Formation",
103
+ "Pod Filling", "Maturity", "Harvesting"
104
+ ]
105
+ },
106
+ "moong": {
107
+ "season": "Kharif/Zaid",
108
+ "planting": "June-July / March-April",
109
+ "harvesting": "September-October / June",
110
+ "stages": [
111
+ "Sowing", "Germination", "Vegetative", "Flowering", "Pod Formation",
112
+ "Pod Filling", "Maturity", "Harvesting"
113
+ ]
114
+ },
115
+ "urd": {
116
+ "season": "Kharif/Zaid",
117
+ "planting": "June-July / March-April",
118
+ "harvesting": "September-October / June",
119
+ "stages": [
120
+ "Sowing", "Germination", "Vegetative", "Flowering", "Pod Formation",
121
+ "Pod Filling", "Maturity", "Harvesting"
122
+ ]
123
+ },
124
+ "jowar": {
125
+ "season": "Kharif",
126
+ "planting": "June-July",
127
+ "harvesting": "September-October",
128
+ "stages": [
129
+ "Sowing", "Germination", "Vegetative", "Booting", "Flowering",
130
+ "Grain Filling", "Maturity", "Harvesting"
131
+ ]
132
+ },
133
+ "bajra": {
134
+ "season": "Kharif",
135
+ "planting": "June-July",
136
+ "harvesting": "September-October",
137
+ "stages": [
138
+ "Sowing", "Germination", "Vegetative", "Booting", "Flowering",
139
+ "Grain Filling", "Maturity", "Harvesting"
140
+ ]
141
+ },
142
+ "groundnut": {
143
+ "season": "Kharif",
144
+ "planting": "June-July",
145
+ "harvesting": "September-October",
146
+ "stages": [
147
+ "Sowing", "Germination", "Vegetative", "Flowering", "Pegging",
148
+ "Pod Formation", "Pod Filling", "Maturity", "Harvesting"
149
+ ]
150
+ },
151
+ "soybean": {
152
+ "season": "Kharif",
153
+ "planting": "June-July",
154
+ "harvesting": "September-October",
155
+ "stages": [
156
+ "Sowing", "Germination", "Vegetative", "Flowering", "Pod Formation",
157
+ "Pod Filling", "Maturity", "Harvesting"
158
+ ]
159
+ },
160
+ "watermelon": {
161
+ "season": "Zaid",
162
+ "planting": "March-April",
163
+ "harvesting": "May-June",
164
+ "stages": [
165
+ "Sowing", "Germination", "Vegetative", "Flowering", "Fruit Setting",
166
+ "Fruit Development", "Maturity", "Harvesting"
167
+ ]
168
+ },
169
+ "cucumber": {
170
+ "season": "Zaid",
171
+ "planting": "March-April",
172
+ "harvesting": "May-June",
173
+ "stages": [
174
+ "Sowing", "Germination", "Vegetative", "Flowering", "Fruit Setting",
175
+ "Fruit Development", "Maturity", "Harvesting"
176
+ ]
177
+ }
178
+ }
179
+
180
+
181
+ from datetime import date
182
+ from .geographic_tools import BIHAR_DATA
183
+
184
+ async def get_crop_calendar(region: str, crop_type: str = None):
185
+ if region.lower() == 'bihar':
186
+ if crop_type:
187
+ crop = crop_type.lower()
188
+ if crop in CROP_CALENDAR:
189
+ return {**CROP_CALENDAR[crop], "crop": crop}
190
+ else:
191
+ return {"error": "Crop not found in Bihar"}
192
+ return {
193
+ "crops": list(CROP_CALENDAR.keys()),
194
+ "districts": list(BIHAR_DATA.keys())
195
+ }
196
+ return {"error": "Region not found"}
197
+
198
+ async def get_prominent_crops(region: str, season: str):
199
+ if region.lower() == 'bihar':
200
+ season = season.lower()
201
+ crops = [crop for crop, data in CROP_CALENDAR.items() if season in data["season"].lower()]
202
+ if crops:
203
+ return {"crops": crops}
204
+ else:
205
+ return {"error": "No crops found for this season in Bihar"}
206
+ return {"error": "Region or season not found"}
207
+
208
+ async def estimate_crop_stage(crop: str, plant_date: str, current_date: str):
209
+ plant_date = date.fromisoformat(plant_date)
210
+ current_date = date.fromisoformat(current_date)
211
+ days_since_planting = (current_date - plant_date).days
212
+
213
+ crop = crop.lower()
214
+ if crop in CROP_CALENDAR:
215
+ stages = CROP_CALENDAR[crop]["stages"]
216
+ # Estimate total duration (in days) for each crop (approximate, can be refined)
217
+ crop_durations = {
218
+ "rice": 120, "wheat": 120, "maize": 110, "barley": 110, "gram": 110,
219
+ "lentil": 110, "pea": 100, "mustard": 110, "linseed": 110, "potato": 100,
220
+ "arhar": 150, "moong": 70, "urd": 70, "jowar": 100, "bajra": 90,
221
+ "groundnut": 110, "soybean": 100, "watermelon": 80, "cucumber": 70
222
+ }
223
+ total_duration = crop_durations.get(crop, 100)
224
+ stage_length = total_duration // len(stages)
225
+ stage_index = min(days_since_planting // stage_length, len(stages) - 1)
226
+ return {"stage": stages[stage_index]}
227
+ return {"error": "Crop not found"}
tools/geographic_tools.py ADDED
The diff for this file is too large to render. See raw diff
 
tools/google_weather.py ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import httpx
3
+ from fastapi import HTTPException
4
+
5
+ API_KEY = os.getenv("GOOGLE_WEATHER_API_KEY")
6
+ BASE_URL = "https://weather.googleapis.com/v1/currentConditions:lookup"
7
+
8
+ async def get_google_weather_current_conditions(latitude: float, longitude: float, api_key: str):
9
+ if not api_key:
10
+ raise HTTPException(status_code=500, detail="Google Weather API key not configured")
11
+
12
+ params = {
13
+ "key": api_key,
14
+ "location.latitude": latitude,
15
+ "location.longitude": longitude
16
+ }
17
+
18
+ async with httpx.AsyncClient() as client:
19
+ response = await client.get(BASE_URL, params=params)
20
+
21
+ if response.status_code != 200:
22
+ raise HTTPException(status_code=response.status_code, detail="Error fetching data from Google Weather API")
23
+
24
+ return response.json()
tools/open_meteo.py ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import httpx
2
+ from fastapi import HTTPException
3
+
4
+ BASE_URL = "https://api.open-meteo.com/v1/forecast"
5
+
6
+ async def get_weather_forecast(latitude: float, longitude: float, days: int = 7, include_hourly: bool = False):
7
+ params = {
8
+ "latitude": latitude,
9
+ "longitude": longitude,
10
+ "daily": "weathercode,temperature_2m_max,temperature_2m_min,precipitation_sum,windspeed_10m_max",
11
+ "forecast_days": days,
12
+ }
13
+ if include_hourly:
14
+ params["hourly"] = "temperature_2m,relativehumidity_2m,weathercode"
15
+
16
+ async with httpx.AsyncClient() as client:
17
+ response = await client.get(BASE_URL, params=params)
18
+
19
+ if response.status_code != 200:
20
+ raise HTTPException(status_code=response.status_code, detail="Error fetching data from OpenMeteo")
21
+
22
+ return response.json()
23
+
24
+ async def analyze_weather_trends(latitude: float, longitude: float, period: str):
25
+ """
26
+ Analyzes weather trends for a given location and period.
27
+ """
28
+ # This is a mock implementation. A real implementation would perform a more complex analysis.
29
+ if period == "7-day":
30
+ return {"trend": "clear", "confidence": 0.8}
31
+ elif period == "30-day":
32
+ return {"trend": "mixed", "confidence": 0.6}
33
+ else:
34
+ return {"error": "Invalid period"}
35
+
36
+ async def get_current_weather(latitude: float, longitude: float):
37
+ params = {
38
+ "latitude": latitude,
39
+ "longitude": longitude,
40
+ "current_weather": True,
41
+ }
42
+
43
+ async with httpx.AsyncClient() as client:
44
+ response = await client.get(BASE_URL, params=params)
45
+
46
+ if response.status_code != 200:
47
+ raise HTTPException(status_code=response.status_code, detail="Error fetching data from OpenMeteo")
48
+
49
+ return response.json()
50
+
51
+ async def get_historical_weather(latitude: float, longitude: float, start_date: str, end_date: str):
52
+ params = {
53
+ "latitude": latitude,
54
+ "longitude": longitude,
55
+ "start_date": start_date,
56
+ "end_date": end_date,
57
+ "daily": "weathercode,temperature_2m_max,temperature_2m_min,precipitation_sum",
58
+ }
59
+ historical_url = "https://archive-api.open-meteo.com/v1/era5"
60
+
61
+ async with httpx.AsyncClient() as client:
62
+ response = await client.get(historical_url, params=params)
63
+
64
+ if response.status_code != 200:
65
+ raise HTTPException(status_code=response.status_code, detail="Error fetching data from OpenMeteo")
66
+
67
+ return response.json()
tools/openai_llm.py ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import json
3
+ from fastapi import HTTPException
4
+ from openai import OpenAI
5
+ from . import open_meteo
6
+
7
+ async def predict_weather_alert(latitude: float, longitude: float, api_key: str):
8
+ """
9
+ Predicts weather alerts for a given location and crops using an OpenAI LLM.
10
+
11
+ Args:
12
+ latitude: The latitude of the location.
13
+ longitude: The longitude of the location.
14
+ crops: A list of crops to consider for the prediction.
15
+
16
+ Returns:
17
+ A dictionary containing the predicted weather alert.
18
+ """
19
+ try:
20
+ weather_data = await open_meteo.get_weather_forecast(latitude, longitude)
21
+ except HTTPException as e:
22
+ raise HTTPException(status_code=e.status_code, detail=f"Error getting weather data: {e.detail}")
23
+
24
+ try:
25
+ client = OpenAI(api_key=api_key)
26
+ prompt = f"""
27
+ Given the following weather data for a location:
28
+ {weather_data}
29
+
30
+ Please predict any potential weather alerts for these crops in the next 7 days.
31
+ For the given region, consider what crops are possible to grow and their sensitivity to weather conditions.
32
+ Include the following details in your response:
33
+ - Expected weather conditions (e.g., temperature, precipitation, wind speed)
34
+ - Potential weather alerts (e.g., frost, drought, heavy rainfall)
35
+ - Impact on crops (e.g., growth, yield, disease risk)
36
+ - Recommended actions for farmers (e.g., irrigation, protection measures)
37
+ - Any other relevant information that could help farmers prepare for the weather conditions.
38
+ Provide a summary of the potential impact on the crops and any recommended actions.
39
+ Format your response as a JSON object with the following structure:
40
+ {{
41
+ "alert": "Description of the alert",
42
+ "impact": "Description of the impact on crops",
43
+ "recommendations": "Recommended actions for farmers"
44
+ }}
45
+ Do not include any additional text outside of the JSON object. no line changes or markdown formatting.
46
+ """
47
+
48
+ response = client.chat.completions.create(
49
+ model="gpt-4o-mini",
50
+ messages=[
51
+ {"role": "system", "content": "You are a helpful assistant that predicts weather alerts for farmers."},
52
+ {"role": "user", "content": prompt}
53
+ ],
54
+ response_format= { "type": "json_object" }
55
+ )
56
+
57
+ response = response.choices[0].message.content
58
+ if response:
59
+ return json.loads(response)
60
+ except Exception as e:
61
+ raise HTTPException(status_code=500, detail=f"Error getting prediction from OpenAI: {str(e)}")
tools/openweathermap.py ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import httpx
3
+ from fastapi import HTTPException
4
+
5
+ BASE_URL = "https://api.openweathermap.org/data/2.5/weather"
6
+
7
+ async def get_openweathermap_weather(lat: float, lon: float, api_key: str):
8
+ if not api_key:
9
+ raise HTTPException(status_code=500, detail="OpenWeatherMap API key not configured")
10
+
11
+ params = {
12
+ "lat": lat,
13
+ "lon": lon,
14
+ "appid": api_key,
15
+ }
16
+
17
+ async with httpx.AsyncClient() as client:
18
+ response = await client.get(BASE_URL, params=params)
19
+
20
+ if response.status_code != 200:
21
+ raise HTTPException(status_code=response.status_code, detail="Error fetching data from OpenWeatherMap")
22
+
23
+ return response.json()
tools/tomorrow_io.py ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import httpx
3
+ from fastapi import HTTPException
4
+
5
+ API_KEY = os.getenv("TOMORROW_IO_API_KEY")
6
+ BASE_URL = "https://api.tomorrow.io/v4"
7
+
8
+ async def get_tomorrow_weather(location: str, fields: str = "temperature,weatherCode", timesteps: str = "1h", units: str = "metric"):
9
+ if not API_KEY:
10
+ raise HTTPException(status_code=500, detail="Tomorrow.io API key not configured")
11
+
12
+ params = {
13
+ "location": location,
14
+ "fields": fields.split(','),
15
+ "timesteps": timesteps.split(','),
16
+ "units": units,
17
+ "apikey": API_KEY,
18
+ }
19
+
20
+ async with httpx.AsyncClient() as client:
21
+ response = await client.get(f"{BASE_URL}/weather/forecast", params=params)
22
+
23
+ if response.status_code != 200:
24
+ raise HTTPException(status_code=response.status_code, detail="Error fetching data from Tomorrow.io")
25
+
26
+ return response.json()
27
+
28
+ async def get_weather_alerts(location: str):
29
+ if not API_KEY:
30
+ raise HTTPException(status_code=500, detail="Tomorrow.io API key not configured")
31
+
32
+ params = {
33
+ "location": location,
34
+ "apikey": API_KEY,
35
+ }
36
+
37
+ async with httpx.AsyncClient() as client:
38
+ response = await client.get(f"{BASE_URL}/weather/alerts", params=params)
39
+
40
+ if response.status_code != 200:
41
+ raise HTTPException(status_code=response.status_code, detail="Error fetching data from Tomorrow.io")
42
+
43
+ return response.json()
utils/.DS_Store ADDED
Binary file (6.15 kB). View file
 
utils/__init__.py ADDED
File without changes
utils/weather_utils.py ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ TOOL_CONFIG = {
2
+ "get_weather_forecast": {"module": "open_meteo"},
3
+ "get_current_weather": {"module": "open_meteo"},
4
+ "get_historical_weather": {"module": "open_meteo"},
5
+ "analyze_weather_trends": {"module": "open_meteo"},
6
+ "get_tomorrow_weather": {"module": "tomorrow_io"},
7
+ "get_weather_alerts": {"module": "tomorrow_io"},
8
+ "get_google_weather_current_conditions": {"module": "google_weather"},
9
+ "get_openweathermap_weather": {"module": "openweathermap"},
10
+ "get_accuweather_current_conditions": {"module": "accuweather"},
11
+ "predict_weather_alert": {"module": "openai_llm"},
12
+ "list_villages": {"module": "geographic_tools"},
13
+ "reverse_geocode": {"module": "geographic_tools"},
14
+ "get_administrative_bounds": {"module": "geographic_tools"},
15
+ "get_crop_calendar": {"module": "crop_calendar_tools"},
16
+ "get_prominent_crops": {"module": "crop_calendar_tools"},
17
+ "estimate_crop_stage": {"module": "crop_calendar_tools"},
18
+ "generate_weather_alert": {"module": "alert_generation_tools"},
19
+ "prioritize_alerts": {"module": "alert_generation_tools"},
20
+ }
21
+
22
+ def get_tool_config(tool_name: str):
23
+ return TOOL_CONFIG.get(tool_name)