Aniket2006 commited on
Commit
1f7cc04
·
1 Parent(s): 2ca56f0

Add all indices timeseries + weather + detailed_analysis to LLM

Browse files
Files changed (1) hide show
  1. app.py +67 -12
app.py CHANGED
@@ -135,7 +135,8 @@ class HeatmapRequest(BaseModel):
135
  gaussian_sigma: float = 1.5
136
  show_field_boundary: bool = True
137
  overlay_mode: bool = False # If True, generate clean heatmap for Google Maps overlay
138
- time_series_data: Optional[Dict[str, Any]] = None # Historical + forecast time series from Flutter cache
 
139
 
140
 
141
  class HeatmapResponse(BaseModel):
@@ -160,6 +161,7 @@ class HeatmapResponse(BaseModel):
160
  # LLM analysis (for risk metrics)
161
  level: Optional[str] = None
162
  analysis: Optional[str] = None
 
163
  stress_score: Optional[float] = None
164
  cluster_distribution: Optional[dict] = None
165
  recommendations: Optional[List[str]] = None
@@ -348,8 +350,9 @@ def generate_heatmap_image(data: np.ndarray, index_type: str, gaussian_sigma: fl
348
  # ============================================================================
349
  # LLM ANALYSIS (for risk metrics)
350
  # ============================================================================
351
- def run_llm_analysis(metric: str, stress_context: dict, indices_data: dict) -> dict:
352
- """Call Groq LLM with full context from stress detection."""
 
353
  try:
354
  from groq import Groq
355
 
@@ -361,33 +364,75 @@ def run_llm_analysis(metric: str, stress_context: dict, indices_data: dict) -> d
361
  # Format stress context
362
  stress_text = format_stress_context(stress_context)
363
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
364
  # Create targeted prompt based on metric
365
- prompt = f"""
366
- CROP STRESS ANALYSIS REQUEST
367
 
368
  {stress_text}
 
 
369
 
370
  METRIC TO ANALYZE: {metric.upper().replace('_', ' ')}
371
 
372
- Based on the stress detection results above, provide analysis for {metric}.
373
 
374
  Respond with ONLY a valid JSON object (no markdown):
375
  {{
376
  "level": "Low" or "Moderate" or "High",
377
  "analysis": "4-5 words describing the current state",
 
378
  "temporal_trend": "Improving" or "Stable" or "Worsening",
379
- "recommendations": ["action 1", "action 2"]
380
  }}
381
  """
382
 
383
  chat_completion = client.chat.completions.create(
384
  messages=[
385
- {"role": "system", "content": "You are an expert agricultural AI. Respond with valid JSON only."},
386
  {"role": "user", "content": prompt}
387
  ],
388
  model=GROQ_MODEL,
389
  temperature=0.7,
390
- max_tokens=1024,
391
  )
392
 
393
  response_text = chat_completion.choices[0].message.content.strip()
@@ -404,7 +449,12 @@ Respond with ONLY a valid JSON object (no markdown):
404
 
405
  except Exception as e:
406
  logger.error(f"LLM analysis failed: {e}")
407
- return {"level": "Moderate", "analysis": "Analysis unavailable", "recommendations": ["Manual inspection recommended"]}
 
 
 
 
 
408
 
409
  # ============================================================================
410
  # API ENDPOINTS
@@ -585,8 +635,12 @@ async def generate_heatmap(request: HeatmapRequest):
585
  index_func = INDEX_FUNCTIONS[primary_index]
586
  index_data = index_func(img_data)
587
 
588
- # Run LLM analysis
589
- llm_result = run_llm_analysis(request.metric, stress_context, {'primary': index_data})
 
 
 
 
590
 
591
  log_step(6, 6, "Generating heatmap")
592
 
@@ -628,6 +682,7 @@ async def generate_heatmap(request: HeatmapRequest):
628
  colorbar_base64=colorbar_b64,
629
  level=llm_result.get('level', 'Unknown'),
630
  analysis=llm_result.get('analysis', ''),
 
631
  stress_score=float(stress_results['stress_scores'].mean()),
632
  cluster_distribution=cluster_dist,
633
  recommendations=llm_result.get('recommendations', [])
 
135
  gaussian_sigma: float = 1.5
136
  show_field_boundary: bool = True
137
  overlay_mode: bool = False # If True, generate clean heatmap for Google Maps overlay
138
+ time_series_data: Optional[Dict[str, Any]] = None # Historical + forecast time series for ALL indices
139
+ weather_data: Optional[Dict[str, Any]] = None # Weather data (temperature, humidity, precipitation)
140
 
141
 
142
  class HeatmapResponse(BaseModel):
 
161
  # LLM analysis (for risk metrics)
162
  level: Optional[str] = None
163
  analysis: Optional[str] = None
164
+ detailed_analysis: Optional[str] = None # Detailed reasoning for timeseries + stress patterns
165
  stress_score: Optional[float] = None
166
  cluster_distribution: Optional[dict] = None
167
  recommendations: Optional[List[str]] = None
 
350
  # ============================================================================
351
  # LLM ANALYSIS (for risk metrics)
352
  # ============================================================================
353
+ def run_llm_analysis(metric: str, stress_context: dict, indices_data: dict,
354
+ time_series_data: dict = None, weather_data: dict = None) -> dict:
355
+ """Call Groq LLM with full context from stress detection, timeseries, and weather."""
356
  try:
357
  from groq import Groq
358
 
 
364
  # Format stress context
365
  stress_text = format_stress_context(stress_context)
366
 
367
+ # Format time series data for all indices
368
+ ts_text = ""
369
+ if time_series_data:
370
+ ts_text = "\n\nTIME SERIES DATA (ALL INDICES - HISTORICAL + FORECAST):\n"
371
+ ts_text += "=" * 50 + "\n"
372
+ for index_name, ts_data in time_series_data.items():
373
+ ts_text += f"\n{index_name}:\n"
374
+ # Historical
375
+ if ts_data.get('historical'):
376
+ hist = ts_data['historical']
377
+ if len(hist) > 0:
378
+ first_val = hist[0].get('value', 0) if isinstance(hist[0], dict) else 0
379
+ last_val = hist[-1].get('value', 0) if isinstance(hist[-1], dict) else 0
380
+ ts_text += f" Historical ({len(hist)} points): from {first_val:.4f} to {last_val:.4f} (change: {last_val-first_val:+.4f})\n"
381
+ # Forecast
382
+ if ts_data.get('forecast'):
383
+ fcast = ts_data['forecast']
384
+ if len(fcast) > 0:
385
+ first_val = fcast[0].get('value', 0) if isinstance(fcast[0], dict) else 0
386
+ last_val = fcast[-1].get('value', 0) if isinstance(fcast[-1], dict) else 0
387
+ ts_text += f" Forecast ({len(fcast)} days): from {first_val:.4f} to {last_val:.4f} (predicted: {last_val-first_val:+.4f})\n"
388
+
389
+ # Format weather data
390
+ weather_text = ""
391
+ if weather_data:
392
+ weather_text = "\n\nWEATHER CONDITIONS:\n"
393
+ weather_text += "=" * 30 + "\n"
394
+ if 'temperature' in weather_data:
395
+ weather_text += f"- Temperature: {weather_data['temperature']}°C\n"
396
+ if 'humidity' in weather_data:
397
+ weather_text += f"- Humidity: {weather_data['humidity']}%\n"
398
+ if 'precipitation' in weather_data:
399
+ weather_text += f"- Precipitation: {weather_data['precipitation']} mm\n"
400
+ if 'wind_speed' in weather_data:
401
+ weather_text += f"- Wind Speed: {weather_data['wind_speed']} km/h\n"
402
+ if 'conditions' in weather_data:
403
+ weather_text += f"- Conditions: {weather_data['conditions']}\n"
404
+ if 'forecast' in weather_data:
405
+ weather_text += f"- Forecast: {weather_data['forecast']}\n"
406
+
407
  # Create targeted prompt based on metric
408
+ prompt = f"""CROP STRESS ANALYSIS REQUEST
 
409
 
410
  {stress_text}
411
+ {ts_text}
412
+ {weather_text}
413
 
414
  METRIC TO ANALYZE: {metric.upper().replace('_', ' ')}
415
 
416
+ Based on the stress detection results, time series trends, and weather conditions above, provide analysis for {metric}.
417
 
418
  Respond with ONLY a valid JSON object (no markdown):
419
  {{
420
  "level": "Low" or "Moderate" or "High",
421
  "analysis": "4-5 words describing the current state",
422
+ "detailed_analysis": "Two detailed sentences: First sentence explaining the reasoning behind time series index changes (what caused the trends). Second sentence explaining the observed stress patterns in the field over time and their likely causes.",
423
  "temporal_trend": "Improving" or "Stable" or "Worsening",
424
+ "recommendations": ["action 1", "action 2", "action 3"]
425
  }}
426
  """
427
 
428
  chat_completion = client.chat.completions.create(
429
  messages=[
430
+ {"role": "system", "content": "You are an expert agricultural AI. Provide detailed, data-driven analysis. Respond with valid JSON only."},
431
  {"role": "user", "content": prompt}
432
  ],
433
  model=GROQ_MODEL,
434
  temperature=0.7,
435
+ max_tokens=1500,
436
  )
437
 
438
  response_text = chat_completion.choices[0].message.content.strip()
 
449
 
450
  except Exception as e:
451
  logger.error(f"LLM analysis failed: {e}")
452
+ return {
453
+ "level": "Moderate",
454
+ "analysis": "Analysis unavailable",
455
+ "detailed_analysis": "Unable to generate detailed analysis due to processing error. Please try refreshing.",
456
+ "recommendations": ["Manual inspection recommended"]
457
+ }
458
 
459
  # ============================================================================
460
  # API ENDPOINTS
 
635
  index_func = INDEX_FUNCTIONS[primary_index]
636
  index_data = index_func(img_data)
637
 
638
+ # Run LLM analysis with timeseries and weather context
639
+ llm_result = run_llm_analysis(
640
+ request.metric, stress_context, {'primary': index_data},
641
+ time_series_data=request.time_series_data,
642
+ weather_data=request.weather_data
643
+ )
644
 
645
  log_step(6, 6, "Generating heatmap")
646
 
 
682
  colorbar_base64=colorbar_b64,
683
  level=llm_result.get('level', 'Unknown'),
684
  analysis=llm_result.get('analysis', ''),
685
+ detailed_analysis=llm_result.get('detailed_analysis', ''),
686
  stress_score=float(stress_results['stress_scores'].mean()),
687
  cluster_distribution=cluster_dist,
688
  recommendations=llm_result.get('recommendations', [])