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=[]
    )