agroplan-ai-cli 1.0.25 → 1.0.26
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.
|
|
3
|
-
"backend_template_version": "1.0.
|
|
2
|
+
"cli_version": "1.0.26",
|
|
3
|
+
"backend_template_version": "1.0.26",
|
|
4
4
|
"zarc_index_version": "2025-2026-fast-index-v2",
|
|
5
5
|
"price_index_version": "2025-05-reference-v1",
|
|
6
6
|
"features": [
|
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
"price_provider_index_fallback",
|
|
14
14
|
"price_display_only",
|
|
15
15
|
"price_unit_normalization",
|
|
16
|
-
"market_profit_estimate"
|
|
16
|
+
"market_profit_estimate",
|
|
17
|
+
"market_profit_validation"
|
|
17
18
|
],
|
|
18
|
-
"generated_at": "2026-05-
|
|
19
|
+
"generated_at": "2026-05-09T22:00:00Z"
|
|
19
20
|
}
|
package/backend-template/api.py
CHANGED
|
@@ -523,6 +523,63 @@ def get_precos_comparar(
|
|
|
523
523
|
except Exception as e:
|
|
524
524
|
raise HTTPException(status_code=500, detail=str(e))
|
|
525
525
|
|
|
526
|
+
@app.get("/debug/lucro-mercado")
|
|
527
|
+
def get_debug_lucro_mercado(
|
|
528
|
+
uf: Optional[str] = Query(None, description="Unidade Federativa (ex: SP, PR)"),
|
|
529
|
+
municipio: Optional[str] = Query(None, description="Município"),
|
|
530
|
+
safra: str = Query("2025/2026", description="Safra ZARC")
|
|
531
|
+
):
|
|
532
|
+
"""Diagnóstico detalhado do lucro de mercado para validação"""
|
|
533
|
+
try:
|
|
534
|
+
from core.loader import carregar_dados
|
|
535
|
+
from core.planner import gerar_plano_inteligente
|
|
536
|
+
from core.price_adapter import aplicar_precos_no_plano
|
|
537
|
+
from core.market_profit_validator import gerar_diagnostico_lucro_mercado
|
|
538
|
+
|
|
539
|
+
# Carregar dados
|
|
540
|
+
culturas, talhoes, regras = carregar_dados()
|
|
541
|
+
|
|
542
|
+
# Gerar plano inteligente
|
|
543
|
+
plano_inteligente = gerar_plano_inteligente(culturas, talhoes, regras)
|
|
544
|
+
|
|
545
|
+
# Mapear para formato esperado pelo price_adapter
|
|
546
|
+
plano = []
|
|
547
|
+
for item in plano_inteligente:
|
|
548
|
+
plano.append({
|
|
549
|
+
'talhao': item['talhao'],
|
|
550
|
+
'area': item['area'],
|
|
551
|
+
'solo': item['solo'],
|
|
552
|
+
'clima': item['clima'],
|
|
553
|
+
'relevo': item['relevo'],
|
|
554
|
+
'agua': item['agua'],
|
|
555
|
+
'cultura': item['cultura_recomendada'], # Mapear cultura_recomendada -> cultura
|
|
556
|
+
'lucro_estimado': item['lucro_estimado'],
|
|
557
|
+
'risco': item['risco'],
|
|
558
|
+
'nota': item['nota'],
|
|
559
|
+
'tempo': item['tempo']
|
|
560
|
+
})
|
|
561
|
+
|
|
562
|
+
resultado = {"plano": plano}
|
|
563
|
+
|
|
564
|
+
# Aplicar preços e normalização
|
|
565
|
+
resultado = aplicar_precos_no_plano(resultado, uf=uf)
|
|
566
|
+
|
|
567
|
+
# Gerar diagnóstico
|
|
568
|
+
diagnostico = gerar_diagnostico_lucro_mercado(resultado["plano"], uf=uf)
|
|
569
|
+
|
|
570
|
+
# Adicionar resumo de validação
|
|
571
|
+
validacao = resultado.get("validacao_lucro_mercado", {})
|
|
572
|
+
|
|
573
|
+
return {
|
|
574
|
+
"diagnostico": diagnostico,
|
|
575
|
+
"validacao_resumo": validacao,
|
|
576
|
+
"municipio": municipio,
|
|
577
|
+
"safra": safra
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
except Exception as e:
|
|
581
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
582
|
+
|
|
526
583
|
@app.get("/dashboard")
|
|
527
584
|
def get_dashboard(
|
|
528
585
|
lat: Optional[float] = None,
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Validador de Lucro de Mercado
|
|
3
|
+
|
|
4
|
+
Classifica confiabilidade dos valores de lucro de mercado comparando com lucro do sistema.
|
|
5
|
+
Detecta distorções e fornece diagnóstico para validação antes de ativar recálculo automático.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import Dict, List, Optional
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def calcular_diferenca_lucro(lucro_sistema: float, lucro_mercado: float) -> Dict:
|
|
12
|
+
"""
|
|
13
|
+
Calcula diferença entre lucro do sistema e lucro de mercado.
|
|
14
|
+
|
|
15
|
+
Args:
|
|
16
|
+
lucro_sistema: Lucro calculado com base interna
|
|
17
|
+
lucro_mercado: Lucro calculado com preços de mercado normalizados
|
|
18
|
+
|
|
19
|
+
Returns:
|
|
20
|
+
Dict com diferença absoluta, percentual e direção
|
|
21
|
+
"""
|
|
22
|
+
diferenca_absoluta = lucro_mercado - lucro_sistema
|
|
23
|
+
|
|
24
|
+
# Calcular percentual baseado no valor absoluto do lucro sistema
|
|
25
|
+
# para evitar divisão por zero e lidar com valores negativos
|
|
26
|
+
if lucro_sistema != 0:
|
|
27
|
+
diferenca_percentual = (diferenca_absoluta / abs(lucro_sistema)) * 100
|
|
28
|
+
else:
|
|
29
|
+
diferenca_percentual = 0 if lucro_mercado == 0 else float('inf')
|
|
30
|
+
|
|
31
|
+
# Determinar direção
|
|
32
|
+
if abs(diferenca_percentual) < 5:
|
|
33
|
+
direcao = "igual"
|
|
34
|
+
elif diferenca_absoluta > 0:
|
|
35
|
+
direcao = "maior"
|
|
36
|
+
else:
|
|
37
|
+
direcao = "menor"
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
"diferenca_absoluta": round(diferenca_absoluta, 2),
|
|
41
|
+
"diferenca_percentual": round(diferenca_percentual, 2),
|
|
42
|
+
"direcao": direcao
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def classificar_confiabilidade_lucro(item: Dict) -> Dict:
|
|
47
|
+
"""
|
|
48
|
+
Classifica confiabilidade do lucro de mercado para um item do plano.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
item: Item do plano com dados de lucro
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
Dict com confiabilidade (alta/media/baixa) e motivos
|
|
55
|
+
"""
|
|
56
|
+
motivos = []
|
|
57
|
+
|
|
58
|
+
# Verificar se tem preço normalizado
|
|
59
|
+
preco_norm = item.get("preco_normalizado", {})
|
|
60
|
+
if not preco_norm.get("normalizado"):
|
|
61
|
+
return {
|
|
62
|
+
"confiabilidade": "baixa",
|
|
63
|
+
"motivos": ["Preço não normalizado ou não disponível"]
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
# Verificar se tem produtividade e custo
|
|
67
|
+
produtividade = item.get("produtividade", 0)
|
|
68
|
+
custo = item.get("custo", 0)
|
|
69
|
+
|
|
70
|
+
if produtividade <= 0:
|
|
71
|
+
motivos.append("Produtividade não disponível ou inválida")
|
|
72
|
+
|
|
73
|
+
if custo <= 0:
|
|
74
|
+
motivos.append("Custo não disponível ou inválido")
|
|
75
|
+
|
|
76
|
+
if motivos:
|
|
77
|
+
return {
|
|
78
|
+
"confiabilidade": "baixa",
|
|
79
|
+
"motivos": motivos
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
# Verificar se tem lucro de mercado calculado
|
|
83
|
+
lucro_mercado = item.get("lucro_mercado_estimado")
|
|
84
|
+
if lucro_mercado is None:
|
|
85
|
+
return {
|
|
86
|
+
"confiabilidade": "baixa",
|
|
87
|
+
"motivos": ["Lucro de mercado não calculado"]
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
# Calcular diferença com lucro do sistema
|
|
91
|
+
lucro_sistema = item.get("lucro_estimado", 0)
|
|
92
|
+
diferenca = calcular_diferenca_lucro(lucro_sistema, lucro_mercado)
|
|
93
|
+
diferenca_percentual = abs(diferenca["diferenca_percentual"])
|
|
94
|
+
|
|
95
|
+
# Classificar baseado na diferença percentual
|
|
96
|
+
if diferenca_percentual > 100:
|
|
97
|
+
confiabilidade = "baixa"
|
|
98
|
+
motivos.append(f"Diferença muito alta ({diferenca_percentual:.1f}%) entre lucro sistema e mercado")
|
|
99
|
+
elif diferenca_percentual > 50:
|
|
100
|
+
confiabilidade = "media"
|
|
101
|
+
motivos.append(f"Diferença moderada ({diferenca_percentual:.1f}%) entre lucro sistema e mercado")
|
|
102
|
+
else:
|
|
103
|
+
confiabilidade = "alta"
|
|
104
|
+
motivos.append(f"Diferença aceitável ({diferenca_percentual:.1f}%) entre lucro sistema e mercado")
|
|
105
|
+
|
|
106
|
+
# Verificar se lucro de mercado é negativo (prejuízo)
|
|
107
|
+
if lucro_mercado < 0:
|
|
108
|
+
if confiabilidade == "alta":
|
|
109
|
+
confiabilidade = "media"
|
|
110
|
+
motivos.append("Lucro de mercado indica prejuízo - requer validação de preço/produtividade")
|
|
111
|
+
|
|
112
|
+
# Verificar se é fallback
|
|
113
|
+
preco_real = item.get("preco_real", {})
|
|
114
|
+
if preco_real.get("fallback"):
|
|
115
|
+
if confiabilidade == "alta":
|
|
116
|
+
confiabilidade = "media"
|
|
117
|
+
motivos.append("Preço usando fallback (referência) - pode não refletir mercado local")
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
"confiabilidade": confiabilidade,
|
|
121
|
+
"motivos": motivos,
|
|
122
|
+
"diferenca": diferenca
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def validar_plano_lucro_mercado(resultado: Dict) -> Dict:
|
|
127
|
+
"""
|
|
128
|
+
Valida lucro de mercado para todo o plano e adiciona classificação de confiabilidade.
|
|
129
|
+
|
|
130
|
+
Args:
|
|
131
|
+
resultado: Resultado do AG ou cenário com plano
|
|
132
|
+
|
|
133
|
+
Returns:
|
|
134
|
+
Resultado enriquecido com validação de lucro de mercado
|
|
135
|
+
"""
|
|
136
|
+
if "plano" not in resultado:
|
|
137
|
+
return resultado
|
|
138
|
+
|
|
139
|
+
# Contadores
|
|
140
|
+
alta_confiabilidade = 0
|
|
141
|
+
media_confiabilidade = 0
|
|
142
|
+
baixa_confiabilidade = 0
|
|
143
|
+
alertas = []
|
|
144
|
+
|
|
145
|
+
# Validar cada item do plano
|
|
146
|
+
for item in resultado["plano"]:
|
|
147
|
+
# Classificar confiabilidade
|
|
148
|
+
validacao = classificar_confiabilidade_lucro(item)
|
|
149
|
+
item["validacao_lucro_mercado"] = validacao
|
|
150
|
+
|
|
151
|
+
# Contabilizar
|
|
152
|
+
conf = validacao["confiabilidade"]
|
|
153
|
+
if conf == "alta":
|
|
154
|
+
alta_confiabilidade += 1
|
|
155
|
+
elif conf == "media":
|
|
156
|
+
media_confiabilidade += 1
|
|
157
|
+
else:
|
|
158
|
+
baixa_confiabilidade += 1
|
|
159
|
+
# Adicionar alerta para baixa confiabilidade
|
|
160
|
+
cultura = item.get("cultura", "desconhecida")
|
|
161
|
+
talhao = item.get("talhao", "?")
|
|
162
|
+
alertas.append(f"Talhão {talhao} ({cultura}): {', '.join(validacao['motivos'])}")
|
|
163
|
+
|
|
164
|
+
# Adicionar resumo de validação
|
|
165
|
+
total = len(resultado["plano"])
|
|
166
|
+
percentual_alta = (alta_confiabilidade / total * 100) if total > 0 else 0
|
|
167
|
+
percentual_baixa = (baixa_confiabilidade / total * 100) if total > 0 else 0
|
|
168
|
+
|
|
169
|
+
resultado["validacao_lucro_mercado"] = {
|
|
170
|
+
"ativo": True,
|
|
171
|
+
"total_itens": total,
|
|
172
|
+
"itens_alta_confiabilidade": alta_confiabilidade,
|
|
173
|
+
"itens_media_confiabilidade": media_confiabilidade,
|
|
174
|
+
"itens_baixa_confiabilidade": baixa_confiabilidade,
|
|
175
|
+
"percentual_alta_confiabilidade": round(percentual_alta, 1),
|
|
176
|
+
"percentual_baixa_confiabilidade": round(percentual_baixa, 1),
|
|
177
|
+
"alertas": alertas[:5], # Limitar a 5 alertas principais
|
|
178
|
+
"total_alertas": len(alertas),
|
|
179
|
+
"recomendacao": _gerar_recomendacao(percentual_alta, percentual_baixa)
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return resultado
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def _gerar_recomendacao(percentual_alta: float, percentual_baixa: float) -> str:
|
|
186
|
+
"""
|
|
187
|
+
Gera recomendação baseada nos percentuais de confiabilidade.
|
|
188
|
+
|
|
189
|
+
Args:
|
|
190
|
+
percentual_alta: Percentual de itens com alta confiabilidade
|
|
191
|
+
percentual_baixa: Percentual de itens com baixa confiabilidade
|
|
192
|
+
|
|
193
|
+
Returns:
|
|
194
|
+
String com recomendação
|
|
195
|
+
"""
|
|
196
|
+
if percentual_alta >= 70:
|
|
197
|
+
return "Lucro de mercado apresenta boa confiabilidade. Considere validação detalhada antes de ativar PRICE_APPLY_TO_PROFIT."
|
|
198
|
+
elif percentual_baixa >= 50:
|
|
199
|
+
return "Muitos itens com baixa confiabilidade. Valide preços, produtividades e custos antes de usar lucro de mercado."
|
|
200
|
+
else:
|
|
201
|
+
return "Confiabilidade mista. Revise itens com baixa confiabilidade e valide dados de mercado."
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def gerar_diagnostico_lucro_mercado(plano: List[Dict], uf: Optional[str] = None) -> Dict:
|
|
205
|
+
"""
|
|
206
|
+
Gera diagnóstico detalhado do lucro de mercado para análise.
|
|
207
|
+
|
|
208
|
+
Args:
|
|
209
|
+
plano: Lista de itens do plano
|
|
210
|
+
uf: Unidade Federativa (opcional)
|
|
211
|
+
|
|
212
|
+
Returns:
|
|
213
|
+
Dict com diagnóstico por cultura
|
|
214
|
+
"""
|
|
215
|
+
diagnostico = {
|
|
216
|
+
"uf": uf,
|
|
217
|
+
"total_culturas": 0,
|
|
218
|
+
"culturas": {}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
# Agrupar por cultura
|
|
222
|
+
culturas_map = {}
|
|
223
|
+
for item in plano:
|
|
224
|
+
cultura = item.get("cultura", "desconhecida")
|
|
225
|
+
|
|
226
|
+
if cultura not in culturas_map:
|
|
227
|
+
culturas_map[cultura] = []
|
|
228
|
+
|
|
229
|
+
culturas_map[cultura].append(item)
|
|
230
|
+
|
|
231
|
+
# Gerar diagnóstico por cultura
|
|
232
|
+
for cultura, itens in culturas_map.items():
|
|
233
|
+
# Calcular médias
|
|
234
|
+
lucro_sistema_total = sum(i.get("lucro_estimado", 0) for i in itens)
|
|
235
|
+
lucro_mercado_total = sum(i.get("lucro_mercado_estimado", 0) for i in itens if i.get("lucro_mercado_estimado") is not None)
|
|
236
|
+
|
|
237
|
+
lucro_sistema_medio = lucro_sistema_total / len(itens) if itens else 0
|
|
238
|
+
lucro_mercado_medio = lucro_mercado_total / len(itens) if itens else 0
|
|
239
|
+
|
|
240
|
+
# Calcular diferença
|
|
241
|
+
diferenca = calcular_diferenca_lucro(lucro_sistema_medio, lucro_mercado_medio)
|
|
242
|
+
|
|
243
|
+
# Obter confiabilidade do primeiro item (representativo)
|
|
244
|
+
validacao = classificar_confiabilidade_lucro(itens[0]) if itens else {"confiabilidade": "baixa", "motivos": []}
|
|
245
|
+
|
|
246
|
+
# Obter preço normalizado
|
|
247
|
+
preco_norm = itens[0].get("preco_normalizado", {}) if itens else {}
|
|
248
|
+
preco_real = itens[0].get("preco_real", {}) if itens else {}
|
|
249
|
+
|
|
250
|
+
diagnostico["culturas"][cultura] = {
|
|
251
|
+
"total_talhoes": len(itens),
|
|
252
|
+
"lucro_sistema_medio": round(lucro_sistema_medio, 2),
|
|
253
|
+
"lucro_mercado_medio": round(lucro_mercado_medio, 2),
|
|
254
|
+
"diferenca": diferenca,
|
|
255
|
+
"confiabilidade": validacao["confiabilidade"],
|
|
256
|
+
"motivos": validacao["motivos"],
|
|
257
|
+
"preco_original": preco_real.get("preco"),
|
|
258
|
+
"unidade_original": preco_real.get("unidade"),
|
|
259
|
+
"preco_por_tonelada": preco_norm.get("preco_por_tonelada"),
|
|
260
|
+
"normalizado": preco_norm.get("normalizado", False),
|
|
261
|
+
"fallback": preco_real.get("fallback", False)
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
diagnostico["total_culturas"] = len(culturas_map)
|
|
265
|
+
|
|
266
|
+
return diagnostico
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Adaptador de Preços Agrícolas
|
|
3
|
-
Integra preços reais no planejamento com normalização de unidades
|
|
3
|
+
Integra preços reais no planejamento com normalização de unidades e validação
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
6
|
import os
|
|
@@ -12,6 +12,7 @@ from core.price_normalizer import (
|
|
|
12
12
|
calcular_lucro_com_preco_normalizado,
|
|
13
13
|
obter_estatisticas_normalizacao
|
|
14
14
|
)
|
|
15
|
+
from core.market_profit_validator import validar_plano_lucro_mercado
|
|
15
16
|
|
|
16
17
|
# Configuração
|
|
17
18
|
PRICE_APPLY_TO_PROFIT = os.getenv("PRICE_APPLY_TO_PROFIT", "false").lower() == "true"
|
|
@@ -171,6 +172,9 @@ def aplicar_precos_no_plano(
|
|
|
171
172
|
}
|
|
172
173
|
}
|
|
173
174
|
|
|
175
|
+
# Validar lucro de mercado e adicionar classificação de confiabilidade
|
|
176
|
+
resultado = validar_plano_lucro_mercado(resultado)
|
|
177
|
+
|
|
174
178
|
return resultado
|
|
175
179
|
|
|
176
180
|
|
|
@@ -169,6 +169,11 @@ def gerar_relatorio_completo(culturas, talhoes, regras, objetivo='equilibrado',
|
|
|
169
169
|
secao_precos = gerar_secao_precos_relatorio(resultado_temp["plano"], uf, formato)
|
|
170
170
|
conteudo += "\n\n" + secao_precos
|
|
171
171
|
|
|
172
|
+
# Adicionar seção de validação de lucro de mercado
|
|
173
|
+
if resultado_temp.get("validacao_lucro_mercado", {}).get("ativo"):
|
|
174
|
+
secao_validacao = gerar_secao_validacao_lucro_mercado(resultado_temp, formato)
|
|
175
|
+
conteudo += "\n\n" + secao_validacao
|
|
176
|
+
|
|
172
177
|
# Salva arquivo
|
|
173
178
|
os.makedirs('reports', exist_ok=True)
|
|
174
179
|
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
|
|
@@ -249,6 +254,193 @@ def gerar_secao_climatica(contexto_climatico, formato='md'):
|
|
|
249
254
|
return secao
|
|
250
255
|
|
|
251
256
|
|
|
257
|
+
def gerar_secao_validacao_lucro_mercado(resultado, formato='md'):
|
|
258
|
+
"""
|
|
259
|
+
Gera seção de validação de lucro de mercado para o relatório
|
|
260
|
+
|
|
261
|
+
Args:
|
|
262
|
+
resultado: Resultado com validacao_lucro_mercado
|
|
263
|
+
formato: 'md' ou 'txt'
|
|
264
|
+
|
|
265
|
+
Returns:
|
|
266
|
+
String com seção formatada
|
|
267
|
+
"""
|
|
268
|
+
validacao = resultado.get("validacao_lucro_mercado", {})
|
|
269
|
+
|
|
270
|
+
if not validacao.get("ativo"):
|
|
271
|
+
return ""
|
|
272
|
+
|
|
273
|
+
if formato == "md":
|
|
274
|
+
secao = "## 🔍 Validação do Lucro de Mercado\n\n"
|
|
275
|
+
|
|
276
|
+
secao += "### Resumo de Confiabilidade\n\n"
|
|
277
|
+
|
|
278
|
+
total = validacao.get("total_itens", 0)
|
|
279
|
+
alta = validacao.get("itens_alta_confiabilidade", 0)
|
|
280
|
+
media = validacao.get("itens_media_confiabilidade", 0)
|
|
281
|
+
baixa = validacao.get("itens_baixa_confiabilidade", 0)
|
|
282
|
+
|
|
283
|
+
perc_alta = validacao.get("percentual_alta_confiabilidade", 0)
|
|
284
|
+
perc_baixa = validacao.get("percentual_baixa_confiabilidade", 0)
|
|
285
|
+
|
|
286
|
+
secao += f"- **Total de itens analisados**: {total}\n"
|
|
287
|
+
secao += f"- **Alta confiabilidade**: {alta} ({perc_alta:.1f}%) 🟢\n"
|
|
288
|
+
secao += f"- **Média confiabilidade**: {media} ({100 - perc_alta - perc_baixa:.1f}%) 🟡\n"
|
|
289
|
+
secao += f"- **Baixa confiabilidade**: {baixa} ({perc_baixa:.1f}%) 🔴\n\n"
|
|
290
|
+
|
|
291
|
+
# Recomendação
|
|
292
|
+
recomendacao = validacao.get("recomendacao", "")
|
|
293
|
+
if recomendacao:
|
|
294
|
+
secao += f"**Recomendação**: {recomendacao}\n\n"
|
|
295
|
+
|
|
296
|
+
# Alertas
|
|
297
|
+
alertas = validacao.get("alertas", [])
|
|
298
|
+
if alertas:
|
|
299
|
+
secao += "### ⚠️ Alertas\n\n"
|
|
300
|
+
for alerta in alertas:
|
|
301
|
+
secao += f"- {alerta}\n"
|
|
302
|
+
secao += "\n"
|
|
303
|
+
|
|
304
|
+
total_alertas = validacao.get("total_alertas", 0)
|
|
305
|
+
if total_alertas > len(alertas):
|
|
306
|
+
secao += f"*Mostrando {len(alertas)} de {total_alertas} alertas*\n\n"
|
|
307
|
+
|
|
308
|
+
# Detalhes por item
|
|
309
|
+
secao += "### Detalhes por Talhão\n\n"
|
|
310
|
+
secao += "| Talhão | Cultura | Lucro Sistema | Lucro Mercado | Diferença % | Confiabilidade |\n"
|
|
311
|
+
secao += "|--------|---------|---------------|---------------|-------------|----------------|\n"
|
|
312
|
+
|
|
313
|
+
for item in resultado["plano"]:
|
|
314
|
+
validacao_item = item.get("validacao_lucro_mercado", {})
|
|
315
|
+
if not validacao_item:
|
|
316
|
+
continue
|
|
317
|
+
|
|
318
|
+
talhao = item.get("talhao", "N/A")
|
|
319
|
+
cultura = item.get("cultura", "").upper()
|
|
320
|
+
lucro_sistema = item.get("lucro_estimado", 0)
|
|
321
|
+
lucro_mercado = item.get("lucro_mercado_estimado", 0)
|
|
322
|
+
|
|
323
|
+
lucro_sistema_fmt = f"R$ {lucro_sistema:,.2f}".replace(",", "X").replace(".", ",").replace("X", ".")
|
|
324
|
+
|
|
325
|
+
if lucro_mercado is not None:
|
|
326
|
+
lucro_mercado_fmt = f"R$ {lucro_mercado:,.2f}".replace(",", "X").replace(".", ",").replace("X", ".")
|
|
327
|
+
diferenca = validacao_item.get("diferenca", {})
|
|
328
|
+
diferenca_perc = diferenca.get("diferenca_percentual", 0)
|
|
329
|
+
diferenca_fmt = f"{diferenca_perc:.1f}%"
|
|
330
|
+
else:
|
|
331
|
+
lucro_mercado_fmt = "N/A"
|
|
332
|
+
diferenca_fmt = "N/A"
|
|
333
|
+
|
|
334
|
+
confiabilidade = validacao_item.get("confiabilidade", "baixa")
|
|
335
|
+
conf_emoji = "🟢" if confiabilidade == "alta" else "🟡" if confiabilidade == "media" else "🔴"
|
|
336
|
+
conf_label = confiabilidade.title()
|
|
337
|
+
|
|
338
|
+
secao += f"| {talhao} | {cultura} | {lucro_sistema_fmt} | {lucro_mercado_fmt} | {diferenca_fmt} | {conf_emoji} {conf_label} |\n"
|
|
339
|
+
|
|
340
|
+
secao += "\n"
|
|
341
|
+
|
|
342
|
+
# Explicação
|
|
343
|
+
secao += "### 📊 Sobre a Classificação de Confiabilidade\n\n"
|
|
344
|
+
secao += "A confiabilidade do lucro de mercado é classificada com base em:\n\n"
|
|
345
|
+
secao += "- **Alta (🟢)**: Diferença < 50% entre lucro sistema e mercado, preço normalizado disponível\n"
|
|
346
|
+
secao += "- **Média (🟡)**: Diferença 50-100%, uso de fallback, ou lucro negativo\n"
|
|
347
|
+
secao += "- **Baixa (🔴)**: Diferença > 100%, dados incompletos, ou preço não disponível\n\n"
|
|
348
|
+
|
|
349
|
+
secao += "### ⚠️ Aviso Importante\n\n"
|
|
350
|
+
secao += "**O lucro de mercado ainda é experimental e não substitui o lucro principal do sistema.**\n\n"
|
|
351
|
+
secao += "Os valores são exibidos apenas como comparação para validação. Diferenças altas indicam "
|
|
352
|
+
secao += "necessidade de validação de produtividade, custos ou unidade comercial.\n\n"
|
|
353
|
+
secao += "**Status atual**: `PRICE_APPLY_TO_PROFIT=false` (lucro de mercado não afeta otimização)\n"
|
|
354
|
+
|
|
355
|
+
else: # txt
|
|
356
|
+
secao = "VALIDAÇÃO DO LUCRO DE MERCADO\n\n"
|
|
357
|
+
|
|
358
|
+
secao += "Resumo de Confiabilidade:\n\n"
|
|
359
|
+
|
|
360
|
+
total = validacao.get("total_itens", 0)
|
|
361
|
+
alta = validacao.get("itens_alta_confiabilidade", 0)
|
|
362
|
+
media = validacao.get("itens_media_confiabilidade", 0)
|
|
363
|
+
baixa = validacao.get("itens_baixa_confiabilidade", 0)
|
|
364
|
+
|
|
365
|
+
perc_alta = validacao.get("percentual_alta_confiabilidade", 0)
|
|
366
|
+
perc_baixa = validacao.get("percentual_baixa_confiabilidade", 0)
|
|
367
|
+
|
|
368
|
+
secao += f" Total de itens analisados: {total}\n"
|
|
369
|
+
secao += f" Alta confiabilidade: {alta} ({perc_alta:.1f}%)\n"
|
|
370
|
+
secao += f" Média confiabilidade: {media} ({100 - perc_alta - perc_baixa:.1f}%)\n"
|
|
371
|
+
secao += f" Baixa confiabilidade: {baixa} ({perc_baixa:.1f}%)\n\n"
|
|
372
|
+
|
|
373
|
+
# Recomendação
|
|
374
|
+
recomendacao = validacao.get("recomendacao", "")
|
|
375
|
+
if recomendacao:
|
|
376
|
+
secao += f"Recomendação: {recomendacao}\n\n"
|
|
377
|
+
|
|
378
|
+
# Alertas
|
|
379
|
+
alertas = validacao.get("alertas", [])
|
|
380
|
+
if alertas:
|
|
381
|
+
secao += "Alertas:\n\n"
|
|
382
|
+
for alerta in alertas:
|
|
383
|
+
secao += f" - {alerta}\n"
|
|
384
|
+
secao += "\n"
|
|
385
|
+
|
|
386
|
+
total_alertas = validacao.get("total_alertas", 0)
|
|
387
|
+
if total_alertas > len(alertas):
|
|
388
|
+
secao += f" (Mostrando {len(alertas)} de {total_alertas} alertas)\n\n"
|
|
389
|
+
|
|
390
|
+
# Detalhes por item
|
|
391
|
+
secao += "Detalhes por Talhão:\n\n"
|
|
392
|
+
|
|
393
|
+
for item in resultado["plano"]:
|
|
394
|
+
validacao_item = item.get("validacao_lucro_mercado", {})
|
|
395
|
+
if not validacao_item:
|
|
396
|
+
continue
|
|
397
|
+
|
|
398
|
+
talhao = item.get("talhao", "N/A")
|
|
399
|
+
cultura = item.get("cultura", "").upper()
|
|
400
|
+
lucro_sistema = item.get("lucro_estimado", 0)
|
|
401
|
+
lucro_mercado = item.get("lucro_mercado_estimado", 0)
|
|
402
|
+
|
|
403
|
+
lucro_sistema_fmt = f"R$ {lucro_sistema:,.2f}".replace(",", "X").replace(".", ",").replace("X", ".")
|
|
404
|
+
|
|
405
|
+
secao += f" Talhão {talhao} ({cultura}):\n"
|
|
406
|
+
secao += f" Lucro Sistema: {lucro_sistema_fmt}\n"
|
|
407
|
+
|
|
408
|
+
if lucro_mercado is not None:
|
|
409
|
+
lucro_mercado_fmt = f"R$ {lucro_mercado:,.2f}".replace(",", "X").replace(".", ",").replace("X", ".")
|
|
410
|
+
diferenca = validacao_item.get("diferenca", {})
|
|
411
|
+
diferenca_perc = diferenca.get("diferenca_percentual", 0)
|
|
412
|
+
secao += f" Lucro Mercado: {lucro_mercado_fmt}\n"
|
|
413
|
+
secao += f" Diferença: {diferenca_perc:.1f}%\n"
|
|
414
|
+
else:
|
|
415
|
+
secao += f" Lucro Mercado: N/A\n"
|
|
416
|
+
|
|
417
|
+
confiabilidade = validacao_item.get("confiabilidade", "baixa")
|
|
418
|
+
secao += f" Confiabilidade: {confiabilidade.title()}\n"
|
|
419
|
+
|
|
420
|
+
motivos = validacao_item.get("motivos", [])
|
|
421
|
+
if motivos:
|
|
422
|
+
secao += f" Motivos: {', '.join(motivos)}\n"
|
|
423
|
+
|
|
424
|
+
secao += "\n"
|
|
425
|
+
|
|
426
|
+
# Explicação
|
|
427
|
+
secao += "Sobre a Classificação de Confiabilidade:\n\n"
|
|
428
|
+
secao += "A confiabilidade do lucro de mercado é classificada com base em:\n\n"
|
|
429
|
+
secao += " - Alta: Diferença < 50% entre lucro sistema e mercado\n"
|
|
430
|
+
secao += " - Média: Diferença 50-100%, uso de fallback, ou lucro negativo\n"
|
|
431
|
+
secao += " - Baixa: Diferença > 100%, dados incompletos, ou preço não disponível\n\n"
|
|
432
|
+
|
|
433
|
+
secao += "Aviso Importante:\n\n"
|
|
434
|
+
secao += "O lucro de mercado ainda é experimental e não substitui o lucro principal\n"
|
|
435
|
+
secao += "do sistema. Os valores são exibidos apenas como comparação para validação.\n"
|
|
436
|
+
secao += "Diferenças altas indicam necessidade de validação de produtividade, custos\n"
|
|
437
|
+
secao += "ou unidade comercial.\n\n"
|
|
438
|
+
secao += "Status atual: PRICE_APPLY_TO_PROFIT=false (lucro de mercado não afeta\n"
|
|
439
|
+
secao += "otimização)\n"
|
|
440
|
+
|
|
441
|
+
return secao
|
|
442
|
+
|
|
443
|
+
|
|
252
444
|
def gerar_relatorio_markdown(culturas, talhoes, regras, objetivo, cenarios, resultado_ag, validacao, estabilidade):
|
|
253
445
|
"""Gera relatório em formato Markdown"""
|
|
254
446
|
|