agroplan-ai-cli 1.0.25 → 1.0.27

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.25",
3
- "backend_template_version": "1.0.25",
2
+ "cli_version": "1.0.27",
3
+ "backend_template_version": "1.0.27",
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,9 @@
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",
18
+ "market_profit_confidence_refinement"
17
19
  ],
18
- "generated_at": "2026-05-09T21:00:00Z"
20
+ "generated_at": "2026-05-09T23:00:00Z"
19
21
  }
@@ -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,315 @@
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
+ Critérios refinados (Fase 9.5):
51
+ - Baixa: diferença >= 150%, dados incompletos, lucro invertido, fallback com diferença >= 100%
52
+ - Média: diferença 50-150%, fallback, lucro negativo
53
+ - Alta: diferença < 50%, sem fallback, dados completos
54
+
55
+ Args:
56
+ item: Item do plano com dados de lucro
57
+
58
+ Returns:
59
+ Dict com confiabilidade (alta/media/baixa), motivos e flag crítico
60
+ """
61
+ motivos = []
62
+ critico = False
63
+
64
+ # Verificar se tem preço normalizado
65
+ preco_norm = item.get("preco_normalizado", {})
66
+ if not preco_norm.get("normalizado"):
67
+ return {
68
+ "confiabilidade": "baixa",
69
+ "motivos": ["Preço não normalizado ou não disponível"],
70
+ "critico": True
71
+ }
72
+
73
+ # Verificar se tem produtividade e custo
74
+ produtividade = item.get("produtividade", 0)
75
+ custo = item.get("custo", 0)
76
+
77
+ if produtividade <= 0:
78
+ motivos.append("Produtividade não disponível ou inválida")
79
+
80
+ if custo <= 0:
81
+ motivos.append("Custo não disponível ou inválido")
82
+
83
+ if motivos:
84
+ return {
85
+ "confiabilidade": "baixa",
86
+ "motivos": motivos,
87
+ "critico": True
88
+ }
89
+
90
+ # Verificar se tem lucro de mercado calculado
91
+ lucro_mercado = item.get("lucro_mercado_estimado")
92
+ if lucro_mercado is None:
93
+ return {
94
+ "confiabilidade": "baixa",
95
+ "motivos": ["Lucro de mercado não calculado"],
96
+ "critico": True
97
+ }
98
+
99
+ # Calcular diferença com lucro do sistema
100
+ lucro_sistema = item.get("lucro_estimado", 0)
101
+ diferenca = calcular_diferenca_lucro(lucro_sistema, lucro_mercado)
102
+ diferenca_percentual = abs(diferenca["diferenca_percentual"])
103
+
104
+ # Verificar se é fallback
105
+ preco_real = item.get("preco_real", {})
106
+ is_fallback = preco_real.get("fallback", False)
107
+
108
+ # Verificar inversão de sinal (lucro vira prejuízo ou vice-versa)
109
+ lucro_invertido = (lucro_sistema > 0 and lucro_mercado < 0) or (lucro_sistema < 0 and lucro_mercado > 0)
110
+
111
+ # CRITÉRIOS REFINADOS (Fase 9.5)
112
+
113
+ # Baixa confiabilidade (crítico)
114
+ if diferenca_percentual >= 150:
115
+ confiabilidade = "baixa"
116
+ motivos.append(f"Diferença extrema ({diferenca_percentual:.1f}%) entre lucro sistema e mercado")
117
+ critico = True
118
+ elif lucro_invertido and lucro_sistema > 0:
119
+ confiabilidade = "baixa"
120
+ motivos.append("Lucro do sistema é positivo mas lucro de mercado indica prejuízo")
121
+ critico = True
122
+ elif is_fallback and diferenca_percentual >= 100:
123
+ confiabilidade = "baixa"
124
+ motivos.append(f"Preço de fallback com diferença muito alta ({diferenca_percentual:.1f}%)")
125
+ critico = True
126
+ elif diferenca_percentual >= 100:
127
+ # Diferença > 100% mesmo sem fallback é baixa confiabilidade
128
+ confiabilidade = "baixa"
129
+ motivos.append(f"Diferença muito alta ({diferenca_percentual:.1f}%) entre lucro sistema e mercado")
130
+ critico = False # Não crítico se não for fallback
131
+
132
+ # Média confiabilidade
133
+ elif diferenca_percentual >= 50:
134
+ confiabilidade = "media"
135
+ motivos.append(f"Diferença moderada ({diferenca_percentual:.1f}%) entre lucro sistema e mercado")
136
+ elif is_fallback:
137
+ confiabilidade = "media"
138
+ motivos.append("Preço de referência (fallback) - pode não refletir mercado local")
139
+ elif lucro_mercado < 0:
140
+ confiabilidade = "media"
141
+ motivos.append("Lucro de mercado indica prejuízo - requer validação")
142
+
143
+ # Alta confiabilidade
144
+ else:
145
+ confiabilidade = "alta"
146
+ motivos.append(f"Diferença aceitável ({diferenca_percentual:.1f}%) entre lucro sistema e mercado")
147
+ if not is_fallback:
148
+ motivos.append("Preço real disponível (não fallback)")
149
+
150
+ # Adicionar informação sobre normalização bem-sucedida
151
+ if preco_norm.get("normalizado") and confiabilidade != "baixa":
152
+ motivos.append("Unidade comercial normalizada com sucesso")
153
+
154
+ return {
155
+ "confiabilidade": confiabilidade,
156
+ "motivos": motivos,
157
+ "diferenca": diferenca,
158
+ "critico": critico
159
+ }
160
+
161
+
162
+ def validar_plano_lucro_mercado(resultado: Dict) -> Dict:
163
+ """
164
+ Valida lucro de mercado para todo o plano e adiciona classificação de confiabilidade.
165
+
166
+ Args:
167
+ resultado: Resultado do AG ou cenário com plano
168
+
169
+ Returns:
170
+ Resultado enriquecido com validação de lucro de mercado
171
+ """
172
+ if "plano" not in resultado:
173
+ return resultado
174
+
175
+ # Contadores
176
+ alta_confiabilidade = 0
177
+ media_confiabilidade = 0
178
+ baixa_confiabilidade = 0
179
+ itens_criticos = 0
180
+ alertas = []
181
+
182
+ # Validar cada item do plano
183
+ for item in resultado["plano"]:
184
+ # Classificar confiabilidade
185
+ validacao = classificar_confiabilidade_lucro(item)
186
+ item["validacao_lucro_mercado"] = validacao
187
+
188
+ # Contabilizar
189
+ conf = validacao["confiabilidade"]
190
+ if conf == "alta":
191
+ alta_confiabilidade += 1
192
+ elif conf == "media":
193
+ media_confiabilidade += 1
194
+ else:
195
+ baixa_confiabilidade += 1
196
+ # Adicionar alerta para baixa confiabilidade
197
+ cultura = item.get("cultura", "desconhecida")
198
+ talhao = item.get("talhao", "?")
199
+ alertas.append(f"Talhão {talhao} ({cultura}): {', '.join(validacao['motivos'])}")
200
+
201
+ # Contabilizar itens críticos
202
+ if validacao.get("critico", False):
203
+ itens_criticos += 1
204
+
205
+ # Adicionar resumo de validação
206
+ total = len(resultado["plano"])
207
+ percentual_alta = (alta_confiabilidade / total * 100) if total > 0 else 0
208
+ percentual_baixa = (baixa_confiabilidade / total * 100) if total > 0 else 0
209
+ percentual_critico = (itens_criticos / total * 100) if total > 0 else 0
210
+
211
+ resultado["validacao_lucro_mercado"] = {
212
+ "ativo": True,
213
+ "total_itens": total,
214
+ "itens_alta_confiabilidade": alta_confiabilidade,
215
+ "itens_media_confiabilidade": media_confiabilidade,
216
+ "itens_baixa_confiabilidade": baixa_confiabilidade,
217
+ "itens_criticos": itens_criticos,
218
+ "percentual_alta_confiabilidade": round(percentual_alta, 1),
219
+ "percentual_baixa_confiabilidade": round(percentual_baixa, 1),
220
+ "percentual_critico": round(percentual_critico, 1),
221
+ "alertas": alertas[:5], # Limitar a 5 alertas principais
222
+ "total_alertas": len(alertas),
223
+ "recomendacao": _gerar_recomendacao(percentual_alta, percentual_baixa, percentual_critico)
224
+ }
225
+
226
+ return resultado
227
+
228
+
229
+ def _gerar_recomendacao(percentual_alta: float, percentual_baixa: float, percentual_critico: float = 0) -> str:
230
+ """
231
+ Gera recomendação baseada nos percentuais de confiabilidade.
232
+
233
+ Args:
234
+ percentual_alta: Percentual de itens com alta confiabilidade
235
+ percentual_baixa: Percentual de itens com baixa confiabilidade
236
+ percentual_critico: Percentual de itens críticos
237
+
238
+ Returns:
239
+ String com recomendação
240
+ """
241
+ if percentual_critico >= 30:
242
+ return "Há valores críticos no lucro de mercado. Eles não devem ser usados para otimização sem validação manual."
243
+ elif percentual_alta >= 70:
244
+ return "Lucro de mercado apresenta boa confiabilidade. Considere validação detalhada antes de ativar PRICE_APPLY_TO_PROFIT."
245
+ elif percentual_baixa >= 50:
246
+ return "Muitos itens com baixa confiabilidade. Valide preços, produtividades e custos antes de usar lucro de mercado."
247
+ elif percentual_critico > 0:
248
+ return "Alguns valores críticos detectados. Revise itens com baixa confiabilidade antes de usar lucro de mercado."
249
+ else:
250
+ return "Confiabilidade mista. Revise itens com baixa confiabilidade e valide dados de mercado."
251
+
252
+
253
+ def gerar_diagnostico_lucro_mercado(plano: List[Dict], uf: Optional[str] = None) -> Dict:
254
+ """
255
+ Gera diagnóstico detalhado do lucro de mercado para análise.
256
+
257
+ Args:
258
+ plano: Lista de itens do plano
259
+ uf: Unidade Federativa (opcional)
260
+
261
+ Returns:
262
+ Dict com diagnóstico por cultura
263
+ """
264
+ diagnostico = {
265
+ "uf": uf,
266
+ "total_culturas": 0,
267
+ "culturas": {}
268
+ }
269
+
270
+ # Agrupar por cultura
271
+ culturas_map = {}
272
+ for item in plano:
273
+ cultura = item.get("cultura", "desconhecida")
274
+
275
+ if cultura not in culturas_map:
276
+ culturas_map[cultura] = []
277
+
278
+ culturas_map[cultura].append(item)
279
+
280
+ # Gerar diagnóstico por cultura
281
+ for cultura, itens in culturas_map.items():
282
+ # Calcular médias
283
+ lucro_sistema_total = sum(i.get("lucro_estimado", 0) for i in itens)
284
+ lucro_mercado_total = sum(i.get("lucro_mercado_estimado", 0) for i in itens if i.get("lucro_mercado_estimado") is not None)
285
+
286
+ lucro_sistema_medio = lucro_sistema_total / len(itens) if itens else 0
287
+ lucro_mercado_medio = lucro_mercado_total / len(itens) if itens else 0
288
+
289
+ # Calcular diferença
290
+ diferenca = calcular_diferenca_lucro(lucro_sistema_medio, lucro_mercado_medio)
291
+
292
+ # Obter confiabilidade do primeiro item (representativo)
293
+ validacao = classificar_confiabilidade_lucro(itens[0]) if itens else {"confiabilidade": "baixa", "motivos": []}
294
+
295
+ # Obter preço normalizado
296
+ preco_norm = itens[0].get("preco_normalizado", {}) if itens else {}
297
+ preco_real = itens[0].get("preco_real", {}) if itens else {}
298
+
299
+ diagnostico["culturas"][cultura] = {
300
+ "total_talhoes": len(itens),
301
+ "lucro_sistema_medio": round(lucro_sistema_medio, 2),
302
+ "lucro_mercado_medio": round(lucro_mercado_medio, 2),
303
+ "diferenca": diferenca,
304
+ "confiabilidade": validacao["confiabilidade"],
305
+ "motivos": validacao["motivos"],
306
+ "preco_original": preco_real.get("preco"),
307
+ "unidade_original": preco_real.get("unidade"),
308
+ "preco_por_tonelada": preco_norm.get("preco_por_tonelada"),
309
+ "normalizado": preco_norm.get("normalizado", False),
310
+ "fallback": preco_real.get("fallback", False)
311
+ }
312
+
313
+ diagnostico["total_culturas"] = len(culturas_map)
314
+
315
+ 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,217 @@ 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
+ criticos = validacao.get("itens_criticos", 0)
283
+
284
+ perc_alta = validacao.get("percentual_alta_confiabilidade", 0)
285
+ perc_baixa = validacao.get("percentual_baixa_confiabilidade", 0)
286
+ perc_critico = validacao.get("percentual_critico", 0)
287
+
288
+ secao += f"- **Total de itens analisados**: {total}\n"
289
+ secao += f"- **Alta confiabilidade**: {alta} ({perc_alta:.1f}%) 🟢\n"
290
+ secao += f"- **Média confiabilidade**: {media} ({100 - perc_alta - perc_baixa:.1f}%) 🟡\n"
291
+ secao += f"- **Baixa confiabilidade**: {baixa} ({perc_baixa:.1f}%) 🔴\n"
292
+
293
+ if criticos > 0:
294
+ secao += f"- **⚠️ Itens críticos**: {criticos} ({perc_critico:.1f}%)\n"
295
+
296
+ secao += "\n"
297
+
298
+ # Aviso de itens críticos
299
+ if criticos > 0:
300
+ secao += "### ⚠️ ATENÇÃO: Valores Críticos Detectados\n\n"
301
+ secao += f"**{criticos} item(ns) apresenta(m) valores críticos** que indicam possível desalinhamento entre:\n"
302
+ secao += "- Preço de mercado vs preço interno\n"
303
+ secao += "- Produtividade estimada vs real\n"
304
+ secao += "- Custos operacionais\n"
305
+ secao += "- Unidade comercial\n\n"
306
+ secao += "**Estes valores não devem ser usados para otimização sem validação manual.**\n\n"
307
+
308
+ # Recomendação
309
+ recomendacao = validacao.get("recomendacao", "")
310
+ if recomendacao:
311
+ secao += f"**Recomendação**: {recomendacao}\n\n"
312
+
313
+ # Alertas
314
+ alertas = validacao.get("alertas", [])
315
+ if alertas:
316
+ secao += "### ⚠️ Alertas\n\n"
317
+ for alerta in alertas:
318
+ secao += f"- {alerta}\n"
319
+ secao += "\n"
320
+
321
+ total_alertas = validacao.get("total_alertas", 0)
322
+ if total_alertas > len(alertas):
323
+ secao += f"*Mostrando {len(alertas)} de {total_alertas} alertas*\n\n"
324
+
325
+ # Detalhes por item
326
+ secao += "### Detalhes por Talhão\n\n"
327
+ secao += "| Talhão | Cultura | Lucro Sistema | Lucro Mercado | Diferença % | Confiabilidade |\n"
328
+ secao += "|--------|---------|---------------|---------------|-------------|----------------|\n"
329
+
330
+ for item in resultado["plano"]:
331
+ validacao_item = item.get("validacao_lucro_mercado", {})
332
+ if not validacao_item:
333
+ continue
334
+
335
+ talhao = item.get("talhao", "N/A")
336
+ cultura = item.get("cultura", "").upper()
337
+ lucro_sistema = item.get("lucro_estimado", 0)
338
+ lucro_mercado = item.get("lucro_mercado_estimado", 0)
339
+
340
+ lucro_sistema_fmt = f"R$ {lucro_sistema:,.2f}".replace(",", "X").replace(".", ",").replace("X", ".")
341
+
342
+ if lucro_mercado is not None:
343
+ lucro_mercado_fmt = f"R$ {lucro_mercado:,.2f}".replace(",", "X").replace(".", ",").replace("X", ".")
344
+ diferenca = validacao_item.get("diferenca", {})
345
+ diferenca_perc = diferenca.get("diferenca_percentual", 0)
346
+ diferenca_fmt = f"{diferenca_perc:.1f}%"
347
+ else:
348
+ lucro_mercado_fmt = "N/A"
349
+ diferenca_fmt = "N/A"
350
+
351
+ confiabilidade = validacao_item.get("confiabilidade", "baixa")
352
+ critico = validacao_item.get("critico", False)
353
+
354
+ if critico:
355
+ conf_emoji = "⚠️🔴"
356
+ conf_label = "**CRÍTICO**"
357
+ else:
358
+ conf_emoji = "🟢" if confiabilidade == "alta" else "🟡" if confiabilidade == "media" else "🔴"
359
+ conf_label = confiabilidade.title()
360
+
361
+ secao += f"| {talhao} | {cultura} | {lucro_sistema_fmt} | {lucro_mercado_fmt} | {diferenca_fmt} | {conf_emoji} {conf_label} |\n"
362
+
363
+ secao += "\n"
364
+
365
+ # Explicação
366
+ secao += "### 📊 Sobre a Classificação de Confiabilidade\n\n"
367
+ secao += "A confiabilidade do lucro de mercado é classificada com base em:\n\n"
368
+ secao += "- **Alta (🟢)**: Diferença < 50% entre lucro sistema e mercado, preço normalizado disponível\n"
369
+ secao += "- **Média (🟡)**: Diferença 50-150%, uso de fallback, ou lucro negativo\n"
370
+ secao += "- **Baixa (🔴)**: Diferença > 150%, dados incompletos, ou preço não disponível\n"
371
+ secao += "- **⚠️ CRÍTICO**: Diferença extrema (>150%), lucro invertido, ou fallback com diferença >100%\n\n"
372
+
373
+ secao += "### ⚠️ Aviso Importante\n\n"
374
+ secao += "**O lucro de mercado ainda é experimental e não substitui o lucro principal do sistema.**\n\n"
375
+ secao += "Os valores são exibidos apenas como comparação para validação. Diferenças altas indicam "
376
+ secao += "necessidade de validação de produtividade, custos ou unidade comercial.\n\n"
377
+ secao += "**Status atual**: `PRICE_APPLY_TO_PROFIT=false` (lucro de mercado não afeta otimização)\n"
378
+
379
+ else: # txt
380
+ secao = "VALIDAÇÃO DO LUCRO DE MERCADO\n\n"
381
+
382
+ secao += "Resumo de Confiabilidade:\n\n"
383
+
384
+ total = validacao.get("total_itens", 0)
385
+ alta = validacao.get("itens_alta_confiabilidade", 0)
386
+ media = validacao.get("itens_media_confiabilidade", 0)
387
+ baixa = validacao.get("itens_baixa_confiabilidade", 0)
388
+
389
+ perc_alta = validacao.get("percentual_alta_confiabilidade", 0)
390
+ perc_baixa = validacao.get("percentual_baixa_confiabilidade", 0)
391
+
392
+ secao += f" Total de itens analisados: {total}\n"
393
+ secao += f" Alta confiabilidade: {alta} ({perc_alta:.1f}%)\n"
394
+ secao += f" Média confiabilidade: {media} ({100 - perc_alta - perc_baixa:.1f}%)\n"
395
+ secao += f" Baixa confiabilidade: {baixa} ({perc_baixa:.1f}%)\n\n"
396
+
397
+ # Recomendação
398
+ recomendacao = validacao.get("recomendacao", "")
399
+ if recomendacao:
400
+ secao += f"Recomendação: {recomendacao}\n\n"
401
+
402
+ # Alertas
403
+ alertas = validacao.get("alertas", [])
404
+ if alertas:
405
+ secao += "Alertas:\n\n"
406
+ for alerta in alertas:
407
+ secao += f" - {alerta}\n"
408
+ secao += "\n"
409
+
410
+ total_alertas = validacao.get("total_alertas", 0)
411
+ if total_alertas > len(alertas):
412
+ secao += f" (Mostrando {len(alertas)} de {total_alertas} alertas)\n\n"
413
+
414
+ # Detalhes por item
415
+ secao += "Detalhes por Talhão:\n\n"
416
+
417
+ for item in resultado["plano"]:
418
+ validacao_item = item.get("validacao_lucro_mercado", {})
419
+ if not validacao_item:
420
+ continue
421
+
422
+ talhao = item.get("talhao", "N/A")
423
+ cultura = item.get("cultura", "").upper()
424
+ lucro_sistema = item.get("lucro_estimado", 0)
425
+ lucro_mercado = item.get("lucro_mercado_estimado", 0)
426
+
427
+ lucro_sistema_fmt = f"R$ {lucro_sistema:,.2f}".replace(",", "X").replace(".", ",").replace("X", ".")
428
+
429
+ secao += f" Talhão {talhao} ({cultura}):\n"
430
+ secao += f" Lucro Sistema: {lucro_sistema_fmt}\n"
431
+
432
+ if lucro_mercado is not None:
433
+ lucro_mercado_fmt = f"R$ {lucro_mercado:,.2f}".replace(",", "X").replace(".", ",").replace("X", ".")
434
+ diferenca = validacao_item.get("diferenca", {})
435
+ diferenca_perc = diferenca.get("diferenca_percentual", 0)
436
+ secao += f" Lucro Mercado: {lucro_mercado_fmt}\n"
437
+ secao += f" Diferença: {diferenca_perc:.1f}%\n"
438
+ else:
439
+ secao += f" Lucro Mercado: N/A\n"
440
+
441
+ confiabilidade = validacao_item.get("confiabilidade", "baixa")
442
+ secao += f" Confiabilidade: {confiabilidade.title()}\n"
443
+
444
+ motivos = validacao_item.get("motivos", [])
445
+ if motivos:
446
+ secao += f" Motivos: {', '.join(motivos)}\n"
447
+
448
+ secao += "\n"
449
+
450
+ # Explicação
451
+ secao += "Sobre a Classificação de Confiabilidade:\n\n"
452
+ secao += "A confiabilidade do lucro de mercado é classificada com base em:\n\n"
453
+ secao += " - Alta: Diferença < 50% entre lucro sistema e mercado\n"
454
+ secao += " - Média: Diferença 50-100%, uso de fallback, ou lucro negativo\n"
455
+ secao += " - Baixa: Diferença > 100%, dados incompletos, ou preço não disponível\n\n"
456
+
457
+ secao += "Aviso Importante:\n\n"
458
+ secao += "O lucro de mercado ainda é experimental e não substitui o lucro principal\n"
459
+ secao += "do sistema. Os valores são exibidos apenas como comparação para validação.\n"
460
+ secao += "Diferenças altas indicam necessidade de validação de produtividade, custos\n"
461
+ secao += "ou unidade comercial.\n\n"
462
+ secao += "Status atual: PRICE_APPLY_TO_PROFIT=false (lucro de mercado não afeta\n"
463
+ secao += "otimização)\n"
464
+
465
+ return secao
466
+
467
+
252
468
  def gerar_relatorio_markdown(culturas, talhoes, regras, objetivo, cenarios, resultado_ag, validacao, estabilidade):
253
469
  """Gera relatório em formato Markdown"""
254
470
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agroplan-ai-cli",
3
- "version": "1.0.25",
3
+ "version": "1.0.27",
4
4
  "description": "CLI global para AgroPlan AI - modo local acelerado",
5
5
  "type": "module",
6
6
  "bin": {