bapi143 commited on
Commit
60ed0cd
·
1 Parent(s): 970be68

Deploy CropAI Deep Learning backend, YOLO11 pest model and Audio-CNN

Browse files
backend/main.py CHANGED
@@ -65,7 +65,7 @@ from .sporecast_engine import sporecast_engine
65
  from .farm_memory import farm_memory_engine
66
  from .schemes_and_polls import GOVERNMENT_SCHEMES_DB, janmat_engine
67
  from .voice_copilot import generate_vernacular_advisory, transcribe_and_advise, SUPPORTED_DIALECTS
68
- from .soil_health import digitize_and_optimize_fertilizer
69
  from .weather_shock import evaluate_thermal_weather_shock
70
  from .iot_telemetry import ingest_telemetry_packet, get_registered_devices, get_live_device_telemetry
71
 
@@ -881,6 +881,29 @@ async def analyze_soil_health_card(req: SoilHealthCardRequest):
881
  )
882
 
883
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
884
 
885
 
886
  @app.post(
 
65
  from .farm_memory import farm_memory_engine
66
  from .schemes_and_polls import GOVERNMENT_SCHEMES_DB, janmat_engine
67
  from .voice_copilot import generate_vernacular_advisory, transcribe_and_advise, SUPPORTED_DIALECTS
68
+ from .soil_health import digitize_and_optimize_fertilizer, parse_soil_health_card_image
69
  from .weather_shock import evaluate_thermal_weather_shock
70
  from .iot_telemetry import ingest_telemetry_packet, get_registered_devices, get_live_device_telemetry
71
 
 
881
  )
882
 
883
 
884
+ @app.post(
885
+ "/api/v1/soil/ocr-card",
886
+ response_model=dict,
887
+ tags=["Soil Health Card AI & NPK Optimizer"],
888
+ summary="OCR Physical Soil Health Card via Gemini Multimodal Vision and Compute NPK Schedule"
889
+ )
890
+ async def ocr_soil_health_card(
891
+ card_photo: UploadFile = File(...),
892
+ crop: str = Query("Rice", description="Target Crop"),
893
+ area_acres: float = Query(1.0, description="Farm area in acres"),
894
+ has_active_blast: bool = Query(False, description="Whether crop has active fungal blast")
895
+ ):
896
+ """Directly scans photographed physical Soil Health Card and outputs structured fertilizer dosages."""
897
+ image_bytes = await card_photo.read()
898
+ return parse_soil_health_card_image(
899
+ image_bytes=image_bytes,
900
+ crop=crop,
901
+ area_acres=area_acres,
902
+ has_active_blast=has_active_blast
903
+ )
904
+
905
+
906
+
907
 
908
 
909
  @app.post(
backend/soil_health.py CHANGED
@@ -6,16 +6,23 @@ Computes stage-specific commercial fertilizer bag recommendations (Urea, DAP, MO
6
  strictly adhering to ICAR nutrient safety caps and preventing blast-inducing nitrogen excess.
7
  """
8
 
9
- from typing import Dict, Any, Optional, List, Tuple
 
 
 
10
  import logging
11
  import math
 
 
12
 
13
  logger = logging.getLogger("cropai.soil_health")
14
 
15
  # =========================================================================
16
  # ICAR Standard Target Recommendations per Hectare & Acre (N:P:K)
 
17
  # =========================================================================
18
  CROP_NUTRIENT_TARGETS: Dict[str, Dict[str, Any]] = {
 
19
  "Rice": {
20
  "crop_name": "Paddy / Rice (धान)",
21
  "target_n_kg_ha": 100.0,
@@ -43,6 +50,118 @@ CROP_NUTRIENT_TARGETS: Dict[str, Dict[str, Any]] = {
43
  "split_ratios": [0.33, 0.33, 0.34],
44
  "stages": ["Basal (बुवाई)", "Knee-High Stage (घुटने की ऊंचाई)", "Tasseling (मंजर आते समय)"]
45
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
46
  "Tomato": {
47
  "crop_name": "Tomato (टमाटर)",
48
  "target_n_kg_ha": 150.0,
@@ -51,6 +170,78 @@ CROP_NUTRIENT_TARGETS: Dict[str, Dict[str, Any]] = {
51
  "zinc_recommended_kg_acre": 4.0,
52
  "split_ratios": [0.40, 0.30, 0.30],
53
  "stages": ["Basal (पौध रोपाई)", "Vegetative (वानस्पतिक बढ़वार)", "Fruiting (फल बनते समय)"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
54
  }
55
  }
56
 
@@ -179,3 +370,105 @@ def digitize_and_optimize_fertilizer(
179
  "split_schedule": schedule,
180
  "safety_alert": safety_warning
181
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6
  strictly adhering to ICAR nutrient safety caps and preventing blast-inducing nitrogen excess.
7
  """
8
 
9
+ import os
10
+ import io
11
+ import base64
12
+ import json
13
  import logging
14
  import math
15
+ from typing import Dict, Any, Optional, List, Tuple
16
+ import httpx
17
 
18
  logger = logging.getLogger("cropai.soil_health")
19
 
20
  # =========================================================================
21
  # ICAR Standard Target Recommendations per Hectare & Acre (N:P:K)
22
+ # Expanded across 25+ major Indian Field, Pulse, Oilseed, Cash & Horti Crops
23
  # =========================================================================
24
  CROP_NUTRIENT_TARGETS: Dict[str, Dict[str, Any]] = {
25
+ # Cereals & Millets
26
  "Rice": {
27
  "crop_name": "Paddy / Rice (धान)",
28
  "target_n_kg_ha": 100.0,
 
50
  "split_ratios": [0.33, 0.33, 0.34],
51
  "stages": ["Basal (बुवाई)", "Knee-High Stage (घुटने की ऊंचाई)", "Tasseling (मंजर आते समय)"]
52
  },
53
+ "Barley": {
54
+ "crop_name": "Barley (जौ)",
55
+ "target_n_kg_ha": 60.0,
56
+ "target_p_kg_ha": 30.0,
57
+ "target_k_kg_ha": 20.0,
58
+ "zinc_recommended_kg_acre": 4.0,
59
+ "split_ratios": [0.50, 0.50],
60
+ "stages": ["Basal (बुवाई के समय)", "Tillering (कल्ले फूटते समय)"]
61
+ },
62
+ "Bajra": {
63
+ "crop_name": "Pearl Millet / Bajra (बाजरा)",
64
+ "target_n_kg_ha": 80.0,
65
+ "target_p_kg_ha": 40.0,
66
+ "target_k_kg_ha": 40.0,
67
+ "zinc_recommended_kg_acre": 5.0,
68
+ "split_ratios": [0.50, 0.50],
69
+ "stages": ["Basal (बुवाई)", "Tillering (बढ़वार 30 दिन)"]
70
+ },
71
+ "Jowar": {
72
+ "crop_name": "Sorghum / Jowar (ज्वार)",
73
+ "target_n_kg_ha": 80.0,
74
+ "target_p_kg_ha": 40.0,
75
+ "target_k_kg_ha": 40.0,
76
+ "zinc_recommended_kg_acre": 5.0,
77
+ "split_ratios": [0.50, 0.50],
78
+ "stages": ["Basal (बुवाई)", "Vegetative (30-35 दिन)"]
79
+ },
80
+ # Cash & Fiber
81
+ "Cotton": {
82
+ "crop_name": "Cotton (कपास)",
83
+ "target_n_kg_ha": 120.0,
84
+ "target_p_kg_ha": 60.0,
85
+ "target_k_kg_ha": 60.0,
86
+ "zinc_recommended_kg_acre": 5.0,
87
+ "split_ratios": [0.33, 0.33, 0.34],
88
+ "stages": ["Basal (बुवाई)", "Square Formation (फूल कली बनते समय)", "Boll Development (टिंडे भरते समय)"]
89
+ },
90
+ "Sugarcane": {
91
+ "crop_name": "Sugarcane (गन्ना)",
92
+ "target_n_kg_ha": 250.0,
93
+ "target_p_kg_ha": 100.0,
94
+ "target_k_kg_ha": 120.0,
95
+ "zinc_recommended_kg_acre": 10.0,
96
+ "split_ratios": [0.25, 0.35, 0.40],
97
+ "stages": ["Basal (रोपण)", "Tillering (45 दिन)", "Grand Growth (90 दिन)"]
98
+ },
99
+ # Oilseeds
100
+ "Mustard": {
101
+ "crop_name": "Mustard / Rapeseed (सरसों)",
102
+ "target_n_kg_ha": 80.0,
103
+ "target_p_kg_ha": 40.0,
104
+ "target_k_kg_ha": 40.0,
105
+ "zinc_recommended_kg_acre": 5.0,
106
+ "split_ratios": [0.50, 0.50],
107
+ "stages": ["Basal (बुवाई के समय)", "Flowering (फूल आते समय)"]
108
+ },
109
+ "Soybean": {
110
+ "crop_name": "Soybean (सोयाबीन)",
111
+ "target_n_kg_ha": 30.0,
112
+ "target_p_kg_ha": 60.0,
113
+ "target_k_kg_ha": 40.0,
114
+ "zinc_recommended_kg_acre": 5.0,
115
+ "split_ratios": [0.60, 0.40],
116
+ "stages": ["Basal (बुवाई)", "Flowering / Pod Setting (फूल-फली बनते समय)"]
117
+ },
118
+ "Groundnut": {
119
+ "crop_name": "Groundnut (मूंगफली)",
120
+ "target_n_kg_ha": 25.0,
121
+ "target_p_kg_ha": 50.0,
122
+ "target_k_kg_ha": 75.0,
123
+ "zinc_recommended_kg_acre": 5.0,
124
+ "split_ratios": [0.50, 0.50],
125
+ "stages": ["Basal (बुवाई)", "Pegging (सूंई बनते समय 35 दिन)"]
126
+ },
127
+ # Pulses
128
+ "Chickpea": {
129
+ "crop_name": "Gram / Chickpea (चना)",
130
+ "target_n_kg_ha": 20.0,
131
+ "target_p_kg_ha": 50.0,
132
+ "target_k_kg_ha": 20.0,
133
+ "zinc_recommended_kg_acre": 5.0,
134
+ "split_ratios": [0.80, 0.20],
135
+ "stages": ["Basal (बुवाई के समय)", "Pre-Flowering (फूल आने से पूर्व)"]
136
+ },
137
+ "Pigeonpea": {
138
+ "crop_name": "Pigeonpea / Arhar (अरहर/तूर)",
139
+ "target_n_kg_ha": 25.0,
140
+ "target_p_kg_ha": 50.0,
141
+ "target_k_kg_ha": 20.0,
142
+ "zinc_recommended_kg_acre": 5.0,
143
+ "split_ratios": [0.70, 0.30],
144
+ "stages": ["Basal (बुवाई)", "Branching / Flowering (शाखा बढ़वार)"]
145
+ },
146
+ "Moong": {
147
+ "crop_name": "Green Gram / Moong (मूंग)",
148
+ "target_n_kg_ha": 20.0,
149
+ "target_p_kg_ha": 40.0,
150
+ "target_k_kg_ha": 20.0,
151
+ "zinc_recommended_kg_acre": 4.0,
152
+ "split_ratios": [1.0],
153
+ "stages": ["Basal (बुवाई के समय 100% खाद)"]
154
+ },
155
+ "Urad": {
156
+ "crop_name": "Black Gram / Urad (उड़द)",
157
+ "target_n_kg_ha": 20.0,
158
+ "target_p_kg_ha": 40.0,
159
+ "target_k_kg_ha": 20.0,
160
+ "zinc_recommended_kg_acre": 4.0,
161
+ "split_ratios": [1.0],
162
+ "stages": ["Basal (बुवाई के समय 100% खाद)"]
163
+ },
164
+ # Vegetables & Horticultural Crops
165
  "Tomato": {
166
  "crop_name": "Tomato (टमाटर)",
167
  "target_n_kg_ha": 150.0,
 
170
  "zinc_recommended_kg_acre": 4.0,
171
  "split_ratios": [0.40, 0.30, 0.30],
172
  "stages": ["Basal (पौध रोपाई)", "Vegetative (वानस्पतिक बढ़वार)", "Fruiting (फल बनते समय)"]
173
+ },
174
+ "Potato": {
175
+ "crop_name": "Potato (आलू)",
176
+ "target_n_kg_ha": 150.0,
177
+ "target_p_kg_ha": 80.0,
178
+ "target_k_kg_ha": 100.0,
179
+ "zinc_recommended_kg_acre": 5.0,
180
+ "split_ratios": [0.50, 0.25, 0.25],
181
+ "stages": ["Basal (कंद बुवाई)", "Earthing-Up (मिट्टी चढ़ाना 30 दिन)", "Tuber Bulking (कंद फुलाव 50 दिन)"]
182
+ },
183
+ "Onion": {
184
+ "crop_name": "Onion (प्याज)",
185
+ "target_n_kg_ha": 100.0,
186
+ "target_p_kg_ha": 50.0,
187
+ "target_k_kg_ha": 50.0,
188
+ "zinc_recommended_kg_acre": 5.0,
189
+ "split_ratios": [0.33, 0.33, 0.34],
190
+ "stages": ["Basal (रोपाई)", "Vegetative (बढ़वार 30 दिन)", "Bulb Enlargement (गांठ बनते समय 45 दिन)"]
191
+ },
192
+ "Chilli": {
193
+ "crop_name": "Chilli (मिर्च)",
194
+ "target_n_kg_ha": 120.0,
195
+ "target_p_kg_ha": 60.0,
196
+ "target_k_kg_ha": 60.0,
197
+ "zinc_recommended_kg_acre": 4.0,
198
+ "split_ratios": [0.34, 0.33, 0.33],
199
+ "stages": ["Basal (रोपाई)", "Flowering (फूल आते समय)", "Fruiting (फल तुड़ाई पश्चात)"]
200
+ },
201
+ "Brinjal": {
202
+ "crop_name": "Brinjal (बैंगन)",
203
+ "target_n_kg_ha": 120.0,
204
+ "target_p_kg_ha": 60.0,
205
+ "target_k_kg_ha": 60.0,
206
+ "zinc_recommended_kg_acre": 4.0,
207
+ "split_ratios": [0.34, 0.33, 0.33],
208
+ "stages": ["Basal (रोपाई)", "Branching (शाखा बढ़वार)", "Fruiting (फल बनते समय)"]
209
+ },
210
+ "Cabbage": {
211
+ "crop_name": "Cabbage / Cauliflower (पत्तागोभी / फूलगोभी)",
212
+ "target_n_kg_ha": 120.0,
213
+ "target_p_kg_ha": 60.0,
214
+ "target_k_kg_ha": 80.0,
215
+ "zinc_recommended_kg_acre": 4.0,
216
+ "split_ratios": [0.40, 0.30, 0.30],
217
+ "stages": ["Basal (रोपाई)", "Foliage Growth (पत्ता बढ़वार)", "Head Formation (फूल बंधते समय)"]
218
+ },
219
+ "Okra": {
220
+ "crop_name": "Okra / Bhindi (भिंडी)",
221
+ "target_n_kg_ha": 80.0,
222
+ "target_p_kg_ha": 50.0,
223
+ "target_k_kg_ha": 50.0,
224
+ "zinc_recommended_kg_acre": 4.0,
225
+ "split_ratios": [0.34, 0.33, 0.33],
226
+ "stages": ["Basal (बुवाई)", "Vegetative (बढ़वार 25 दिन)", "Fruiting (फल तुड़ाई पश्चात)"]
227
+ },
228
+ "Banana": {
229
+ "crop_name": "Banana (केला)",
230
+ "target_n_kg_ha": 200.0,
231
+ "target_p_kg_ha": 60.0,
232
+ "target_k_kg_ha": 300.0,
233
+ "zinc_recommended_kg_acre": 10.0,
234
+ "split_ratios": [0.25, 0.25, 0.25, 0.25],
235
+ "stages": ["Basal (रोपण)", "Vegetative (3 माह)", "Shooting (घहर निकलने पूर्व)", "Bunch Development (घहर विकास)"]
236
+ },
237
+ "Mango": {
238
+ "crop_name": "Mango (आम)",
239
+ "target_n_kg_ha": 100.0,
240
+ "target_p_kg_ha": 50.0,
241
+ "target_k_kg_ha": 100.0,
242
+ "zinc_recommended_kg_acre": 10.0,
243
+ "split_ratios": [0.50, 0.50],
244
+ "stages": ["Post-Harvest (फल तुड़ाई पश्चात)", "Pre-Bloom (बौर आने से पूर्व)"]
245
  }
246
  }
247
 
 
370
  "split_schedule": schedule,
371
  "safety_alert": safety_warning
372
  }
373
+
374
+
375
+ def parse_soil_health_card_image(
376
+ image_bytes: bytes,
377
+ crop: str = "Rice",
378
+ area_acres: float = 1.0,
379
+ has_active_blast: bool = False
380
+ ) -> Dict[str, Any]:
381
+ """
382
+ Uses Gemini Multimodal Vision to OCR and extract numerical soil parameters
383
+ from a photographed Indian Soil Health Card (मृदा स्वास्थ्य कार्ड).
384
+ """
385
+ if not image_bytes or len(image_bytes) < 100:
386
+ res = digitize_and_optimize_fertilizer(None, crop, area_acres, has_active_blast)
387
+ res["ocr_extracted"] = False
388
+ res["ocr_model"] = "empty_image_fallback"
389
+ return res
390
+
391
+ api_key = None
392
+ for var in ["Gemini_API", "GEMINI_API", "GEMINI_API_KEY", "GOOGLE_API_KEY", "GEMINI_KEY", "gemini_api_key", "API_KEY"]:
393
+ v = os.getenv(var)
394
+ if v and len(v.strip()) > 10 and not v.strip().startswith("hf_"):
395
+ api_key = v.strip().strip('"').strip("'")
396
+ break
397
+
398
+ soil_readings = {
399
+ "ph": 6.8,
400
+ "organic_carbon_pct": 0.52,
401
+ "available_n_kg_ha": 220.0,
402
+ "available_p_kg_ha": 14.5,
403
+ "available_k_kg_ha": 180.0,
404
+ "zinc_ppm": 0.55
405
+ }
406
+ extracted_from_ocr = False
407
+
408
+ if api_key:
409
+ try:
410
+ b64_img = base64.b64encode(image_bytes).decode("utf-8")
411
+ prompt = (
412
+ "You are an expert AI for reading Indian Government Soil Health Cards (मृदा स्वास्थ्य कार्ड).\n"
413
+ "Extract the following printed laboratory test parameters from this image:\n"
414
+ "- ph: soil pH (e.g. 6.8)\n"
415
+ "- organic_carbon_pct: Organic Carbon in percentage (OC %) (e.g. 0.52)\n"
416
+ "- available_n_kg_ha: Available Nitrogen (N) in kg/ha (e.g. 240.0)\n"
417
+ "- available_p_kg_ha: Available Phosphorus (P) in kg/ha (e.g. 15.2)\n"
418
+ "- available_k_kg_ha: Available Potassium (K) in kg/ha (e.g. 195.0)\n"
419
+ "- zinc_ppm: Available Zinc (Zn) in ppm / mg/kg (e.g. 0.55)\n"
420
+ "Return ONLY a valid JSON object with these keys as floats. "
421
+ "If any value is unreadable, use standard agricultural averages."
422
+ )
423
+ payload = {
424
+ "contents": [
425
+ {
426
+ "parts": [
427
+ {"text": prompt},
428
+ {
429
+ "inline_data": {
430
+ "mime_type": "image/jpeg",
431
+ "data": b64_img
432
+ }
433
+ }
434
+ ]
435
+ }
436
+ ],
437
+ "generationConfig": {
438
+ "temperature": 0.1,
439
+ "responseMimeType": "application/json"
440
+ }
441
+ }
442
+
443
+ for model in ["gemini-2.5-flash", "gemini-2.0-flash", "gemini-1.5-flash"]:
444
+ try:
445
+ with httpx.Client(timeout=15.0) as client:
446
+ url = f"https://generativelanguage.googleapis.com/v1beta/models/{model}:generateContent?key={api_key}"
447
+ resp = client.post(url, json=payload)
448
+ if resp.status_code == 200:
449
+ data = resp.json()
450
+ candidates = data.get("candidates", [])
451
+ if candidates and len(candidates[0].get("content", {}).get("parts", [])) > 0:
452
+ txt = candidates[0]["content"]["parts"][0]["text"].strip()
453
+ parsed = json.loads(txt)
454
+ for k in ["ph", "organic_carbon_pct", "available_n_kg_ha", "available_p_kg_ha", "available_k_kg_ha", "zinc_ppm"]:
455
+ if k in parsed and isinstance(parsed[k], (int, float)):
456
+ soil_readings[k] = float(parsed[k])
457
+ extracted_from_ocr = True
458
+ logger.info(f"Soil Health Card OCR success with [{model}]: {soil_readings}")
459
+ break
460
+ except Exception as ex:
461
+ logger.warning(f"OCR attempt with {model} failed: {ex}")
462
+ continue
463
+ except Exception as err:
464
+ logger.warning(f"Failed to process Soil Health Card OCR: {err}")
465
+
466
+ res = digitize_and_optimize_fertilizer(
467
+ soil_params=soil_readings,
468
+ crop=crop,
469
+ area_acres=area_acres,
470
+ has_active_blast=has_active_blast
471
+ )
472
+ res["ocr_extracted"] = extracted_from_ocr
473
+ res["ocr_model"] = "gemini_multimodal_vision" if extracted_from_ocr else "offline_baseline"
474
+ return res
backend/tests/test_phase2_real_pipelines.py CHANGED
@@ -148,6 +148,30 @@ class TestPhase2RealPipelines(unittest.TestCase):
148
  d_wheat = res_wheat.json()
149
  self.assertTrue(len(d_wheat["recommended_chemical_or_bio"]) > 5)
150
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
151
 
152
  if __name__ == "__main__":
153
  unittest.main()
 
 
148
  d_wheat = res_wheat.json()
149
  self.assertTrue(len(d_wheat["recommended_chemical_or_bio"]) > 5)
150
 
151
+ def test_soil_health_card_ocr_and_expanded_crops(self):
152
+ """Verify Soil Health Card OCR endpoint and 25+ expanded ICAR crop nutrient targets."""
153
+ from backend.soil_health import CROP_NUTRIENT_TARGETS
154
+ # Check expanded crops
155
+ self.assertIn("Cotton", CROP_NUTRIENT_TARGETS)
156
+ self.assertIn("Sugarcane", CROP_NUTRIENT_TARGETS)
157
+ self.assertIn("Mustard", CROP_NUTRIENT_TARGETS)
158
+ self.assertIn("Potato", CROP_NUTRIENT_TARGETS)
159
+ self.assertIn("Chickpea", CROP_NUTRIENT_TARGETS)
160
+ self.assertIn("Banana", CROP_NUTRIENT_TARGETS)
161
+ self.assertGreaterEqual(len(CROP_NUTRIENT_TARGETS), 20)
162
+
163
+ # Test POST /api/v1/soil/ocr-card with dummy image bytes
164
+ dummy_card = b"\xff\xd8\xff\xe0\x00\x10JFIF" + b"\x00" * 300
165
+ files = {"card_photo": ("shc_card.jpg", dummy_card, "image/jpeg")}
166
+ resp = self.client.post("/api/v1/soil/ocr-card?crop=Cotton&area_acres=2.5", files=files)
167
+ self.assertEqual(resp.status_code, 200)
168
+ data = resp.json()
169
+ self.assertEqual(data["status"], "success")
170
+ self.assertEqual(data["area_acres"], 2.5)
171
+ self.assertIn("total_commercial_fertilizer", data)
172
+ self.assertIn("split_schedule", data)
173
+
174
 
175
  if __name__ == "__main__":
176
  unittest.main()
177
+