agroplan-ai-cli 1.0.8 → 1.0.10

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.
@@ -70,6 +70,9 @@ class OtimizarRequest(BaseModel):
70
70
  seed: Optional[int] = 42
71
71
  geracoes: Optional[int] = 100
72
72
  populacao: Optional[int] = 50
73
+ lat: Optional[float] = None
74
+ lon: Optional[float] = None
75
+ days: Optional[int] = 30
73
76
 
74
77
  class ValidarRequest(BaseModel):
75
78
  objetivo: str = "equilibrado"
@@ -78,6 +81,9 @@ class ValidarRequest(BaseModel):
78
81
  class RelatorioRequest(BaseModel):
79
82
  objetivo: str = "equilibrado"
80
83
  formato: str = "md"
84
+ lat: Optional[float] = None
85
+ lon: Optional[float] = None
86
+ days: Optional[int] = 30
81
87
 
82
88
  class RodadasRequest(BaseModel):
83
89
  objetivo: str = "equilibrado"
@@ -181,21 +187,46 @@ def get_clima(
181
187
  raise HTTPException(status_code=500, detail=str(e))
182
188
 
183
189
  @app.get("/dashboard")
184
- def get_dashboard():
185
- """Retorna resumo do dashboard"""
190
+ def get_dashboard(
191
+ lat: Optional[float] = None,
192
+ lon: Optional[float] = None,
193
+ days: int = Query(30, description="Número de dias para análise climática")
194
+ ):
195
+ """Retorna resumo do dashboard com contexto climático opcional"""
186
196
  try:
187
- def montar_dashboard():
188
- culturas, talhoes, regras = get_dados()
189
-
190
- # Usa AG cacheado
191
- resultado_ag = get_ag_cacheado(objetivo='equilibrado', seed=42)
197
+ culturas, talhoes, regras = get_dados()
198
+
199
+ # Obter contexto climático se coordenadas foram fornecidas
200
+ contexto_climatico = None
201
+ if lat is not None and lon is not None:
202
+ from core.climate_adapter import obter_contexto_climatico_por_coordenadas
203
+ contexto_climatico = obter_contexto_climatico_por_coordenadas(lat, lon, days)
204
+
205
+ # Gerar chave de cache considerando clima
206
+ cache_params = {"objetivo": "equilibrado", "seed": 42}
207
+ if lat is not None and lon is not None:
208
+ cache_params.update({"lat": lat, "lon": lon, "days": days})
209
+
210
+ cache_key = get_cache_key("dashboard", **cache_params)
211
+
212
+ def compute_dashboard():
213
+ # Usar AG com clima se disponível
214
+ if contexto_climatico:
215
+ from core.climate_adapter import gerar_plano_com_clima
216
+ resultado_ag = gerar_plano_com_clima(
217
+ culturas, talhoes, regras,
218
+ objetivo='equilibrado', seed=42,
219
+ lat=lat, lon=lon, days=days
220
+ )
221
+ else:
222
+ resultado_ag = get_ag_cacheado(objetivo='equilibrado', seed=42)
192
223
 
193
224
  # Tenta validar
194
225
  validacao = comparar_ag_com_forca_bruta(culturas, talhoes, regras, objetivo='equilibrado', seed=42)
195
226
 
196
- # Se força bruta é inviável, retorna dados especiais
227
+ # Preparar resultado base
197
228
  if validacao.get('erro'):
198
- return {
229
+ resultado_base = {
199
230
  "lucro_total": float(resultado_ag['lucro_total']),
200
231
  "risco_medio": float(resultado_ag['risco_medio']),
201
232
  "fitness": float(resultado_ag['fitness']),
@@ -223,47 +254,62 @@ def get_dashboard():
223
254
  for p in resultado_ag['plano']
224
255
  ]
225
256
  }
257
+ else:
258
+ resultado_base = {
259
+ "lucro_total": float(resultado_ag['lucro_total']),
260
+ "risco_medio": float(resultado_ag['risco_medio']),
261
+ "fitness": float(resultado_ag['fitness']),
262
+ "diversidade": int(resultado_ag['diversidade']),
263
+ "objetivo": str(resultado_ag['objetivo']),
264
+ "culturas_escolhidas": [str(p['cultura']) for p in resultado_ag['plano']],
265
+ "validacao": {
266
+ "otimo_global": bool(validacao.get('ag_encontrou_otimo_global', False)),
267
+ "total_combinacoes": int(validacao.get('forca_bruta', {}).get('total_combinacoes', 0))
268
+ },
269
+ "plano": [
270
+ {
271
+ "talhao": int(p['talhao']),
272
+ "area": float(p['area']),
273
+ "solo": str(p['solo']),
274
+ "clima": str(p['clima']),
275
+ "relevo": str(p['relevo']),
276
+ "agua": str(p['agua']),
277
+ "cultura": str(p['cultura']),
278
+ "lucro_estimado": float(p['lucro_estimado']),
279
+ "risco": float(p['risco']),
280
+ "nota": float(p['nota']),
281
+ "tempo": int(p['tempo'])
282
+ }
283
+ for p in resultado_ag['plano']
284
+ ]
285
+ }
226
286
 
227
- # Converte tipos numpy para Python nativos
228
- return {
229
- "lucro_total": float(resultado_ag['lucro_total']),
230
- "risco_medio": float(resultado_ag['risco_medio']),
231
- "fitness": float(resultado_ag['fitness']),
232
- "diversidade": int(resultado_ag['diversidade']),
233
- "objetivo": str(resultado_ag['objetivo']),
234
- "culturas_escolhidas": [str(p['cultura']) for p in resultado_ag['plano']],
235
- "validacao": {
236
- "otimo_global": bool(validacao.get('ag_encontrou_otimo_global', False)),
237
- "total_combinacoes": int(validacao.get('forca_bruta', {}).get('total_combinacoes', 0))
238
- },
239
- "plano": [
240
- {
241
- "talhao": int(p['talhao']),
242
- "area": float(p['area']),
243
- "solo": str(p['solo']),
244
- "clima": str(p['clima']),
245
- "relevo": str(p['relevo']),
246
- "agua": str(p['agua']),
247
- "cultura": str(p['cultura']),
248
- "lucro_estimado": float(p['lucro_estimado']),
249
- "risco": float(p['risco']),
250
- "nota": float(p['nota']),
251
- "tempo": int(p['tempo'])
252
- }
253
- for p in resultado_ag['plano']
254
- ]
255
- }
287
+ # Adicionar informações de clima real
288
+ if contexto_climatico:
289
+ resultado_base["clima_real"] = {
290
+ "ativo": True,
291
+ "source": contexto_climatico.get("fonte", "unknown"),
292
+ "temperatura_media": contexto_climatico.get("temperatura_media"),
293
+ "precipitacao_total": contexto_climatico.get("precipitacao_total"),
294
+ "risco_climatico_estimado": contexto_climatico.get("risco_climatico_estimado"),
295
+ "clima_observado": contexto_climatico.get("clima_observado"),
296
+ "agua_observada": contexto_climatico.get("agua_observada"),
297
+ "ajuste_risco": contexto_climatico.get("ajuste_risco", 0),
298
+ "fallback": contexto_climatico.get("fallback", False),
299
+ "error": contexto_climatico.get("error")
300
+ }
301
+ else:
302
+ resultado_base["clima_real"] = {"ativo": False}
303
+
304
+ return resultado_base
256
305
 
257
- # Usa cache para dashboard
258
- key = get_cache_key("dashboard", objetivo="equilibrado", seed=42)
259
- resultado = get_or_compute_cache(key, montar_dashboard)
306
+ # Usa cache para dashboard com contexto climático
307
+ resultado = get_or_compute_cache(cache_key, compute_dashboard)
260
308
 
261
309
  # Converte tipos Python (por segurança)
262
310
  return converter_tipos_python(resultado)
263
311
  except Exception as e:
264
312
  raise HTTPException(status_code=500, detail=str(e))
265
- except Exception as e:
266
- raise HTTPException(status_code=500, detail=str(e))
267
313
 
268
314
  @app.get("/talhoes")
269
315
  def get_talhoes():
@@ -333,17 +379,42 @@ def get_culturas():
333
379
  raise HTTPException(status_code=500, detail=str(e))
334
380
 
335
381
  @app.get("/cenarios")
336
- def get_cenarios():
337
- """Retorna comparação de cenários"""
382
+ def get_cenarios(
383
+ lat: Optional[float] = None,
384
+ lon: Optional[float] = None,
385
+ days: int = Query(30, description="Número de dias para análise climática")
386
+ ):
387
+ """Retorna comparação de cenários com contexto climático opcional"""
338
388
  try:
389
+ # Obter contexto climático se coordenadas foram fornecidas
390
+ contexto_climatico = None
391
+ if lat is not None and lon is not None:
392
+ from core.climate_adapter import obter_contexto_climatico_por_coordenadas
393
+ contexto_climatico = obter_contexto_climatico_por_coordenadas(lat, lon, days)
394
+
395
+ # Gerar chave de cache considerando clima
396
+ cache_params = {"cenarios": True}
397
+ if lat is not None and lon is not None:
398
+ cache_params.update({"lat": lat, "lon": lon, "days": days})
399
+
400
+ cache_key = get_cache_key("cenarios", **cache_params)
401
+
339
402
  def montar_cenarios():
340
403
  culturas, talhoes, regras = get_dados()
341
404
 
342
405
  # Gera todos os cenários
343
406
  cenarios = gerar_cenarios(culturas, talhoes, regras)
344
407
 
345
- # Usa AG cacheado
346
- resultado_ag = get_ag_cacheado(objetivo='equilibrado', seed=42)
408
+ # Usar AG com clima se disponível
409
+ if contexto_climatico:
410
+ from core.climate_adapter import gerar_plano_com_clima
411
+ resultado_ag = gerar_plano_com_clima(
412
+ culturas, talhoes, regras,
413
+ objetivo='equilibrado', seed=42,
414
+ lat=lat, lon=lon, days=days
415
+ )
416
+ else:
417
+ resultado_ag = get_ag_cacheado(objetivo='equilibrado', seed=42)
347
418
 
348
419
  # Cria um mapa de talhões para facilitar o acesso
349
420
  talhoes_dict = {int(row['id']): row for _, row in talhoes.iterrows()}
@@ -376,7 +447,7 @@ def get_cenarios():
376
447
  ]
377
448
  }
378
449
 
379
- # Adiciona AG
450
+ # Adiciona AG com contexto climático
380
451
  cenarios_formatados['genetico'] = {
381
452
  'nome': 'Algoritmo Genético',
382
453
  'descricao': 'Solução otimizada automaticamente',
@@ -401,13 +472,28 @@ def get_cenarios():
401
472
  ]
402
473
  }
403
474
 
404
- return {
405
- "cenarios": cenarios_formatados
406
- }
475
+ # Adicionar informações de clima real se disponível
476
+ resultado_final = {"cenarios": cenarios_formatados}
477
+ if contexto_climatico:
478
+ resultado_final["clima_real"] = {
479
+ "ativo": True,
480
+ "source": contexto_climatico.get("fonte", "unknown"),
481
+ "temperatura_media": contexto_climatico.get("temperatura_media"),
482
+ "precipitacao_total": contexto_climatico.get("precipitacao_total"),
483
+ "risco_climatico_estimado": contexto_climatico.get("risco_climatico_estimado"),
484
+ "clima_observado": contexto_climatico.get("clima_observado"),
485
+ "agua_observada": contexto_climatico.get("agua_observada"),
486
+ "ajuste_risco": contexto_climatico.get("ajuste_risco", 0),
487
+ "fallback": contexto_climatico.get("fallback", False),
488
+ "error": contexto_climatico.get("error")
489
+ }
490
+ else:
491
+ resultado_final["clima_real"] = {"ativo": False}
492
+
493
+ return resultado_final
407
494
 
408
- # Usa cache para cenários
409
- key = get_cache_key("cenarios")
410
- resultado = get_or_compute_cache(key, montar_cenarios)
495
+ # Usa cache para cenários com contexto climático
496
+ resultado = get_or_compute_cache(cache_key, montar_cenarios)
411
497
 
412
498
  return converter_tipos_python(resultado)
413
499
  except Exception as e:
@@ -417,34 +503,54 @@ def get_cenarios():
417
503
 
418
504
  @app.post("/otimizar")
419
505
  def otimizar(request: OtimizarRequest):
420
- """Executa otimização com Algoritmo Genético"""
506
+ """Executa otimização com Algoritmo Genético e contexto climático opcional"""
421
507
  try:
422
508
  # Valida objetivo
423
509
  objetivos_validos = ['equilibrado', 'lucro', 'risco', 'sustentavel']
424
510
  if request.objetivo not in objetivos_validos:
425
511
  raise HTTPException(status_code=400, detail=f"Objetivo inválido. Use: {objetivos_validos}")
426
512
 
427
- # Usa AG cacheado se parâmetros forem padrão
428
- if (request.objetivo == "equilibrado" and
429
- request.seed == 42 and
430
- request.geracoes == 100 and
431
- request.populacao == 50):
432
- resultado = get_ag_cacheado(
513
+ # Obter contexto climático se coordenadas foram fornecidas
514
+ contexto_climatico = None
515
+ if request.lat is not None and request.lon is not None:
516
+ from core.climate_adapter import obter_contexto_climatico_por_coordenadas
517
+ contexto_climatico = obter_contexto_climatico_por_coordenadas(request.lat, request.lon, request.days)
518
+
519
+ # Usar AG com clima se disponível
520
+ if contexto_climatico:
521
+ from core.climate_adapter import gerar_plano_com_clima
522
+ resultado = gerar_plano_com_clima(
523
+ *get_dados(),
433
524
  objetivo=request.objetivo,
434
525
  seed=request.seed,
435
526
  geracoes=request.geracoes,
436
- populacao=request.populacao
437
- )
438
- else:
439
- # Executa AG sem cache para parâmetros customizados
440
- culturas, talhoes, regras = get_dados()
441
- resultado = gerar_plano_genetico(
442
- culturas, talhoes, regras,
443
- objetivo=request.objetivo,
444
- geracoes=request.geracoes,
445
527
  populacao=request.populacao,
446
- seed=request.seed
528
+ lat=request.lat,
529
+ lon=request.lon,
530
+ days=request.days
447
531
  )
532
+ else:
533
+ # Usa AG cacheado se parâmetros forem padrão e sem clima
534
+ if (request.objetivo == "equilibrado" and
535
+ request.seed == 42 and
536
+ request.geracoes == 100 and
537
+ request.populacao == 50):
538
+ resultado = get_ag_cacheado(
539
+ objetivo=request.objetivo,
540
+ seed=request.seed,
541
+ geracoes=request.geracoes,
542
+ populacao=request.populacao
543
+ )
544
+ else:
545
+ # Executa AG sem cache para parâmetros customizados
546
+ culturas, talhoes, regras = get_dados()
547
+ resultado = gerar_plano_genetico(
548
+ culturas, talhoes, regras,
549
+ objetivo=request.objetivo,
550
+ geracoes=request.geracoes,
551
+ populacao=request.populacao,
552
+ seed=request.seed
553
+ )
448
554
 
449
555
  # Converte tipos numpy para Python nativos
450
556
  resultado_convertido = converter_tipos_python(resultado)
@@ -516,7 +622,7 @@ def rodadas(request: RodadasRequest):
516
622
 
517
623
  @app.post("/relatorio")
518
624
  def relatorio(request: RelatorioRequest):
519
- """Gera relatório"""
625
+ """Gera relatório com contexto climático opcional"""
520
626
  try:
521
627
  culturas, talhoes, regras = get_dados()
522
628
 
@@ -530,7 +636,14 @@ def relatorio(request: RelatorioRequest):
530
636
  if request.formato not in formatos_validos:
531
637
  raise HTTPException(status_code=400, detail=f"Formato inválido. Use: {formatos_validos}")
532
638
 
533
- # Gera relatório
639
+ # Obter contexto climático se coordenadas foram fornecidas
640
+ contexto_climatico = None
641
+ if request.lat is not None and request.lon is not None:
642
+ from core.climate_adapter import obter_contexto_climatico_por_coordenadas
643
+ contexto_climatico = obter_contexto_climatico_por_coordenadas(request.lat, request.lon, request.days)
644
+
645
+ # Gera relatório (por enquanto sem integração climática no gerador)
646
+ # TODO: Atualizar report_generator para aceitar contexto climático
534
647
  caminho = gerar_relatorio_completo(
535
648
  culturas, talhoes, regras,
536
649
  objetivo=request.objetivo,
@@ -541,11 +654,48 @@ def relatorio(request: RelatorioRequest):
541
654
  with open(caminho, 'r', encoding='utf-8') as f:
542
655
  conteudo = f.read()
543
656
 
544
- return {
657
+ # Adiciona informações climáticas ao final se disponível
658
+ if contexto_climatico and not contexto_climatico.get("fallback", True):
659
+ clima_info = f"""
660
+
661
+ ## Dados Climáticos Reais
662
+
663
+ **Fonte:** {contexto_climatico.get('fonte', 'N/A')}
664
+ **Temperatura Média:** {contexto_climatico.get('temperatura_media', 'N/A')}°C
665
+ **Precipitação Total:** {contexto_climatico.get('precipitacao_total', 'N/A')}mm
666
+ **Risco Climático:** {contexto_climatico.get('risco_climatico_estimado', 'N/A')}
667
+ **Clima Observado:** {contexto_climatico.get('clima_observado', 'N/A')}
668
+ **Água Observada:** {contexto_climatico.get('agua_observada', 'N/A')}
669
+ **Ajuste de Risco:** {contexto_climatico.get('ajuste_risco', 0):+.1%}
670
+
671
+ *Dados climáticos integrados ao planejamento para maior precisão.*
672
+ """
673
+ conteudo += clima_info
674
+
675
+ resultado = {
545
676
  "caminho": caminho,
546
677
  "conteudo": conteudo,
547
678
  "formato": request.formato
548
679
  }
680
+
681
+ # Adicionar informações de clima real
682
+ if contexto_climatico:
683
+ resultado["clima_real"] = {
684
+ "ativo": True,
685
+ "source": contexto_climatico.get("fonte", "unknown"),
686
+ "temperatura_media": contexto_climatico.get("temperatura_media"),
687
+ "precipitacao_total": contexto_climatico.get("precipitacao_total"),
688
+ "risco_climatico_estimado": contexto_climatico.get("risco_climatico_estimado"),
689
+ "clima_observado": contexto_climatico.get("clima_observado"),
690
+ "agua_observada": contexto_climatico.get("agua_observada"),
691
+ "ajuste_risco": contexto_climatico.get("ajuste_risco", 0),
692
+ "fallback": contexto_climatico.get("fallback", False),
693
+ "error": contexto_climatico.get("error")
694
+ }
695
+ else:
696
+ resultado["clima_real"] = {"ativo": False}
697
+
698
+ return resultado
549
699
  except HTTPException:
550
700
  raise
551
701
  except Exception as e:
@@ -0,0 +1,142 @@
1
+ """
2
+ Adaptador para integrar dados climáticos reais no planejamento agrícola
3
+ """
4
+ from typing import Optional, Dict, Any
5
+ from providers.weather_provider import get_weather_summary
6
+ from providers.types import WeatherSummary
7
+
8
+ def classificar_clima_por_temperatura(temp_media: Optional[float]) -> Optional[str]:
9
+ """Classifica clima baseado na temperatura média"""
10
+ if temp_media is None:
11
+ return None
12
+
13
+ if temp_media >= 28:
14
+ return "quente"
15
+ if temp_media < 18:
16
+ return "frio"
17
+ return "ameno"
18
+
19
+ def classificar_agua_por_precipitacao(precipitacao_total: Optional[float]) -> Optional[str]:
20
+ """Classifica disponibilidade de água baseada na precipitação"""
21
+ if precipitacao_total is None:
22
+ return None
23
+
24
+ if precipitacao_total < 50:
25
+ return "baixa"
26
+ if precipitacao_total > 120:
27
+ return "alta"
28
+ return "media"
29
+
30
+ def calcular_ajuste_risco_climatico(risco_climatico: str) -> float:
31
+ """Calcula ajuste de risco baseado no risco climático estimado"""
32
+ ajustes = {
33
+ "alto": 0.15, # +15% risco
34
+ "medio": 0.05, # +5% risco
35
+ "baixo": -0.03, # -3% risco (leve benefício)
36
+ "indeterminado": 0
37
+ }
38
+ return ajustes.get(risco_climatico, 0)
39
+
40
+ def criar_contexto_climatico(weather_summary: WeatherSummary) -> Dict[str, Any]:
41
+ """Cria contexto climático para uso no planejamento"""
42
+
43
+ clima_observado = classificar_clima_por_temperatura(weather_summary.temperatura_media)
44
+ agua_observada = classificar_agua_por_precipitacao(weather_summary.precipitacao_total)
45
+ ajuste_risco = calcular_ajuste_risco_climatico(weather_summary.risco_climatico_estimado)
46
+
47
+ return {
48
+ "clima_observado": clima_observado,
49
+ "agua_observada": agua_observada,
50
+ "ajuste_risco": ajuste_risco,
51
+ "fonte": weather_summary.source,
52
+ "fallback": weather_summary.fallback,
53
+ "temperatura_media": weather_summary.temperatura_media,
54
+ "temperatura_maxima": weather_summary.temperatura_maxima,
55
+ "temperatura_minima": weather_summary.temperatura_minima,
56
+ "precipitacao_total": weather_summary.precipitacao_total,
57
+ "evapotranspiracao": weather_summary.evapotranspiracao,
58
+ "umidade_media": weather_summary.umidade_media,
59
+ "radiacao_solar": weather_summary.radiacao_solar,
60
+ "risco_climatico_estimado": weather_summary.risco_climatico_estimado,
61
+ "error": weather_summary.error
62
+ }
63
+
64
+ def aplicar_contexto_climatico_no_plano(resultado_ag: Dict[str, Any], contexto_climatico: Dict[str, Any]) -> Dict[str, Any]:
65
+ """Aplica ajustes climáticos no resultado do algoritmo genético"""
66
+
67
+ if not contexto_climatico or contexto_climatico.get("ajuste_risco", 0) == 0:
68
+ # Sem ajuste necessário
69
+ resultado_ag["ajuste_climatico_aplicado"] = False
70
+ resultado_ag["contexto_climatico"] = contexto_climatico
71
+ return resultado_ag
72
+
73
+ ajuste_risco = contexto_climatico["ajuste_risco"]
74
+
75
+ # Aplicar ajuste no plano otimizado
76
+ if "plano_otimizado" in resultado_ag:
77
+ for item in resultado_ag["plano_otimizado"]:
78
+ if "risco" in item:
79
+ risco_original = item["risco"]
80
+ # Aplicar ajuste mantendo risco entre 0.05 e 0.95
81
+ novo_risco = min(0.95, max(0.05, risco_original + ajuste_risco))
82
+ item["risco"] = round(novo_risco, 3)
83
+ item["risco_original"] = risco_original
84
+ item["ajuste_aplicado"] = ajuste_risco
85
+
86
+ # Recalcular métricas gerais se existirem
87
+ if "metricas" in resultado_ag and "risco_medio" in resultado_ag["metricas"]:
88
+ risco_original = resultado_ag["metricas"]["risco_medio"]
89
+ novo_risco = min(0.95, max(0.05, risco_original + ajuste_risco))
90
+ resultado_ag["metricas"]["risco_medio"] = round(novo_risco, 3)
91
+ resultado_ag["metricas"]["risco_medio_original"] = risco_original
92
+
93
+ # Marcar que ajuste foi aplicado
94
+ resultado_ag["ajuste_climatico_aplicado"] = True
95
+ resultado_ag["contexto_climatico"] = contexto_climatico
96
+
97
+ return resultado_ag
98
+
99
+ def obter_contexto_climatico_por_coordenadas(lat: float, lon: float, days: int = 30) -> Optional[Dict[str, Any]]:
100
+ """Obtém contexto climático para coordenadas específicas"""
101
+ try:
102
+ weather_summary = get_weather_summary(lat, lon, days)
103
+ return criar_contexto_climatico(weather_summary)
104
+ except Exception as e:
105
+ # Em caso de erro, retornar contexto vazio
106
+ return {
107
+ "clima_observado": None,
108
+ "agua_observada": None,
109
+ "ajuste_risco": 0,
110
+ "fonte": "erro",
111
+ "fallback": True,
112
+ "error": str(e),
113
+ "temperatura_media": None,
114
+ "precipitacao_total": None,
115
+ "risco_climatico_estimado": "indeterminado"
116
+ }
117
+
118
+ def gerar_plano_com_clima(culturas, talhoes, regras, objetivo="equilibrado", seed=42,
119
+ geracoes=100, populacao=50, lat=None, lon=None, days=30):
120
+ """
121
+ Wrapper para gerar plano genético com contexto climático opcional
122
+ """
123
+ from .planner import gerar_plano_genetico
124
+
125
+ # Gerar plano normalmente
126
+ resultado = gerar_plano_genetico(
127
+ culturas, talhoes, regras,
128
+ objetivo=objetivo, seed=seed,
129
+ geracoes=geracoes, populacao=populacao
130
+ )
131
+
132
+ # Se coordenadas foram fornecidas, aplicar contexto climático
133
+ if lat is not None and lon is not None:
134
+ contexto_climatico = obter_contexto_climatico_por_coordenadas(lat, lon, days)
135
+ if contexto_climatico:
136
+ resultado = aplicar_contexto_climatico_no_plano(resultado, contexto_climatico)
137
+ else:
138
+ # Marcar que não há clima real aplicado
139
+ resultado["ajuste_climatico_aplicado"] = False
140
+ resultado["contexto_climatico"] = None
141
+
142
+ return resultado
package/dist/index.js CHANGED
@@ -310,6 +310,15 @@ async function doctorCommand() {
310
310
  console.log(` Talh\xF5es: ${data.talhoes}`);
311
311
  if (data.cache_items !== undefined)
312
312
  console.log(` Cache items: ${data.cache_items}`);
313
+ if (data.data_mode) {
314
+ console.log(` Data mode: ${data.data_mode}`);
315
+ if (data.providers && data.providers.weather) {
316
+ console.log(` Weather provider: ${data.providers.weather}`);
317
+ }
318
+ } else {
319
+ console.log(" \u26A0\uFE0F Backend local desatualizado (sem suporte a clima real)");
320
+ console.log(" Rode: agroplan setup --force");
321
+ }
313
322
  } else {
314
323
  console.log(" \u26A0\uFE0F Porta 8000 ocupada por outro servi\xE7o");
315
324
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agroplan-ai-cli",
3
- "version": "1.0.8",
3
+ "version": "1.0.10",
4
4
  "description": "CLI global para AgroPlan AI - modo local acelerado",
5
5
  "type": "module",
6
6
  "bin": {