agroplan-ai-cli 1.0.10 → 1.0.11
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.
package/backend-template/api.py
CHANGED
|
@@ -422,30 +422,65 @@ def get_cenarios(
|
|
|
422
422
|
# Formata resposta
|
|
423
423
|
cenarios_formatados = {}
|
|
424
424
|
|
|
425
|
+
# Aplicar ajuste climático em todos os cenários se disponível
|
|
426
|
+
ajuste_risco = contexto_climatico.get("ajuste_risco", 0) if contexto_climatico else 0
|
|
427
|
+
|
|
425
428
|
for key, cenario in cenarios.items():
|
|
429
|
+
# Aplicar ajuste climático no risco de cada item do plano
|
|
430
|
+
plano_ajustado = []
|
|
431
|
+
soma_risco_area = 0
|
|
432
|
+
soma_area = 0
|
|
433
|
+
|
|
434
|
+
for p in cenario['plano']:
|
|
435
|
+
risco_original = float(p['risco']) # Em pontos percentuais (30, 35, etc.)
|
|
436
|
+
area = float(p['area'])
|
|
437
|
+
|
|
438
|
+
# Aplicar ajuste se houver (ajuste também em pontos percentuais: -3, +5, +15)
|
|
439
|
+
if ajuste_risco != 0:
|
|
440
|
+
novo_risco = min(95, max(5, risco_original + ajuste_risco))
|
|
441
|
+
else:
|
|
442
|
+
novo_risco = risco_original
|
|
443
|
+
|
|
444
|
+
item_plano = {
|
|
445
|
+
"talhao": int(p['talhao']),
|
|
446
|
+
"area": area,
|
|
447
|
+
"solo": str(talhoes_dict[int(p['talhao'])]['solo']),
|
|
448
|
+
"clima": str(talhoes_dict[int(p['talhao'])]['clima']),
|
|
449
|
+
"relevo": str(talhoes_dict[int(p['talhao'])]['relevo']),
|
|
450
|
+
"agua": str(talhoes_dict[int(p['talhao'])]['agua']),
|
|
451
|
+
"cultura": str(p['cultura']),
|
|
452
|
+
"lucro_estimado": float(p['lucro_estimado']),
|
|
453
|
+
"risco": round(novo_risco, 1), # Em pontos percentuais
|
|
454
|
+
"nota": float(p['nota']),
|
|
455
|
+
"tempo": 0
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
# Adicionar campos de ajuste se aplicado
|
|
459
|
+
if ajuste_risco != 0:
|
|
460
|
+
item_plano["risco_original"] = round(risco_original, 1)
|
|
461
|
+
item_plano["ajuste_aplicado"] = round(ajuste_risco, 1)
|
|
462
|
+
|
|
463
|
+
plano_ajustado.append(item_plano)
|
|
464
|
+
|
|
465
|
+
# Acumular para recalcular risco médio
|
|
466
|
+
soma_risco_area += novo_risco * area
|
|
467
|
+
soma_area += area
|
|
468
|
+
|
|
469
|
+
# Recalcular risco médio ponderado
|
|
470
|
+
risco_medio_ajustado = soma_risco_area / soma_area if soma_area > 0 else cenario['risco_medio']
|
|
471
|
+
|
|
426
472
|
cenarios_formatados[key] = {
|
|
427
473
|
'nome': str(cenario['nome']),
|
|
428
474
|
'descricao': str(cenario['descricao']),
|
|
429
475
|
'lucro_total': float(cenario['lucro_total']),
|
|
430
|
-
'risco_medio':
|
|
476
|
+
'risco_medio': round(risco_medio_ajustado, 1), # Em pontos percentuais
|
|
431
477
|
'area_total': float(cenario['area_total']),
|
|
432
|
-
'plano':
|
|
433
|
-
{
|
|
434
|
-
"talhao": int(p['talhao']),
|
|
435
|
-
"area": float(p['area']),
|
|
436
|
-
"solo": str(talhoes_dict[int(p['talhao'])]['solo']),
|
|
437
|
-
"clima": str(talhoes_dict[int(p['talhao'])]['clima']),
|
|
438
|
-
"relevo": str(talhoes_dict[int(p['talhao'])]['relevo']),
|
|
439
|
-
"agua": str(talhoes_dict[int(p['talhao'])]['agua']),
|
|
440
|
-
"cultura": str(p['cultura']),
|
|
441
|
-
"lucro_estimado": float(p['lucro_estimado']),
|
|
442
|
-
"risco": float(p['risco']),
|
|
443
|
-
"nota": float(p['nota']),
|
|
444
|
-
"tempo": 0 # Não disponível nos cenários simples
|
|
445
|
-
}
|
|
446
|
-
for p in cenario['plano']
|
|
447
|
-
]
|
|
478
|
+
'plano': plano_ajustado
|
|
448
479
|
}
|
|
480
|
+
|
|
481
|
+
# Adicionar risco médio original se ajuste foi aplicado
|
|
482
|
+
if ajuste_risco != 0:
|
|
483
|
+
cenarios_formatados[key]['risco_medio_original'] = round(float(cenario['risco_medio']), 1)
|
|
449
484
|
|
|
450
485
|
# Adiciona AG com contexto climático
|
|
451
486
|
cenarios_formatados['genetico'] = {
|
|
@@ -642,36 +677,18 @@ def relatorio(request: RelatorioRequest):
|
|
|
642
677
|
from core.climate_adapter import obter_contexto_climatico_por_coordenadas
|
|
643
678
|
contexto_climatico = obter_contexto_climatico_por_coordenadas(request.lat, request.lon, request.days)
|
|
644
679
|
|
|
645
|
-
# Gera relatório
|
|
646
|
-
# TODO: Atualizar report_generator para aceitar contexto climático
|
|
680
|
+
# Gera relatório com contexto climático integrado
|
|
647
681
|
caminho = gerar_relatorio_completo(
|
|
648
682
|
culturas, talhoes, regras,
|
|
649
683
|
objetivo=request.objetivo,
|
|
650
|
-
formato=request.formato
|
|
684
|
+
formato=request.formato,
|
|
685
|
+
contexto_climatico=contexto_climatico
|
|
651
686
|
)
|
|
652
687
|
|
|
653
688
|
# Lê conteúdo
|
|
654
689
|
with open(caminho, 'r', encoding='utf-8') as f:
|
|
655
690
|
conteudo = f.read()
|
|
656
691
|
|
|
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
692
|
resultado = {
|
|
676
693
|
"caminho": caminho,
|
|
677
694
|
"conteudo": conteudo,
|
|
@@ -28,11 +28,15 @@ def classificar_agua_por_precipitacao(precipitacao_total: Optional[float]) -> Op
|
|
|
28
28
|
return "media"
|
|
29
29
|
|
|
30
30
|
def calcular_ajuste_risco_climatico(risco_climatico: str) -> float:
|
|
31
|
-
"""Calcula ajuste de risco baseado no risco climático estimado
|
|
31
|
+
"""Calcula ajuste de risco baseado no risco climático estimado
|
|
32
|
+
|
|
33
|
+
Retorna ajuste em pontos percentuais (não decimal)
|
|
34
|
+
Exemplo: retorna 15 para +15 pontos percentuais, não 0.15
|
|
35
|
+
"""
|
|
32
36
|
ajustes = {
|
|
33
|
-
"alto":
|
|
34
|
-
"medio":
|
|
35
|
-
"baixo": -
|
|
37
|
+
"alto": 15, # +15 pontos percentuais
|
|
38
|
+
"medio": 5, # +5 pontos percentuais
|
|
39
|
+
"baixo": -3, # -3 pontos percentuais (leve benefício)
|
|
36
40
|
"indeterminado": 0
|
|
37
41
|
}
|
|
38
42
|
return ajustes.get(risco_climatico, 0)
|
|
@@ -62,33 +66,66 @@ def criar_contexto_climatico(weather_summary: WeatherSummary) -> Dict[str, Any]:
|
|
|
62
66
|
}
|
|
63
67
|
|
|
64
68
|
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
|
|
69
|
+
"""Aplica ajustes climáticos no resultado do algoritmo genético
|
|
70
|
+
|
|
71
|
+
O risco no sistema está em formato de pontos percentuais (30 = 30%, não 0.30)
|
|
72
|
+
"""
|
|
66
73
|
|
|
67
74
|
if not contexto_climatico or contexto_climatico.get("ajuste_risco", 0) == 0:
|
|
68
|
-
# Sem ajuste necessário
|
|
75
|
+
# Sem ajuste necessário, mas ainda adiciona contexto
|
|
69
76
|
resultado_ag["ajuste_climatico_aplicado"] = False
|
|
70
77
|
resultado_ag["contexto_climatico"] = contexto_climatico
|
|
71
78
|
return resultado_ag
|
|
72
79
|
|
|
73
|
-
ajuste_risco = contexto_climatico["ajuste_risco"]
|
|
80
|
+
ajuste_risco = contexto_climatico["ajuste_risco"] # Em pontos percentuais (ex: -3, +5, +15)
|
|
81
|
+
|
|
82
|
+
# Salvar risco médio original antes de aplicar ajustes
|
|
83
|
+
risco_medio_original = resultado_ag.get("risco_medio", 0)
|
|
84
|
+
|
|
85
|
+
# Aplicar ajuste no plano (formato padrão do gerar_plano_genetico)
|
|
86
|
+
if "plano" in resultado_ag:
|
|
87
|
+
soma_risco_area = 0
|
|
88
|
+
soma_area = 0
|
|
89
|
+
|
|
90
|
+
for item in resultado_ag["plano"]:
|
|
91
|
+
if "risco" in item and "area" in item:
|
|
92
|
+
risco_original = item["risco"] # Em pontos percentuais (ex: 30, 35, 40)
|
|
93
|
+
area = item["area"]
|
|
94
|
+
|
|
95
|
+
# Aplicar ajuste mantendo risco entre 5 e 95 pontos percentuais
|
|
96
|
+
novo_risco = min(95, max(5, risco_original + ajuste_risco))
|
|
97
|
+
|
|
98
|
+
# Salvar valores
|
|
99
|
+
item["risco_original"] = round(risco_original, 1)
|
|
100
|
+
item["risco"] = round(novo_risco, 1)
|
|
101
|
+
item["ajuste_aplicado"] = round(ajuste_risco, 1)
|
|
102
|
+
|
|
103
|
+
# Acumular para recalcular risco médio
|
|
104
|
+
soma_risco_area += novo_risco * area
|
|
105
|
+
soma_area += area
|
|
106
|
+
|
|
107
|
+
# Recalcular risco médio ponderado por área
|
|
108
|
+
if soma_area > 0:
|
|
109
|
+
novo_risco_medio = soma_risco_area / soma_area
|
|
110
|
+
resultado_ag["risco_medio_original"] = round(risco_medio_original, 1)
|
|
111
|
+
resultado_ag["risco_medio"] = round(novo_risco_medio, 1)
|
|
74
112
|
|
|
75
|
-
#
|
|
76
|
-
|
|
113
|
+
# Também suportar formato alternativo (plano_otimizado)
|
|
114
|
+
elif "plano_otimizado" in resultado_ag:
|
|
77
115
|
for item in resultado_ag["plano_otimizado"]:
|
|
78
116
|
if "risco" in item:
|
|
79
117
|
risco_original = item["risco"]
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
item["risco"] = round(novo_risco,
|
|
83
|
-
item["
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
resultado_ag["metricas"]["risco_medio_original"] = risco_original
|
|
118
|
+
novo_risco = min(95, max(5, risco_original + ajuste_risco))
|
|
119
|
+
item["risco_original"] = round(risco_original, 1)
|
|
120
|
+
item["risco"] = round(novo_risco, 1)
|
|
121
|
+
item["ajuste_aplicado"] = round(ajuste_risco, 1)
|
|
122
|
+
|
|
123
|
+
# Recalcular métricas se existirem
|
|
124
|
+
if "metricas" in resultado_ag and "risco_medio" in resultado_ag["metricas"]:
|
|
125
|
+
risco_original = resultado_ag["metricas"]["risco_medio"]
|
|
126
|
+
novo_risco = min(95, max(5, risco_original + ajuste_risco))
|
|
127
|
+
resultado_ag["metricas"]["risco_medio_original"] = round(risco_original, 1)
|
|
128
|
+
resultado_ag["metricas"]["risco_medio"] = round(novo_risco, 1)
|
|
92
129
|
|
|
93
130
|
# Marcar que ajuste foi aplicado
|
|
94
131
|
resultado_ag["ajuste_climatico_aplicado"] = True
|
|
@@ -85,7 +85,7 @@ def get_objetivo_description(objetivo):
|
|
|
85
85
|
return descriptions.get(objetivo, "otimizou múltiplos critérios")
|
|
86
86
|
|
|
87
87
|
|
|
88
|
-
def gerar_relatorio_completo(culturas, talhoes, regras, objetivo='equilibrado', formato='md'):
|
|
88
|
+
def gerar_relatorio_completo(culturas, talhoes, regras, objetivo='equilibrado', formato='md', contexto_climatico=None):
|
|
89
89
|
"""
|
|
90
90
|
Gera relatório completo do sistema
|
|
91
91
|
|
|
@@ -95,6 +95,7 @@ def gerar_relatorio_completo(culturas, talhoes, regras, objetivo='equilibrado',
|
|
|
95
95
|
regras: DataFrame com regras
|
|
96
96
|
objetivo: objetivo do AG
|
|
97
97
|
formato: 'md' ou 'txt'
|
|
98
|
+
contexto_climatico: Dicionário com dados climáticos reais (opcional)
|
|
98
99
|
|
|
99
100
|
Returns:
|
|
100
101
|
Caminho do arquivo gerado
|
|
@@ -104,7 +105,13 @@ def gerar_relatorio_completo(culturas, talhoes, regras, objetivo='equilibrado',
|
|
|
104
105
|
cenarios = gerar_cenarios(culturas, talhoes, regras)
|
|
105
106
|
|
|
106
107
|
print(" 🧬 Executando Algoritmo Genético...")
|
|
107
|
-
|
|
108
|
+
# Aplicar contexto climático se disponível
|
|
109
|
+
if contexto_climatico:
|
|
110
|
+
from core.climate_adapter import aplicar_contexto_climatico_no_plano
|
|
111
|
+
resultado_ag = gerar_plano_genetico(culturas, talhoes, regras, objetivo, seed=42)
|
|
112
|
+
resultado_ag = aplicar_contexto_climatico_no_plano(resultado_ag, contexto_climatico)
|
|
113
|
+
else:
|
|
114
|
+
resultado_ag = gerar_plano_genetico(culturas, talhoes, regras, objetivo, seed=42)
|
|
108
115
|
|
|
109
116
|
print(" 🔬 Validando com força bruta...")
|
|
110
117
|
validacao = comparar_ag_com_forca_bruta(culturas, talhoes, regras, objetivo, seed=42)
|
|
@@ -126,6 +133,11 @@ def gerar_relatorio_completo(culturas, talhoes, regras, objetivo='equilibrado',
|
|
|
126
133
|
)
|
|
127
134
|
extensao = 'txt'
|
|
128
135
|
|
|
136
|
+
# Adicionar seção climática se disponível
|
|
137
|
+
if contexto_climatico:
|
|
138
|
+
secao_clima = gerar_secao_climatica(contexto_climatico, formato)
|
|
139
|
+
conteudo += "\n\n" + secao_clima
|
|
140
|
+
|
|
129
141
|
# Salva arquivo
|
|
130
142
|
os.makedirs('reports', exist_ok=True)
|
|
131
143
|
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
|
|
@@ -138,6 +150,74 @@ def gerar_relatorio_completo(culturas, talhoes, regras, objetivo='equilibrado',
|
|
|
138
150
|
return caminho
|
|
139
151
|
|
|
140
152
|
|
|
153
|
+
def gerar_secao_climatica(contexto_climatico, formato='md'):
|
|
154
|
+
"""Gera seção de dados climáticos para o relatório"""
|
|
155
|
+
if not contexto_climatico or contexto_climatico.get("fallback", True):
|
|
156
|
+
if formato == 'md':
|
|
157
|
+
return "## Dados Climáticos\n\nEste relatório foi gerado com base nos dados internos simulados."
|
|
158
|
+
else:
|
|
159
|
+
return "DADOS CLIMÁTICOS\n\nEste relatório foi gerado com base nos dados internos simulados."
|
|
160
|
+
|
|
161
|
+
if formato == 'md':
|
|
162
|
+
secao = "## Dados Climáticos Reais Utilizados\n\n"
|
|
163
|
+
secao += f"**Fonte:** {contexto_climatico.get('fonte', 'N/A')}\n\n"
|
|
164
|
+
|
|
165
|
+
if contexto_climatico.get('temperatura_media'):
|
|
166
|
+
secao += f"**Temperatura Média:** {contexto_climatico['temperatura_media']:.1f}°C\n\n"
|
|
167
|
+
if contexto_climatico.get('temperatura_maxima'):
|
|
168
|
+
secao += f"**Temperatura Máxima:** {contexto_climatico['temperatura_maxima']:.1f}°C\n\n"
|
|
169
|
+
if contexto_climatico.get('temperatura_minima'):
|
|
170
|
+
secao += f"**Temperatura Mínima:** {contexto_climatico['temperatura_minima']:.1f}°C\n\n"
|
|
171
|
+
if contexto_climatico.get('precipitacao_total'):
|
|
172
|
+
secao += f"**Precipitação Total (30 dias):** {contexto_climatico['precipitacao_total']:.1f}mm\n\n"
|
|
173
|
+
if contexto_climatico.get('risco_climatico_estimado'):
|
|
174
|
+
secao += f"**Risco Climático Estimado:** {contexto_climatico['risco_climatico_estimado']}\n\n"
|
|
175
|
+
if contexto_climatico.get('clima_observado'):
|
|
176
|
+
secao += f"**Clima Observado:** {contexto_climatico['clima_observado']}\n\n"
|
|
177
|
+
if contexto_climatico.get('agua_observada'):
|
|
178
|
+
secao += f"**Disponibilidade Hídrica:** {contexto_climatico['agua_observada']}\n\n"
|
|
179
|
+
if contexto_climatico.get('ajuste_risco') is not None:
|
|
180
|
+
ajuste = contexto_climatico['ajuste_risco']
|
|
181
|
+
sinal = '+' if ajuste > 0 else ''
|
|
182
|
+
secao += f"**Ajuste de Risco Aplicado:** {sinal}{ajuste} pontos percentuais\n\n"
|
|
183
|
+
|
|
184
|
+
secao += "**Impacto no Planejamento:**\n\n"
|
|
185
|
+
secao += "Os dados climáticos reais foram integrados ao algoritmo de otimização, "
|
|
186
|
+
secao += "ajustando automaticamente os níveis de risco de cada cultura com base nas "
|
|
187
|
+
secao += "condições meteorológicas observadas. Este ajuste proporciona maior precisão "
|
|
188
|
+
secao += "nas recomendações de plantio.\n"
|
|
189
|
+
else:
|
|
190
|
+
secao = "DADOS CLIMÁTICOS REAIS UTILIZADOS\n\n"
|
|
191
|
+
secao += f"Fonte: {contexto_climatico.get('fonte', 'N/A')}\n\n"
|
|
192
|
+
|
|
193
|
+
if contexto_climatico.get('temperatura_media'):
|
|
194
|
+
secao += f"Temperatura Média: {contexto_climatico['temperatura_media']:.1f}°C\n"
|
|
195
|
+
if contexto_climatico.get('temperatura_maxima'):
|
|
196
|
+
secao += f"Temperatura Máxima: {contexto_climatico['temperatura_maxima']:.1f}°C\n"
|
|
197
|
+
if contexto_climatico.get('temperatura_minima'):
|
|
198
|
+
secao += f"Temperatura Mínima: {contexto_climatico['temperatura_minima']:.1f}°C\n"
|
|
199
|
+
if contexto_climatico.get('precipitacao_total'):
|
|
200
|
+
secao += f"Precipitação Total (30 dias): {contexto_climatico['precipitacao_total']:.1f}mm\n"
|
|
201
|
+
if contexto_climatico.get('risco_climatico_estimado'):
|
|
202
|
+
secao += f"Risco Climático Estimado: {contexto_climatico['risco_climatico_estimado']}\n"
|
|
203
|
+
if contexto_climatico.get('clima_observado'):
|
|
204
|
+
secao += f"Clima Observado: {contexto_climatico['clima_observado']}\n"
|
|
205
|
+
if contexto_climatico.get('agua_observada'):
|
|
206
|
+
secao += f"Disponibilidade Hídrica: {contexto_climatico['agua_observada']}\n"
|
|
207
|
+
if contexto_climatico.get('ajuste_risco') is not None:
|
|
208
|
+
ajuste = contexto_climatico['ajuste_risco']
|
|
209
|
+
sinal = '+' if ajuste > 0 else ''
|
|
210
|
+
secao += f"Ajuste de Risco Aplicado: {sinal}{ajuste} pontos percentuais\n"
|
|
211
|
+
|
|
212
|
+
secao += "\nImpacto no Planejamento:\n\n"
|
|
213
|
+
secao += "Os dados climáticos reais foram integrados ao algoritmo de otimização, "
|
|
214
|
+
secao += "ajustando automaticamente os níveis de risco de cada cultura com base nas "
|
|
215
|
+
secao += "condições meteorológicas observadas. Este ajuste proporciona maior precisão "
|
|
216
|
+
secao += "nas recomendações de plantio.\n"
|
|
217
|
+
|
|
218
|
+
return secao
|
|
219
|
+
|
|
220
|
+
|
|
141
221
|
def gerar_relatorio_markdown(culturas, talhoes, regras, objetivo, cenarios, resultado_ag, validacao, estabilidade):
|
|
142
222
|
"""Gera relatório em formato Markdown"""
|
|
143
223
|
|