RushiMane2003 commited on
Commit
7dcc422
·
verified ·
1 Parent(s): 8cadfdd

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +237 -79
app.py CHANGED
@@ -6,6 +6,8 @@ import hmac
6
  import hashlib
7
  import pandas as pd
8
  import os
 
 
9
  from datetime import datetime
10
  from typing import Optional, Dict, Any, List
11
 
@@ -14,16 +16,19 @@ app = FastAPI()
14
  # Your Retell.ai secret key (get from Retell.ai dashboard)
15
  RETELL_SECRET_KEY = "key_bdb05277a4587c7441bdad4a2c1b"
16
 
 
 
 
17
  # Load CSV data on startup
18
  def load_csv_data():
19
  """Load all CSV files into memory"""
20
  data = {}
21
  csv_files = {
22
  'contact_info': '/app/data/contact_info.csv',
23
- 'crop_advisory': '/app/data/crop_advisory.csv',
24
  'government_schemes': '/app/data/government_schemes.csv'
25
  }
26
-
27
  for key, file_path in csv_files.items():
28
  try:
29
  if os.path.exists(file_path):
@@ -39,22 +44,24 @@ def load_csv_data():
39
  except Exception as e:
40
  print(f"Error loading {key}: {str(e)}")
41
  data[key] = pd.DataFrame()
42
-
43
  return data
44
 
45
  # Load CSV data
46
  csv_data = load_csv_data()
47
- WEATHER_API_KEY = "ee75ffd59875aa5ca6c207e594336b30"
48
 
49
  def get_weather(city: str):
50
  """Fetches weather data from OpenWeatherMap API."""
 
 
51
  url = f"https://api.openweathermap.org/data/2.5/weather?q={city}&appid={WEATHER_API_KEY}&units=metric"
52
  try:
53
  response = requests.get(url, timeout=5)
54
  response.raise_for_status()
55
  data = response.json()
56
 
57
- if data.get("cod") == 200:
 
58
  weather_description = data['weather'][0]['description']
59
  temperature = data['main']['temp']
60
  humidity = data['main']['humidity']
@@ -83,39 +90,192 @@ def search_csv_data(df: pd.DataFrame, search_terms: Dict[str, str]) -> pd.DataFr
83
  """Search dataframe based on multiple criteria"""
84
  if df.empty:
85
  return df
86
-
87
  result = df.copy()
88
  for column, value in search_terms.items():
89
  if column in df.columns and value:
90
  # Case-insensitive partial matching
91
  result = result[result[column].astype(str).str.contains(value, case=False, na=False)]
92
-
93
  return result
94
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
95
  @app.post("/api/market-prices")
96
  async def market_prices(request: dict):
 
97
  crop_name = request.get("query", {}).get("crop_name", "").strip()
98
  state = request.get("query", {}).get("state", "").strip()
99
  district = request.get("query", {}).get("district", "").strip()
100
 
101
- if "crop_advisory" in csv_data:
102
- df = csv_data["crop_advisory"]
103
- mask = (
104
- df["crop_name"].str.lower() == crop_name.lower()
105
- ) & (df["state"].str.lower() == state.lower()) & (
106
- df["district"].str.lower() == district.lower()
107
- )
 
 
 
 
 
 
 
 
 
 
 
 
108
  matches = df[mask]
109
 
110
  if not matches.empty:
111
- try:
112
- avg_price = matches["price"].astype(float).mean()
113
- result = (
114
- f"The average market price of {crop_name} in {district}, {state} "
115
- f"is ₹{avg_price:.2f} per quintal."
116
- )
117
- except Exception:
118
- result = f"Market data found but price column not numeric for {crop_name} in {district}, {state}."
 
 
 
 
 
 
 
 
 
 
 
119
 
120
  return {
121
  "success": True,
@@ -123,12 +283,13 @@ async def market_prices(request: dict):
123
  "data": matches.to_dict(orient="records")
124
  }
125
 
 
126
  return {
127
  "success": False,
128
  "result": f"No market price data found for {crop_name} in {district}, {state}."
129
  }
130
 
131
- @app.post("/api/scheme-eligibility")
132
  async def scheme_eligibility_endpoint(
133
  request: Request,
134
  x_retell_signature: str = Header(None, alias="X-Retell-Signature")
@@ -136,75 +297,71 @@ async def scheme_eligibility_endpoint(
136
  """Handle scheme eligibility function call from Retell.ai"""
137
  request_body = await request.body()
138
  retell_request = json.loads(request_body.decode('utf-8'))
139
-
140
  # Extract arguments
141
  farmer_category = retell_request["args"].get("farmer_category", "")
142
  land_size = retell_request["args"].get("land_size", 0)
143
  state = retell_request["args"].get("state", "")
144
  crop_type = retell_request["args"].get("crop_type", "")
145
-
146
  try:
147
  eligible_schemes = []
148
-
149
- # Search government schemes CSV
150
  if not csv_data['government_schemes'].empty:
151
- # Get all schemes first, then filter if needed
152
- scheme_data = csv_data['government_schemes'].copy()
153
-
154
- for _, scheme in scheme_data.iterrows():
155
- eligible_schemes.append({
156
- "scheme": scheme.get('scheme_name', 'N/A'),
157
- "benefit": scheme.get('benefit', 'N/A'),
158
- "description": scheme.get('description', ''),
159
- "eligibility": scheme.get('eligibility', ''),
160
- "process": scheme.get('application_process', 'Contact local agriculture office'),
161
- "contact": scheme.get('contact', '')
162
- })
163
-
164
- # Add default schemes if no CSV data
165
  if not eligible_schemes:
166
  # PM-KISAN eligibility
167
- if land_size > 0:
168
  eligible_schemes.append({
169
  "scheme": "PM-KISAN",
170
  "benefit": "₹6,000 per year in 3 installments",
171
- "process": "Apply online at pmkisan.gov.in or visit nearest CSC"
 
 
 
172
  })
173
-
174
  # Crop Insurance
175
  eligible_schemes.append({
176
  "scheme": "Pradhan Mantri Fasal Bima Yojana",
177
  "benefit": "Comprehensive crop insurance coverage",
178
- "process": "Contact your nearest bank or insurance agent"
 
 
 
179
  })
180
-
181
  # State-specific schemes
182
- if state.lower() == "punjab":
183
  eligible_schemes.append({
184
  "scheme": "Punjab Crop Diversification Scheme",
185
  "benefit": "₹17,500 per hectare for diversification",
186
- "process": "Contact District Agriculture Officer"
 
187
  })
188
-
189
- # Format response for voice
190
  if eligible_schemes:
191
  schemes_text = f"You are eligible for {len(eligible_schemes)} government schemes: "
192
  for i, scheme in enumerate(eligible_schemes[:3]): # Limit to first 3 for voice response
193
- contact_info = f" Apply through {scheme['process']}" if scheme['process'] else ""
194
  if scheme.get('contact'):
195
- contact_info += f" or contact {scheme['contact']}"
196
- schemes_text += f"{i+1}. {scheme['scheme']} - {scheme['benefit']}.{contact_info}. "
197
-
198
  if len(eligible_schemes) > 3:
199
  schemes_text += f"And {len(eligible_schemes)-3} more schemes available."
200
  else:
201
  schemes_text = "I couldn't find specific schemes for your profile. Please contact your local agriculture department for personalized advice."
202
-
203
  return {
204
  "result": schemes_text,
205
  "eligible_schemes": eligible_schemes
206
  }
207
-
208
  except Exception as e:
209
  return {
210
  "result": "I'm having trouble accessing scheme information right now. Please contact your local agriculture officer or visit the nearest CSC for scheme details.",
@@ -213,6 +370,7 @@ async def scheme_eligibility_endpoint(
213
 
214
  @app.post("/api/weather-advisory")
215
  async def weather_advisory(request: dict):
 
216
  city = request.get("query", {}).get("location", "").strip()
217
 
218
  temperature, humidity, description, pressure = get_weather(city)
@@ -252,22 +410,22 @@ async def weather_advisory(request: dict):
252
 
253
  @app.post("/api/crop-advisory")
254
  async def crop_advisory_endpoint(
255
- request: Request,
256
  x_retell_signature: str = Header(None, alias="X-Retell-Signature")
257
  ):
258
  """Handle crop advisory function call from Retell.ai"""
259
  request_body = await request.body()
260
  retell_request = json.loads(request_body.decode('utf-8'))
261
-
262
  crop_name = retell_request["args"].get("crop_name", "")
263
  growth_stage = retell_request["args"].get("growth_stage", "")
264
  issue_type = retell_request["args"].get("issue_type", "general")
265
  state = retell_request["args"].get("state", "")
266
-
267
  try:
268
  advisory = None
269
  contact_info = ""
270
-
271
  # Search crop advisory CSV
272
  if not csv_data['crop_advisory'].empty:
273
  search_terms = {}
@@ -276,13 +434,13 @@ async def crop_advisory_endpoint(
276
  crop_matches = csv_data['crop_advisory'][
277
  csv_data['crop_advisory']['crop'].str.contains(crop_name, case=False, na=False)
278
  ]
279
-
280
  if not crop_matches.empty:
281
  crop_info = crop_matches.iloc[0]
282
-
283
  # Build advisory based on available data
284
  advisory_parts = []
285
-
286
  if issue_type == "general":
287
  if pd.notna(crop_info.get('sowing_time')):
288
  advisory_parts.append(f"Sowing time: {crop_info['sowing_time']}")
@@ -290,38 +448,38 @@ async def crop_advisory_endpoint(
290
  advisory_parts.append(f"Recommended fertilizer: {crop_info['fertilizer']}")
291
  if pd.notna(crop_info.get('season')):
292
  advisory_parts.append(f"Best season: {crop_info['season']}")
293
-
294
  # Check for specific issues
295
  if pd.notna(crop_info.get('common_issues')) and pd.notna(crop_info.get('solution')):
296
  if issue_type in ['pest', 'disease'] or issue_type == 'general':
297
  advisory_parts.append(f"For {crop_info['common_issues']}: {crop_info['solution']}")
298
-
299
  if advisory_parts:
300
  advisory = f"For {crop_name}: " + ". ".join(advisory_parts)
301
-
302
  # Search contact info CSV
303
  if not csv_data['contact_info'].empty and state:
304
  # Search by state
305
  contact_matches = csv_data['contact_info'][
306
  csv_data['contact_info']['state'].str.contains(state, case=False, na=False)
307
  ]
308
-
309
  if not contact_matches.empty:
310
  contact_match = contact_matches.iloc[0]
311
  contact_parts = []
312
-
313
  if pd.notna(contact_match.get('agriculture_officer')):
314
  contact_parts.append(f"Agriculture Officer at {contact_match['agriculture_officer']}")
315
-
316
  if pd.notna(contact_match.get('kvk_contact')):
317
  contact_parts.append(f"KVK at {contact_match['kvk_contact']}")
318
-
319
  if pd.notna(contact_match.get('kisan_call_center')):
320
  contact_parts.append(f"Kisan Call Center at {contact_match['kisan_call_center']}")
321
-
322
  if contact_parts:
323
  contact_info = f"For detailed advice in {state}, contact: " + " or ".join(contact_parts) + "."
324
-
325
  # Fallback advisory
326
  if not advisory:
327
  if crop_name.lower() == "wheat" and issue_type == "pest":
@@ -330,18 +488,18 @@ async def crop_advisory_endpoint(
330
  advisory = "For rice disease management: If you see brown spots on leaves, it might be blast disease. Apply Tricyclazole 75% WP at 0.6g per liter. Ensure proper drainage."
331
  else:
332
  advisory = f"For {crop_name} at {growth_stage} stage: Monitor crop regularly, maintain proper spacing, apply fertilizers as per soil test recommendations."
333
-
334
  if not contact_info:
335
  contact_info = f"For detailed advice, contact your local Krishi Vigyan Kendra or Agriculture Officer in {state}. You can also call the Kisan Call Centre at 1800-1801-551."
336
-
337
  result_text = f"{advisory} {contact_info}"
338
-
339
  return {
340
  "result": result_text,
341
  "recommendations": advisory,
342
  "contact_info": contact_info
343
  }
344
-
345
  except Exception as e:
346
  return {
347
  "result": f"I couldn't provide specific advice for {crop_name} right now. Please contact your local agriculture extension officer for crop-specific guidance.",
@@ -364,7 +522,7 @@ async def csv_status():
364
  @app.get("/health")
365
  async def health_check():
366
  return {
367
- "status": "healthy",
368
  "service": "Krishi Mitra API",
369
  "csv_files_loaded": {key: len(df) for key, df in csv_data.items()}
370
  }
@@ -375,7 +533,7 @@ async def root():
375
  "message": "Krishi Mitra API is running!",
376
  "endpoints": [
377
  "/api/market-prices",
378
- "/api/scheme-eligibility",
379
  "/api/weather-advisory",
380
  "/api/crop-advisory",
381
  "/api/csv-status",
@@ -385,4 +543,4 @@ async def root():
385
 
386
  if __name__ == "__main__":
387
  import uvicorn
388
- uvicorn.run(app, host="0.0.0.0", port=7860)
 
6
  import hashlib
7
  import pandas as pd
8
  import os
9
+ import re
10
+ import statistics
11
  from datetime import datetime
12
  from typing import Optional, Dict, Any, List
13
 
 
16
  # Your Retell.ai secret key (get from Retell.ai dashboard)
17
  RETELL_SECRET_KEY = "key_bdb05277a4587c7441bdad4a2c1b"
18
 
19
+ # --- WEATHER CONFIG ---
20
+ WEATHER_API_KEY = "ee75ffd59875aa5ca6c207e594336b30"
21
+
22
  # Load CSV data on startup
23
  def load_csv_data():
24
  """Load all CSV files into memory"""
25
  data = {}
26
  csv_files = {
27
  'contact_info': '/app/data/contact_info.csv',
28
+ 'crop_advisory': '/app/data/crop_advisory.csv',
29
  'government_schemes': '/app/data/government_schemes.csv'
30
  }
31
+
32
  for key, file_path in csv_files.items():
33
  try:
34
  if os.path.exists(file_path):
 
44
  except Exception as e:
45
  print(f"Error loading {key}: {str(e)}")
46
  data[key] = pd.DataFrame()
47
+
48
  return data
49
 
50
  # Load CSV data
51
  csv_data = load_csv_data()
 
52
 
53
  def get_weather(city: str):
54
  """Fetches weather data from OpenWeatherMap API."""
55
+ if not city:
56
+ return None, None, None, None
57
  url = f"https://api.openweathermap.org/data/2.5/weather?q={city}&appid={WEATHER_API_KEY}&units=metric"
58
  try:
59
  response = requests.get(url, timeout=5)
60
  response.raise_for_status()
61
  data = response.json()
62
 
63
+ # OpenWeather returns cod as int or string depending on response
64
+ if str(data.get("cod")) == "200":
65
  weather_description = data['weather'][0]['description']
66
  temperature = data['main']['temp']
67
  humidity = data['main']['humidity']
 
90
  """Search dataframe based on multiple criteria"""
91
  if df.empty:
92
  return df
93
+
94
  result = df.copy()
95
  for column, value in search_terms.items():
96
  if column in df.columns and value:
97
  # Case-insensitive partial matching
98
  result = result[result[column].astype(str).str.contains(value, case=False, na=False)]
99
+
100
  return result
101
 
102
+ # -------------------------
103
+ # Helper utilities
104
+ # -------------------------
105
+ def find_column(df: pd.DataFrame, candidates: List[str]) -> Optional[str]:
106
+ """Return first matching column name from candidates (case-insensitive) or None."""
107
+ cols = {c.lower(): c for c in df.columns}
108
+ for cand in candidates:
109
+ if cand.lower() in cols:
110
+ return cols[cand.lower()]
111
+ return None
112
+
113
+ def extract_number_from_price(val: Any) -> Optional[float]:
114
+ """
115
+ Try to extract numeric value from price strings like "₹2,180 per quintal" or "2180".
116
+ Returns float or None if not parseable.
117
+ """
118
+ if pd.isna(val):
119
+ return None
120
+ if isinstance(val, (int, float)):
121
+ return float(val)
122
+ s = str(val)
123
+ # remove currency symbols and non-digit characters except dot and minus
124
+ # first try to find first numeric group
125
+ # remove common words like per, quintal
126
+ # Use regex to capture numbers like 2,180.50 or 2180.5
127
+ match = re.search(r"(-?\d{1,3}(?:[,]\d{3})*(?:\.\d+)?|-?\d+(?:\.\d+)?)", s.replace('₹','').replace('Rs','').replace('INR',''))
128
+ if match:
129
+ num = match.group(0).replace(',', '')
130
+ try:
131
+ return float(num)
132
+ except:
133
+ return None
134
+ return None
135
+
136
+ def format_scheme_row(row: pd.Series, mapping: Dict[str,str]) -> Dict[str,str]:
137
+ """Build a consistent scheme dict from a CSV row using mapping of fields to column names."""
138
+ return {
139
+ "scheme": row.get(mapping.get("name", ""), "N/A"),
140
+ "introduction": row.get(mapping.get("introduction", ""), ""),
141
+ "objective": row.get(mapping.get("objective", ""), ""),
142
+ "benefit": row.get(mapping.get("benefit", ""), ""),
143
+ "eligibility": row.get(mapping.get("eligibility", ""), ""),
144
+ "process": row.get(mapping.get("process", ""), "Contact local agriculture office"),
145
+ "contact": row.get(mapping.get("contact", ""), ""),
146
+ "extra": row.get(mapping.get("extra", ""), ""),
147
+ }
148
+
149
+ def get_schemes_from_csv(farmer_category: str, land_size: float, state: str, crop_type: str) -> List[Dict[str,str]]:
150
+ """
151
+ Read government_schemes dataframe and return a list of scheme dicts.
152
+ This function attempts to surface the most relevant schemes first but will
153
+ return all schemes if filtering doesn't match.
154
+ """
155
+ schemes_out = []
156
+ df = csv_data.get('government_schemes', pd.DataFrame())
157
+ if df.empty:
158
+ return []
159
+
160
+ # build mapping for column names (supports different CSV header variants)
161
+ mapping = {
162
+ "name": find_column(df, ["Name", "scheme_name", "Scheme", "Scheme Name"]),
163
+ "introduction": find_column(df, ["Introduction", "introduction", "Description"]),
164
+ "objective": find_column(df, ["Objective", "objective"]),
165
+ "benefit": find_column(df, ["Benefits", "Benefit", "benefit"]),
166
+ "eligibility": find_column(df, ["Eligibility Criteria", "eligibility", "Eligibility", "eligibility_criteria"]),
167
+ "process": find_column(df, ["Application Process & Required Documents", "application_process", "Process", "application_process & required_documents"]),
168
+ "contact": find_column(df, ["Helpline & Website", "contact", "Helpline", "helpline"]),
169
+ "extra": find_column(df, ["Extra Details", "extra_details", "Extra"])
170
+ }
171
+
172
+ # Build list of all schemes with formatting
173
+ all_schemes = []
174
+ for _, r in df.iterrows():
175
+ all_schemes.append(format_scheme_row(r, mapping))
176
+
177
+ # Try to filter schemes based on simple heuristics:
178
+ prioritized = []
179
+ others = []
180
+
181
+ state_lower = state.lower() if state else ""
182
+ farmer_cat_lower = farmer_category.lower() if farmer_category else ""
183
+ crop_lower = crop_type.lower() if crop_type else ""
184
+
185
+ for s in all_schemes:
186
+ elig = str(s.get("eligibility", "")).lower()
187
+ text_blob = " ".join([
188
+ str(s.get("scheme","") or ""),
189
+ str(s.get("introduction","") or ""),
190
+ str(s.get("objective","") or ""),
191
+ str(s.get("benefit","") or ""),
192
+ str(s.get("eligibility","") or ""),
193
+ str(s.get("extra","") or "")
194
+ ]).lower()
195
+
196
+ score = 0
197
+ # If scheme mentions the state explicitly -> higher relevance
198
+ if state_lower and state_lower in text_blob:
199
+ score += 2
200
+ # If eligibility explicitly mentions landholding and user has land_size > 0
201
+ if land_size and ("land" in elig or "landholding" in elig or "land" in text_blob):
202
+ score += 2
203
+ # If eligibility says "all farmers" or similar, raise modestly
204
+ if "all" in elig or "all farmers" in elig or "all landholding" in elig:
205
+ score += 1
206
+ # crop-specific mention
207
+ if crop_lower and crop_lower in text_blob:
208
+ score += 2
209
+ # farmer category mention
210
+ if farmer_cat_lower and farmer_cat_lower in text_blob:
211
+ score += 1
212
+
213
+ # Put high-scored into prioritized list
214
+ if score >= 2:
215
+ prioritized.append((score, s))
216
+ else:
217
+ others.append((score, s))
218
+
219
+ # sort priority by score desc
220
+ prioritized.sort(key=lambda x: x[0], reverse=True)
221
+ others.sort(key=lambda x: x[0], reverse=True)
222
+
223
+ # return only scheme dicts, prioritized first
224
+ schemes_out = [s for _, s in prioritized] + [s for _, s in others]
225
+ return schemes_out
226
+
227
+ # -------------------------
228
+ # End helpers
229
+ # -------------------------
230
+
231
  @app.post("/api/market-prices")
232
  async def market_prices(request: dict):
233
+ # Keep your request shape usage intact
234
  crop_name = request.get("query", {}).get("crop_name", "").strip()
235
  state = request.get("query", {}).get("state", "").strip()
236
  district = request.get("query", {}).get("district", "").strip()
237
 
238
+ # Safely handle missing CSV or missing expected columns
239
+ if "crop_advisory" in csv_data and not csv_data["crop_advisory"].empty:
240
+ df = csv_data["crop_advisory"].copy()
241
+
242
+ # find likely column names for crop, state, district, price
243
+ crop_col = find_column(df, ["crop_name", "crop", "Crop", "Crop Name"])
244
+ state_col = find_column(df, ["state", "State", "state_name"])
245
+ district_col = find_column(df, ["district", "District", "district_name"])
246
+ price_col = find_column(df, ["price", "Price", "market_price", "market price", "price_per_quintal"])
247
+
248
+ # build mask progressively (use contains if exact match column not present)
249
+ mask = pd.Series([True] * len(df))
250
+ if crop_col and crop_name:
251
+ mask = mask & df[crop_col].astype(str).str.contains(crop_name, case=False, na=False)
252
+ if state_col and state:
253
+ mask = mask & df[state_col].astype(str).str.contains(state, case=False, na=False)
254
+ if district_col and district:
255
+ mask = mask & df[district_col].astype(str).str.contains(district, case=False, na=False)
256
+
257
  matches = df[mask]
258
 
259
  if not matches.empty:
260
+ # compute average over numeric-parsable values in price_col if exists
261
+ avg_price = None
262
+ parsed_prices = []
263
+ if price_col:
264
+ for v in matches[price_col].tolist():
265
+ num = extract_number_from_price(v)
266
+ if num is not None:
267
+ parsed_prices.append(num)
268
+ if parsed_prices:
269
+ try:
270
+ avg_price = statistics.mean(parsed_prices)
271
+ except Exception:
272
+ avg_price = None
273
+
274
+ if avg_price is not None:
275
+ result = f"The average market price of {crop_name} in {district}, {state} is ₹{avg_price:.2f} per quintal."
276
+ else:
277
+ # If price_col absent or non-numeric, fallback to your previous text but mention CSV found
278
+ result = f"Market data found for {crop_name} in {district}, {state} but numeric price values were not available."
279
 
280
  return {
281
  "success": True,
 
283
  "data": matches.to_dict(orient="records")
284
  }
285
 
286
+ # fallback to previous mock behavior (keeps your logic)
287
  return {
288
  "success": False,
289
  "result": f"No market price data found for {crop_name} in {district}, {state}."
290
  }
291
 
292
+ @app.post("/api/scheme-eligibility")
293
  async def scheme_eligibility_endpoint(
294
  request: Request,
295
  x_retell_signature: str = Header(None, alias="X-Retell-Signature")
 
297
  """Handle scheme eligibility function call from Retell.ai"""
298
  request_body = await request.body()
299
  retell_request = json.loads(request_body.decode('utf-8'))
300
+
301
  # Extract arguments
302
  farmer_category = retell_request["args"].get("farmer_category", "")
303
  land_size = retell_request["args"].get("land_size", 0)
304
  state = retell_request["args"].get("state", "")
305
  crop_type = retell_request["args"].get("crop_type", "")
306
+
307
  try:
308
  eligible_schemes = []
309
+
310
+ # Search government schemes CSV and apply simple relevance heuristics
311
  if not csv_data['government_schemes'].empty:
312
+ eligible_schemes = get_schemes_from_csv(farmer_category, land_size, state, crop_type)
313
+
314
+ # Add default schemes if no CSV data or as fallback
 
 
 
 
 
 
 
 
 
 
 
315
  if not eligible_schemes:
316
  # PM-KISAN eligibility
317
+ if land_size and float(land_size) > 0:
318
  eligible_schemes.append({
319
  "scheme": "PM-KISAN",
320
  "benefit": "₹6,000 per year in 3 installments",
321
+ "description": "Direct income support to landholding farmer families.",
322
+ "eligibility": "All landholding farmer families.",
323
+ "process": "Apply online at pmkisan.gov.in or visit nearest CSC",
324
+ "contact": "https://pmkisan.gov.in/"
325
  })
326
+
327
  # Crop Insurance
328
  eligible_schemes.append({
329
  "scheme": "Pradhan Mantri Fasal Bima Yojana",
330
  "benefit": "Comprehensive crop insurance coverage",
331
+ "description": "Crop insurance against natural calamities, pests, and diseases.",
332
+ "eligibility": "All farmers in notified crops/areas",
333
+ "process": "Contact your nearest bank, CSC or PMFBY portal",
334
+ "contact": "https://pmfby.gov.in/"
335
  })
336
+
337
  # State-specific schemes
338
+ if state and state.lower() == "punjab":
339
  eligible_schemes.append({
340
  "scheme": "Punjab Crop Diversification Scheme",
341
  "benefit": "₹17,500 per hectare for diversification",
342
+ "process": "Contact District Agriculture Officer",
343
+ "contact": ""
344
  })
345
+
346
+ # Format response for voice (limit to first 3 items, keep original style)
347
  if eligible_schemes:
348
  schemes_text = f"You are eligible for {len(eligible_schemes)} government schemes: "
349
  for i, scheme in enumerate(eligible_schemes[:3]): # Limit to first 3 for voice response
350
+ contact_info = f" Apply through {scheme.get('process','Contact local agriculture office')}" if scheme.get('process') else ""
351
  if scheme.get('contact'):
352
+ contact_info += f" or contact {scheme.get('contact')}"
353
+ schemes_text += f"{i+1}. {scheme.get('scheme','N/A')} - {scheme.get('benefit', scheme.get('description','N/A'))}.{contact_info}. "
354
+
355
  if len(eligible_schemes) > 3:
356
  schemes_text += f"And {len(eligible_schemes)-3} more schemes available."
357
  else:
358
  schemes_text = "I couldn't find specific schemes for your profile. Please contact your local agriculture department for personalized advice."
359
+
360
  return {
361
  "result": schemes_text,
362
  "eligible_schemes": eligible_schemes
363
  }
364
+
365
  except Exception as e:
366
  return {
367
  "result": "I'm having trouble accessing scheme information right now. Please contact your local agriculture officer or visit the nearest CSC for scheme details.",
 
370
 
371
  @app.post("/api/weather-advisory")
372
  async def weather_advisory(request: dict):
373
+ # Keep your request shape usage intact
374
  city = request.get("query", {}).get("location", "").strip()
375
 
376
  temperature, humidity, description, pressure = get_weather(city)
 
410
 
411
  @app.post("/api/crop-advisory")
412
  async def crop_advisory_endpoint(
413
+ request: Request,
414
  x_retell_signature: str = Header(None, alias="X-Retell-Signature")
415
  ):
416
  """Handle crop advisory function call from Retell.ai"""
417
  request_body = await request.body()
418
  retell_request = json.loads(request_body.decode('utf-8'))
419
+
420
  crop_name = retell_request["args"].get("crop_name", "")
421
  growth_stage = retell_request["args"].get("growth_stage", "")
422
  issue_type = retell_request["args"].get("issue_type", "general")
423
  state = retell_request["args"].get("state", "")
424
+
425
  try:
426
  advisory = None
427
  contact_info = ""
428
+
429
  # Search crop advisory CSV
430
  if not csv_data['crop_advisory'].empty:
431
  search_terms = {}
 
434
  crop_matches = csv_data['crop_advisory'][
435
  csv_data['crop_advisory']['crop'].str.contains(crop_name, case=False, na=False)
436
  ]
437
+
438
  if not crop_matches.empty:
439
  crop_info = crop_matches.iloc[0]
440
+
441
  # Build advisory based on available data
442
  advisory_parts = []
443
+
444
  if issue_type == "general":
445
  if pd.notna(crop_info.get('sowing_time')):
446
  advisory_parts.append(f"Sowing time: {crop_info['sowing_time']}")
 
448
  advisory_parts.append(f"Recommended fertilizer: {crop_info['fertilizer']}")
449
  if pd.notna(crop_info.get('season')):
450
  advisory_parts.append(f"Best season: {crop_info['season']}")
451
+
452
  # Check for specific issues
453
  if pd.notna(crop_info.get('common_issues')) and pd.notna(crop_info.get('solution')):
454
  if issue_type in ['pest', 'disease'] or issue_type == 'general':
455
  advisory_parts.append(f"For {crop_info['common_issues']}: {crop_info['solution']}")
456
+
457
  if advisory_parts:
458
  advisory = f"For {crop_name}: " + ". ".join(advisory_parts)
459
+
460
  # Search contact info CSV
461
  if not csv_data['contact_info'].empty and state:
462
  # Search by state
463
  contact_matches = csv_data['contact_info'][
464
  csv_data['contact_info']['state'].str.contains(state, case=False, na=False)
465
  ]
466
+
467
  if not contact_matches.empty:
468
  contact_match = contact_matches.iloc[0]
469
  contact_parts = []
470
+
471
  if pd.notna(contact_match.get('agriculture_officer')):
472
  contact_parts.append(f"Agriculture Officer at {contact_match['agriculture_officer']}")
473
+
474
  if pd.notna(contact_match.get('kvk_contact')):
475
  contact_parts.append(f"KVK at {contact_match['kvk_contact']}")
476
+
477
  if pd.notna(contact_match.get('kisan_call_center')):
478
  contact_parts.append(f"Kisan Call Center at {contact_match['kisan_call_center']}")
479
+
480
  if contact_parts:
481
  contact_info = f"For detailed advice in {state}, contact: " + " or ".join(contact_parts) + "."
482
+
483
  # Fallback advisory
484
  if not advisory:
485
  if crop_name.lower() == "wheat" and issue_type == "pest":
 
488
  advisory = "For rice disease management: If you see brown spots on leaves, it might be blast disease. Apply Tricyclazole 75% WP at 0.6g per liter. Ensure proper drainage."
489
  else:
490
  advisory = f"For {crop_name} at {growth_stage} stage: Monitor crop regularly, maintain proper spacing, apply fertilizers as per soil test recommendations."
491
+
492
  if not contact_info:
493
  contact_info = f"For detailed advice, contact your local Krishi Vigyan Kendra or Agriculture Officer in {state}. You can also call the Kisan Call Centre at 1800-1801-551."
494
+
495
  result_text = f"{advisory} {contact_info}"
496
+
497
  return {
498
  "result": result_text,
499
  "recommendations": advisory,
500
  "contact_info": contact_info
501
  }
502
+
503
  except Exception as e:
504
  return {
505
  "result": f"I couldn't provide specific advice for {crop_name} right now. Please contact your local agriculture extension officer for crop-specific guidance.",
 
522
  @app.get("/health")
523
  async def health_check():
524
  return {
525
+ "status": "healthy",
526
  "service": "Krishi Mitra API",
527
  "csv_files_loaded": {key: len(df) for key, df in csv_data.items()}
528
  }
 
533
  "message": "Krishi Mitra API is running!",
534
  "endpoints": [
535
  "/api/market-prices",
536
+ "/api/scheme-eligibility",
537
  "/api/weather-advisory",
538
  "/api/crop-advisory",
539
  "/api/csv-status",
 
543
 
544
  if __name__ == "__main__":
545
  import uvicorn
546
+ uvicorn.run(app, host="0.0.0.0", port=7860)