agroplan-ai-cli 1.0.0

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.
@@ -0,0 +1,785 @@
1
+ """
2
+ Gerador de relatórios explicáveis para o AgroPlan AI
3
+ """
4
+
5
+ import os
6
+ from datetime import datetime
7
+ from core.planner import gerar_cenarios, gerar_plano_genetico
8
+ from core.bruteforce_validator import comparar_ag_com_forca_bruta, executar_multiplas_rodadas
9
+
10
+
11
+ def format_currency_brl(value):
12
+ """Formata valor monetário em padrão brasileiro"""
13
+ return f"R$ {value:,.2f}".replace(",", "X").replace(".", ",").replace("X", ".")
14
+
15
+
16
+ def display_name(value):
17
+ """Retorna nome com acentuação correta"""
18
+ mapping = {
19
+ "cafe": "café",
20
+ "CAFE": "CAFÉ",
21
+ "Cafe": "Café",
22
+ "feijao": "feijão",
23
+ "FEIJAO": "FEIJÃO",
24
+ "Feijao": "Feijão",
25
+ "algodao": "algodão",
26
+ "ALGODAO": "ALGODÃO",
27
+ "Algodao": "Algodão",
28
+ "sustentavel": "sustentável",
29
+ "SUSTENTAVEL": "SUSTENTÁVEL",
30
+ "Sustentavel": "Sustentável",
31
+ "media": "média",
32
+ "Media": "Média",
33
+ "MEDIA": "MÉDIA",
34
+ "ingreme": "íngreme",
35
+ "Ingreme": "Íngreme",
36
+ "INGREME": "ÍNGREME",
37
+ "cana": "cana",
38
+ "CANA": "CANA",
39
+ "Cana": "Cana",
40
+ "soja": "soja",
41
+ "SOJA": "SOJA",
42
+ "Soja": "Soja",
43
+ "milho": "milho",
44
+ "MILHO": "MILHO",
45
+ "Milho": "Milho",
46
+ "trigo": "trigo",
47
+ "TRIGO": "TRIGO",
48
+ "Trigo": "Trigo",
49
+ "sorgo": "sorgo",
50
+ "SORGO": "SORGO",
51
+ "Sorgo": "Sorgo",
52
+ "mandioca": "mandioca",
53
+ "MANDIOCA": "MANDIOCA",
54
+ "Mandioca": "Mandioca",
55
+ "arroz": "arroz",
56
+ "ARROZ": "ARROZ",
57
+ "Arroz": "Arroz",
58
+ }
59
+ return mapping.get(str(value), str(value))
60
+
61
+
62
+ def format_duration(seconds):
63
+ """Formata duração em formato legível"""
64
+ if seconds < 60:
65
+ return f"aproximadamente {seconds:.0f} segundos"
66
+ elif seconds < 3600:
67
+ minutes = seconds / 60
68
+ return f"aproximadamente {minutes:.1f} minutos"
69
+ elif seconds < 86400:
70
+ hours = seconds / 3600
71
+ return f"aproximadamente {hours:.1f} horas"
72
+ else:
73
+ days = seconds / 86400
74
+ return f"aproximadamente {days:.1f} dias"
75
+
76
+
77
+ def get_objetivo_description(objetivo):
78
+ """Retorna descrição adequada do objetivo"""
79
+ descriptions = {
80
+ "equilibrado": "buscou equilíbrio entre retorno financeiro, controle de risco e compatibilidade agronômica",
81
+ "lucro": "priorizou retorno financeiro dentro das restrições do modelo",
82
+ "risco": "reduziu a exposição média ao risco dentro das restrições do modelo",
83
+ "sustentavel": "priorizou compatibilidade com o terreno, diversidade de culturas e uso adequado dos recursos disponíveis"
84
+ }
85
+ return descriptions.get(objetivo, "otimizou múltiplos critérios")
86
+
87
+
88
+ def gerar_relatorio_completo(culturas, talhoes, regras, objetivo='equilibrado', formato='md'):
89
+ """
90
+ Gera relatório completo do sistema
91
+
92
+ Args:
93
+ culturas: DataFrame com culturas
94
+ talhoes: DataFrame com talhões
95
+ regras: DataFrame com regras
96
+ objetivo: objetivo do AG
97
+ formato: 'md' ou 'txt'
98
+
99
+ Returns:
100
+ Caminho do arquivo gerado
101
+ """
102
+ # Executa todas as análises
103
+ print(" 📊 Gerando cenários...")
104
+ cenarios = gerar_cenarios(culturas, talhoes, regras)
105
+
106
+ print(" 🧬 Executando Algoritmo Genético...")
107
+ resultado_ag = gerar_plano_genetico(culturas, talhoes, regras, objetivo, seed=42)
108
+
109
+ print(" 🔬 Validando com força bruta...")
110
+ validacao = comparar_ag_com_forca_bruta(culturas, talhoes, regras, objetivo, seed=42)
111
+
112
+ print(" 🔄 Analisando estabilidade (5 rodadas)...")
113
+ estabilidade = executar_multiplas_rodadas(culturas, talhoes, regras, objetivo, rodadas=5)
114
+
115
+ # Gera conteúdo do relatório
116
+ if formato == 'md':
117
+ conteudo = gerar_relatorio_markdown(
118
+ culturas, talhoes, regras, objetivo,
119
+ cenarios, resultado_ag, validacao, estabilidade
120
+ )
121
+ extensao = 'md'
122
+ else:
123
+ conteudo = gerar_relatorio_txt(
124
+ culturas, talhoes, regras, objetivo,
125
+ cenarios, resultado_ag, validacao, estabilidade
126
+ )
127
+ extensao = 'txt'
128
+
129
+ # Salva arquivo
130
+ os.makedirs('reports', exist_ok=True)
131
+ timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
132
+ nome_arquivo = f'relatorio_agroplan_{objetivo}_{timestamp}.{extensao}'
133
+ caminho = os.path.join('reports', nome_arquivo)
134
+
135
+ with open(caminho, 'w', encoding='utf-8') as f:
136
+ f.write(conteudo)
137
+
138
+ return caminho
139
+
140
+
141
+ def gerar_relatorio_markdown(culturas, talhoes, regras, objetivo, cenarios, resultado_ag, validacao, estabilidade):
142
+ """Gera relatório em formato Markdown"""
143
+
144
+ md = []
145
+ md.append("# 📊 Relatório AgroPlan AI - Sistema de Planejamento de Plantio")
146
+ md.append("")
147
+ md.append(f"**Data:** {datetime.now().strftime('%d/%m/%Y %H:%M:%S')}")
148
+ md.append(f"**Objetivo:** {display_name(objetivo).title()}")
149
+ md.append("")
150
+ md.append("---")
151
+ md.append("")
152
+
153
+ # 1. Resumo Executivo
154
+ md.append("## 1. 📋 Resumo Executivo")
155
+ md.append("")
156
+ md.append("### Plano Recomendado")
157
+ md.append("")
158
+
159
+ for p in resultado_ag['plano']:
160
+ cultura_display = display_name(p['cultura']).upper()
161
+ md.append(f"- **Talhão {p['talhao']}** ({p['area']} ha): **{cultura_display}**")
162
+ md.append(f" - Lucro estimado: {format_currency_brl(p['lucro_estimado'])}")
163
+ md.append(f" - Risco: {p['risco']}%")
164
+ md.append(f" - Tempo de colheita: {p['tempo']} dias")
165
+
166
+ md.append("")
167
+ md.append("### Métricas Gerais")
168
+ md.append("")
169
+ md.append(f"- **Lucro Total:** {format_currency_brl(resultado_ag['lucro_total'])}")
170
+ md.append(f"- **Risco Médio Ponderado:** {resultado_ag['risco_medio']:.1f}%")
171
+ md.append(f"- **Diversidade:** {resultado_ag['diversidade']} cultura(s) diferente(s)")
172
+ md.append(f"- **Fitness:** {resultado_ag['fitness']:.2f}")
173
+ md.append(f"- **Área Total:** {resultado_ag['area_total']} ha")
174
+ md.append("")
175
+ md.append("### Justificativa")
176
+ md.append("")
177
+
178
+ # Usa descrição adequada do objetivo
179
+ objetivo_desc = get_objetivo_description(objetivo)
180
+ md.append(f"O plano recomendado {objetivo_desc}.")
181
+
182
+ if objetivo == "sustentavel":
183
+ md.append("")
184
+ md.append("**Sobre Sustentabilidade:** Neste sistema, sustentabilidade considera compatibilidade com o terreno, diversidade de culturas e uso adequado dos recursos disponíveis.")
185
+
186
+ md.append("")
187
+ md.append("---")
188
+ md.append("")
189
+
190
+ # 2. Características dos Talhões
191
+ md.append("## 2. 🌾 Características dos Talhões")
192
+ md.append("")
193
+
194
+ for _, talhao in talhoes.iterrows():
195
+ md.append(f"### Talhão {talhao['id']}")
196
+ md.append("")
197
+ md.append(f"- **Área:** {talhao['area']} hectares")
198
+ md.append(f"- **Solo:** {display_name(talhao['solo']).title()}")
199
+ md.append(f"- **Clima:** {display_name(talhao['clima']).title()}")
200
+ md.append(f"- **Relevo:** {display_name(talhao['relevo']).title()}")
201
+ md.append(f"- **Disponibilidade de Água:** {display_name(talhao['agua']).title()}")
202
+ md.append("")
203
+
204
+ md.append("---")
205
+ md.append("")
206
+
207
+ # 3. Comparação de Cenários
208
+ md.append("## 3. 📊 Comparação de Cenários")
209
+ md.append("")
210
+ md.append("| Cenário | Lucro Total | Risco Médio | Culturas Escolhidas |")
211
+ md.append("|---------|-------------|-------------|---------------------|")
212
+
213
+ # AG
214
+ culturas_ag = " + ".join([display_name(p['cultura']).title() for p in resultado_ag['plano']])
215
+ objetivo_label = display_name(objetivo).title()
216
+ md.append(f"| **🧬 AG {objetivo_label}** | **{format_currency_brl(resultado_ag['lucro_total'])}** | **{resultado_ag['risco_medio']:.1f}%** | **{culturas_ag}** |")
217
+
218
+ # Cenários manuais
219
+ ordem = ['equilibrado', 'maximo_lucro', 'baixo_risco', 'sustentavel', 'conservador']
220
+ nomes = {
221
+ 'equilibrado': 'Equilibrado',
222
+ 'maximo_lucro': 'Máximo Lucro',
223
+ 'baixo_risco': 'Baixo Risco',
224
+ 'sustentavel': 'Sustentável',
225
+ 'conservador': 'Conservador'
226
+ }
227
+
228
+ for key in ordem:
229
+ cenario = cenarios[key]
230
+ culturas_cenario = " + ".join(set([display_name(p['cultura']).title() for p in cenario['plano']]))
231
+ md.append(f"| {nomes[key]} | {format_currency_brl(cenario['lucro_total'])} | {cenario['risco_medio']:.1f}% | {culturas_cenario} |")
232
+
233
+ md.append("")
234
+ md.append("### Observações")
235
+ md.append("")
236
+ md.append("- O **Algoritmo Genético** encontrou uma solução otimizada considerando múltiplos objetivos")
237
+ md.append("- Cenários manuais seguem estratégias pré-definidas")
238
+ md.append("- A escolha final depende do perfil de risco do produtor")
239
+ md.append("")
240
+ md.append("---")
241
+ md.append("")
242
+
243
+ # 4. Resultado do Algoritmo Genético
244
+ md.append("## 4. 🧬 Resultado do Algoritmo Genético")
245
+ md.append("")
246
+ md.append("### Configuração")
247
+ md.append("")
248
+ md.append(f"- **Objetivo:** {display_name(objetivo).title()}")
249
+ md.append(f"- **Gerações:** {resultado_ag['geracoes']}")
250
+ md.append("- **População:** 50 indivíduos")
251
+ md.append(f"- **Seed:** {resultado_ag.get('seed', 'Não especificada')}")
252
+ md.append("")
253
+ md.append("### Resultado")
254
+ md.append("")
255
+ md.append(f"- **Fitness Final:** {resultado_ag['fitness']:.2f}")
256
+ md.append(f"- **Lucro Total:** {format_currency_brl(resultado_ag['lucro_total'])}")
257
+ md.append(f"- **Risco Médio:** {resultado_ag['risco_medio']:.1f}%")
258
+ md.append(f"- **Diversidade:** {resultado_ag['diversidade']} cultura(s)")
259
+ md.append("")
260
+ md.append("### Plano Detalhado")
261
+ md.append("")
262
+
263
+ for p in resultado_ag['plano']:
264
+ cultura_display = display_name(p['cultura']).upper()
265
+ solo_display = display_name(p['solo']).title()
266
+ clima_display = display_name(p['clima']).title()
267
+ md.append(f"**Talhão {p['talhao']}** ({p['area']} ha) - Solo {solo_display}, Clima {clima_display}")
268
+ md.append(f"- Cultura: **{cultura_display}**")
269
+ md.append(f"- Lucro: {format_currency_brl(p['lucro_estimado'])}")
270
+ md.append(f"- Risco: {p['risco']}%")
271
+ md.append(f"- Nota de compatibilidade: {p['nota']:.2f}")
272
+ md.append("")
273
+
274
+ md.append("---")
275
+ md.append("")
276
+
277
+ # 5. Validação
278
+ md.append("## 5. 🔬 Validação do Algoritmo")
279
+ md.append("")
280
+
281
+ if validacao.get('erro'):
282
+ # Força bruta inviável
283
+ total_comb = validacao.get('total_combinacoes', 0)
284
+ md.append(f"**Total de combinações possíveis:** {total_comb:,}".replace(",", "."))
285
+ md.append("")
286
+ md.append("### ⚠️ Força Bruta Inviável")
287
+ md.append("")
288
+ md.append(f"A busca exaustiva por força bruta foi considerada **inviável** neste conjunto, ")
289
+ md.append(f"pois existem aproximadamente **{total_comb:,} combinações possíveis**.".replace(",", "."))
290
+ md.append("")
291
+ md.append("Por isso, a validação foi realizada por meio de:")
292
+ md.append("")
293
+ md.append("- ✅ Múltiplas rodadas do Algoritmo Genético")
294
+ md.append("- ✅ Análise de estabilidade estatística")
295
+ md.append("- ✅ Comparação com cenários manuais")
296
+ md.append("")
297
+ md.append("### Por que a força bruta é inviável?")
298
+ md.append("")
299
+ md.append(f"Com {len(talhoes)} talhões e {len(culturas)} culturas, o número de combinações cresce exponencialmente.")
300
+ md.append("")
301
+
302
+ if total_comb >= 1_000_000_000:
303
+ # Calcula tempo em segundos
304
+ tempo_1m = total_comb / 1_000_000 # segundos
305
+ tempo_1b = total_comb / 1_000_000_000 # segundos
306
+
307
+ md.append(f"Testar **{total_comb:,} combinações** levaria:".replace(",", "."))
308
+ md.append(f"- 1 milhão/s: {format_duration(tempo_1m)}")
309
+ md.append(f"- 1 bilhão/s: {format_duration(tempo_1b)}")
310
+ else:
311
+ md.append(f"Testar **{total_comb:,} combinações** seria computacionalmente custoso e desnecessário.".replace(",", "."))
312
+
313
+ md.append("")
314
+ md.append("O **Algoritmo Genético** é a solução ideal para este cenário, pois:")
315
+ md.append("")
316
+ md.append("- 🚀 Encontra soluções de alta qualidade em tempo viável")
317
+ md.append("- 🎯 Explora o espaço de busca de forma inteligente")
318
+ md.append("- 📊 Apresenta resultados consistentes (veja seção de Estabilidade)")
319
+ md.append("- ⚡ Escala para problemas ainda maiores")
320
+ md.append("")
321
+ else:
322
+ # Força bruta viável
323
+ md.append(f"**Total de combinações testadas:** {validacao['forca_bruta']['total_combinacoes']}")
324
+ md.append("")
325
+
326
+ md.append("### Melhor Solução por Força Bruta")
327
+ md.append("")
328
+ for p in validacao['forca_bruta']['plano']:
329
+ md.append(f"- Talhão {p['talhao']}: {display_name(p['cultura']).title()}")
330
+ md.append(f"- **Fitness:** {validacao['forca_bruta']['melhor_fitness']:.2f}")
331
+ md.append(f"- **Lucro:** {format_currency_brl(validacao['forca_bruta']['lucro_total'])}")
332
+ md.append("")
333
+
334
+ md.append("### Melhor Solução pelo AG")
335
+ md.append("")
336
+ for p in validacao['ag']['plano']:
337
+ md.append(f"- Talhão {p['talhao']}: {display_name(p['cultura']).title()}")
338
+ md.append(f"- **Fitness:** {validacao['ag']['fitness']:.2f}")
339
+ md.append(f"- **Lucro:** {format_currency_brl(validacao['ag']['lucro_total'])}")
340
+ md.append("")
341
+
342
+ if validacao['ag_encontrou_otimo_global']:
343
+ md.append("✅ **Status:** O Algoritmo Genético encontrou o ótimo global!")
344
+ else:
345
+ md.append("⚠️ **Status:** O AG encontrou uma solução próxima, mas não o ótimo global.")
346
+ md.append(f"- Diferença de fitness: {validacao['diferenca_fitness']:.2f}")
347
+
348
+ md.append("")
349
+ md.append("### Escalabilidade")
350
+ md.append("")
351
+ md.append("Em conjuntos pequenos (3 talhões, 5 culturas = 125 combinações), a força bruta ainda é viável.")
352
+ md.append("Porém, em cenários maiores:")
353
+ md.append("")
354
+ md.append("- **10 talhões, 10 culturas:** ~10 bilhões de combinações (inviável)")
355
+ md.append("- **20 talhões, 10 culturas:** 10²⁰ combinações (impossível)")
356
+ md.append("")
357
+ md.append("O Algoritmo Genético torna-se **essencial** em problemas de grande escala.")
358
+ md.append("")
359
+
360
+ md.append("---")
361
+ md.append("")
362
+
363
+ # 6. Estabilidade do Algoritmo
364
+ md.append("## 6. 📈 Estabilidade do Algoritmo")
365
+ md.append("")
366
+ md.append(f"**Rodadas executadas:** {estabilidade['rodadas']}")
367
+ md.append("")
368
+ md.append("### Estatísticas")
369
+ md.append("")
370
+ md.append(f"- **Melhor Fitness:** {estabilidade['melhor_fitness']:.2f}")
371
+ md.append(f"- **Fitness Médio:** {estabilidade['fitness_medio']:.2f}")
372
+ md.append(f"- **Pior Fitness:** {estabilidade['pior_fitness']:.2f}")
373
+ md.append(f"- **Desvio Padrão:** {estabilidade['desvio_padrao']:.2f}")
374
+ md.append(f"- **Coeficiente de Variação:** {estabilidade['coeficiente_variacao']:.2f}%")
375
+ md.append("")
376
+
377
+ emoji = "🟢" if estabilidade['estabilidade'] == 'alta' else "🟡" if estabilidade['estabilidade'] == 'média' else "🔴"
378
+ md.append(f"{emoji} **Estabilidade:** {estabilidade['estabilidade'].upper()}")
379
+ md.append("")
380
+ md.append(estabilidade['estabilidade_descricao'])
381
+ md.append("")
382
+ md.append("---")
383
+ md.append("")
384
+
385
+ # 7. Justificativa Agronômica
386
+ md.append("## 7. 🌱 Justificativa Agronômica")
387
+ md.append("")
388
+
389
+ for p in resultado_ag['plano']:
390
+ cultura_display = display_name(p['cultura']).upper()
391
+ solo_display = display_name(p['solo'])
392
+ clima_display = display_name(p['clima'])
393
+ relevo_display = display_name(p['relevo'])
394
+ agua_display = display_name(p['agua'])
395
+
396
+ md.append(f"### Talhão {p['talhao']}: {cultura_display}")
397
+ md.append("")
398
+ md.append(f"**Por que {display_name(p['cultura'])} foi escolhida para este talhão?**")
399
+ md.append("")
400
+ md.append(f"- **Solo {solo_display}:** Compatível com as necessidades da cultura")
401
+ md.append(f"- **Clima {clima_display}:** Adequado para o desenvolvimento")
402
+ md.append(f"- **Relevo {relevo_display}:** Favorável ao cultivo")
403
+ md.append(f"- **Água {agua_display}:** Atende às necessidades hídricas")
404
+ md.append(f"- **Nota de compatibilidade:** {p['nota']:.2f}/100")
405
+ md.append(f"- **Lucro estimado:** {format_currency_brl(p['lucro_estimado'])}")
406
+ md.append(f"- **Risco:** {p['risco']}%")
407
+ md.append("")
408
+
409
+ md.append("---")
410
+ md.append("")
411
+
412
+ # 8. Limitações do Sistema
413
+ md.append("## 8. ⚠️ Limitações do Sistema")
414
+ md.append("")
415
+ md.append("Este sistema fornece **recomendações baseadas em dados e algoritmos**, mas possui limitações:")
416
+ md.append("")
417
+ md.append("1. **Dados Simulados:** Os dados atuais são estimativas, não medições reais de campo")
418
+ md.append("2. **Modelo Simplificado:** Não considera todos os fatores agronômicos (pragas, doenças, mercado local)")
419
+ md.append("3. **Sem Análise Laboratorial:** Não utiliza análise química e física do solo")
420
+ md.append("4. **Sem Dados Climáticos Reais:** Não integra com estações meteorológicas ou previsões")
421
+ md.append("5. **Sem Preços de Mercado Reais:** Usa valores estimados, não cotações atuais")
422
+ md.append("6. **Não Substitui Agrônomo:** As recomendações devem ser validadas por profissional qualificado")
423
+ md.append("")
424
+ md.append("**Recomendação:** Use este sistema como ferramenta de apoio à decisão, não como decisão final.")
425
+ md.append("")
426
+ md.append("---")
427
+ md.append("")
428
+
429
+ # 9. Próximas Evoluções
430
+ md.append("## 9. 🚀 Próximas Evoluções")
431
+ md.append("")
432
+ md.append("### Fase 5 - Interface Web")
433
+ md.append("- Dashboard interativo")
434
+ md.append("- Visualização de mapas")
435
+ md.append("- Gráficos de evolução")
436
+ md.append("- Comparação visual de cenários")
437
+ md.append("")
438
+ md.append("### Fase 6 - Integração com APIs Reais")
439
+ md.append("- Dados climáticos (INMET, OpenWeather)")
440
+ md.append("- Preços de mercado (CEPEA, CONAB)")
441
+ md.append("- Análise de solo (laboratórios)")
442
+ md.append("- Imagens de satélite")
443
+ md.append("")
444
+ md.append("### Fase 7 - Machine Learning")
445
+ md.append("- Previsão de produtividade")
446
+ md.append("- Previsão de preços")
447
+ md.append("- Detecção de anomalias")
448
+ md.append("- Recomendação personalizada")
449
+ md.append("")
450
+ md.append("### Fase 8 - Sistema Completo")
451
+ md.append("- Cadastro de propriedades")
452
+ md.append("- Gestão de usuários")
453
+ md.append("- Histórico de safras")
454
+ md.append("- Relatórios em PDF")
455
+ md.append("- Aplicativo mobile")
456
+ md.append("")
457
+ md.append("---")
458
+ md.append("")
459
+ md.append("## 📝 Conclusão")
460
+ md.append("")
461
+ md.append(f"O sistema AgroPlan AI recomenda o plano apresentado neste relatório com base no objetivo **{display_name(objetivo)}**.")
462
+ md.append("")
463
+
464
+ # Conclusão adaptada ao tipo de validação
465
+ if validacao.get('erro'):
466
+ estabilidade_label = display_name(estabilidade['estabilidade'])
467
+ md.append(f"A solução foi validada por meio de **múltiplas rodadas** e apresenta estabilidade **{estabilidade_label}**.")
468
+ md.append("")
469
+ total_comb_fmt = f"{validacao.get('total_combinacoes', 0):,}".replace(",", ".")
470
+ md.append(f"Com **{total_comb_fmt} combinações possíveis**, o Algoritmo Genético ")
471
+ md.append("demonstra sua **essencialidade** para resolver problemas de planejamento agrícola em escala real.")
472
+ else:
473
+ estabilidade_label = display_name(estabilidade['estabilidade'])
474
+ md.append(f"A solução foi validada por **força bruta** e apresenta estabilidade **{estabilidade_label}**.")
475
+
476
+ md.append("")
477
+ md.append("**Próximos passos sugeridos:**")
478
+ md.append("1. Validar recomendações com agrônomo")
479
+ md.append("2. Considerar fatores locais não modelados")
480
+ md.append("3. Ajustar conforme disponibilidade de recursos")
481
+ md.append("4. Monitorar resultados para melhorias futuras")
482
+ md.append("")
483
+ md.append("---")
484
+ md.append("")
485
+ md.append("*Relatório gerado automaticamente pelo AgroPlan AI*")
486
+
487
+ return "\n".join(md)
488
+
489
+
490
+ def gerar_relatorio_txt(culturas, talhoes, regras, objetivo, cenarios, resultado_ag, validacao, estabilidade):
491
+ """Gera relatório em formato TXT"""
492
+
493
+ txt = []
494
+ txt.append("=" * 80)
495
+ txt.append("RELATÓRIO AGROPLAN AI - SISTEMA DE PLANEJAMENTO DE PLANTIO")
496
+ txt.append("=" * 80)
497
+ txt.append("")
498
+ txt.append(f"Data: {datetime.now().strftime('%d/%m/%Y %H:%M:%S')}")
499
+ txt.append(f"Objetivo: {display_name(objetivo).upper()}")
500
+ txt.append("")
501
+ txt.append("=" * 80)
502
+ txt.append("")
503
+
504
+ # 1. Resumo Executivo
505
+ txt.append("1. RESUMO EXECUTIVO")
506
+ txt.append("-" * 80)
507
+ txt.append("")
508
+ txt.append("PLANO RECOMENDADO:")
509
+ txt.append("")
510
+
511
+ for p in resultado_ag['plano']:
512
+ cultura_display = display_name(p['cultura']).upper()
513
+ txt.append(f" Talhão {p['talhao']} ({p['area']} ha): {cultura_display}")
514
+ txt.append(f" Lucro estimado: {format_currency_brl(p['lucro_estimado'])}")
515
+ txt.append(f" Risco: {p['risco']}%")
516
+ txt.append(f" Tempo: {p['tempo']} dias")
517
+ txt.append("")
518
+
519
+ txt.append("MÉTRICAS GERAIS:")
520
+ txt.append(f" Lucro Total: {format_currency_brl(resultado_ag['lucro_total'])}")
521
+ txt.append(f" Risco Médio: {resultado_ag['risco_medio']:.1f}%")
522
+ txt.append(f" Diversidade: {resultado_ag['diversidade']} cultura(s)")
523
+ txt.append(f" Fitness: {resultado_ag['fitness']:.2f}")
524
+ txt.append("")
525
+ txt.append("JUSTIFICATIVA:")
526
+ objetivo_desc = get_objetivo_description(objetivo)
527
+ txt.append(f" O plano recomendado {objetivo_desc}.")
528
+ txt.append("")
529
+
530
+ if objetivo == "sustentavel":
531
+ txt.append(" Sobre Sustentabilidade: Neste sistema, sustentabilidade considera")
532
+ txt.append(" compatibilidade com o terreno, diversidade de culturas e uso adequado")
533
+ txt.append(" dos recursos disponíveis.")
534
+ txt.append("")
535
+
536
+ txt.append("=" * 80)
537
+ txt.append("")
538
+
539
+ # 2. Características dos Talhões
540
+ txt.append("2. CARACTERÍSTICAS DOS TALHÕES")
541
+ txt.append("-" * 80)
542
+ txt.append("")
543
+
544
+ for _, talhao in talhoes.iterrows():
545
+ txt.append(f"Talhão {talhao['id']}:")
546
+ txt.append(f" Área: {talhao['area']} hectares")
547
+ txt.append(f" Solo: {display_name(talhao['solo']).title()}")
548
+ txt.append(f" Clima: {display_name(talhao['clima']).title()}")
549
+ txt.append(f" Relevo: {display_name(talhao['relevo']).title()}")
550
+ txt.append(f" Disponibilidade de Água: {display_name(talhao['agua']).title()}")
551
+ txt.append("")
552
+
553
+ txt.append("=" * 80)
554
+ txt.append("")
555
+
556
+ # 3. Comparação de Cenários
557
+ txt.append("3. COMPARAÇÃO DE CENÁRIOS")
558
+ txt.append("-" * 80)
559
+ txt.append("")
560
+
561
+ objetivo_label = display_name(objetivo).title()
562
+ txt.append(f"AG {objetivo_label}:")
563
+ txt.append(f" Lucro: {format_currency_brl(resultado_ag['lucro_total'])}")
564
+ txt.append(f" Risco: {resultado_ag['risco_medio']:.1f}%")
565
+ txt.append("")
566
+
567
+ ordem = ['equilibrado', 'maximo_lucro', 'baixo_risco', 'sustentavel', 'conservador']
568
+ nomes = {
569
+ 'equilibrado': 'Equilibrado',
570
+ 'maximo_lucro': 'Máximo Lucro',
571
+ 'baixo_risco': 'Baixo Risco',
572
+ 'sustentavel': 'Sustentável',
573
+ 'conservador': 'Conservador'
574
+ }
575
+
576
+ for key in ordem:
577
+ cenario = cenarios[key]
578
+ txt.append(f"{nomes[key]}:")
579
+ txt.append(f" Lucro: {format_currency_brl(cenario['lucro_total'])}")
580
+ txt.append(f" Risco: {cenario['risco_medio']:.1f}%")
581
+ txt.append("")
582
+
583
+ txt.append("=" * 80)
584
+ txt.append("")
585
+
586
+ # 4. Resultado do Algoritmo Genético
587
+ txt.append("4. RESULTADO DO ALGORITMO GENÉTICO")
588
+ txt.append("-" * 80)
589
+ txt.append("")
590
+ txt.append("CONFIGURAÇÃO:")
591
+ txt.append(f" Objetivo: {display_name(objetivo).title()}")
592
+ txt.append(f" Gerações: {resultado_ag['geracoes']}")
593
+ txt.append(" População: 50 indivíduos")
594
+ txt.append(f" Seed: {resultado_ag.get('seed', 'Não especificada')}")
595
+ txt.append("")
596
+ txt.append("RESULTADO:")
597
+ txt.append(f" Fitness Final: {resultado_ag['fitness']:.2f}")
598
+ txt.append(f" Lucro Total: {format_currency_brl(resultado_ag['lucro_total'])}")
599
+ txt.append(f" Risco Médio: {resultado_ag['risco_medio']:.1f}%")
600
+ txt.append(f" Diversidade: {resultado_ag['diversidade']} cultura(s)")
601
+ txt.append("")
602
+ txt.append("PLANO DETALHADO:")
603
+ txt.append("")
604
+
605
+ for p in resultado_ag['plano']:
606
+ cultura_display = display_name(p['cultura']).upper()
607
+ solo_display = display_name(p['solo']).title()
608
+ clima_display = display_name(p['clima']).title()
609
+ txt.append(f"Talhão {p['talhao']} ({p['area']} ha) - Solo {solo_display}, Clima {clima_display}")
610
+ txt.append(f" Cultura: {cultura_display}")
611
+ txt.append(f" Lucro: {format_currency_brl(p['lucro_estimado'])}")
612
+ txt.append(f" Risco: {p['risco']}%")
613
+ txt.append(f" Nota de compatibilidade: {p['nota']:.2f}")
614
+ txt.append("")
615
+
616
+ txt.append("=" * 80)
617
+ txt.append("")
618
+
619
+ # 5. Validação
620
+ txt.append("5. VALIDAÇÃO DO ALGORITMO")
621
+ txt.append("-" * 80)
622
+ txt.append("")
623
+
624
+ if validacao.get('erro'):
625
+ # Força bruta inviável
626
+ total_comb = validacao.get('total_combinacoes', 0)
627
+ txt.append(f"Total de combinações possíveis: {total_comb:,}".replace(",", "."))
628
+ txt.append("")
629
+ txt.append("FORÇA BRUTA INVIÁVEL")
630
+ txt.append("")
631
+ txt.append(f"A busca exaustiva por força bruta foi considerada inviável neste conjunto,")
632
+ txt.append(f"pois existem aproximadamente {total_comb:,} combinações possíveis.".replace(",", "."))
633
+ txt.append("")
634
+ txt.append("Por isso, a validação foi realizada por meio de:")
635
+ txt.append(" - Múltiplas rodadas do Algoritmo Genético")
636
+ txt.append(" - Análise de estabilidade estatística")
637
+ txt.append(" - Comparação com cenários manuais")
638
+ txt.append("")
639
+ txt.append("Por que a força bruta é inviável?")
640
+ txt.append("")
641
+ txt.append(f"Com {len(talhoes)} talhões e {len(culturas)} culturas, o número de")
642
+ txt.append("combinações cresce exponencialmente.")
643
+ txt.append("")
644
+
645
+ if total_comb >= 1_000_000_000:
646
+ tempo_1m = total_comb / 1_000_000
647
+ tempo_1b = total_comb / 1_000_000_000
648
+ txt.append(f"Testar {total_comb:,} combinações levaria:".replace(",", "."))
649
+ txt.append(f" - 1 milhão/s: {format_duration(tempo_1m)}")
650
+ txt.append(f" - 1 bilhão/s: {format_duration(tempo_1b)}")
651
+ else:
652
+ txt.append(f"Testar {total_comb:,} combinações seria computacionalmente custoso.".replace(",", "."))
653
+
654
+ txt.append("")
655
+ txt.append("O Algoritmo Genético é a solução ideal para este cenário.")
656
+ txt.append("")
657
+ else:
658
+ # Força bruta viável
659
+ txt.append(f"Total de combinações testadas: {validacao['forca_bruta']['total_combinacoes']}")
660
+ txt.append("")
661
+ txt.append("MELHOR SOLUÇÃO POR FORÇA BRUTA:")
662
+ for p in validacao['forca_bruta']['plano']:
663
+ txt.append(f" Talhão {p['talhao']}: {display_name(p['cultura']).title()}")
664
+ txt.append(f" Fitness: {validacao['forca_bruta']['melhor_fitness']:.2f}")
665
+ txt.append(f" Lucro: {format_currency_brl(validacao['forca_bruta']['lucro_total'])}")
666
+ txt.append("")
667
+ txt.append("MELHOR SOLUÇÃO PELO AG:")
668
+ for p in validacao['ag']['plano']:
669
+ txt.append(f" Talhão {p['talhao']}: {display_name(p['cultura']).title()}")
670
+ txt.append(f" Fitness: {validacao['ag']['fitness']:.2f}")
671
+ txt.append(f" Lucro: {format_currency_brl(validacao['ag']['lucro_total'])}")
672
+ txt.append("")
673
+
674
+ if validacao['ag_encontrou_otimo_global']:
675
+ txt.append("STATUS: O Algoritmo Genético encontrou o ótimo global!")
676
+ else:
677
+ txt.append("STATUS: O AG encontrou uma solução próxima, mas não o ótimo global.")
678
+ txt.append(f" Diferença de fitness: {validacao['diferenca_fitness']:.2f}")
679
+ txt.append("")
680
+
681
+ txt.append("=" * 80)
682
+ txt.append("")
683
+
684
+ # 6. Estabilidade
685
+ txt.append("6. ESTABILIDADE DO ALGORITMO")
686
+ txt.append("-" * 80)
687
+ txt.append("")
688
+ txt.append(f"Rodadas executadas: {estabilidade['rodadas']}")
689
+ txt.append("")
690
+ txt.append("ESTATÍSTICAS:")
691
+ txt.append(f" Melhor Fitness: {estabilidade['melhor_fitness']:.2f}")
692
+ txt.append(f" Fitness Médio: {estabilidade['fitness_medio']:.2f}")
693
+ txt.append(f" Pior Fitness: {estabilidade['pior_fitness']:.2f}")
694
+ txt.append(f" Desvio Padrão: {estabilidade['desvio_padrao']:.2f}")
695
+ txt.append(f" Coeficiente de Variação: {estabilidade['coeficiente_variacao']:.2f}%")
696
+ txt.append("")
697
+ estabilidade_label = display_name(estabilidade['estabilidade']).upper()
698
+ txt.append(f"ESTABILIDADE: {estabilidade_label}")
699
+ txt.append("")
700
+ txt.append(estabilidade['estabilidade_descricao'])
701
+ txt.append("")
702
+ txt.append("=" * 80)
703
+ txt.append("")
704
+
705
+ # 7. Justificativa Agronômica
706
+ txt.append("7. JUSTIFICATIVA AGRONÔMICA")
707
+ txt.append("-" * 80)
708
+ txt.append("")
709
+
710
+ for p in resultado_ag['plano']:
711
+ cultura_display = display_name(p['cultura']).upper()
712
+ solo_display = display_name(p['solo'])
713
+ clima_display = display_name(p['clima'])
714
+ relevo_display = display_name(p['relevo'])
715
+ agua_display = display_name(p['agua'])
716
+
717
+ txt.append(f"Talhão {p['talhao']}: {cultura_display}")
718
+ txt.append("")
719
+ txt.append(f"Por que {display_name(p['cultura'])} foi escolhida para este talhão?")
720
+ txt.append(f" - Solo {solo_display}: Compatível com as necessidades da cultura")
721
+ txt.append(f" - Clima {clima_display}: Adequado para o desenvolvimento")
722
+ txt.append(f" - Relevo {relevo_display}: Favorável ao cultivo")
723
+ txt.append(f" - Água {agua_display}: Atende às necessidades hídricas")
724
+ txt.append(f" - Nota de compatibilidade: {p['nota']:.2f}/100")
725
+ txt.append(f" - Lucro estimado: {format_currency_brl(p['lucro_estimado'])}")
726
+ txt.append(f" - Risco: {p['risco']}%")
727
+ txt.append("")
728
+
729
+ txt.append("=" * 80)
730
+ txt.append("")
731
+
732
+ # 8. Limitações
733
+ txt.append("8. LIMITAÇÕES DO SISTEMA")
734
+ txt.append("-" * 80)
735
+ txt.append("")
736
+ txt.append("Este sistema fornece recomendações baseadas em dados e algoritmos,")
737
+ txt.append("mas possui limitações:")
738
+ txt.append("")
739
+ txt.append("1. Dados Simulados: Os dados atuais são estimativas, não medições reais")
740
+ txt.append("2. Modelo Simplificado: Não considera todos os fatores agronômicos")
741
+ txt.append("3. Sem Análise Laboratorial: Não utiliza análise química e física do solo")
742
+ txt.append("4. Sem Dados Climáticos Reais: Não integra com estações meteorológicas")
743
+ txt.append("5. Sem Preços de Mercado Reais: Usa valores estimados")
744
+ txt.append("6. Não Substitui Agrônomo: As recomendações devem ser validadas")
745
+ txt.append("")
746
+ txt.append("RECOMENDAÇÃO: Use este sistema como ferramenta de apoio à decisão,")
747
+ txt.append("não como decisão final.")
748
+ txt.append("")
749
+ txt.append("=" * 80)
750
+ txt.append("")
751
+
752
+ # 9. Conclusão
753
+ txt.append("9. CONCLUSÃO")
754
+ txt.append("-" * 80)
755
+ txt.append("")
756
+ txt.append(f"O sistema AgroPlan AI recomenda o plano apresentado neste relatório")
757
+ txt.append(f"com base no objetivo {display_name(objetivo)}.")
758
+ txt.append("")
759
+
760
+ if validacao.get('erro'):
761
+ estabilidade_label = display_name(estabilidade['estabilidade'])
762
+ txt.append(f"A solução foi validada por meio de múltiplas rodadas e apresenta")
763
+ txt.append(f"estabilidade {estabilidade_label}.")
764
+ txt.append("")
765
+ total_comb_fmt = f"{validacao.get('total_combinacoes', 0):,}".replace(",", ".")
766
+ txt.append(f"Com {total_comb_fmt} combinações possíveis, o Algoritmo Genético")
767
+ txt.append("demonstra sua essencialidade para resolver problemas de planejamento")
768
+ txt.append("agrícola em escala real.")
769
+ else:
770
+ estabilidade_label = display_name(estabilidade['estabilidade'])
771
+ txt.append(f"A solução foi validada por força bruta e apresenta estabilidade")
772
+ txt.append(f"{estabilidade_label}.")
773
+
774
+ txt.append("")
775
+ txt.append("PRÓXIMOS PASSOS SUGERIDOS:")
776
+ txt.append("1. Validar recomendações com agrônomo")
777
+ txt.append("2. Considerar fatores locais não modelados")
778
+ txt.append("3. Ajustar conforme disponibilidade de recursos")
779
+ txt.append("4. Monitorar resultados para melhorias futuras")
780
+ txt.append("")
781
+ txt.append("=" * 80)
782
+ txt.append("")
783
+ txt.append("*Relatório gerado automaticamente pelo AgroPlan AI*")
784
+
785
+ return "\n".join(txt)