agroplan-ai-cli 1.0.29 → 1.0.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  {
2
- "cli_version": "1.0.29",
3
- "backend_template_version": "1.0.29",
2
+ "cli_version": "1.0.30",
3
+ "backend_template_version": "1.0.30",
4
4
  "zarc_index_version": "2025-2026-fast-index-v2",
5
5
  "price_index_version": "2025-05-reference-v1",
6
6
  "features": [
@@ -17,7 +17,8 @@
17
17
  "market_profit_validation",
18
18
  "market_profit_confidence_refinement",
19
19
  "market_profit_comparative_evaluation",
20
- "market_profit_experimental_optimizer"
20
+ "market_profit_experimental_optimizer",
21
+ "smart_crop_calendar_engine"
21
22
  ],
22
- "generated_at": "2026-05-09T23:45:00Z"
23
+ "generated_at": "2026-05-10T15:30:00Z"
23
24
  }
@@ -8,6 +8,7 @@ from pydantic import BaseModel
8
8
  from typing import Optional
9
9
  import sys
10
10
  import os
11
+ import uuid
11
12
 
12
13
  # Adiciona o diretório backend ao path para imports
13
14
  sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
@@ -1308,6 +1309,114 @@ def otimizar_lucro_mercado_experimental(
1308
1309
  detail="Erro ao gerar otimização experimental de lucro de mercado."
1309
1310
  )
1310
1311
 
1312
+ @app.post("/planejamento/calendario")
1313
+ def gerar_calendario(request: dict):
1314
+ """
1315
+ Gera calendário agrícola para uma cultura.
1316
+
1317
+ Fase 10.1: Base local para soja, milho e feijão
1318
+ Fase futura: Integração com clima real e replanejamento
1319
+ """
1320
+ try:
1321
+ from core.crop_calendar_engine import gerar_calendario_cultura
1322
+ from core.planning_models import Field, SoilType, Slope, WaterAvailability
1323
+ from datetime import datetime
1324
+
1325
+ # Validar campos obrigatórios
1326
+ if "cultura" not in request:
1327
+ raise HTTPException(status_code=400, detail="Campo 'cultura' é obrigatório")
1328
+ if "planting_date" not in request:
1329
+ raise HTTPException(status_code=400, detail="Campo 'planting_date' é obrigatório")
1330
+ if "field" not in request:
1331
+ raise HTTPException(status_code=400, detail="Campo 'field' é obrigatório")
1332
+
1333
+ # Parsear data de plantio
1334
+ try:
1335
+ planting_date = datetime.fromisoformat(request["planting_date"]).date()
1336
+ except Exception:
1337
+ raise HTTPException(status_code=400, detail="Formato de data inválido. Use ISO 8601 (YYYY-MM-DD)")
1338
+
1339
+ # Criar objeto Field
1340
+ field_data = request["field"]
1341
+ field = Field(
1342
+ id=field_data.get("id", str(uuid.uuid4())),
1343
+ property_id=field_data.get("property_id", "temp"),
1344
+ name=field_data.get("name", "Talhão Temporário"),
1345
+ area_ha=float(field_data.get("area_ha", 10)),
1346
+ soil_type=SoilType(field_data.get("soil_type", "argiloso")),
1347
+ slope=Slope(field_data.get("slope", "plano")),
1348
+ water_availability=WaterAvailability(field_data.get("water_availability", "media"))
1349
+ )
1350
+
1351
+ # Gerar calendário
1352
+ resultado = gerar_calendario_cultura(
1353
+ cultura=request["cultura"],
1354
+ planting_date=planting_date,
1355
+ field=field,
1356
+ crop_plan_id=request.get("crop_plan_id"),
1357
+ weather_context=request.get("weather_context"),
1358
+ zarc_context=request.get("zarc_context")
1359
+ )
1360
+
1361
+ return converter_tipos_python(resultado)
1362
+
1363
+ except HTTPException:
1364
+ raise
1365
+ except Exception as e:
1366
+ import traceback
1367
+ if DEBUG_ERRORS:
1368
+ raise HTTPException(
1369
+ status_code=500,
1370
+ detail={
1371
+ "error": str(e),
1372
+ "traceback": traceback.format_exc()
1373
+ }
1374
+ )
1375
+ else:
1376
+ raise HTTPException(
1377
+ status_code=500,
1378
+ detail="Erro ao gerar calendário agrícola."
1379
+ )
1380
+
1381
+ @app.get("/planejamento/culturas")
1382
+ def listar_culturas():
1383
+ """Lista culturas disponíveis no sistema de planejamento"""
1384
+ try:
1385
+ from core.crop_calendar_engine import get_culturas_disponiveis, get_cultura_info
1386
+
1387
+ culturas = get_culturas_disponiveis()
1388
+
1389
+ return {
1390
+ "total": len(culturas),
1391
+ "culturas": culturas,
1392
+ "detalhes": {
1393
+ cultura: get_cultura_info(cultura)
1394
+ for cultura in culturas
1395
+ }
1396
+ }
1397
+ except Exception as e:
1398
+ raise HTTPException(status_code=500, detail=str(e))
1399
+
1400
+ @app.get("/planejamento/culturas/{cultura}")
1401
+ def obter_cultura_info(cultura: str):
1402
+ """Obtém informações detalhadas de uma cultura"""
1403
+ try:
1404
+ from core.crop_calendar_engine import get_cultura_info
1405
+
1406
+ info = get_cultura_info(cultura)
1407
+
1408
+ if not info:
1409
+ raise HTTPException(
1410
+ status_code=404,
1411
+ detail=f"Cultura '{cultura}' não encontrada"
1412
+ )
1413
+
1414
+ return info
1415
+ except HTTPException:
1416
+ raise
1417
+ except Exception as e:
1418
+ raise HTTPException(status_code=500, detail=str(e))
1419
+
1311
1420
  @app.post("/cache/limpar")
1312
1421
  def limpar_cache(request: Request):
1313
1422
  """Limpa o cache de resultados pesados (protegido por token)"""
@@ -0,0 +1,426 @@
1
+ """
2
+ Engine de Calendário Agrícola
3
+
4
+ Gera calendários de tarefas para culturas baseado em:
5
+ - Ciclo da cultura
6
+ - Data de plantio
7
+ - Características do talhão
8
+ - Contexto climático (opcional)
9
+ - Contexto ZARC (opcional)
10
+
11
+ Fase inicial: Base local para soja, milho e feijão
12
+ Fase futura: Integração com clima real e replanejamento
13
+ """
14
+
15
+ from datetime import date, timedelta
16
+ from typing import Dict, List, Optional
17
+ import uuid
18
+
19
+ from .planning_models import (
20
+ Field, CropCycle, CropPhase, CalendarTask,
21
+ TaskType, TaskPriority, TaskStatus
22
+ )
23
+
24
+
25
+ # Base de Conhecimento Local - Fase 10.1
26
+
27
+ CROP_CYCLES: Dict[str, Dict] = {
28
+ "soja": {
29
+ "cycle_days": 120,
30
+ "phases": [
31
+ {
32
+ "name": "germinacao",
33
+ "days": 10,
34
+ "description": "Emergência das plântulas",
35
+ "critical_water": True,
36
+ "tasks": [
37
+ {"type": "irrigate", "title": "Irrigar se não houver chuva", "priority": "high", "weather_sensitive": True},
38
+ {"type": "inspect_pests", "title": "Inspecionar germinação", "priority": "medium", "weather_sensitive": False}
39
+ ]
40
+ },
41
+ {
42
+ "name": "vegetativa",
43
+ "days": 40,
44
+ "description": "Crescimento vegetativo",
45
+ "critical_water": False,
46
+ "tasks": [
47
+ {"type": "fertilize", "title": "Aplicar fertilizante de cobertura", "priority": "high", "weather_sensitive": True},
48
+ {"type": "inspect_pests", "title": "Inspecionar pragas", "priority": "medium", "weather_sensitive": False},
49
+ {"type": "irrigate", "title": "Irrigar moderadamente", "priority": "medium", "weather_sensitive": True}
50
+ ]
51
+ },
52
+ {
53
+ "name": "florescimento",
54
+ "days": 30,
55
+ "description": "Floração e formação de vagens",
56
+ "critical_water": True,
57
+ "tasks": [
58
+ {"type": "irrigate", "title": "Irrigar - fase crítica", "priority": "critical", "weather_sensitive": True},
59
+ {"type": "inspect_diseases", "title": "Inspecionar doenças", "priority": "high", "weather_sensitive": False},
60
+ {"type": "monitor_growth", "title": "Monitorar temperatura", "priority": "medium", "weather_sensitive": False}
61
+ ]
62
+ },
63
+ {
64
+ "name": "enchimento_graos",
65
+ "days": 30,
66
+ "description": "Enchimento de grãos",
67
+ "critical_water": True,
68
+ "tasks": [
69
+ {"type": "irrigate", "title": "Irrigar - fase crítica", "priority": "critical", "weather_sensitive": True},
70
+ {"type": "monitor_growth", "title": "Monitorar maturação", "priority": "high", "weather_sensitive": False}
71
+ ]
72
+ },
73
+ {
74
+ "name": "maturacao",
75
+ "days": 10,
76
+ "description": "Maturação e secagem",
77
+ "critical_water": False,
78
+ "tasks": [
79
+ {"type": "harvest", "title": "Preparar colheita", "priority": "high", "weather_sensitive": True},
80
+ {"type": "monitor_growth", "title": "Monitorar umidade dos grãos", "priority": "medium", "weather_sensitive": False}
81
+ ]
82
+ }
83
+ ],
84
+ "optimal_temp_min": 20,
85
+ "optimal_temp_max": 30,
86
+ "critical_water_phases": ["germinacao", "florescimento", "enchimento_graos"],
87
+ "harvest_window_days": 15
88
+ },
89
+ "milho": {
90
+ "cycle_days": 140,
91
+ "phases": [
92
+ {
93
+ "name": "germinacao",
94
+ "days": 12,
95
+ "description": "Emergência das plântulas",
96
+ "critical_water": True,
97
+ "tasks": [
98
+ {"type": "irrigate", "title": "Irrigar se não houver chuva", "priority": "high", "weather_sensitive": True},
99
+ {"type": "inspect_pests", "title": "Inspecionar germinação", "priority": "medium", "weather_sensitive": False}
100
+ ]
101
+ },
102
+ {
103
+ "name": "vegetativa",
104
+ "days": 50,
105
+ "description": "Crescimento vegetativo",
106
+ "critical_water": False,
107
+ "tasks": [
108
+ {"type": "fertilize", "title": "Aplicar fertilizante nitrogenado", "priority": "high", "weather_sensitive": True},
109
+ {"type": "inspect_pests", "title": "Inspecionar pragas (lagarta)", "priority": "high", "weather_sensitive": False},
110
+ {"type": "irrigate", "title": "Irrigar moderadamente", "priority": "medium", "weather_sensitive": True}
111
+ ]
112
+ },
113
+ {
114
+ "name": "florescimento",
115
+ "days": 28,
116
+ "description": "Floração e polinização",
117
+ "critical_water": True,
118
+ "tasks": [
119
+ {"type": "irrigate", "title": "Irrigar - fase crítica", "priority": "critical", "weather_sensitive": True},
120
+ {"type": "monitor_growth", "title": "Monitorar polinização", "priority": "high", "weather_sensitive": False}
121
+ ]
122
+ },
123
+ {
124
+ "name": "enchimento_graos",
125
+ "days": 40,
126
+ "description": "Enchimento de grãos",
127
+ "critical_water": True,
128
+ "tasks": [
129
+ {"type": "irrigate", "title": "Irrigar - fase crítica", "priority": "critical", "weather_sensitive": True},
130
+ {"type": "inspect_diseases", "title": "Inspecionar doenças foliares", "priority": "high", "weather_sensitive": False},
131
+ {"type": "monitor_growth", "title": "Monitorar desenvolvimento das espigas", "priority": "medium", "weather_sensitive": False}
132
+ ]
133
+ },
134
+ {
135
+ "name": "maturacao",
136
+ "days": 10,
137
+ "description": "Maturação fisiológica",
138
+ "critical_water": False,
139
+ "tasks": [
140
+ {"type": "harvest", "title": "Preparar colheita", "priority": "high", "weather_sensitive": True},
141
+ {"type": "monitor_growth", "title": "Monitorar umidade dos grãos", "priority": "medium", "weather_sensitive": False}
142
+ ]
143
+ }
144
+ ],
145
+ "optimal_temp_min": 18,
146
+ "optimal_temp_max": 32,
147
+ "critical_water_phases": ["germinacao", "florescimento", "enchimento_graos"],
148
+ "harvest_window_days": 20
149
+ },
150
+ "feijao": {
151
+ "cycle_days": 90,
152
+ "phases": [
153
+ {
154
+ "name": "germinacao",
155
+ "days": 8,
156
+ "description": "Emergência das plântulas",
157
+ "critical_water": True,
158
+ "tasks": [
159
+ {"type": "irrigate", "title": "Irrigar se não houver chuva", "priority": "high", "weather_sensitive": True},
160
+ {"type": "inspect_pests", "title": "Inspecionar germinação", "priority": "medium", "weather_sensitive": False}
161
+ ]
162
+ },
163
+ {
164
+ "name": "vegetativa",
165
+ "days": 30,
166
+ "description": "Crescimento vegetativo",
167
+ "critical_water": False,
168
+ "tasks": [
169
+ {"type": "fertilize", "title": "Aplicar fertilizante de cobertura", "priority": "high", "weather_sensitive": True},
170
+ {"type": "inspect_pests", "title": "Inspecionar pragas (vaquinha, mosca-branca)", "priority": "high", "weather_sensitive": False},
171
+ {"type": "irrigate", "title": "Irrigar moderadamente", "priority": "medium", "weather_sensitive": True}
172
+ ]
173
+ },
174
+ {
175
+ "name": "florescimento",
176
+ "days": 22,
177
+ "description": "Floração e formação de vagens",
178
+ "critical_water": True,
179
+ "tasks": [
180
+ {"type": "irrigate", "title": "Irrigar - fase crítica", "priority": "critical", "weather_sensitive": True},
181
+ {"type": "inspect_diseases", "title": "Inspecionar doenças (antracnose, ferrugem)", "priority": "high", "weather_sensitive": False},
182
+ {"type": "monitor_growth", "title": "Monitorar formação de vagens", "priority": "medium", "weather_sensitive": False}
183
+ ]
184
+ },
185
+ {
186
+ "name": "enchimento_graos",
187
+ "days": 20,
188
+ "description": "Enchimento de grãos",
189
+ "critical_water": True,
190
+ "tasks": [
191
+ {"type": "irrigate", "title": "Irrigar - fase crítica", "priority": "critical", "weather_sensitive": True},
192
+ {"type": "monitor_growth", "title": "Monitorar desenvolvimento das vagens", "priority": "high", "weather_sensitive": False}
193
+ ]
194
+ },
195
+ {
196
+ "name": "maturacao",
197
+ "days": 10,
198
+ "description": "Maturação e secagem",
199
+ "critical_water": False,
200
+ "tasks": [
201
+ {"type": "harvest", "title": "Preparar colheita", "priority": "high", "weather_sensitive": True},
202
+ {"type": "monitor_growth", "title": "Monitorar umidade dos grãos", "priority": "medium", "weather_sensitive": False}
203
+ ]
204
+ }
205
+ ],
206
+ "optimal_temp_min": 18,
207
+ "optimal_temp_max": 29,
208
+ "critical_water_phases": ["germinacao", "florescimento", "enchimento_graos"],
209
+ "harvest_window_days": 10
210
+ }
211
+ }
212
+
213
+
214
+ def get_crop_cycle(cultura: str) -> Optional[CropCycle]:
215
+ """
216
+ Retorna o ciclo de uma cultura.
217
+
218
+ Args:
219
+ cultura: Nome da cultura
220
+
221
+ Returns:
222
+ CropCycle ou None se cultura não encontrada
223
+ """
224
+ if cultura not in CROP_CYCLES:
225
+ return None
226
+
227
+ cycle_data = CROP_CYCLES[cultura]
228
+
229
+ phases = [
230
+ CropPhase(
231
+ name=phase["name"],
232
+ days=phase["days"],
233
+ description=phase["description"],
234
+ critical_water=phase["critical_water"],
235
+ tasks=[task["type"] for task in phase["tasks"]]
236
+ )
237
+ for phase in cycle_data["phases"]
238
+ ]
239
+
240
+ return CropCycle(
241
+ culture=cultura,
242
+ cycle_days=cycle_data["cycle_days"],
243
+ phases=phases,
244
+ critical_water_phases=cycle_data["critical_water_phases"],
245
+ optimal_temp_min=cycle_data["optimal_temp_min"],
246
+ optimal_temp_max=cycle_data["optimal_temp_max"],
247
+ harvest_window_days=cycle_data["harvest_window_days"]
248
+ )
249
+
250
+
251
+ def gerar_calendario_cultura(
252
+ cultura: str,
253
+ planting_date: date,
254
+ field: Field,
255
+ crop_plan_id: Optional[str] = None,
256
+ weather_context: Optional[Dict] = None,
257
+ zarc_context: Optional[Dict] = None
258
+ ) -> Dict:
259
+ """
260
+ Gera calendário de tarefas para uma cultura.
261
+
262
+ Args:
263
+ cultura: Nome da cultura
264
+ planting_date: Data de plantio
265
+ field: Dados do talhão
266
+ crop_plan_id: ID do plano de cultura (opcional)
267
+ weather_context: Contexto climático (opcional, fase futura)
268
+ zarc_context: Contexto ZARC (opcional, fase futura)
269
+
270
+ Returns:
271
+ Dict com calendário e informações do ciclo
272
+ """
273
+
274
+ # Verificar se cultura existe
275
+ if cultura not in CROP_CYCLES:
276
+ return {
277
+ "error": f"Cultura '{cultura}' não encontrada",
278
+ "culturas_disponiveis": list(CROP_CYCLES.keys())
279
+ }
280
+
281
+ cycle_data = CROP_CYCLES[cultura]
282
+ crop_plan_id = crop_plan_id or str(uuid.uuid4())
283
+
284
+ # Calcular data estimada de colheita
285
+ estimated_harvest_date = planting_date + timedelta(days=cycle_data["cycle_days"])
286
+
287
+ # Gerar tarefas
288
+ tasks = []
289
+ current_date = planting_date
290
+
291
+ # Tarefa de preparação do solo (7 dias antes do plantio)
292
+ prepare_date = planting_date - timedelta(days=7)
293
+ tasks.append(
294
+ CalendarTask(
295
+ id=str(uuid.uuid4()),
296
+ crop_plan_id=crop_plan_id,
297
+ date=prepare_date,
298
+ type=TaskType.PREPARE_SOIL,
299
+ title="Preparar solo para plantio",
300
+ description=f"Preparar solo {field.soil_type.value} para plantio de {cultura}",
301
+ priority=TaskPriority.HIGH,
302
+ source="system",
303
+ weather_sensitive=False
304
+ )
305
+ )
306
+
307
+ # Tarefa de plantio
308
+ tasks.append(
309
+ CalendarTask(
310
+ id=str(uuid.uuid4()),
311
+ crop_plan_id=crop_plan_id,
312
+ date=planting_date,
313
+ type=TaskType.PLANT,
314
+ title=f"Plantar {cultura}",
315
+ description=f"Plantio de {cultura} em {field.area_ha} ha",
316
+ priority=TaskPriority.CRITICAL,
317
+ source="system",
318
+ weather_sensitive=True
319
+ )
320
+ )
321
+
322
+ # Tarefas por fase
323
+ for phase in cycle_data["phases"]:
324
+ phase_start = current_date
325
+ phase_end = current_date + timedelta(days=phase["days"])
326
+
327
+ # Distribuir tarefas ao longo da fase
328
+ for i, task_data in enumerate(phase["tasks"]):
329
+ # Espaçar tarefas uniformemente na fase
330
+ task_offset = (phase["days"] // (len(phase["tasks"]) + 1)) * (i + 1)
331
+ task_date = phase_start + timedelta(days=task_offset)
332
+
333
+ tasks.append(
334
+ CalendarTask(
335
+ id=str(uuid.uuid4()),
336
+ crop_plan_id=crop_plan_id,
337
+ date=task_date,
338
+ type=TaskType[task_data["type"].upper()],
339
+ title=task_data["title"],
340
+ description=f"{task_data['title']} - Fase: {phase['description']}",
341
+ priority=TaskPriority[task_data["priority"].upper()],
342
+ source="system",
343
+ weather_sensitive=task_data["weather_sensitive"]
344
+ )
345
+ )
346
+
347
+ current_date = phase_end
348
+
349
+ # Tarefa de colheita
350
+ tasks.append(
351
+ CalendarTask(
352
+ id=str(uuid.uuid4()),
353
+ crop_plan_id=crop_plan_id,
354
+ date=estimated_harvest_date,
355
+ type=TaskType.HARVEST,
356
+ title=f"Colher {cultura}",
357
+ description=f"Colheita de {cultura} - Janela de {cycle_data['harvest_window_days']} dias",
358
+ priority=TaskPriority.CRITICAL,
359
+ source="system",
360
+ weather_sensitive=True
361
+ )
362
+ )
363
+
364
+ # Ordenar tarefas por data
365
+ tasks.sort(key=lambda t: t.date)
366
+
367
+ # Montar resposta
368
+ return {
369
+ "cultura": cultura,
370
+ "planting_date": planting_date.isoformat(),
371
+ "estimated_harvest_date": estimated_harvest_date.isoformat(),
372
+ "cycle_days": cycle_data["cycle_days"],
373
+ "field": field.to_dict(),
374
+ "crop_plan_id": crop_plan_id,
375
+ "cycle_info": {
376
+ "optimal_temp_min": cycle_data["optimal_temp_min"],
377
+ "optimal_temp_max": cycle_data["optimal_temp_max"],
378
+ "critical_water_phases": cycle_data["critical_water_phases"],
379
+ "harvest_window_days": cycle_data["harvest_window_days"],
380
+ "phases": [
381
+ {
382
+ "name": phase["name"],
383
+ "days": phase["days"],
384
+ "description": phase["description"],
385
+ "critical_water": phase["critical_water"]
386
+ }
387
+ for phase in cycle_data["phases"]
388
+ ]
389
+ },
390
+ "tasks": [task.to_dict() for task in tasks],
391
+ "total_tasks": len(tasks),
392
+ "weather_sensitive_tasks": sum(1 for task in tasks if task.weather_sensitive),
393
+ "critical_tasks": sum(1 for task in tasks if task.priority == TaskPriority.CRITICAL)
394
+ }
395
+
396
+
397
+ def get_culturas_disponiveis() -> List[str]:
398
+ """Retorna lista de culturas disponíveis no sistema"""
399
+ return list(CROP_CYCLES.keys())
400
+
401
+
402
+ def get_cultura_info(cultura: str) -> Optional[Dict]:
403
+ """
404
+ Retorna informações resumidas de uma cultura.
405
+
406
+ Args:
407
+ cultura: Nome da cultura
408
+
409
+ Returns:
410
+ Dict com informações ou None se não encontrada
411
+ """
412
+ if cultura not in CROP_CYCLES:
413
+ return None
414
+
415
+ cycle_data = CROP_CYCLES[cultura]
416
+
417
+ return {
418
+ "cultura": cultura,
419
+ "cycle_days": cycle_data["cycle_days"],
420
+ "optimal_temp_min": cycle_data["optimal_temp_min"],
421
+ "optimal_temp_max": cycle_data["optimal_temp_max"],
422
+ "critical_water_phases": cycle_data["critical_water_phases"],
423
+ "harvest_window_days": cycle_data["harvest_window_days"],
424
+ "total_phases": len(cycle_data["phases"]),
425
+ "phases_names": [phase["name"] for phase in cycle_data["phases"]]
426
+ }
@@ -0,0 +1,366 @@
1
+ """
2
+ Modelos de Domínio para Planejador de Safra Inteligente
3
+
4
+ Define as entidades principais do sistema de planejamento agrícola:
5
+ - Property: Propriedade rural
6
+ - Field: Talhão/campo
7
+ - CropPlan: Plano de cultura
8
+ - CropCycle: Ciclo da cultura
9
+ - CalendarTask: Tarefa do calendário
10
+ - WeatherAlert: Alerta climático
11
+ - UserObservation: Observação do usuário
12
+ - Intervention: Intervenção/replanejamento
13
+ - PlanningSession: Sessão de planejamento
14
+ """
15
+
16
+ from dataclasses import dataclass, field
17
+ from datetime import date, datetime
18
+ from typing import Optional, List, Dict
19
+ from enum import Enum
20
+
21
+
22
+ # Enums para tipos padronizados
23
+
24
+ class SoilType(str, Enum):
25
+ """Tipos de solo"""
26
+ ARGILOSO = "argiloso"
27
+ ARENOSO = "arenoso"
28
+ MISTO = "misto"
29
+ SILTOSO = "siltoso"
30
+
31
+
32
+ class Slope(str, Enum):
33
+ """Tipos de relevo"""
34
+ PLANO = "plano"
35
+ LEVE = "leve"
36
+ MEDIO = "medio"
37
+ INGREME = "ingreme"
38
+
39
+
40
+ class WaterAvailability(str, Enum):
41
+ """Disponibilidade de água"""
42
+ BAIXA = "baixa"
43
+ MEDIA = "media"
44
+ ALTA = "alta"
45
+
46
+
47
+ class Objective(str, Enum):
48
+ """Objetivos de otimização"""
49
+ EQUILIBRADO = "equilibrado"
50
+ LUCRO = "lucro"
51
+ RISCO = "risco"
52
+ SUSTENTAVEL = "sustentavel"
53
+
54
+
55
+ class PlanStatus(str, Enum):
56
+ """Status do plano de cultura"""
57
+ PLANNED = "planned"
58
+ ACTIVE = "active"
59
+ COMPLETED = "completed"
60
+ CANCELLED = "cancelled"
61
+
62
+
63
+ class TaskType(str, Enum):
64
+ """Tipos de tarefa"""
65
+ PREPARE_SOIL = "prepare_soil"
66
+ PLANT = "plant"
67
+ IRRIGATE = "irrigate"
68
+ FERTILIZE = "fertilize"
69
+ INSPECT_PESTS = "inspect_pests"
70
+ INSPECT_DISEASES = "inspect_diseases"
71
+ MONITOR_GROWTH = "monitor_growth"
72
+ HARVEST = "harvest"
73
+
74
+
75
+ class TaskPriority(str, Enum):
76
+ """Prioridade da tarefa"""
77
+ LOW = "low"
78
+ MEDIUM = "medium"
79
+ HIGH = "high"
80
+ CRITICAL = "critical"
81
+
82
+
83
+ class TaskStatus(str, Enum):
84
+ """Status da tarefa"""
85
+ PENDING = "pending"
86
+ COMPLETED = "completed"
87
+ SKIPPED = "skipped"
88
+ RESCHEDULED = "rescheduled"
89
+
90
+
91
+ class AlertType(str, Enum):
92
+ """Tipos de alerta climático"""
93
+ RAIN = "rain"
94
+ DROUGHT = "drought"
95
+ HEAT = "heat"
96
+ COLD = "cold"
97
+ FROST = "frost"
98
+
99
+
100
+ class AlertSeverity(str, Enum):
101
+ """Severidade do alerta"""
102
+ INFO = "info"
103
+ WARNING = "warning"
104
+ CRITICAL = "critical"
105
+
106
+
107
+ class InterventionReason(str, Enum):
108
+ """Razão da intervenção"""
109
+ MISSED_TASK = "missed_task"
110
+ WEATHER_EVENT = "weather_event"
111
+ SOIL_CONDITION = "soil_condition"
112
+ USER_REQUEST = "user_request"
113
+
114
+
115
+ class PlanningMode(str, Enum):
116
+ """Modo de planejamento"""
117
+ GUIDED = "guided"
118
+ ADVANCED = "advanced"
119
+
120
+
121
+ # Modelos de Domínio
122
+
123
+ @dataclass
124
+ class Property:
125
+ """Propriedade rural"""
126
+ id: str
127
+ name: str
128
+ uf: str
129
+ municipio: str
130
+ lat: Optional[float] = None
131
+ lon: Optional[float] = None
132
+ created_at: datetime = field(default_factory=datetime.now)
133
+ updated_at: datetime = field(default_factory=datetime.now)
134
+
135
+ def to_dict(self) -> Dict:
136
+ return {
137
+ "id": self.id,
138
+ "name": self.name,
139
+ "uf": self.uf,
140
+ "municipio": self.municipio,
141
+ "lat": self.lat,
142
+ "lon": self.lon,
143
+ "created_at": self.created_at.isoformat(),
144
+ "updated_at": self.updated_at.isoformat()
145
+ }
146
+
147
+
148
+ @dataclass
149
+ class Field:
150
+ """Talhão/campo"""
151
+ id: str
152
+ property_id: str
153
+ name: str
154
+ area_ha: float
155
+ soil_type: SoilType
156
+ slope: Slope
157
+ water_availability: WaterAvailability
158
+ geometry: Optional[Dict] = None # GeoJSON para mapa
159
+ created_at: datetime = field(default_factory=datetime.now)
160
+
161
+ def to_dict(self) -> Dict:
162
+ return {
163
+ "id": self.id,
164
+ "property_id": self.property_id,
165
+ "name": self.name,
166
+ "area_ha": self.area_ha,
167
+ "soil_type": self.soil_type.value,
168
+ "slope": self.slope.value,
169
+ "water_availability": self.water_availability.value,
170
+ "geometry": self.geometry,
171
+ "created_at": self.created_at.isoformat()
172
+ }
173
+
174
+
175
+ @dataclass
176
+ class CropPlan:
177
+ """Plano de cultura para um talhão"""
178
+ id: str
179
+ field_id: str
180
+ culture: str
181
+ planting_date: date
182
+ estimated_harvest_date: date
183
+ objective: Objective
184
+ status: PlanStatus = PlanStatus.PLANNED
185
+ created_at: datetime = field(default_factory=datetime.now)
186
+
187
+ def to_dict(self) -> Dict:
188
+ return {
189
+ "id": self.id,
190
+ "field_id": self.field_id,
191
+ "culture": self.culture,
192
+ "planting_date": self.planting_date.isoformat(),
193
+ "estimated_harvest_date": self.estimated_harvest_date.isoformat(),
194
+ "objective": self.objective.value,
195
+ "status": self.status.value,
196
+ "created_at": self.created_at.isoformat()
197
+ }
198
+
199
+
200
+ @dataclass
201
+ class CropPhase:
202
+ """Fase do ciclo da cultura"""
203
+ name: str
204
+ days: int
205
+ description: str
206
+ critical_water: bool
207
+ tasks: List[str]
208
+
209
+
210
+ @dataclass
211
+ class CropCycle:
212
+ """Ciclo completo de uma cultura"""
213
+ culture: str
214
+ cycle_days: int
215
+ phases: List[CropPhase]
216
+ critical_water_phases: List[str]
217
+ optimal_temp_min: float
218
+ optimal_temp_max: float
219
+ harvest_window_days: int
220
+
221
+ def to_dict(self) -> Dict:
222
+ return {
223
+ "culture": self.culture,
224
+ "cycle_days": self.cycle_days,
225
+ "phases": [
226
+ {
227
+ "name": phase.name,
228
+ "days": phase.days,
229
+ "description": phase.description,
230
+ "critical_water": phase.critical_water,
231
+ "tasks": phase.tasks
232
+ }
233
+ for phase in self.phases
234
+ ],
235
+ "critical_water_phases": self.critical_water_phases,
236
+ "optimal_temp_min": self.optimal_temp_min,
237
+ "optimal_temp_max": self.optimal_temp_max,
238
+ "harvest_window_days": self.harvest_window_days
239
+ }
240
+
241
+
242
+ @dataclass
243
+ class CalendarTask:
244
+ """Tarefa do calendário agrícola"""
245
+ id: str
246
+ crop_plan_id: str
247
+ date: date
248
+ type: TaskType
249
+ title: str
250
+ description: str
251
+ priority: TaskPriority
252
+ source: str # system, user, weather_alert
253
+ status: TaskStatus = TaskStatus.PENDING
254
+ weather_sensitive: bool = False
255
+ completed_at: Optional[datetime] = None
256
+
257
+ def to_dict(self) -> Dict:
258
+ return {
259
+ "id": self.id,
260
+ "crop_plan_id": self.crop_plan_id,
261
+ "date": self.date.isoformat(),
262
+ "type": self.type.value,
263
+ "title": self.title,
264
+ "description": self.description,
265
+ "priority": self.priority.value,
266
+ "source": self.source,
267
+ "status": self.status.value,
268
+ "weather_sensitive": self.weather_sensitive,
269
+ "completed_at": self.completed_at.isoformat() if self.completed_at else None
270
+ }
271
+
272
+
273
+ @dataclass
274
+ class WeatherAlert:
275
+ """Alerta climático"""
276
+ id: str
277
+ crop_plan_id: str
278
+ date: date
279
+ alert_type: AlertType
280
+ severity: AlertSeverity
281
+ message: str
282
+ action_suggested: Optional[str] = None
283
+ created_at: datetime = field(default_factory=datetime.now)
284
+
285
+ def to_dict(self) -> Dict:
286
+ return {
287
+ "id": self.id,
288
+ "crop_plan_id": self.crop_plan_id,
289
+ "date": self.date.isoformat(),
290
+ "alert_type": self.alert_type.value,
291
+ "severity": self.severity.value,
292
+ "message": self.message,
293
+ "action_suggested": self.action_suggested,
294
+ "created_at": self.created_at.isoformat()
295
+ }
296
+
297
+
298
+ @dataclass
299
+ class UserObservation:
300
+ """Observação do usuário sobre o cultivo"""
301
+ id: str
302
+ crop_plan_id: str
303
+ date: date
304
+ note: str
305
+ impact: str # positive, neutral, negative
306
+ created_at: datetime = field(default_factory=datetime.now)
307
+
308
+ def to_dict(self) -> Dict:
309
+ return {
310
+ "id": self.id,
311
+ "crop_plan_id": self.crop_plan_id,
312
+ "date": self.date.isoformat(),
313
+ "note": self.note,
314
+ "impact": self.impact,
315
+ "created_at": self.created_at.isoformat()
316
+ }
317
+
318
+
319
+ @dataclass
320
+ class Intervention:
321
+ """Intervenção/replanejamento"""
322
+ id: str
323
+ crop_plan_id: str
324
+ reason: InterventionReason
325
+ suggested_action: str
326
+ original_task_id: Optional[str] = None
327
+ new_date: Optional[date] = None
328
+ risk_adjustment: float = 0.0
329
+ created_at: datetime = field(default_factory=datetime.now)
330
+ applied: bool = False
331
+
332
+ def to_dict(self) -> Dict:
333
+ return {
334
+ "id": self.id,
335
+ "crop_plan_id": self.crop_plan_id,
336
+ "original_task_id": self.original_task_id,
337
+ "reason": self.reason.value,
338
+ "suggested_action": self.suggested_action,
339
+ "new_date": self.new_date.isoformat() if self.new_date else None,
340
+ "risk_adjustment": self.risk_adjustment,
341
+ "created_at": self.created_at.isoformat(),
342
+ "applied": self.applied
343
+ }
344
+
345
+
346
+ @dataclass
347
+ class PlanningSession:
348
+ """Sessão de planejamento"""
349
+ id: str
350
+ property_id: str
351
+ mode: PlanningMode
352
+ objective: Objective
353
+ fields_count: int
354
+ cultures_recommended: List[str]
355
+ created_at: datetime = field(default_factory=datetime.now)
356
+
357
+ def to_dict(self) -> Dict:
358
+ return {
359
+ "id": self.id,
360
+ "property_id": self.property_id,
361
+ "mode": self.mode.value,
362
+ "objective": self.objective.value,
363
+ "fields_count": self.fields_count,
364
+ "cultures_recommended": self.cultures_recommended,
365
+ "created_at": self.created_at.isoformat()
366
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agroplan-ai-cli",
3
- "version": "1.0.29",
3
+ "version": "1.0.30",
4
4
  "description": "CLI global para AgroPlan AI - modo local acelerado",
5
5
  "type": "module",
6
6
  "bin": {