Spaces:
Sleeping
Sleeping
File size: 10,033 Bytes
ee8289e | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 | """
Pydantic Models for Agricultural Chatbot
=========================================
Structured data models matching the Developer Specification.
"""
from pydantic import BaseModel, Field
from typing import Optional, List, Dict, Any
from datetime import datetime
# =============================================================================
# VEGETATION INDICES MODELS
# =============================================================================
class IndexValue(BaseModel):
"""Single vegetation index with value and interpretation."""
current: Optional[float] = None
trend_7d: Optional[float] = None
trend_30d: Optional[float] = None
min: Optional[float] = None
max: Optional[float] = None
interpretation: Optional[str] = None
zone_values: Optional[Dict[str, float]] = None # NW, NE, SW, SE
class VegetationIndices(BaseModel):
"""Complete vegetation indices data."""
NDVI: Optional[IndexValue] = None
EVI: Optional[IndexValue] = None
NDRE: Optional[IndexValue] = None
RECI: Optional[IndexValue] = None
NDWI: Optional[IndexValue] = None
SMI: Optional[IndexValue] = None
PSRI: Optional[IndexValue] = None
PRI: Optional[IndexValue] = None
MCARI: Optional[IndexValue] = None
SOMI: Optional[IndexValue] = None
SFI: Optional[IndexValue] = None
# =============================================================================
# SAR DATA MODELS
# =============================================================================
class SARBands(BaseModel):
"""SAR band data from Sentinel-1."""
VV: Optional[float] = None
VH: Optional[float] = None
VV_VH_ratio: Optional[float] = None
VV_trend: Optional[str] = None # "increasing", "decreasing", "stable"
VH_trend: Optional[str] = None
interpretation: Optional[str] = None
# =============================================================================
# CLUSTERING/STRESS MODELS
# =============================================================================
class ClusterStats(BaseModel):
"""Statistics for a stress cluster."""
cluster_id: int
num_patches: int
percentage: float # % of field
stress_score_mean: float
stress_score_std: Optional[float] = None
dominant_location: Optional[str] = None # "northeast", "southwest", etc.
spectral_signature: Optional[Dict[str, float]] = None
class AnomalyPatch(BaseModel):
"""Detected anomaly patch."""
patch_id: int
location: Optional[str] = None
anomaly_type: str # "spectral_outlier", "temporal_anomaly", etc.
stress_score: float
coordinates: Optional[List[float]] = None
class ClusteringData(BaseModel):
"""All clustering and anomaly data."""
clusters: List[ClusterStats] = []
stressed_clusters: List[ClusterStats] = []
anomalies_detected: int = 0
anomaly_patches: List[AnomalyPatch] = []
# =============================================================================
# WEATHER MODELS
# =============================================================================
class WeatherData(BaseModel):
"""Weather information."""
avg_temp_max: Optional[float] = None
avg_temp_min: Optional[float] = None
heat_stress_days: Optional[int] = None
total_precipitation_mm: Optional[float] = None
avg_humidity: Optional[float] = None
consecutive_dry_days: Optional[int] = None
heat_stress: bool = False
drought_stress: bool = False
class WeatherForecast(BaseModel):
"""Weather forecast data."""
rain_expected: bool = False
rain_mm: Optional[float] = None
temperature_trend: Optional[str] = None
# =============================================================================
# FARMER CONTEXT MODELS
# =============================================================================
class FarmerActions(BaseModel):
"""Farmer's recent actions."""
last_irrigation: Optional[str] = None # ISO date
days_since_irrigation: Optional[int] = None
last_fertilizer: Optional[str] = None
days_since_fertilizer: Optional[int] = None
last_spraying: Optional[str] = None
notes: List[str] = []
class FarmerProfile(BaseModel):
"""Farmer profile information."""
role: Optional[str] = None
years_farming: Optional[int] = None
irrigation_method: Optional[str] = None
farming_goal: Optional[str] = None
# =============================================================================
# PRIORITY CONTEXT MODELS
# =============================================================================
class Priority1Context(BaseModel):
"""Primary evidence - most diagnostic data."""
NDVI: Optional[Dict] = None
EVI: Optional[Dict] = None
NDRE: Optional[Dict] = None
RECI: Optional[Dict] = None
temporal_trends: Optional[Dict] = None
class Priority2Context(BaseModel):
"""Supporting evidence - clusters, anomalies."""
clustering: Optional[Dict] = None
anomalies: Optional[Dict] = None
PSRI: Optional[Dict] = None
class Priority3Context(BaseModel):
"""Causal factors - weather, soil, bands."""
weather: Optional[Dict] = None
SMI: Optional[Dict] = None
band_values: Optional[Dict] = None
class Priority4Context(BaseModel):
"""Validation - SAR, previous analysis, farmer actions."""
SAR: Optional[Dict] = None
previous_analysis: Optional[Dict] = None
farmer_actions: Optional[Dict] = None
# =============================================================================
# REASONING STAGE MODELS
# =============================================================================
class ClaimStage(BaseModel):
"""Stage 3A: Initial claim output."""
initial_claim: str
hypothesis: str
evidence_cited: List[str] = []
confidence: float
uncertainties: List[str] = []
class ValidateStage(BaseModel):
"""Stage 3B: Validation output."""
validation_result: str # "confirmed", "weakened", "neutral"
confidence_updated: float
spatial_notes: Optional[str] = None
new_evidence_summary: Optional[str] = None
class ContradictStage(BaseModel):
"""Stage 3C: Contradiction output."""
contradiction_found: bool
contradicting_evidence: List[str] = []
alternative_hypothesis: Optional[str] = None
alternative_confidence: float = 0.0
reasoning: Optional[str] = None
class ConfirmStage(BaseModel):
"""Stage 3D: Confirmation output."""
final_diagnosis: str
confidence: float
causal_chain: Optional[str] = None
root_cause: Optional[str] = None
symptoms: List[str] = []
evidence_summary: Optional[Dict[str, List[str]]] = None
recommendation: Optional[str] = None
# =============================================================================
# REASONING TRACE MODELS
# =============================================================================
class StageTrace(BaseModel):
"""Trace for a single reasoning stage."""
hypothesis: Optional[str] = None
result: Optional[str] = None
found: Optional[bool] = None
alternative: Optional[str] = None
final: Optional[str] = None
confidence: float
context_used: List[str]
class ReasoningTrace(BaseModel):
"""Complete reasoning trace for transparency."""
intent_detected: str
intent_confidence: float
sub_intents: List[str]
stages: Dict[str, StageTrace]
causal_chain: Optional[str] = None
evidence_summary: Dict[str, List[str]]
class ContextPriorityUsed(BaseModel):
"""Which context was used at each priority level."""
priority_1: List[str] = []
priority_2: List[str] = []
priority_3: List[str] = []
priority_4: List[str] = []
# =============================================================================
# API REQUEST/RESPONSE MODELS
# =============================================================================
class ChatRequest(BaseModel):
"""Chat request from Flutter app."""
session_id: str
message: str
user_id: Optional[str] = None
field_context: Optional[Dict[str, Any]] = None
field_name: Optional[str] = None # Optional specific field
class ResponseContent(BaseModel):
"""Main response content."""
message: str
confidence: float
diagnosis: Optional[str] = None
class ChatResponse(BaseModel):
"""Full chat response matching spec."""
response: ResponseContent
session_id: str
message_id: str
timestamp: str
reasoning_trace: Optional[ReasoningTrace] = None
context_priority_used: Optional[ContextPriorityUsed] = None
suggested_followups: List[str] = []
# =============================================================================
# FULL SATELLITE CONTEXT
# =============================================================================
class FullSatelliteContext(BaseModel):
"""Complete satellite context for reasoning."""
field_info: Dict[str, Any] = {}
vegetation_indices: Optional[VegetationIndices] = None
sar_bands: Optional[SARBands] = None
clustering: Optional[ClusteringData] = None
weather: Optional[WeatherData] = None
weather_forecast: Optional[WeatherForecast] = None
farmer_actions: Optional[FarmerActions] = None
previous_analysis: Optional[Dict[str, Any]] = None
temporal_trends: Optional[Dict[str, Any]] = None
band_values: Optional[Dict[str, float]] = None
# =============================================================================
# HELPER FUNCTIONS
# =============================================================================
def create_empty_response(session_id: str, message: str = "Unable to process") -> ChatResponse:
"""Create a fallback empty response."""
return ChatResponse(
response=ResponseContent(
message=message,
confidence=0.0,
diagnosis=None
),
session_id=session_id,
message_id="",
timestamp=datetime.now().isoformat(),
reasoning_trace=None,
context_priority_used=None,
suggested_followups=[]
)
|