NitinBot001 commited on
Commit
374cabb
·
verified ·
1 Parent(s): c6121d2

Upload 3 files

Browse files
Files changed (3) hide show
  1. alert.py +405 -0
  2. easy_agents.py +685 -0
  3. weatherr.py +79 -0
alert.py ADDED
@@ -0,0 +1,405 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # alert.py
2
+
3
+ import json
4
+ import os
5
+ import re
6
+ from typing import Any, Dict, List
7
+ from weatherr import get_weather
8
+ from dotenv import load_dotenv
9
+ from openai import OpenAI
10
+ import requests
11
+ load_dotenv()
12
+
13
+
14
+ class WeatherAlertAPI:
15
+ def __init__(self):
16
+ self.api_url = "http://sachet.ndma.gov.in/cap_public_website/FetchAllAlertDetails"
17
+
18
+ def fetch_alerts(self) -> List[Dict[str, Any]]:
19
+ """Fetch all weather alerts from the NDMA API"""
20
+ try:
21
+ response = requests.get(self.api_url, timeout=30)
22
+ response.raise_for_status()
23
+ data = response.json()
24
+
25
+ if isinstance(data, list):
26
+ return data
27
+ elif isinstance(data, dict) and "nowcastDetails" in data:
28
+ return data["nowcastDetails"]
29
+ else:
30
+ return []
31
+
32
+ except Exception as e:
33
+ print(f"Error fetching alerts: {e}")
34
+ return []
35
+
36
+ def normalize_text(self, text: str) -> str:
37
+ """Normalize text for better matching"""
38
+ return re.sub(r'\s+', ' ', text.strip().lower())
39
+
40
+ def location_matches(self, area_description: str, search_terms: List[str]) -> bool:
41
+ """Check if any search terms match the area description"""
42
+ normalized_area = self.normalize_text(area_description)
43
+ for term in search_terms:
44
+ normalized_term = self.normalize_text(term)
45
+ if normalized_term in normalized_area:
46
+ return True
47
+ return False
48
+
49
+ # Initialize the weather API
50
+ weather_api = WeatherAlertAPI()
51
+
52
+ # OpenAI Tool Definitions (Updated format)
53
+ WEATHER_TOOLS = [
54
+ {
55
+ "type": "function",
56
+ "function": {
57
+ "name": "get_weather_alerts",
58
+ "description": "Get weather alerts for one or more locations in India. Can search by city, state, district, or region name.",
59
+ "parameters": {
60
+ "type": "object",
61
+ "properties": {
62
+ "locations": {
63
+ "type": "array",
64
+ "items": {
65
+ "type": "string"
66
+ },
67
+ "description": "List of locations to search for (up to 5). Can be city names, state names, districts, etc.",
68
+ "maxItems": 5,
69
+ "minItems": 1
70
+ },
71
+ "include_details": {
72
+ "type": "boolean",
73
+ "description": "Whether to include detailed alert information like warning messages and coordinates",
74
+ "default": True
75
+ }
76
+ },
77
+ "required": ["locations"]
78
+ }
79
+ }
80
+ },{
81
+ "type": "function",
82
+ "function": {
83
+ "name": "get_weather",
84
+ "description": "Fetch current weather using address, coordinates, or district/state.",
85
+ "parameters": {
86
+ "type": "object",
87
+ "properties": {
88
+ "latitude": {"type": "number", "description": "Latitude coordinate"},
89
+ "longitude": {"type": "number", "description": "Longitude coordinate"},
90
+ "address": {"type": "string", "description": "Full address or location name"},
91
+ "district": {"type": "string", "description": "District name"},
92
+ "state": {"type": "string", "description": "State name"}
93
+ },
94
+ "required": [] # none are strictly required; function will validate
95
+ }
96
+ }
97
+ },
98
+ {
99
+ "type": "function",
100
+ "function": {
101
+ "name": "get_alert_summary",
102
+ "description": "Get a summary of all current weather alerts by severity level",
103
+ "parameters": {
104
+ "type": "object",
105
+ "properties": {},
106
+ "required": []
107
+ }
108
+ }
109
+ },
110
+ {
111
+ "type": "function",
112
+ "function": {
113
+ "name": "get_available_locations",
114
+ "description": "Get a list of all locations that currently have weather alerts",
115
+ "parameters": {
116
+ "type": "object",
117
+ "properties": {
118
+ "limit": {
119
+ "type": "integer",
120
+ "description": "Maximum number of locations to return",
121
+ "default": 50,
122
+ "minimum": 1,
123
+ "maximum": 200
124
+ }
125
+ },
126
+ "required": []
127
+ }
128
+ }
129
+ }
130
+ ]
131
+
132
+ def get_current_weather(latitude: float = None, longitude: float = None, address: str = None, district: str = None, state: str = None):
133
+ return get_weather(latitude=latitude, longitude=longitude, address=address, district=district, state=state)
134
+
135
+ # Function Implementations
136
+ def get_weather_alerts(locations: List[str], include_details: bool = True) -> Dict[str, Any]:
137
+ """
138
+ Get weather alerts for specified locations
139
+ """
140
+ try:
141
+ all_alerts = weather_api.fetch_alerts()
142
+
143
+ if not all_alerts:
144
+ return {
145
+ "status": "error",
146
+ "message": "No alerts found or error fetching data",
147
+ "data": {}
148
+ }
149
+
150
+ results = {}
151
+ total_alerts = 0
152
+
153
+ for location in locations:
154
+ search_terms = [term.strip() for term in location.split(',') if term.strip()]
155
+ location_alerts = []
156
+
157
+ for alert in all_alerts:
158
+ area_desc = alert.get("area_description", "")
159
+ if weather_api.location_matches(area_desc, search_terms):
160
+ if include_details:
161
+ # Full alert details
162
+ alert_data = {
163
+ "alert_id": alert.get("identifier"),
164
+ "severity": alert.get("severity"),
165
+ "severity_level": alert.get("severity_level"),
166
+ "severity_color": alert.get("severity_color"),
167
+ "disaster_type": alert.get("disaster_type"),
168
+ "area_description": alert.get("area_description"),
169
+ "start_time": alert.get("effective_start_time"),
170
+ "end_time": alert.get("effective_end_time"),
171
+ "source": alert.get("alert_source"),
172
+ "area_covered": alert.get("area_covered"),
173
+ "language": alert.get("actual_lang"),
174
+ "warning_message": alert.get("warning_message"),
175
+ "coordinates": alert.get("centroid"),
176
+ "disseminated": alert.get("disseminated")
177
+ }
178
+ else:
179
+ # Summary only
180
+ alert_data = {
181
+ "alert_id": alert.get("identifier"),
182
+ "severity": alert.get("severity"),
183
+ "disaster_type": alert.get("disaster_type"),
184
+ "area_description": alert.get("area_description"),
185
+ "start_time": alert.get("effective_start_time"),
186
+ "end_time": alert.get("effective_end_time")
187
+ }
188
+
189
+ location_alerts.append(alert_data)
190
+
191
+ results[location] = location_alerts
192
+ total_alerts += len(location_alerts)
193
+
194
+ return {
195
+ "status": "success",
196
+ "total_alerts": total_alerts,
197
+ "locations_searched": len(locations),
198
+ "data": results
199
+ }
200
+
201
+ except Exception as e:
202
+ return {
203
+ "status": "error",
204
+ "message": f"Error processing request: {str(e)}",
205
+ "data": {}
206
+ }
207
+
208
+ def get_alert_summary() -> Dict[str, Any]:
209
+ """
210
+ Get summary of all current alerts by severity
211
+ """
212
+ try:
213
+ all_alerts = weather_api.fetch_alerts()
214
+
215
+ if not all_alerts:
216
+ return {
217
+ "status": "error",
218
+ "message": "No alerts found or error fetching data",
219
+ "summary": {}
220
+ }
221
+
222
+ summary = {"WARNING": 0, "ALERT": 0, "WATCH": 0, "Yellow": 0, "Orange": 0, "Other": 0}
223
+ disaster_types = {}
224
+ states = {}
225
+
226
+ for alert in all_alerts:
227
+ # Count by severity
228
+ severity = alert.get("severity", "Other")
229
+ if severity in summary:
230
+ summary[severity] += 1
231
+ else:
232
+ summary["Other"] += 1
233
+
234
+ # Count by disaster type
235
+ disaster_type = alert.get("disaster_type", "Unknown")
236
+ disaster_types[disaster_type] = disaster_types.get(disaster_type, 0) + 1
237
+
238
+ # Count by state (extract from area_description)
239
+ area_desc = alert.get("area_description", "")
240
+ if "districts of" in area_desc:
241
+ state = area_desc.split("districts of")[-1].strip()
242
+ states[state] = states.get(state, 0) + 1
243
+
244
+ total = sum(summary.values())
245
+
246
+ return {
247
+ "status": "success",
248
+ "total_alerts": total,
249
+ "severity_breakdown": summary,
250
+ "top_disaster_types": dict(sorted(disaster_types.items(), key=lambda x: x[1], reverse=True)[:10]),
251
+ "top_affected_states": dict(sorted(states.items(), key=lambda x: x[1], reverse=True)[:10])
252
+ }
253
+
254
+ except Exception as e:
255
+ return {
256
+ "status": "error",
257
+ "message": f"Error getting summary: {str(e)}",
258
+ "summary": {}
259
+ }
260
+
261
+ def get_available_locations(limit: int = 50) -> Dict[str, Any]:
262
+ """
263
+ Get list of locations with current alerts
264
+ """
265
+ try:
266
+ all_alerts = weather_api.fetch_alerts()
267
+
268
+ if not all_alerts:
269
+ return {
270
+ "status": "error",
271
+ "message": "No alerts found or error fetching data",
272
+ "locations": []
273
+ }
274
+
275
+ locations = set()
276
+ for alert in all_alerts:
277
+ area_desc = alert.get("area_description", "").strip()
278
+ if area_desc:
279
+ locations.add(area_desc)
280
+
281
+ sorted_locations = sorted(list(locations))[:limit]
282
+
283
+ return {
284
+ "status": "success",
285
+ "total_available": len(locations),
286
+ "returned": len(sorted_locations),
287
+ "locations": sorted_locations
288
+ }
289
+
290
+ except Exception as e:
291
+ return {
292
+ "status": "error",
293
+ "message": f"Error getting locations: {str(e)}",
294
+ "locations": []
295
+ }
296
+
297
+ # Function dispatcher for OpenAI (Updated for tool_calls)
298
+ def execute_function(function_name: str, arguments: Dict[str, Any]) -> Dict[str, Any]:
299
+ """
300
+ Execute the appropriate function based on OpenAI's tool call
301
+ """
302
+ if function_name == "get_weather_alerts":
303
+ return get_weather_alerts(**arguments)
304
+ elif function_name == "get_weather":
305
+ return get_weather(**arguments)
306
+ elif function_name == "get_alert_summary":
307
+ return get_alert_summary(**arguments)
308
+ elif function_name == "get_available_locations":
309
+ return get_available_locations(**arguments)
310
+ else:
311
+ return {
312
+ "status": "error",
313
+ "message": f"Unknown function: {function_name}"
314
+ }
315
+
316
+ def chat_with_weather_assistant(user_query: str = "", loop: bool = False, api_key: str = os.getenv("API_KEY"), api_url: str = os.getenv("API_URL"), model_name: str = os.getenv("MODEL_NAME")) -> None:
317
+ """
318
+ Chat with the weather alert assistant.
319
+
320
+ Args:
321
+ user_query (str, optional): The user's query about weather alerts. If empty, will prompt for input.
322
+ loop (bool, optional): Whether to continue the conversation in a loop.
323
+ If True, will keep asking for new queries until 'quit' is entered.
324
+ If False, will process a single query and return. Defaults to False.
325
+ """
326
+ if not api_key or not api_url or not model_name:
327
+ raise ValueError("API key, API URL, and model name are required.")
328
+
329
+ client = OpenAI(api_key=api_key, base_url=api_url)
330
+
331
+ while True:
332
+ # Get user input if not provided or in loop mode
333
+ if not user_query:
334
+ user_input = input("\nAsk about weather alerts (or 'quit' to exit): ")
335
+ if user_input.lower() == 'quit':
336
+ break
337
+ else:
338
+ user_input = user_query
339
+ user_query = None # Clear the query after first use
340
+
341
+ try:
342
+ # Initial API call
343
+ response = client.chat.completions.create(
344
+ model=model_name,
345
+ messages=[
346
+ {
347
+ "role": "system",
348
+ "content": "You are a weather information/alert assistant for India. Use the provided functions to help users get weather alert information. Always provide helpful and accurate information about weather alerts. Your respose should be in same language as user query. Response only should in paragraph only there should not be any table or list till user say to show table or list and response should be in easy language which can understand by uneducated person."
349
+ },
350
+ {"role": "user", "content": user_input}
351
+ ],
352
+ tools=WEATHER_TOOLS,
353
+ tool_choice="auto"
354
+ )
355
+
356
+ message = response.choices[0].message
357
+
358
+ if hasattr(message, 'tool_calls') and message.tool_calls:
359
+ # Prepare messages for function execution
360
+ messages = [
361
+ {
362
+ "role": "system",
363
+ "content": "You are a weather information/alert assistant for India. Use the provided functions to help users get weather alert information. Always provide helpful and accurate information about weather alerts. Your response should be in the same language as the user query. Response should be in paragraph format only (no tables or lists unless explicitly requested) and in simple language that can be understood by everyone."
364
+ },
365
+ {"role": "user", "content": user_input},
366
+ message # Add the assistant message with tool calls
367
+ ]
368
+
369
+ # Execute all tool calls
370
+ for tool_call in message.tool_calls:
371
+ function_name = tool_call.function.name
372
+ function_args = json.loads(tool_call.function.arguments)
373
+ function_result = execute_function(function_name, function_args)
374
+
375
+ messages.append({
376
+ "role": "tool",
377
+ "tool_call_id": tool_call.id,
378
+ "content": json.dumps(function_result)
379
+ })
380
+
381
+ # Get final response
382
+ final_response = client.chat.completions.create(
383
+ model=model_name,
384
+ messages=messages
385
+ )
386
+
387
+ final_message = final_response.choices[0].message.content
388
+ print(f"\n🤖 Assistant: {final_message}")
389
+
390
+ else:
391
+ print(f"\n🤖 Assistant: {message.content}")
392
+
393
+ # If not in loop mode, break after one iteration
394
+ if not loop:
395
+ break
396
+
397
+ except Exception as e:
398
+ print(f"❌ Error: {e}")
399
+ if not loop:
400
+ break
401
+
402
+ if __name__ == "__main__":
403
+ chat_with_weather_assistant()
404
+ # print(WEATHER_TOOLS)
405
+ # get me weather alert of mumbai
easy_agents.py ADDED
@@ -0,0 +1,685 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # easy_agents.py
2
+
3
+ import json
4
+ import requests
5
+ import logging
6
+ from typing import Optional, Dict, Any, Union, List
7
+ from functools import lru_cache
8
+ import time
9
+
10
+ # Configure logging
11
+ logging.basicConfig(level=logging.INFO)
12
+ logger = logging.getLogger(__name__)
13
+
14
+ class EasyFarmsAgent:
15
+ """
16
+ EasyFarms AI Agent optimized for function calling in AI systems.
17
+ """
18
+
19
+ def __init__(self, timeout: int = 30, max_retries: int = 3):
20
+ """Initialize the agent with configuration options."""
21
+ self.timeout = timeout
22
+ self.max_retries = max_retries
23
+
24
+ # Create a session for connection pooling
25
+ self.session = requests.Session()
26
+ self.session.headers.update({
27
+ 'User-Agent': 'EasyFarms-Agent/1.0',
28
+ 'Accept': 'application/json'
29
+ })
30
+
31
+ # Load mappings
32
+ self._load_mappings()
33
+
34
+ def _load_mappings(self):
35
+ """Pre-load all mappings for faster access."""
36
+ self.soil_mapping = {
37
+ "black": 0, "clayey": 1, "loamy": 2, "red": 3, "sandy": 4
38
+ }
39
+
40
+ self.fertilizer_crop_mapping = {
41
+ "barley": 0, "cotton": 1, "ground nuts": 2, "maize": 3, "millets": 4,
42
+ "oil seeds": 5, "paddy": 6, "pulses": 7, "sugarcane": 8, "tobacco": 9, "wheat": 10
43
+ }
44
+
45
+ self.disease_crop_mapping = {
46
+ "almond": "almond", "aloe": "aloe", "apple": "apple", "apricot": "apricot",
47
+ "avocado": "avocado", "bamboo": "bamboo", "banana": "banana", "barley": "barley",
48
+ "bean": "bean", "bitter_gourd": "bitter_gourd", "black_plum": "black_plum",
49
+ "blackberry": "blackberry", "bottle_gourd": "bottle_gourd", "cabbage": "cabbage",
50
+ "canola": "canola", "carrot": "carrot", "cashew": "cashew", "cauliflower": "cauliflower",
51
+ "chard": "chard", "cherry": "cherry", "chickpea": "chickpea", "citrus": "citrus",
52
+ "cocoa": "cocoa", "coconut": "coconut", "coffee": "coffee", "cotton": "cotton",
53
+ "cucumber": "cucumber", "currant": "currant", "curry_leaf_tree": "curry_leaf_tree",
54
+ "date": "date", "eggplant": "eggplant", "fig": "fig", "garlic": "garlic",
55
+ "ginger": "ginger", "gram": "gram", "grape": "grape", "guava": "guava",
56
+ "herb": "herb", "jackfruit": "jackfruit", "leek": "leek", "lentil": "lentil",
57
+ "lettuce": "lettuce", "maize": "maize", "mango": "mango", "manioc": "manioc",
58
+ "melon": "melon", "millet": "millet", "mustard": "mustard", "okra": "okra",
59
+ "olive": "olive", "onion": "onion", "papaya": "papaya", "pea": "pea",
60
+ "peach": "peach", "peanut": "peanut", "pear": "pear", "pepper": "pepper",
61
+ "pigeonpea": "pigeonpea", "pineapple": "pineapple", "pistachio": "pistachio",
62
+ "plum": "plum", "pomegranate": "pomegranate", "potato": "potato",
63
+ "pumpkin": "pumpkin", "radish": "radish", "raspberry": "raspberry",
64
+ "rice": "rice", "rose": "rose", "rye": "rye", "sorghum": "sorghum",
65
+ "soybean": "soybean", "strawberry": "strawberry", "sugarbeet": "sugarbeet",
66
+ "sugarcane": "sugarcane", "sunflower": "sunflower", "sweetpotato": "sweetpotato",
67
+ "tea": "tea", "tamarind": "tamarind", "tobacco": "tobacco", "tomato": "tomato",
68
+ "turmeric": "turmeric", "turnip": "turnip", "wheat": "wheat", "zucchini": "zucchini"
69
+ }
70
+
71
+ self.endpoints = {
72
+ "crop": "https://nitinbot001-agrigo.hf.space/crop-recommendation",
73
+ "fertilizer": "https://nitinbot001-agrigo.hf.space/fertilizer-recommendation",
74
+ "disease": "https://api-for-disease-detection.vercel.app/analyze-plant"
75
+ }
76
+
77
+ def _make_request_with_retry(self, method: str, url: str, **kwargs) -> Dict[str, Any]:
78
+ """Make HTTP request with retry logic and handle both JSON and HTML responses."""
79
+ last_exception = None
80
+
81
+ for attempt in range(self.max_retries):
82
+ try:
83
+ response = self.session.request(method, url, timeout=self.timeout, **kwargs)
84
+ response.raise_for_status()
85
+
86
+ content_type = response.headers.get('content-type', '')
87
+
88
+ if 'application/json' in content_type:
89
+ try:
90
+ return response.json()
91
+ except ValueError as e:
92
+ error_msg = f"Invalid JSON response: {response.text[:200]}..."
93
+ logger.error(error_msg)
94
+ raise ValueError(error_msg) from e
95
+
96
+ elif 'text/html' in content_type:
97
+ # Handle HTML response
98
+ logger.warning(f"Received HTML response from {url}, attempting to parse...")
99
+ return self._parse_html_response(response.text)
100
+
101
+ else:
102
+ error_msg = f"Unexpected content type: {content_type}. Response: {response.text[:200]}..."
103
+ logger.error(error_msg)
104
+ raise ValueError(error_msg)
105
+
106
+ except requests.exceptions.RequestException as e:
107
+ last_exception = e
108
+ if attempt < self.max_retries - 1:
109
+ wait_time = (attempt + 1) * 2
110
+ logger.warning(f"Request failed (attempt {attempt + 1}), retrying in {wait_time}s: {e}")
111
+ time.sleep(wait_time)
112
+
113
+ raise last_exception or Exception("Failed to get valid response after retries")
114
+
115
+ def _parse_html_response(self, html_content: str) -> Dict[str, Any]:
116
+ """
117
+ Parse HTML response to extract the recommendation from the specified selector path.
118
+ """
119
+ try:
120
+ from bs4 import BeautifulSoup
121
+ except ImportError:
122
+ return {
123
+ 'status': 'error',
124
+ 'message': 'BeautifulSoup4 is required for HTML parsing. Install it with: pip install beautifulsoup4',
125
+ 'raw_html': html_content[:500] + '...'
126
+ }
127
+
128
+ try:
129
+ soup = BeautifulSoup(html_content, 'html.parser')
130
+ result = {}
131
+
132
+ # Extract recommendation using the specified CSS selector
133
+ recommendation = soup.select_one("body > div > div > h3 > b > span")
134
+
135
+ if recommendation:
136
+ result['prediction'] = recommendation.text.strip()
137
+ result['status'] = 'success'
138
+ else:
139
+ # Try to find any error messages if the recommendation isn't found
140
+ error_msg = soup.find('div', class_='error') or soup.find('p', class_='error')
141
+ if error_msg:
142
+ result['error'] = error_msg.text.strip()
143
+ else:
144
+ result['error'] = 'No recommendation found in the response'
145
+
146
+ result['status'] = 'error'
147
+ result['raw_html'] = html_content[:500] + '...'
148
+
149
+ return result
150
+
151
+ except Exception as e:
152
+ return {
153
+ 'status': 'error',
154
+ 'message': f'Error parsing HTML response: {str(e)}',
155
+ 'raw_html': html_content[:500] + '...'
156
+ }
157
+
158
+ # Global agent instance
159
+ _easyfarms_agent = EasyFarmsAgent()
160
+
161
+ # =============================================================================
162
+ # FUNCTION DEFINITIONS FOR AI AGENTS
163
+ # =============================================================================
164
+
165
+ # Function schemas for AI agent function calling
166
+ EASYFARMS_FUNCTION_SCHEMAS = [
167
+ {
168
+ "name": "get_crop_recommendation",
169
+ "description": "Get crop recommendation based on soil nutrients (N, P, K) and environmental conditions. Returns the best crop to grow given current soil and weather conditions.",
170
+ "parameters": {
171
+ "type": "object",
172
+ "properties": {
173
+ "N": {
174
+ "type": "integer",
175
+ "description": "Nitrogen content in soil (ppm)",
176
+ "minimum": 0,
177
+ "maximum": 200
178
+ },
179
+ "P": {
180
+ "type": "integer",
181
+ "description": "Phosphorous content in soil (ppm)",
182
+ "minimum": 0,
183
+ "maximum": 150
184
+ },
185
+ "K": {
186
+ "type": "integer",
187
+ "description": "Potassium content in soil (ppm)",
188
+ "minimum": 0,
189
+ "maximum": 300
190
+ },
191
+ "temperature": {
192
+ "type": "number",
193
+ "description": "Temperature in Celsius",
194
+ "minimum": -10,
195
+ "maximum": 50
196
+ },
197
+ "humidity": {
198
+ "type": "number",
199
+ "description": "Relative humidity percentage",
200
+ "minimum": 0,
201
+ "maximum": 100
202
+ },
203
+ "ph": {
204
+ "type": "number",
205
+ "description": "Soil pH level",
206
+ "minimum": 0,
207
+ "maximum": 14
208
+ },
209
+ "rainfall": {
210
+ "type": "number",
211
+ "description": "Average rainfall in mm (default: 100)",
212
+ "minimum": 0,
213
+ "maximum": 500,
214
+ "default": 100
215
+ }
216
+ },
217
+ "required": ["N", "P", "K", "temperature", "humidity"]
218
+ }
219
+ },
220
+ {
221
+ "name": "get_fertilizer_recommendation",
222
+ "description": "Get fertilizer recommendation for a specific crop based on soil conditions and nutrient levels. Returns the best fertilizer type to use.",
223
+ "parameters": {
224
+ "type": "object",
225
+ "properties": {
226
+ "crop": {
227
+ "type": "string",
228
+ "description": "Crop type",
229
+ "enum": ["barley", "cotton", "ground nuts", "maize", "millets", "oil seeds", "paddy", "pulses", "sugarcane", "tobacco", "wheat"]
230
+ },
231
+ "soil": {
232
+ "type": "string",
233
+ "description": "Soil type",
234
+ "enum": ["black", "clayey", "loamy", "red", "sandy"]
235
+ },
236
+ "temperature": {
237
+ "type": "number",
238
+ "description": "Temperature in Celsius",
239
+ "minimum": -10,
240
+ "maximum": 50
241
+ },
242
+ "humidity": {
243
+ "type": "number",
244
+ "description": "Relative humidity percentage",
245
+ "minimum": 0,
246
+ "maximum": 100
247
+ },
248
+ "moisture": {
249
+ "type": "number",
250
+ "description": "Soil moisture percentage",
251
+ "minimum": 0,
252
+ "maximum": 100
253
+ },
254
+ "N": {
255
+ "type": "integer",
256
+ "description": "Nitrogen content in soil (ppm)",
257
+ "minimum": 0,
258
+ "maximum": 200
259
+ },
260
+ "P": {
261
+ "type": "integer",
262
+ "description": "Phosphorous content in soil (ppm)",
263
+ "minimum": 0,
264
+ "maximum": 150
265
+ },
266
+ "K": {
267
+ "type": "integer",
268
+ "description": "Potassium content in soil (ppm)",
269
+ "minimum": 0,
270
+ "maximum": 300
271
+ }
272
+ },
273
+ "required": ["crop", "soil", "temperature", "humidity", "moisture", "N", "P", "K"]
274
+ }
275
+ },
276
+ {
277
+ "name": "detect_plant_disease",
278
+ "description": "Detect plant diseases from an image of a plant leaf or crop. Analyzes the image to identify potential diseases and provides treatment recommendations.",
279
+ "parameters": {
280
+ "type": "object",
281
+ "properties": {
282
+ "crop": {
283
+ "type": "string",
284
+ "description": "Type of crop/plant in the image",
285
+ "enum": ["almond", "aloe", "apple", "apricot", "avocado", "bamboo", "banana", "barley", "bean", "bitter_gourd", "black_plum", "blackberry", "bottle_gourd", "cabbage", "canola", "carrot", "cashew", "cauliflower", "chard", "cherry", "chickpea", "citrus", "cocoa", "coconut", "coffee", "cotton", "cucumber", "currant", "curry_leaf_tree", "date", "eggplant", "fig", "garlic", "ginger", "gram", "grape", "guava", "herb", "jackfruit", "leek", "lentil", "lettuce", "maize", "mango", "manioc", "melon", "millet", "mustard", "okra", "olive", "onion", "papaya", "pea", "peach", "peanut", "pear", "pepper", "pigeonpea", "pineapple", "pistachio", "plum", "pomegranate", "potato", "pumpkin", "radish", "raspberry", "rice", "rose", "rye", "sorghum", "soybean", "strawberry", "sugarbeet", "sugarcane", "sunflower", "sweetpotato", "tea", "tamarind", "tobacco", "tomato", "turmeric", "turnip", "wheat", "zucchini"]
286
+ },
287
+ "image_path": {
288
+ "type": "string",
289
+ "description": "Path to the plant/leaf image file"
290
+ },
291
+ "language": {
292
+ "type": "string",
293
+ "description": "Response language code (default: 'en')",
294
+ "default": "en",
295
+ "enum": ["en", "hi", "es", "fr", "de"]
296
+ }
297
+ },
298
+ "required": ["crop", "image_path"]
299
+ }
300
+ },
301
+ {
302
+ "name": "get_supported_options",
303
+ "description": "Get lists of supported crops and soil types for different analysis modes. Useful for showing available options to users.",
304
+ "parameters": {
305
+ "type": "object",
306
+ "properties": {
307
+ "mode": {
308
+ "type": "string",
309
+ "description": "Mode to get supported options for",
310
+ "enum": ["fertilizer_crops", "disease_crops", "soil_types", "all"]
311
+ }
312
+ },
313
+ "required": ["mode"]
314
+ }
315
+ }
316
+ ]
317
+
318
+ # =============================================================================
319
+ # FUNCTION IMPLEMENTATIONS
320
+ # =============================================================================
321
+
322
+ def get_crop_recommendation(
323
+ N: int,
324
+ P: int,
325
+ K: int,
326
+ temperature: float,
327
+ humidity: float,
328
+ ph: float = None,
329
+ rainfall: float = 100
330
+ ) -> Dict[str, Any]:
331
+ try:
332
+ data = {
333
+ "N": N, "P": P, "K": K,
334
+ "temperature": temperature,
335
+ "humidity": humidity,
336
+ "ph": ph if ph is not None else 6.5,
337
+ "rainfall": rainfall
338
+ }
339
+
340
+ # The response will be parsed by _make_request_with_retry
341
+ response = _easyfarms_agent._make_request_with_retry(
342
+ "POST",
343
+ _easyfarms_agent.endpoints["crop"],
344
+ data=data
345
+ )
346
+
347
+ # Add input parameters to the response
348
+ if isinstance(response, dict):
349
+ response['input_parameters'] = data
350
+ if 'mode' not in response:
351
+ response['mode'] = 'crop_recommendation'
352
+
353
+ logger.info(f"Crop recommendation response: {response}")
354
+ return response
355
+
356
+ except Exception as e:
357
+ error_result = {
358
+ "error": str(e),
359
+ "status": "error",
360
+ "mode": "crop_recommendation"
361
+ }
362
+ logger.error(f"Crop recommendation failed: {e}")
363
+ return error_result
364
+
365
+
366
+ def get_fertilizer_recommendation(
367
+ crop: str,
368
+ soil: str,
369
+ temperature: float,
370
+ humidity: float,
371
+ moisture: float,
372
+ N: int,
373
+ P: int,
374
+ K: int
375
+ ) -> Dict[str, Any]:
376
+ try:
377
+ # Map soil and crop to codes
378
+ soil_code = _easyfarms_agent.soil_mapping.get(soil.lower(), soil)
379
+ crop_code = _easyfarms_agent.fertilizer_crop_mapping.get(crop.lower(), crop)
380
+
381
+ data = {
382
+ "temperature": temperature,
383
+ "humidity": humidity,
384
+ "moisture": moisture,
385
+ "N": N, "P": P, "K": K,
386
+ "soil": soil_code,
387
+ "crop": crop_code
388
+ }
389
+
390
+ # The response will be parsed by _make_request_with_retry
391
+ response = _easyfarms_agent._make_request_with_retry(
392
+ "POST",
393
+ _easyfarms_agent.endpoints["fertilizer"],
394
+ data=data
395
+ )
396
+
397
+ # Add input parameters to the response
398
+ if isinstance(response, dict):
399
+ response['input_parameters'] = {
400
+ **data,
401
+ "original_soil": soil,
402
+ "original_crop": crop
403
+ }
404
+ if 'mode' not in response:
405
+ response['mode'] = 'fertilizer_recommendation'
406
+
407
+ logger.info(f"Fertilizer recommendation response: {response}")
408
+ return response
409
+
410
+ except Exception as e:
411
+ error_result = {
412
+ "status": "error",
413
+ "error": str(e),
414
+ "mode": "fertilizer_recommendation"
415
+ }
416
+ logger.error(f"Fertilizer recommendation failed: {e}")
417
+ return error_result
418
+
419
+ def detect_plant_disease(
420
+ crop: str,
421
+ image_path: str,
422
+ language: str = "en"
423
+ ) -> Dict[str, Any]:
424
+ """
425
+ Detect plant diseases from an image using the exact API format from curl.
426
+
427
+ Args:
428
+ crop: Type of crop/plant in the image
429
+ image_path: Path to the plant/leaf image file
430
+ language: Response language code (default: 'en')
431
+
432
+ Returns:
433
+ Dictionary with disease detection results and metadata
434
+ """
435
+ try:
436
+ # Map crop to disease API format
437
+ crop_key = _easyfarms_agent.disease_crop_mapping.get(crop.lower(), crop.lower())
438
+
439
+ # Check if file exists
440
+ import os
441
+ if not os.path.exists(image_path):
442
+ return {
443
+ "error": f"Image file not found: {image_path}",
444
+ "status": "error",
445
+ "mode": "disease_detection"
446
+ }
447
+
448
+ # Prepare headers exactly like the curl command
449
+ headers = {
450
+ 'accept': '*/*',
451
+ 'accept-language': 'en-IN,en-US;q=0.9,en;q=0.8,hi;q=0.7',
452
+ 'origin': 'https://app.easyfarms.in',
453
+ 'referer': 'https://app.easyfarms.in/',
454
+ 'sec-ch-ua': '"Chromium";v="140", "Not=A?Brand";v="24", "Google Chrome";v="140"',
455
+ 'sec-ch-ua-mobile': '?1',
456
+ 'sec-ch-ua-platform': '"Android"',
457
+ 'sec-fetch-dest': 'empty',
458
+ 'sec-fetch-mode': 'cors',
459
+ 'sec-fetch-site': 'cross-site',
460
+ 'user-agent': 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Mobile Safari/537.36'
461
+ }
462
+
463
+ # Make request with image using the exact format
464
+ with open(image_path, "rb") as f:
465
+ files = {
466
+ 'image': ('blob', f, 'image/jpeg')
467
+ }
468
+ data = {
469
+ 'crop': crop_key,
470
+ 'language': language
471
+ }
472
+
473
+ # Use direct requests instead of the agent's retry method for this specific API
474
+ response = _easyfarms_agent.session.post(
475
+ _easyfarms_agent.endpoints["disease"],
476
+ files=files,
477
+ data=data,
478
+ headers=headers,
479
+ timeout=_easyfarms_agent.timeout
480
+ )
481
+ response.raise_for_status()
482
+
483
+ # Try to parse JSON response
484
+ try:
485
+ result = response.json()
486
+ except ValueError:
487
+ # If JSON parsing fails, return the raw text
488
+ result = {
489
+ "raw_response": response.text,
490
+ "content_type": response.headers.get('content-type', ''),
491
+ "status_code": response.status_code
492
+ }
493
+
494
+ # Add metadata to result
495
+ if isinstance(result, dict):
496
+ result.update({
497
+ "status": "success",
498
+ "mode": "disease_detection",
499
+ "input_parameters": {
500
+ "crop": crop,
501
+ "crop_key": crop_key,
502
+ "language": language,
503
+ "image_path": image_path
504
+ }
505
+ })
506
+ else:
507
+ # If result is not a dict, wrap it
508
+ result = {
509
+ "detection_result": result,
510
+ "status": "success",
511
+ "mode": "disease_detection",
512
+ "input_parameters": {
513
+ "crop": crop,
514
+ "crop_key": crop_key,
515
+ "language": language,
516
+ "image_path": image_path
517
+ }
518
+ }
519
+
520
+ logger.info(f"Disease detection successful for {crop}")
521
+ return result
522
+
523
+ except Exception as e:
524
+ error_result = {
525
+ "error": str(e),
526
+ "status": "error",
527
+ "mode": "disease_detection",
528
+ "input_parameters": {
529
+ "crop": crop,
530
+ "language": language,
531
+ "image_path": image_path
532
+ }
533
+ }
534
+ logger.error(f"Disease detection failed: {e}")
535
+ return error_result
536
+
537
+
538
+ def get_supported_options(mode: str) -> Dict[str, Any]:
539
+ """
540
+ Get lists of supported options for different modes.
541
+
542
+ Args:
543
+ mode: Mode to get options for ("fertilizer_crops", "disease_crops", "soil_types", "all")
544
+
545
+ Returns:
546
+ Dictionary with supported options
547
+ """
548
+ try:
549
+ result = {"status": "success", "mode": "supported_options"}
550
+
551
+ if mode == "fertilizer_crops":
552
+ result["fertilizer_crops"] = list(_easyfarms_agent.fertilizer_crop_mapping.keys())
553
+ elif mode == "disease_crops":
554
+ result["disease_crops"] = list(_easyfarms_agent.disease_crop_mapping.keys())
555
+ elif mode == "soil_types":
556
+ result["soil_types"] = list(_easyfarms_agent.soil_mapping.keys())
557
+ elif mode == "all":
558
+ result.update({
559
+ "fertilizer_crops": list(_easyfarms_agent.fertilizer_crop_mapping.keys()),
560
+ "disease_crops": list(_easyfarms_agent.disease_crop_mapping.keys()),
561
+ "soil_types": list(_easyfarms_agent.soil_mapping.keys())
562
+ })
563
+ else:
564
+ return {
565
+ "error": f"Invalid mode: {mode}. Use 'fertilizer_crops', 'disease_crops', 'soil_types', or 'all'",
566
+ "status": "error"
567
+ }
568
+
569
+ return result
570
+
571
+ except Exception as e:
572
+ return {
573
+ "error": str(e),
574
+ "status": "error",
575
+ "mode": "supported_options"
576
+ }
577
+
578
+ # =============================================================================
579
+ # FUNCTION MAPPING FOR AI AGENTS
580
+ # =============================================================================
581
+
582
+ # Map function names to implementations
583
+ EASYFARMS_FUNCTIONS = {
584
+ "get_crop_recommendation": get_crop_recommendation,
585
+ "get_fertilizer_recommendation": get_fertilizer_recommendation,
586
+ "detect_plant_disease": detect_plant_disease,
587
+ "get_supported_options": get_supported_options
588
+ }
589
+
590
+ # =============================================================================
591
+ # UTILITY FUNCTIONS FOR AI AGENT INTEGRATION
592
+ # =============================================================================
593
+
594
+ def execute_easyfarms_function(function_name: str, **kwargs) -> Dict[str, Any]:
595
+ """
596
+ Execute an EasyFarms function by name with given parameters.
597
+
598
+ Args:
599
+ function_name: Name of the function to execute
600
+ **kwargs: Function parameters
601
+
602
+ Returns:
603
+ Function execution result
604
+ """
605
+ if function_name not in EASYFARMS_FUNCTIONS:
606
+ return {
607
+ "error": f"Unknown function: {function_name}. Available: {list(EASYFARMS_FUNCTIONS.keys())}",
608
+ "status": "error"
609
+ }
610
+
611
+ try:
612
+ return EASYFARMS_FUNCTIONS[function_name](**kwargs)
613
+ except TypeError as e:
614
+ return {
615
+ "error": f"Invalid parameters for {function_name}: {str(e)}",
616
+ "status": "error"
617
+ }
618
+ except Exception as e:
619
+ return {
620
+ "error": f"Execution failed for {function_name}: {str(e)}",
621
+ "status": "error"
622
+ }
623
+
624
+ def get_function_schemas() -> List[Dict]:
625
+ """Get all function schemas for AI agent registration."""
626
+ return EASYFARMS_FUNCTION_SCHEMAS
627
+
628
+ def get_function_names() -> List[str]:
629
+ """Get list of available function names."""
630
+ return list(EASYFARMS_FUNCTIONS.keys())
631
+
632
+ # =============================================================================
633
+ # EXAMPLE USAGE FOR AI AGENTS
634
+ # =============================================================================
635
+
636
+ if __name__ == "__main__":
637
+ # Example of how to use in an AI agent
638
+
639
+ print("=== EasyFarms Function Schemas ===")
640
+ schemas = get_function_schemas()
641
+ for schema in schemas:
642
+ print(f"Function: {schema['name']}")
643
+ print(f"Description: {schema['description']}")
644
+ print()
645
+
646
+ print("=== Example Function Calls ===")
647
+
648
+ # Crop recommendation
649
+ crop_result = execute_easyfarms_function(
650
+ "get_crop_recommendation",
651
+ N=90, P=42, K=43,
652
+ temperature=20.87,
653
+ humidity=82.0,
654
+ ph=6.5,
655
+ rainfall=202.9
656
+ )
657
+ print("Crop recommendation:", json.dumps(crop_result, indent=2))
658
+
659
+ # Get supported options
660
+ options_result = execute_easyfarms_function(
661
+ "get_supported_options",
662
+ mode="all"
663
+ )
664
+ print("Supported options:", json.dumps(options_result, indent=2))
665
+
666
+ # Fertilizer recommendation
667
+ fertilizer_result = execute_easyfarms_function(
668
+ "get_fertilizer_recommendation",
669
+ crop="paddy",
670
+ soil="loamy",
671
+ temperature=26.0,
672
+ humidity=52.0,
673
+ moisture=38.0,
674
+ N=37, P=0, K=0
675
+ )
676
+ print("Fertilizer recommendation:", json.dumps(fertilizer_result, indent=2))
677
+
678
+ # Test disease detection (if you have an image file)
679
+ disease_result = execute_easyfarms_function(
680
+ "detect_plant_disease",
681
+ crop="potato",
682
+ image_path="potato-diseases.jpg",
683
+ language="en"
684
+ )
685
+ print("Disease detection:", json.dumps(disease_result, indent=2))
weatherr.py ADDED
@@ -0,0 +1,79 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # weatherr.py
2
+
3
+ import requests
4
+ import os
5
+ from dotenv import load_dotenv
6
+ load_dotenv()
7
+
8
+ # Replace with your API keys
9
+ OPENCAGE_API_KEY = os.getenv("OPENCAGE_API_KEY")
10
+ OPENWEATHER_API_KEY = os.getenv("OPENWEATHER_API_KEY")
11
+
12
+ def get_weather(
13
+ latitude: float = None,
14
+ longitude: float = None,
15
+ address: str = None,
16
+ district: str = None,
17
+ state: str = None,
18
+ units: str = "metric" # "metric" or "imperial"
19
+ ) -> dict:
20
+ """
21
+ Fetch current weather using coordinates or location name (address/district/state).
22
+
23
+ Returns structured weather info from OpenWeatherMap.
24
+ """
25
+ # Step 1: Determine coordinates
26
+ if latitude is None or longitude is None:
27
+ if address:
28
+ query = address
29
+ elif district and state:
30
+ query = f"{district}, {state}"
31
+ elif district:
32
+ query = district
33
+ else:
34
+ return {"error": "Provide either coordinates or location name (address/district+state)."}
35
+
36
+ # Use OpenCageData geocoding
37
+ geocode_url = "https://api.opencagedata.com/geocode/v1/json"
38
+ params = {
39
+ "q": query,
40
+ "key": OPENCAGE_API_KEY,
41
+ "limit": 1,
42
+ "no_annotations": 1
43
+ }
44
+ try:
45
+ resp = requests.get(geocode_url, params=params, timeout=10)
46
+ resp.raise_for_status()
47
+ data = resp.json()
48
+ if data["results"]:
49
+ latitude = data["results"][0]["geometry"]["lat"]
50
+ longitude = data["results"][0]["geometry"]["lng"]
51
+ else:
52
+ return {"error": f"Location not found: {query}"}
53
+ except Exception as e:
54
+ return {"error": f"Geocoding error: {e}"}
55
+
56
+ # Step 2: Fetch weather from OpenWeatherMap
57
+ weather_url = "https://api.openweathermap.org/data/2.5/weather"
58
+ params = {
59
+ "lat": latitude,
60
+ "lon": longitude,
61
+ "appid": OPENWEATHER_API_KEY,
62
+ "units": units
63
+ }
64
+
65
+ try:
66
+ resp = requests.get(weather_url, params=params, timeout=10)
67
+ resp.raise_for_status()
68
+ return {"success": True, "data": resp.json()}
69
+ except Exception as e:
70
+ return {"error": f"Weather API error: {e}"}
71
+
72
+ # Example usage:
73
+ if __name__ == "__main__":
74
+ res = get_weather(district="Bilaspur", state="Chhattisgarh")
75
+ res2 = get_weather(latitude=22.0934, longitude=82.1564)
76
+ res3 = get_weather(address="Bilaspur, Chhattisgarh")
77
+ print(res)
78
+ print(res2)
79
+ print(res3)