elanuk
commited on
Commit
·
d2a1db5
1
Parent(s):
1b36082
- .DS_Store +0 -0
- a2a_agents/__init__.py +0 -0
- a2a_agents/alert_agent.py +63 -0
- a2a_agents/crop_agent.py +53 -0
- a2a_agents/ivr_agent.py +62 -0
- a2a_agents/location_agent.py +37 -0
- a2a_agents/sms_agent.py +69 -0
- a2a_agents/telegram_agent.py +63 -0
- a2a_agents/ussd_agent.py +71 -0
- a2a_agents/weather_agent.py +26 -0
- a2a_agents/whatsapp_agent.py +57 -0
- requirements.txt +7 -0
- server.py +581 -0
- tools/.DS_Store +0 -0
- tools/__init__.py +0 -0
- tools/accuweather.py +22 -0
- tools/alert_generation_tools.py +21 -0
- tools/crop_calendar_tools.py +227 -0
- tools/geographic_tools.py +0 -0
- tools/google_weather.py +24 -0
- tools/open_meteo.py +67 -0
- tools/openai_llm.py +61 -0
- tools/openweathermap.py +23 -0
- tools/tomorrow_io.py +43 -0
- utils/.DS_Store +0 -0
- utils/__init__.py +0 -0
- utils/weather_utils.py +23 -0
.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)
|