httpsAkayush commited on
Commit
7169e00
·
1 Parent(s): 6d869b0

Add application file

Browse files
Files changed (3) hide show
  1. Dockerfile +16 -0
  2. app.py +692 -0
  3. requirements.txt +14 -0
Dockerfile ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Read the doc: https://huggingface.co/docs/hub/spaces-sdks-docker
2
+ # you will also find guides on how best to write your Dockerfile
3
+
4
+ FROM python:3.9
5
+
6
+ RUN useradd -m -u 1000 user
7
+ USER user
8
+ ENV PATH="/home/user/.local/bin:$PATH"
9
+
10
+ WORKDIR /app
11
+
12
+ COPY --chown=user ./requirements.txt requirements.txt
13
+ RUN pip install --no-cache-dir --upgrade -r requirements.txt
14
+
15
+ COPY --chown=user . /app
16
+ CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
app.py ADDED
@@ -0,0 +1,692 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # app.py - Production-ready Hugging Face Spaces deployment
2
+ from fastapi import FastAPI, HTTPException
3
+ from fastapi.middleware.cors import CORSMiddleware
4
+ from fastapi.staticfiles import StaticFiles
5
+ from fastapi.responses import HTMLResponse
6
+ from pydantic import BaseModel, Field
7
+ import torch
8
+ import numpy as np
9
+ import pandas as pd
10
+ import json
11
+ import gc
12
+ import os
13
+ import logging
14
+ from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig
15
+ from peft import PeftModel
16
+ from huggingface_hub import hf_hub_download
17
+ from typing import List, Dict, Any, Optional
18
+ import uvicorn
19
+
20
+ # Setup logging
21
+ logging.basicConfig(level=logging.INFO)
22
+ logger = logging.getLogger(__name__)
23
+
24
+ # Clear cache
25
+ torch.cuda.empty_cache()
26
+ gc.collect()
27
+
28
+ PARAMS = ["N","P","K","temperature","pH","rainfall","humidity"]
29
+
30
+ # Acceptable ranges
31
+ IGNORE_RANGES = {
32
+ "N": (-10, 10),
33
+ "P": (-10, 10),
34
+ "K": (-10, 10),
35
+ "temperature": (-0.2, 0.2),
36
+ "pH": (-0.2, 0.2),
37
+ "humidity": (-5, 5),
38
+ "rainfall": (-15, 15)
39
+ }
40
+
41
+ def evaluate_problems_and_diffs(required: np.ndarray, given: np.ndarray):
42
+ problems = []
43
+ diff_dict = {}
44
+
45
+ for i, param in enumerate(PARAMS):
46
+ diff = given[i] - required[i]
47
+ low, high = IGNORE_RANGES[param]
48
+ if not (low <= diff <= high):
49
+ status = "deficiency" if diff < 0 else "excess"
50
+ problems.append(f"{param}_{status}")
51
+ diff_dict[param] = diff
52
+ return problems, diff_dict
53
+
54
+ class AgriculturalAdvisor:
55
+ def __init__(self):
56
+ self.model = None
57
+ self.tokenizer = None
58
+ self.df1 = None
59
+ self.df2 = None
60
+ self.template = None
61
+ self.model_loaded = False
62
+ self.data_loaded = False
63
+
64
+ try:
65
+ self.load_data()
66
+ self.load_model()
67
+ logger.info("✅ Agricultural Advisor initialized successfully!")
68
+ except Exception as e:
69
+ logger.error(f"❌ Failed to initialize: {str(e)}")
70
+
71
+ def load_data(self):
72
+ """Load datasets with fallback options"""
73
+ try:
74
+ # Try to load datasets
75
+ if os.path.exists('Crop_recommendation.csv'):
76
+ self.df1 = pd.read_csv('Crop_recommendation.csv')
77
+ logger.info("✅ Crop_recommendation.csv loaded")
78
+ else:
79
+ # Create fallback dataset
80
+ logger.warning("⚠️ Crop_recommendation.csv not found, creating fallback")
81
+ self.df1 = self.create_fallback_dataset()
82
+
83
+ if os.path.exists('sensor_Crop_Dataset.csv'):
84
+ self.df2 = pd.read_csv('sensor_Crop_Dataset.csv')
85
+ self.df2.rename(columns={"crop": "label"}, inplace=True)
86
+ self.df2 = self.df2.drop(["soil","variety"], axis=1, errors='ignore')
87
+ logger.info("✅ sensor_Crop_Dataset.csv loaded")
88
+ else:
89
+ logger.warning("⚠️ sensor_Crop_Dataset.csv not found")
90
+ self.df2 = pd.DataFrame()
91
+
92
+ # Load template
93
+ if os.path.exists("crop_template.json"):
94
+ with open("crop_template.json") as f:
95
+ self.template = json.load(f)
96
+ logger.info("✅ Template loaded")
97
+ else:
98
+ logger.warning("⚠️ Template not found, creating fallback")
99
+ self.template = self.create_fallback_template()
100
+
101
+ self.data_loaded = True
102
+
103
+ except Exception as e:
104
+ logger.error(f"❌ Error loading data: {str(e)}")
105
+ # Create minimal fallbacks
106
+ self.df1 = self.create_fallback_dataset()
107
+ self.df2 = pd.DataFrame()
108
+ self.template = self.create_fallback_template()
109
+ self.data_loaded = True
110
+
111
+ def create_fallback_dataset(self):
112
+ """Create minimal dataset for demo"""
113
+ return pd.DataFrame({
114
+ 'N': [80, 75, 85, 70, 90],
115
+ 'P': [40, 35, 45, 30, 50],
116
+ 'K': [67, 60, 70, 55, 75],
117
+ 'temperature': [25, 27, 23, 30, 20],
118
+ 'pH': [7.0, 6.8, 7.2, 6.5, 7.5],
119
+ 'rainfall': [200, 180, 220, 150, 250],
120
+ 'humidity': [60, 65, 55, 70, 50],
121
+ 'label': ['rice', 'wheat', 'maize', 'cotton', 'sugarcane']
122
+ })
123
+
124
+ def create_fallback_template(self):
125
+ """Create minimal template"""
126
+ return {
127
+ "rice": {
128
+ "N_deficiency": {
129
+ "Description": "Nitrogen deficiency causes yellowing of older leaves and stunted growth",
130
+ "Homemade/Natural Remedies": "Apply compost, farmyard manure, or green manures",
131
+ "Commercial Suggestions": "Apply urea fertilizer in split doses",
132
+ "Cultural Practices": "Use alternate wetting and drying irrigation",
133
+ "Crop-Specific Notes": "Critical during tillering stage"
134
+ },
135
+ "P_deficiency": {
136
+ "Description": "Phosphorus deficiency causes dark green to purplish leaves",
137
+ "Homemade/Natural Remedies": "Apply bone meal or rock phosphate",
138
+ "Commercial Suggestions": "Apply superphosphate as basal dose",
139
+ "Cultural Practices": "Maintain soil pH near neutral",
140
+ "Crop-Specific Notes": "Important for root and flower development"
141
+ }
142
+ },
143
+ "wheat": {
144
+ "N_deficiency": {
145
+ "Description": "Nitrogen deficiency in wheat causes chlorosis and poor tillering",
146
+ "Homemade/Natural Remedies": "Apply compost and green manures",
147
+ "Commercial Suggestions": "Apply urea in 2-3 splits",
148
+ "Cultural Practices": "Ensure proper drainage",
149
+ "Crop-Specific Notes": "Critical at tillering and grain filling"
150
+ }
151
+ }
152
+ }
153
+
154
+ def load_model(self):
155
+ """Load model with error handling"""
156
+ try:
157
+ # Model configuration
158
+ base_model = "unsloth/gemma-3-1b-it"
159
+ adapter_path = "./unified_crop_model" # Local path
160
+
161
+ # Check if running on CPU or GPU
162
+ device = "cuda" if torch.cuda.is_available() else "cpu"
163
+ logger.info(f"🖥️ Using device: {device}")
164
+
165
+ # Configure quantization only for GPU
166
+ if device == "cuda":
167
+ bnb_config = BitsAndBytesConfig(
168
+ load_in_4bit=True,
169
+ bnb_4bit_quant_type="nf4",
170
+ bnb_4bit_use_double_quant=True,
171
+ bnb_4bit_compute_dtype="bfloat16"
172
+ )
173
+
174
+ self.model = AutoModelForCausalLM.from_pretrained(
175
+ base_model,
176
+ quantization_config=bnb_config,
177
+ device_map="auto",
178
+ trust_remote_code=True
179
+ )
180
+ else:
181
+ # CPU inference
182
+ self.model = AutoModelForCausalLM.from_pretrained(
183
+ base_model,
184
+ torch_dtype=torch.float32,
185
+ trust_remote_code=True
186
+ )
187
+
188
+ # Try to load LoRA adapter
189
+ if os.path.exists(adapter_path):
190
+ try:
191
+ self.model = PeftModel.from_pretrained(
192
+ self.model,
193
+ adapter_path,
194
+ device_map="auto" if device == "cuda" else None
195
+ )
196
+ logger.info("✅ LoRA adapter loaded")
197
+ except Exception as e:
198
+ logger.warning(f"⚠️ Could not load LoRA adapter: {str(e)}")
199
+ logger.info("📝 Using base model without fine-tuning")
200
+ else:
201
+ logger.warning("⚠️ LoRA adapter not found, using base model")
202
+
203
+ # Load tokenizer
204
+ tokenizer_path = adapter_path if os.path.exists(adapter_path) else base_model
205
+ self.tokenizer = AutoTokenizer.from_pretrained(tokenizer_path, trust_remote_code=True)
206
+
207
+ # Set pad token if not exists
208
+ if self.tokenizer.pad_token is None:
209
+ self.tokenizer.pad_token = self.tokenizer.eos_token
210
+
211
+ self.model_loaded = True
212
+ logger.info("✅ Model loaded successfully!")
213
+
214
+ except Exception as e:
215
+ logger.error(f"❌ Failed to load model: {str(e)}")
216
+ self.model_loaded = False
217
+
218
+ def analyze_crop_conditions(self, crop, N, P, K, temp, humidity, pH, rainfall):
219
+ """Analyze crop conditions with comprehensive error handling"""
220
+
221
+ if not self.data_loaded:
222
+ return "❌ Data not loaded properly. Please refresh the page."
223
+
224
+ if not self.model_loaded:
225
+ return "⚠️ Model not loaded. Providing basic analysis without AI recommendations."
226
+
227
+ try:
228
+ given = [N, P, K, temp, pH, rainfall, humidity]
229
+
230
+ # Find crop in datasets
231
+ if crop in self.df1['label'].values:
232
+ df = self.df1[self.df1['label']==crop]
233
+ elif not self.df2.empty and crop in self.df2['label'].values:
234
+ df = self.df2[self.df2['label']==crop]
235
+ else:
236
+ available_crops = list(self.df1['label'].unique())
237
+ return f"❌ Crop '{crop}' not found in database. Available crops: {', '.join(available_crops)}"
238
+
239
+ df_values = df.drop('label', axis=1)
240
+ df_array = np.array(df_values)
241
+
242
+ # MSE computation
243
+ mse_list = []
244
+ for row in df_array:
245
+ mse = np.mean((np.array(row) - np.array(given))**2)
246
+ mse_list.append(mse)
247
+ best_index = np.argmin(mse_list)
248
+ required = df_array[best_index].tolist()
249
+
250
+ problems, diff_dict = evaluate_problems_and_diffs(required, given)
251
+
252
+ if not problems:
253
+ return "✅ **Great!** No significant issues detected. Current conditions are within acceptable ranges for optimal growth."
254
+
255
+
256
+ # ==============================
257
+ # Detailed Default Template
258
+ # ==============================
259
+ default_template = {
260
+ "general": {
261
+ "nitrogen_deficiency": {
262
+ "Description": "Leaves appear pale or yellowish; growth may be slow.",
263
+ "Homemade/Natural Remedies": "Apply composted manure, cow dung, or green manure.",
264
+ "Commercial Suggestions": "Use balanced NPK fertilizer with higher nitrogen content.",
265
+ "Cultural Practices": "Rotate crops; avoid over-harvesting nitrogen-rich leaves.",
266
+ "Crop-Specific Notes": "Sensitive crops like leafy greens show symptoms faster."
267
+ },
268
+ "nitrogen_excess": {
269
+ "Description": "Excessive vegetative growth; flowering/fruiting may be delayed.",
270
+ "Homemade/Natural Remedies": "Limit nitrogen-rich organic inputs like fresh manure.",
271
+ "Commercial Suggestions": "Reduce nitrogen fertilizer; maintain balanced NPK ratios.",
272
+ "Cultural Practices": "Prune excess growth; monitor soil nutrient levels.",
273
+ "Crop-Specific Notes": "Fruit crops may produce fewer fruits if over-fertilized with nitrogen."
274
+ },
275
+ "phosphorus_deficiency": {
276
+ "Description": "Stunted growth; leaves may show dark green/purplish coloration.",
277
+ "Homemade/Natural Remedies": "Use bone meal, rock phosphate, or composted organic matter.",
278
+ "Commercial Suggestions": "Apply phosphorus-rich fertilizers like single superphosphate (SSP).",
279
+ "Cultural Practices": "Maintain soil pH around 6–7; avoid acidic soils.",
280
+ "Crop-Specific Notes": "Root crops may be most affected due to poor root development."
281
+ },
282
+ "phosphorus_excess": {
283
+ "Description": "Can interfere with micronutrient absorption (Zn, Fe).",
284
+ "Homemade/Natural Remedies": "Avoid adding extra phosphorus-containing amendments.",
285
+ "Commercial Suggestions": "Use balanced fertilizers; avoid repeated high P applications.",
286
+ "Cultural Practices": "Rotate crops to prevent phosphorus build-up.",
287
+ "Crop-Specific Notes": "Cereals are more sensitive to high phosphorus than legumes."
288
+ },
289
+ "potassium_deficiency": {
290
+ "Description": "Leaf edges turn brown, scorching; weak stems.",
291
+ "Homemade/Natural Remedies": "Add wood ash or composted banana peels.",
292
+ "Commercial Suggestions": "Apply potassium sulfate or muriate of potash.",
293
+ "Cultural Practices": "Ensure proper irrigation; avoid water stress.",
294
+ "Crop-Specific Notes": "Potato and tomato show clear leaf-edge symptoms."
295
+ },
296
+ "potassium_excess": {
297
+ "Description": "May reduce magnesium and calcium uptake.",
298
+ "Homemade/Natural Remedies": "Avoid excessive potassium-containing composts.",
299
+ "Commercial Suggestions": "Balance with magnesium/calcium fertilizers.",
300
+ "Cultural Practices": "Test soil regularly for K levels.",
301
+ "Crop-Specific Notes": "Leafy vegetables may show interveinal chlorosis if Mg is low."
302
+ },
303
+ "iron_deficiency": {
304
+ "Description": "Young leaves turn yellow with green veins (chlorosis).",
305
+ "Homemade/Natural Remedies": "Foliar spray with iron sulfate or iron chelates.",
306
+ "Commercial Suggestions": "Apply chelated iron to soil or foliage.",
307
+ "Cultural Practices": "Maintain soil pH below 7.5 for better uptake.",
308
+ "Crop-Specific Notes": "Fruit trees like apple and citrus are sensitive."
309
+ },
310
+ "iron_excess": {
311
+ "Description": "Can cause nutrient imbalance and toxicity.",
312
+ "Homemade/Natural Remedies": "Avoid iron-rich amendments in high-Fe soils.",
313
+ "Commercial Suggestions": "Test soil before adding iron fertilizers.",
314
+ "Cultural Practices": "Improve drainage in high-iron soils.",
315
+ "Crop-Specific Notes": "Rice paddies may tolerate slightly higher iron naturally."
316
+ },
317
+ "water_deficiency": {
318
+ "Description": "Wilting, leaf curl, and reduced yield.",
319
+ "Homemade/Natural Remedies": "Mulch soil to retain moisture; use organic matter.",
320
+ "Commercial Suggestions": "Implement drip or sprinkler irrigation.",
321
+ "Cultural Practices": "Schedule watering based on crop stage and weather.",
322
+ "Crop-Specific Notes": "Tomatoes and peppers are highly sensitive to water stress."
323
+ },
324
+ "water_excess": {
325
+ "Description": "Root rot, yellowing leaves, poor aeration.",
326
+ "Homemade/Natural Remedies": "Improve soil drainage using sand or organic matter.",
327
+ "Commercial Suggestions": "Raised beds; controlled irrigation.",
328
+ "Cultural Practices": "Avoid waterlogging; monitor soil moisture regularly.",
329
+ "Crop-Specific Notes": "Root crops like carrots and potatoes are prone to rot."
330
+ },
331
+ "pH_deficiency": {
332
+ "Description": "Soil too acidic (<5.5); stunted growth.",
333
+ "Homemade/Natural Remedies": "Apply wood ash or crushed eggshells.",
334
+ "Commercial Suggestions": "Use agricultural lime to raise pH.",
335
+ "Cultural Practices": "Test soil pH regularly; avoid acid-forming fertilizers.",
336
+ "Crop-Specific Notes": "Legumes prefer slightly acidic to neutral pH."
337
+ },
338
+ "pH_excess": {
339
+ "Description": "Soil too alkaline (>8); micronutrient deficiencies.",
340
+ "Homemade/Natural Remedies": "Incorporate organic matter like compost.",
341
+ "Commercial Suggestions": "Apply elemental sulfur to lower soil pH.",
342
+ "Cultural Practices": "Select tolerant crop varieties.",
343
+ "Crop-Specific Notes": "Tomatoes and spinach are sensitive to high pH."
344
+ },
345
+ "temperature_stress": {
346
+ "Description": "Too high or too low temperature affects growth and yield.",
347
+ "Homemade/Natural Remedies": "Shade nets or mulching to regulate temperature.",
348
+ "Commercial Suggestions": "Use protective covers or greenhouses.",
349
+ "Cultural Practices": "Plant at optimal seasonal windows.",
350
+ "Crop-Specific Notes": "Tomato, cucumber, and leafy greens are sensitive."
351
+ },
352
+ "pest_disease_issue": {
353
+ "Description": "Presence of pests or disease symptoms.",
354
+ "Homemade/Natural Remedies": "Neem oil, garlic extract, or organic sprays.",
355
+ "Commercial Suggestions": "Use approved pesticides or fungicides; follow IPM.",
356
+ "Cultural Practices": "Sanitation, crop rotation, resistant varieties.",
357
+ "Crop-Specific Notes": "Leafy vegetables and solanaceous crops need regular monitoring."
358
+ }
359
+ }
360
+ }
361
+
362
+
363
+ # selected issues dictionary
364
+ selected = {}
365
+
366
+ # Step 1: Check crop-specific template first
367
+ for prob in problems:
368
+ if prob in self.template.get(crop, {}):
369
+ selected[prob] = self.template[crop][prob]
370
+
371
+ # Step 2: If nothing found, use default template
372
+ if not selected:
373
+ for prob in problems:
374
+ if prob in default_template.get("general", {}):
375
+ selected[prob] = default_template["general"][prob]
376
+
377
+ # Step 3: If still nothing found, fallback message
378
+ if not selected:
379
+ issues_text = ', '.join(problems)
380
+ return f"⚠️ **Issues detected:** {issues_text}\n\n❗ No recommendations available even in the default template."
381
+
382
+ # Step 4: Build formatted output
383
+ context = f"Crop: {crop}\n"
384
+ for issue, details in selected.items():
385
+ context += f"\n## {issue.replace('_',' ').title()}\n"
386
+ for k, v in details.items():
387
+ context += f"💠 {k}: {v}\n"
388
+
389
+
390
+ # Generate AI recommendations if model available
391
+ ai_response = ""
392
+ if self.model_loaded:
393
+ try:
394
+ ai_response = self.generate_ai_recommendations(context)
395
+ except Exception as e:
396
+ logger.error(f"AI generation failed: {str(e)}")
397
+ ai_response = "AI recommendations temporarily unavailable."
398
+
399
+ # Format response
400
+ issues_summary = f"📊 **Issues Detected:** {', '.join(problems)}\n\n"
401
+ diff_summary = f"📈 **Parameter Differences:** {', '.join([f'{k}: {v:+.1f}' for k, v in diff_dict.items()])}\n\n"
402
+
403
+ # template_info = "📋 **Available Information:**\n"
404
+ # for issue, details in selected.items():
405
+ # template_info += f"\n**{issue.replace('_', ' ').title()}:**\n"
406
+ # template_info += f"• Description: {details.get('Description', 'N/A')}\n"
407
+ # template_info += f"• Natural Remedies: {details.get('Homemade/Natural Remedies', 'N/A')}\n"
408
+ # template_info += f"• Commercial Solutions: {details.get('Commercial Suggestions', 'N/A')}\n\n"
409
+
410
+ ai_section = f"🤖 **AI Recommendations:**\n{ai_response}\n" if ai_response else ""
411
+
412
+ return f"{issues_summary}{ai_section}"
413
+
414
+ except Exception as e:
415
+ logger.error(f"Analysis error: {str(e)}")
416
+ return f"❌ Error during analysis: {str(e)}"
417
+
418
+ def generate_ai_recommendations(self, context):
419
+ """Generate AI recommendations with proper error handling"""
420
+ try:
421
+ messages = [
422
+ {
423
+ "role": "system",
424
+ "content": [{"type": "text", "text": "You are a helpful agronomy assistant. Based on soil conditions, suggest remedies for the detected crop issues."}]
425
+ },
426
+ {
427
+ "role": "user",
428
+ "content": [{"type": "text", "text": f"Here is reference info:\n{context}\n\nPlease give a concise recommendation."}]
429
+ }
430
+ ]
431
+
432
+ inputs = self.tokenizer.apply_chat_template(
433
+ messages,
434
+ add_generation_prompt=True,
435
+ return_tensors="pt",
436
+ tokenize=True,
437
+ return_dict=True,
438
+ ).to(self.model.device)
439
+
440
+ with torch.no_grad():
441
+ output = self.model.generate(
442
+ **inputs,
443
+ max_new_tokens=200,
444
+ temperature=0.7,
445
+ top_p=0.9,
446
+ pad_token_id=self.tokenizer.eos_token_id,
447
+ do_sample=True
448
+ )
449
+
450
+ # Decode response
451
+ response = self.tokenizer.decode(
452
+ output[0][inputs['input_ids'].shape[1]:],
453
+ skip_special_tokens=True
454
+ )
455
+
456
+ return response.strip()
457
+
458
+ except Exception as e:
459
+ logger.error(f"AI generation error: {str(e)}")
460
+ return f"AI recommendations temporarily unavailable due to: {str(e)}"
461
+
462
+ # Initialize advisor with error handling
463
+ logger.info("🚀 Initializing Agricultural Advisor...")
464
+ try:
465
+ advisor = AgriculturalAdvisor()
466
+ initialization_status = "✅ System Ready"
467
+ crops_available = list(advisor.df1['label'].unique())
468
+ except Exception as e:
469
+ logger.error(f"❌ Failed to initialize advisor: {str(e)}")
470
+ advisor = None
471
+ initialization_status = f"❌ Initialization Failed: {str(e)}"
472
+ crops_available = ["rice", "wheat", "maize"] # Fallback
473
+
474
+ # def get_crop_recommendations(crop, N, P, K, temperature, humidity, pH, rainfall):
475
+ # """Gradio interface function"""
476
+ # if advisor is None:
477
+ # return f"❌ System not initialized properly. Status: {initialization_status}"
478
+
479
+ # try:
480
+ # return advisor.analyze_crop_conditions(
481
+ # crop, N, P, K, temperature, humidity, pH, rainfall
482
+ # )
483
+ # except Exception as e:
484
+ # logger.error(f"Interface error: {str(e)}")
485
+ # return f"❌ Error processing request: {str(e)}"
486
+
487
+ ## Pydantic models for API
488
+ class CropAnalysisRequest(BaseModel):
489
+ crop: str = Field(..., description="Name of the crop to analyze")
490
+ N: float = Field(..., ge=0, le=300, description="Nitrogen content (kg/ha)")
491
+ P: float = Field(..., ge=0, le=150, description="Phosphorus content (kg/ha)")
492
+ K: float = Field(..., ge=0, le=200, description="Potassium content (kg/ha)")
493
+ temperature: float = Field(..., ge=0, le=50, description="Temperature (°C)")
494
+ humidity: float = Field(..., ge=0, le=100, description="Humidity (%)")
495
+ pH: float = Field(..., ge=3, le=10, description="Soil pH level")
496
+ rainfall: float = Field(..., ge=0, le=2000, description="Rainfall (mm)")
497
+
498
+ class CropAnalysisResponse(BaseModel):
499
+ success: bool
500
+ message: str
501
+ recommendations: str
502
+ status: str
503
+
504
+ class SystemStatusResponse(BaseModel):
505
+ status: str
506
+ model_loaded: bool
507
+ data_loaded: bool
508
+ available_crops: List[str]
509
+
510
+ # Initialize advisor with error handling
511
+ logger.info("🚀 Initializing Agricultural Advisor...")
512
+ try:
513
+ advisor = AgriculturalAdvisor()
514
+ initialization_status = "✅ System Ready"
515
+ crops_available = list(advisor.df1['label'].unique())
516
+ except Exception as e:
517
+ logger.error(f"❌ Failed to initialize advisor: {str(e)}")
518
+ advisor = None
519
+ initialization_status = f"❌ Initialization Failed: {str(e)}"
520
+ crops_available = ["rice", "wheat", "maize"] # Fallback
521
+
522
+ # FastAPI app
523
+ app = FastAPI(
524
+ title="🌾 Agricultural Advisor API",
525
+ description="AI-powered agricultural advisor for crop recommendations based on soil and climate conditions",
526
+ version="1.0.0"
527
+ )
528
+
529
+ # CORS middleware
530
+ app.add_middleware(
531
+ CORSMiddleware,
532
+ allow_origins=["*"], # Configure as needed for production
533
+ allow_credentials=True,
534
+ allow_methods=["*"],
535
+ allow_headers=["*"],
536
+ )
537
+
538
+ @app.get("/", response_class=HTMLResponse)
539
+ async def root():
540
+ """Serve basic HTML interface"""
541
+ html_content = """
542
+ <!DOCTYPE html>
543
+ <html>
544
+ <head>
545
+ <title>🌾 Agricultural Advisor API</title>
546
+ <style>
547
+ body { font-family: Arial, sans-serif; margin: 40px; background: #f5f5f5; }
548
+ .container { max-width: 800px; margin: 0 auto; background: white; padding: 30px; border-radius: 10px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }
549
+ h1 { color: #2e7d32; text-align: center; }
550
+ .endpoint { background: #f8f9fa; padding: 15px; margin: 10px 0; border-radius: 5px; border-left: 4px solid #4caf50; }
551
+ .method { color: #1976d2; font-weight: bold; }
552
+ .example { background: #e8f5e8; padding: 10px; margin: 10px 0; border-radius: 5px; font-family: monospace; }
553
+ pre { overflow-x: auto; }
554
+ </style>
555
+ </head>
556
+ <body>
557
+ <div class="container">
558
+ <h1>🌾 Agricultural Advisor API</h1>
559
+ <p>AI-powered agricultural advisor for crop recommendations based on soil and climate conditions.</p>
560
+
561
+ <h2>📋 Available Endpoints</h2>
562
+
563
+ <div class="endpoint">
564
+ <span class="method">GET</span> <strong>/status</strong>
565
+ <p>Get system status and available crops</p>
566
+ </div>
567
+
568
+ <div class="endpoint">
569
+ <span class="method">POST</span> <strong>/analyze</strong>
570
+ <p>Analyze crop conditions and get recommendations</p>
571
+ <div class="example">
572
+ <strong>Example Request:</strong>
573
+ <pre>{
574
+ "crop": "rice",
575
+ "N": 80,
576
+ "P": 40,
577
+ "K": 67,
578
+ "temperature": 25,
579
+ "humidity": 60,
580
+ "pH": 7.0,
581
+ "rainfall": 200
582
+ }</pre>
583
+ </div>
584
+ </div>
585
+
586
+ <div class="endpoint">
587
+ <span class="method">GET</span> <strong>/crops</strong>
588
+ <p>Get list of available crops</p>
589
+ </div>
590
+
591
+ <h2>📖 Documentation</h2>
592
+ <p>Visit <a href="/docs">/docs</a> for interactive API documentation</p>
593
+ <p>Visit <a href="/redoc">/redoc</a> for alternative documentation</p>
594
+
595
+ <h2>🔧 System Status</h2>
596
+ <p><strong>Status:</strong> """ + initialization_status + """</p>
597
+ <p><strong>Available Crops:</strong> """ + ", ".join(crops_available) + """</p>
598
+ </div>
599
+ </body>
600
+ </html>
601
+ """
602
+ return HTMLResponse(content=html_content)
603
+
604
+ @app.get("/status", response_model=SystemStatusResponse)
605
+ async def get_system_status():
606
+ """Get system status"""
607
+ if advisor is None:
608
+ return SystemStatusResponse(
609
+ status=initialization_status,
610
+ model_loaded=False,
611
+ data_loaded=False,
612
+ available_crops=crops_available
613
+ )
614
+
615
+ return SystemStatusResponse(
616
+ status=initialization_status,
617
+ model_loaded=advisor.model_loaded,
618
+ data_loaded=advisor.data_loaded,
619
+ available_crops=crops_available
620
+ )
621
+
622
+ @app.get("/crops")
623
+ async def get_available_crops():
624
+ """Get list of available crops"""
625
+ return {"crops": crops_available}
626
+
627
+ @app.post("/analyze", response_model=CropAnalysisResponse)
628
+ async def analyze_crop(request: CropAnalysisRequest):
629
+ """Analyze crop conditions and provide recommendations"""
630
+
631
+ if advisor is None:
632
+ raise HTTPException(
633
+ status_code=503,
634
+ detail=f"System not initialized properly. Status: {initialization_status}"
635
+ )
636
+
637
+ try:
638
+ # Validate crop
639
+ if request.crop not in crops_available:
640
+ raise HTTPException(
641
+ status_code=400,
642
+ detail=f"Crop '{request.crop}' not available. Available crops: {', '.join(crops_available)}"
643
+ )
644
+
645
+ # Analyze crop conditions using the same method as Gradio version
646
+ recommendations = advisor.analyze_crop_conditions(
647
+ request.crop, request.N, request.P, request.K,
648
+ request.temperature, request.humidity, request.pH, request.rainfall
649
+ )
650
+
651
+ # Determine status based on recommendations
652
+ if "❌" in recommendations:
653
+ status = "error"
654
+ elif "⚠️" in recommendations:
655
+ status = "warning"
656
+ elif "✅" in recommendations:
657
+ status = "success"
658
+ else:
659
+ status = "info"
660
+
661
+ return CropAnalysisResponse(
662
+ success=True,
663
+ message="Analysis completed successfully",
664
+ recommendations=recommendations,
665
+ status=status
666
+ )
667
+
668
+ except HTTPException:
669
+ raise
670
+ except Exception as e:
671
+ logger.error(f"Analysis error: {str(e)}")
672
+ raise HTTPException(
673
+ status_code=500,
674
+ detail=f"Error processing request: {str(e)}"
675
+ )
676
+
677
+ @app.get("/health")
678
+ async def health_check():
679
+ """Health check endpoint"""
680
+ return {
681
+ "status": "healthy",
682
+ "system_status": initialization_status,
683
+ "timestamp": pd.Timestamp.now().isoformat()
684
+ }
685
+
686
+ if __name__ == "__main__":
687
+ uvicorn.run(
688
+ app,
689
+ host="0.0.0.0",
690
+ port=8000,
691
+ log_level="info"
692
+ )
requirements.txt ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ torch==2.4.0
2
+ transformers==4.44.0
3
+ peft==0.12.0
4
+ accelerate==0.33.0
5
+ numpy==1.26.4
6
+ pandas==2.2.2
7
+ gradio==4.42.0
8
+ huggingface-hub==0.24.0
9
+ requests==2.32.0
10
+ packaging==24.0
11
+ tqdm==4.66.0
12
+ # Skip CPU-incompatible packages
13
+ # bitsandbytes
14
+ # unsloth