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.
- package/README.md +95 -0
- package/backend-template/.env.example +21 -0
- package/backend-template/Dockerfile +21 -0
- package/backend-template/README.md +274 -0
- package/backend-template/api.py +538 -0
- package/backend-template/core/bruteforce_validator.py +248 -0
- package/backend-template/core/genetic_optimizer.py +328 -0
- package/backend-template/core/loader.py +8 -0
- package/backend-template/core/planner.py +79 -0
- package/backend-template/core/report_generator.py +785 -0
- package/backend-template/core/scenario_simulator.py +286 -0
- package/backend-template/core/scorer.py +101 -0
- package/backend-template/core/terrain_analyzer.py +123 -0
- package/backend-template/data/culturas.csv +11 -0
- package/backend-template/data/regras_culturas.csv +11 -0
- package/backend-template/data/talhoes.csv +11 -0
- package/backend-template/main.py +487 -0
- package/backend-template/reports/.gitkeep +1 -0
- package/backend-template/requirements.txt +6 -0
- package/dist/index.js +719 -0
- package/package.json +51 -0
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Validador por força bruta para comparar com o Algoritmo Genético
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import itertools
|
|
6
|
+
from core.terrain_analyzer import analisar_todos_talhoes
|
|
7
|
+
from core.genetic_optimizer import criar_funcao_fitness
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def validar_por_forca_bruta(culturas, talhoes, regras, objetivo='equilibrado'):
|
|
11
|
+
"""
|
|
12
|
+
Testa todas as combinações possíveis de culturas por talhão
|
|
13
|
+
|
|
14
|
+
Útil para validar o AG em conjuntos pequenos de dados.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
culturas: DataFrame com culturas
|
|
18
|
+
talhoes: DataFrame com talhões
|
|
19
|
+
regras: DataFrame com regras
|
|
20
|
+
objetivo: 'equilibrado', 'lucro', 'risco' ou 'sustentavel'
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
Dicionário com melhor solução encontrada
|
|
24
|
+
"""
|
|
25
|
+
# Analisa todos os talhões
|
|
26
|
+
analises = analisar_todos_talhoes(talhoes, culturas, regras)
|
|
27
|
+
|
|
28
|
+
# Cria função fitness
|
|
29
|
+
fitness_function = criar_funcao_fitness(analises, culturas, objetivo)
|
|
30
|
+
|
|
31
|
+
# Gera todas as combinações possíveis
|
|
32
|
+
# Cada talhão pode ter qualquer cultura do seu ranking
|
|
33
|
+
espacos_genes = []
|
|
34
|
+
for analise in analises:
|
|
35
|
+
num_culturas = len(analise['ranking'])
|
|
36
|
+
espacos_genes.append(range(num_culturas))
|
|
37
|
+
|
|
38
|
+
# Calcula total de combinações
|
|
39
|
+
total_combinacoes = 1
|
|
40
|
+
for espaco in espacos_genes:
|
|
41
|
+
total_combinacoes *= len(espaco)
|
|
42
|
+
|
|
43
|
+
# Se for muito grande, retorna aviso
|
|
44
|
+
if total_combinacoes > 10000:
|
|
45
|
+
return {
|
|
46
|
+
'erro': True,
|
|
47
|
+
'mensagem': f'Número de combinações muito grande ({total_combinacoes}). Força bruta não é viável.',
|
|
48
|
+
'total_combinacoes': total_combinacoes
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
# Testa todas as combinações
|
|
52
|
+
melhor_solucao = None
|
|
53
|
+
melhor_fitness = -float('inf')
|
|
54
|
+
melhor_plano = None
|
|
55
|
+
|
|
56
|
+
for combinacao in itertools.product(*espacos_genes):
|
|
57
|
+
# Calcula fitness desta combinação
|
|
58
|
+
fitness = fitness_function(None, list(combinacao), 0)
|
|
59
|
+
|
|
60
|
+
if fitness > melhor_fitness:
|
|
61
|
+
melhor_fitness = fitness
|
|
62
|
+
melhor_solucao = list(combinacao)
|
|
63
|
+
|
|
64
|
+
# Constrói o plano da melhor solução
|
|
65
|
+
plano = []
|
|
66
|
+
lucro_total = 0
|
|
67
|
+
area_total = 0
|
|
68
|
+
risco_ponderado = 0
|
|
69
|
+
culturas_usadas = set()
|
|
70
|
+
|
|
71
|
+
for i, gene in enumerate(melhor_solucao):
|
|
72
|
+
analise = analises[i]
|
|
73
|
+
talhao = analise['talhao']
|
|
74
|
+
ranking = analise['ranking']
|
|
75
|
+
|
|
76
|
+
cultura_idx = int(gene) % len(ranking)
|
|
77
|
+
cultura = ranking[cultura_idx]
|
|
78
|
+
|
|
79
|
+
plano.append({
|
|
80
|
+
'talhao': talhao['id'],
|
|
81
|
+
'area': talhao['area'],
|
|
82
|
+
'solo': talhao['solo'],
|
|
83
|
+
'clima': talhao['clima'],
|
|
84
|
+
'relevo': talhao['relevo'],
|
|
85
|
+
'agua': talhao['agua'],
|
|
86
|
+
'cultura': cultura['cultura'],
|
|
87
|
+
'lucro_estimado': cultura['lucro_total'],
|
|
88
|
+
'risco': cultura['risco'],
|
|
89
|
+
'nota': cultura['nota'],
|
|
90
|
+
'tempo': cultura['tempo']
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
lucro_total += cultura['lucro_total']
|
|
94
|
+
area_total += talhao['area']
|
|
95
|
+
risco_ponderado += cultura['risco'] * talhao['area']
|
|
96
|
+
culturas_usadas.add(cultura['cultura'])
|
|
97
|
+
|
|
98
|
+
risco_medio = risco_ponderado / area_total if area_total > 0 else 0
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
'erro': False,
|
|
102
|
+
'plano': plano,
|
|
103
|
+
'melhor_fitness': melhor_fitness,
|
|
104
|
+
'total_combinacoes': total_combinacoes,
|
|
105
|
+
'lucro_total': lucro_total,
|
|
106
|
+
'risco_medio': risco_medio,
|
|
107
|
+
'area_total': area_total,
|
|
108
|
+
'diversidade': len(culturas_usadas),
|
|
109
|
+
'objetivo': objetivo,
|
|
110
|
+
'solucao': melhor_solucao
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def comparar_ag_com_forca_bruta(culturas, talhoes, regras, objetivo='equilibrado', seed=42):
|
|
115
|
+
"""
|
|
116
|
+
Compara resultado do AG com força bruta
|
|
117
|
+
|
|
118
|
+
Args:
|
|
119
|
+
culturas: DataFrame com culturas
|
|
120
|
+
talhoes: DataFrame com talhões
|
|
121
|
+
regras: DataFrame com regras
|
|
122
|
+
objetivo: objetivo do AG
|
|
123
|
+
seed: seed para reprodutibilidade do AG
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
Dicionário com comparação
|
|
127
|
+
"""
|
|
128
|
+
from core.genetic_optimizer import otimizar_plano_genetico
|
|
129
|
+
|
|
130
|
+
# Executa força bruta
|
|
131
|
+
print(" Testando todas as combinações possíveis...")
|
|
132
|
+
resultado_fb = validar_por_forca_bruta(culturas, talhoes, regras, objetivo)
|
|
133
|
+
|
|
134
|
+
if resultado_fb.get('erro'):
|
|
135
|
+
return resultado_fb
|
|
136
|
+
|
|
137
|
+
# Executa AG
|
|
138
|
+
print(" Executando Algoritmo Genético...")
|
|
139
|
+
resultado_ag = otimizar_plano_genetico(culturas, talhoes, regras, objetivo, seed=seed)
|
|
140
|
+
|
|
141
|
+
# Compara resultados
|
|
142
|
+
ag_encontrou_otimo = abs(resultado_ag['fitness'] - resultado_fb['melhor_fitness']) < 0.01
|
|
143
|
+
diferenca_fitness = resultado_ag['fitness'] - resultado_fb['melhor_fitness']
|
|
144
|
+
diferenca_lucro = resultado_ag['lucro_total'] - resultado_fb['lucro_total']
|
|
145
|
+
|
|
146
|
+
# Gera análise
|
|
147
|
+
if ag_encontrou_otimo:
|
|
148
|
+
analise = (
|
|
149
|
+
"✅ O Algoritmo Genético encontrou o ótimo global neste conjunto de dados. "
|
|
150
|
+
f"Como o conjunto atual possui apenas {resultado_fb['total_combinacoes']} combinações, "
|
|
151
|
+
"a força bruta ainda é viável. Porém, em cenários maiores, como 10 talhões e 8 culturas, "
|
|
152
|
+
"seriam mais de 1 bilhão de combinações, tornando o Algoritmo Genético essencial."
|
|
153
|
+
)
|
|
154
|
+
else:
|
|
155
|
+
percentual = (diferenca_fitness / resultado_fb['melhor_fitness']) * 100 if resultado_fb['melhor_fitness'] > 0 else 0
|
|
156
|
+
analise = (
|
|
157
|
+
f"⚠️ O Algoritmo Genético encontrou uma solução {abs(percentual):.1f}% "
|
|
158
|
+
f"{'melhor' if diferenca_fitness > 0 else 'pior'} que o ótimo global. "
|
|
159
|
+
f"Diferença de fitness: {diferenca_fitness:.2f}. "
|
|
160
|
+
"Isso pode ocorrer devido à aleatoriedade do AG. "
|
|
161
|
+
"Execute múltiplas rodadas para avaliar a estabilidade."
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
return {
|
|
165
|
+
'erro': False,
|
|
166
|
+
'ag': resultado_ag,
|
|
167
|
+
'forca_bruta': resultado_fb,
|
|
168
|
+
'ag_encontrou_otimo_global': ag_encontrou_otimo,
|
|
169
|
+
'diferenca_fitness': diferenca_fitness,
|
|
170
|
+
'diferenca_lucro': diferenca_lucro,
|
|
171
|
+
'analise': analise
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def executar_multiplas_rodadas(culturas, talhoes, regras, objetivo='equilibrado', rodadas=10):
|
|
176
|
+
"""
|
|
177
|
+
Executa o AG múltiplas vezes para avaliar estabilidade
|
|
178
|
+
|
|
179
|
+
Args:
|
|
180
|
+
culturas: DataFrame com culturas
|
|
181
|
+
talhoes: DataFrame com talhões
|
|
182
|
+
regras: DataFrame com regras
|
|
183
|
+
objetivo: objetivo do AG
|
|
184
|
+
rodadas: número de execuções
|
|
185
|
+
|
|
186
|
+
Returns:
|
|
187
|
+
Dicionário com estatísticas
|
|
188
|
+
"""
|
|
189
|
+
from core.genetic_optimizer import otimizar_plano_genetico
|
|
190
|
+
import statistics
|
|
191
|
+
|
|
192
|
+
resultados = []
|
|
193
|
+
fitness_list = []
|
|
194
|
+
lucros_list = []
|
|
195
|
+
riscos_list = []
|
|
196
|
+
|
|
197
|
+
print(f" Executando {rodadas} rodadas do Algoritmo Genético...")
|
|
198
|
+
|
|
199
|
+
for i in range(rodadas):
|
|
200
|
+
# Usa seed diferente para cada rodada
|
|
201
|
+
resultado = otimizar_plano_genetico(culturas, talhoes, regras, objetivo, seed=i)
|
|
202
|
+
resultados.append(resultado)
|
|
203
|
+
fitness_list.append(resultado['fitness'])
|
|
204
|
+
lucros_list.append(resultado['lucro_total'])
|
|
205
|
+
riscos_list.append(resultado['risco_medio'])
|
|
206
|
+
print(f" Rodada {i+1}/{rodadas}: Fitness = {resultado['fitness']:.2f}")
|
|
207
|
+
|
|
208
|
+
# Encontra melhor e pior
|
|
209
|
+
melhor_idx = fitness_list.index(max(fitness_list))
|
|
210
|
+
pior_idx = fitness_list.index(min(fitness_list))
|
|
211
|
+
|
|
212
|
+
melhor_resultado = resultados[melhor_idx]
|
|
213
|
+
pior_resultado = resultados[pior_idx]
|
|
214
|
+
|
|
215
|
+
# Calcula estatísticas
|
|
216
|
+
fitness_medio = statistics.mean(fitness_list)
|
|
217
|
+
fitness_desvio = statistics.stdev(fitness_list) if len(fitness_list) > 1 else 0
|
|
218
|
+
lucro_medio = statistics.mean(lucros_list)
|
|
219
|
+
risco_medio = statistics.mean(riscos_list)
|
|
220
|
+
|
|
221
|
+
# Avalia estabilidade
|
|
222
|
+
coef_variacao = (fitness_desvio / fitness_medio * 100) if fitness_medio > 0 else 0
|
|
223
|
+
|
|
224
|
+
if coef_variacao < 2:
|
|
225
|
+
estabilidade = 'alta'
|
|
226
|
+
estabilidade_desc = 'O algoritmo apresentou alta estabilidade, encontrando soluções muito semelhantes em todas as execuções.'
|
|
227
|
+
elif coef_variacao < 5:
|
|
228
|
+
estabilidade = 'média'
|
|
229
|
+
estabilidade_desc = 'O algoritmo apresentou estabilidade média, com alguma variação entre as execuções.'
|
|
230
|
+
else:
|
|
231
|
+
estabilidade = 'baixa'
|
|
232
|
+
estabilidade_desc = 'O algoritmo apresentou variação significativa entre as execuções. Considere aumentar o número de gerações ou população.'
|
|
233
|
+
|
|
234
|
+
return {
|
|
235
|
+
'rodadas': rodadas,
|
|
236
|
+
'melhor_fitness': max(fitness_list),
|
|
237
|
+
'fitness_medio': fitness_medio,
|
|
238
|
+
'pior_fitness': min(fitness_list),
|
|
239
|
+
'desvio_padrao': fitness_desvio,
|
|
240
|
+
'coeficiente_variacao': coef_variacao,
|
|
241
|
+
'lucro_medio': lucro_medio,
|
|
242
|
+
'risco_medio': risco_medio,
|
|
243
|
+
'melhor_plano': melhor_resultado,
|
|
244
|
+
'pior_plano': pior_resultado,
|
|
245
|
+
'estabilidade': estabilidade,
|
|
246
|
+
'estabilidade_descricao': estabilidade_desc,
|
|
247
|
+
'todos_resultados': resultados
|
|
248
|
+
}
|
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Otimizador de plantio usando Algoritmo Genético (PyGAD)
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import pygad
|
|
6
|
+
import numpy as np
|
|
7
|
+
from core.terrain_analyzer import analisar_todos_talhoes
|
|
8
|
+
from core.scorer import calcular_lucro_por_hectare
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
# Configurações de pesos por objetivo
|
|
12
|
+
PESOS_OBJETIVOS = {
|
|
13
|
+
'equilibrado': {
|
|
14
|
+
'lucro': 0.40,
|
|
15
|
+
'compatibilidade': 0.30,
|
|
16
|
+
'seguranca': 0.20,
|
|
17
|
+
'diversidade': 0.10
|
|
18
|
+
},
|
|
19
|
+
'lucro': {
|
|
20
|
+
'lucro': 0.60,
|
|
21
|
+
'compatibilidade': 0.20,
|
|
22
|
+
'seguranca': 0.10,
|
|
23
|
+
'diversidade': 0.10
|
|
24
|
+
},
|
|
25
|
+
'risco': {
|
|
26
|
+
'lucro': 0.20,
|
|
27
|
+
'compatibilidade': 0.30,
|
|
28
|
+
'seguranca': 0.40,
|
|
29
|
+
'diversidade': 0.10
|
|
30
|
+
},
|
|
31
|
+
'sustentavel': {
|
|
32
|
+
'lucro': 0.20,
|
|
33
|
+
'compatibilidade': 0.40,
|
|
34
|
+
'seguranca': 0.20,
|
|
35
|
+
'diversidade': 0.20
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def criar_funcao_fitness(analises, culturas, objetivo='equilibrado'):
|
|
41
|
+
"""
|
|
42
|
+
Cria a função fitness para o Algoritmo Genético
|
|
43
|
+
|
|
44
|
+
A função avalia cada solução (combinação de culturas) considerando:
|
|
45
|
+
- Lucro total
|
|
46
|
+
- Compatibilidade com terreno
|
|
47
|
+
- Risco
|
|
48
|
+
- Diversidade de culturas
|
|
49
|
+
- Penalidades agronômicas
|
|
50
|
+
"""
|
|
51
|
+
pesos = PESOS_OBJETIVOS.get(objetivo, PESOS_OBJETIVOS['equilibrado'])
|
|
52
|
+
|
|
53
|
+
# Calcula valores máximos para normalização
|
|
54
|
+
todos_lucros = []
|
|
55
|
+
todas_notas = []
|
|
56
|
+
|
|
57
|
+
for analise in analises:
|
|
58
|
+
for cultura in analise['ranking']:
|
|
59
|
+
todos_lucros.append(cultura['lucro_total'])
|
|
60
|
+
todas_notas.append(cultura['nota'])
|
|
61
|
+
|
|
62
|
+
lucro_max = max(todos_lucros) if todos_lucros else 1
|
|
63
|
+
lucro_min = min(todos_lucros) if todos_lucros else 0
|
|
64
|
+
nota_max = max(todas_notas) if todas_notas else 1
|
|
65
|
+
|
|
66
|
+
def fitness_func(ga_instance, solution, solution_idx):
|
|
67
|
+
"""
|
|
68
|
+
Calcula fitness de uma solução
|
|
69
|
+
|
|
70
|
+
solution: array de índices de culturas, ex: [0, 1, 1]
|
|
71
|
+
"""
|
|
72
|
+
lucro_total = 0
|
|
73
|
+
notas_total = 0
|
|
74
|
+
area_total = 0
|
|
75
|
+
risco_ponderado = 0
|
|
76
|
+
culturas_usadas = set()
|
|
77
|
+
penalidades = 0
|
|
78
|
+
|
|
79
|
+
# Avalia cada talhão
|
|
80
|
+
for i, gene in enumerate(solution):
|
|
81
|
+
analise = analises[i]
|
|
82
|
+
talhao = analise['talhao']
|
|
83
|
+
ranking = analise['ranking']
|
|
84
|
+
|
|
85
|
+
# Gene representa índice da cultura no ranking
|
|
86
|
+
cultura_idx = int(gene) % len(ranking)
|
|
87
|
+
cultura = ranking[cultura_idx]
|
|
88
|
+
|
|
89
|
+
# Acumula métricas
|
|
90
|
+
lucro_total += cultura['lucro_total']
|
|
91
|
+
notas_total += cultura['nota']
|
|
92
|
+
area_total += talhao['area']
|
|
93
|
+
risco_ponderado += cultura['risco'] * talhao['area']
|
|
94
|
+
culturas_usadas.add(cultura['cultura'])
|
|
95
|
+
|
|
96
|
+
# PENALIDADES
|
|
97
|
+
|
|
98
|
+
# 1. Compatibilidade geral baixa (nota < 60)
|
|
99
|
+
if cultura['nota'] < 60:
|
|
100
|
+
penalidades += 15
|
|
101
|
+
|
|
102
|
+
# 2. Água insuficiente (alta necessidade + baixa disponibilidade)
|
|
103
|
+
if cultura['compatibilidade_agua'] == 0:
|
|
104
|
+
penalidades += 20
|
|
105
|
+
|
|
106
|
+
# 3. Solo incompatível
|
|
107
|
+
if cultura['compatibilidade_solo'] == 0:
|
|
108
|
+
penalidades += 10
|
|
109
|
+
|
|
110
|
+
# 4. Clima incompatível
|
|
111
|
+
if cultura['compatibilidade_clima'] == 0:
|
|
112
|
+
penalidades += 10
|
|
113
|
+
|
|
114
|
+
# Calcula métricas agregadas
|
|
115
|
+
risco_medio = risco_ponderado / area_total if area_total > 0 else 0
|
|
116
|
+
nota_media = notas_total / len(solution) if len(solution) > 0 else 0
|
|
117
|
+
|
|
118
|
+
# PENALIDADES GLOBAIS
|
|
119
|
+
|
|
120
|
+
# 5. Monocultura (mesma cultura em todos os talhões)
|
|
121
|
+
if len(culturas_usadas) == 1:
|
|
122
|
+
penalidades += 25
|
|
123
|
+
|
|
124
|
+
# 6. Risco muito alto (> 45%)
|
|
125
|
+
if risco_medio > 45:
|
|
126
|
+
penalidades += 20
|
|
127
|
+
|
|
128
|
+
# 7. Nota média muito baixa (< 70)
|
|
129
|
+
if nota_media < 70:
|
|
130
|
+
penalidades += 10
|
|
131
|
+
|
|
132
|
+
# NORMALIZAÇÃO DOS COMPONENTES (todos entre 0-100)
|
|
133
|
+
|
|
134
|
+
# Lucro normalizado (0-100)
|
|
135
|
+
if lucro_max > lucro_min:
|
|
136
|
+
lucro_normalizado = min(100 * (lucro_total - lucro_min) / (lucro_max - lucro_min), 100)
|
|
137
|
+
else:
|
|
138
|
+
lucro_normalizado = 50
|
|
139
|
+
|
|
140
|
+
# Compatibilidade normalizada (0-100)
|
|
141
|
+
compatibilidade_normalizada = min((nota_media / nota_max) * 100 if nota_max > 0 else 50, 100)
|
|
142
|
+
|
|
143
|
+
# Segurança (inverso do risco, 0-100)
|
|
144
|
+
seguranca = min(max(0, 100 - risco_medio), 100)
|
|
145
|
+
|
|
146
|
+
# Diversidade (0-100)
|
|
147
|
+
max_diversidade = len(solution)
|
|
148
|
+
diversidade = min((len(culturas_usadas) / max_diversidade) * 100 if max_diversidade > 0 else 0, 100)
|
|
149
|
+
|
|
150
|
+
# CÁLCULO DO FITNESS
|
|
151
|
+
fitness = (
|
|
152
|
+
lucro_normalizado * pesos['lucro'] +
|
|
153
|
+
compatibilidade_normalizada * pesos['compatibilidade'] +
|
|
154
|
+
seguranca * pesos['seguranca'] +
|
|
155
|
+
diversidade * pesos['diversidade']
|
|
156
|
+
) - penalidades
|
|
157
|
+
|
|
158
|
+
return max(0, fitness) # Garante que fitness não seja negativo
|
|
159
|
+
|
|
160
|
+
return fitness_func
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def otimizar_plano_genetico(culturas, talhoes, regras, objetivo='equilibrado', geracoes=100, populacao=50, seed=None):
|
|
164
|
+
"""
|
|
165
|
+
Otimiza o plano de plantio usando Algoritmo Genético
|
|
166
|
+
|
|
167
|
+
Args:
|
|
168
|
+
culturas: DataFrame com culturas disponíveis
|
|
169
|
+
talhoes: DataFrame com talhões
|
|
170
|
+
regras: DataFrame com regras de cultivo
|
|
171
|
+
objetivo: 'equilibrado', 'lucro', 'risco' ou 'sustentavel'
|
|
172
|
+
geracoes: número de gerações do AG
|
|
173
|
+
populacao: tamanho da população
|
|
174
|
+
seed: seed para reprodutibilidade (opcional)
|
|
175
|
+
|
|
176
|
+
Returns:
|
|
177
|
+
Dicionário com plano otimizado e métricas
|
|
178
|
+
"""
|
|
179
|
+
# Analisa todos os talhões
|
|
180
|
+
analises = analisar_todos_talhoes(talhoes, culturas, regras)
|
|
181
|
+
|
|
182
|
+
# Número de genes = número de talhões
|
|
183
|
+
num_genes = len(analises)
|
|
184
|
+
|
|
185
|
+
# Cada gene pode ter valor de 0 até (número de culturas - 1)
|
|
186
|
+
# Usamos o tamanho do ranking como limite
|
|
187
|
+
gene_space = []
|
|
188
|
+
for analise in analises:
|
|
189
|
+
num_culturas = len(analise['ranking'])
|
|
190
|
+
gene_space.append(range(num_culturas))
|
|
191
|
+
|
|
192
|
+
# Cria função fitness
|
|
193
|
+
fitness_function = criar_funcao_fitness(analises, culturas, objetivo)
|
|
194
|
+
|
|
195
|
+
# Define seed se fornecida
|
|
196
|
+
random_seed = seed if seed is not None else None
|
|
197
|
+
|
|
198
|
+
# Lista para armazenar histórico de fitness
|
|
199
|
+
historico_fitness = []
|
|
200
|
+
|
|
201
|
+
def on_generation(ga_instance):
|
|
202
|
+
"""Callback chamado a cada geração para salvar histórico"""
|
|
203
|
+
geracao = ga_instance.generations_completed
|
|
204
|
+
melhor_fitness = ga_instance.best_solution()[1]
|
|
205
|
+
fitness_medio = np.mean(ga_instance.last_generation_fitness)
|
|
206
|
+
historico_fitness.append({
|
|
207
|
+
'geracao': geracao,
|
|
208
|
+
'melhor_fitness': melhor_fitness,
|
|
209
|
+
'fitness_medio': fitness_medio
|
|
210
|
+
})
|
|
211
|
+
|
|
212
|
+
# Configura o Algoritmo Genético
|
|
213
|
+
ga_instance = pygad.GA(
|
|
214
|
+
num_generations=geracoes,
|
|
215
|
+
num_parents_mating=max(2, populacao // 4),
|
|
216
|
+
fitness_func=fitness_function,
|
|
217
|
+
sol_per_pop=populacao,
|
|
218
|
+
num_genes=num_genes,
|
|
219
|
+
gene_space=gene_space,
|
|
220
|
+
gene_type=int,
|
|
221
|
+
parent_selection_type="sss", # Steady-state selection
|
|
222
|
+
keep_parents=2,
|
|
223
|
+
crossover_type="single_point",
|
|
224
|
+
mutation_type="random",
|
|
225
|
+
mutation_percent_genes=20,
|
|
226
|
+
random_seed=random_seed,
|
|
227
|
+
on_generation=on_generation
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
# Executa o AG
|
|
231
|
+
ga_instance.run()
|
|
232
|
+
|
|
233
|
+
# Obtém a melhor solução
|
|
234
|
+
solution, solution_fitness, solution_idx = ga_instance.best_solution()
|
|
235
|
+
|
|
236
|
+
# Constrói o plano a partir da solução
|
|
237
|
+
plano = []
|
|
238
|
+
lucro_total = 0
|
|
239
|
+
area_total = 0
|
|
240
|
+
risco_ponderado = 0
|
|
241
|
+
culturas_usadas = set()
|
|
242
|
+
|
|
243
|
+
for i, gene in enumerate(solution):
|
|
244
|
+
analise = analises[i]
|
|
245
|
+
talhao = analise['talhao']
|
|
246
|
+
ranking = analise['ranking']
|
|
247
|
+
|
|
248
|
+
cultura_idx = int(gene) % len(ranking)
|
|
249
|
+
cultura = ranking[cultura_idx]
|
|
250
|
+
|
|
251
|
+
plano.append({
|
|
252
|
+
'talhao': talhao['id'],
|
|
253
|
+
'area': talhao['area'],
|
|
254
|
+
'solo': talhao['solo'],
|
|
255
|
+
'clima': talhao['clima'],
|
|
256
|
+
'relevo': talhao['relevo'],
|
|
257
|
+
'agua': talhao['agua'],
|
|
258
|
+
'cultura': cultura['cultura'],
|
|
259
|
+
'lucro_estimado': cultura['lucro_total'],
|
|
260
|
+
'risco': cultura['risco'],
|
|
261
|
+
'nota': cultura['nota'],
|
|
262
|
+
'tempo': cultura['tempo']
|
|
263
|
+
})
|
|
264
|
+
|
|
265
|
+
lucro_total += cultura['lucro_total']
|
|
266
|
+
area_total += talhao['area']
|
|
267
|
+
risco_ponderado += cultura['risco'] * talhao['area']
|
|
268
|
+
culturas_usadas.add(cultura['cultura'])
|
|
269
|
+
|
|
270
|
+
risco_medio = risco_ponderado / area_total if area_total > 0 else 0
|
|
271
|
+
|
|
272
|
+
# Gera justificativa
|
|
273
|
+
justificativa = gerar_justificativa_ag(objetivo, lucro_total, risco_medio, len(culturas_usadas), solution_fitness)
|
|
274
|
+
|
|
275
|
+
return {
|
|
276
|
+
'plano': plano,
|
|
277
|
+
'lucro_total': lucro_total,
|
|
278
|
+
'risco_medio': risco_medio,
|
|
279
|
+
'area_total': area_total,
|
|
280
|
+
'fitness': solution_fitness,
|
|
281
|
+
'geracoes': geracoes,
|
|
282
|
+
'objetivo': objetivo,
|
|
283
|
+
'diversidade': len(culturas_usadas),
|
|
284
|
+
'justificativa': justificativa,
|
|
285
|
+
'historico_fitness': historico_fitness,
|
|
286
|
+
'seed': seed
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def gerar_justificativa_ag(objetivo, lucro, risco, diversidade, fitness):
|
|
291
|
+
"""Gera justificativa para o plano otimizado pelo AG"""
|
|
292
|
+
|
|
293
|
+
justificativas_base = {
|
|
294
|
+
'equilibrado': (
|
|
295
|
+
f"O algoritmo genético encontrou um plano equilibrado entre lucro, risco e "
|
|
296
|
+
f"compatibilidade do terreno. A solução mantém alto retorno financeiro "
|
|
297
|
+
f"(R$ {lucro:,.2f}) sem ultrapassar níveis críticos de risco ({risco:.1f}%). "
|
|
298
|
+
),
|
|
299
|
+
'lucro': (
|
|
300
|
+
f"O algoritmo genético priorizou maximização de lucro, encontrando uma solução "
|
|
301
|
+
f"com retorno de R$ {lucro:,.2f}. O risco médio de {risco:.1f}% foi considerado "
|
|
302
|
+
f"aceitável para este objetivo. "
|
|
303
|
+
),
|
|
304
|
+
'risco': (
|
|
305
|
+
f"O algoritmo genético priorizou segurança, mantendo o risco médio em apenas "
|
|
306
|
+
f"{risco:.1f}%. O lucro de R$ {lucro:,.2f} foi otimizado dentro das restrições "
|
|
307
|
+
f"de baixo risco. "
|
|
308
|
+
),
|
|
309
|
+
'sustentavel': (
|
|
310
|
+
f"O algoritmo genético priorizou compatibilidade ambiental e uso eficiente de "
|
|
311
|
+
f"recursos. A solução alcançou lucro de R$ {lucro:,.2f} com risco de {risco:.1f}%, "
|
|
312
|
+
f"mantendo alta compatibilidade com as características dos talhões. "
|
|
313
|
+
)
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
justificativa = justificativas_base.get(objetivo, justificativas_base['equilibrado'])
|
|
317
|
+
|
|
318
|
+
# Adiciona informação sobre diversidade
|
|
319
|
+
if diversidade == 1:
|
|
320
|
+
justificativa += "Nota: O plano utiliza apenas uma cultura (monocultura)."
|
|
321
|
+
elif diversidade == 2:
|
|
322
|
+
justificativa += f"O plano utiliza {diversidade} culturas diferentes, oferecendo alguma diversificação."
|
|
323
|
+
else:
|
|
324
|
+
justificativa += f"O plano utiliza {diversidade} culturas diferentes, oferecendo boa diversificação."
|
|
325
|
+
|
|
326
|
+
justificativa += f" Fitness final: {fitness:.2f}."
|
|
327
|
+
|
|
328
|
+
return justificativa
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
|
|
3
|
+
def carregar_dados():
|
|
4
|
+
"""Carrega os dados de culturas, talhões e regras dos arquivos CSV"""
|
|
5
|
+
culturas = pd.read_csv("data/culturas.csv")
|
|
6
|
+
talhoes = pd.read_csv("data/talhoes.csv")
|
|
7
|
+
regras = pd.read_csv("data/regras_culturas.csv")
|
|
8
|
+
return culturas, talhoes, regras
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
from core.terrain_analyzer import analisar_todos_talhoes
|
|
2
|
+
from core.scenario_simulator import simular_cenarios
|
|
3
|
+
from core.genetic_optimizer import otimizar_plano_genetico
|
|
4
|
+
|
|
5
|
+
def gerar_plano_inteligente(culturas, talhoes, regras):
|
|
6
|
+
"""
|
|
7
|
+
Gera um plano de plantio inteligente usando análise de terreno
|
|
8
|
+
|
|
9
|
+
Em vez de escolher culturas aleatoriamente, analisa as características
|
|
10
|
+
de cada talhão e recomenda a cultura mais adequada com base em:
|
|
11
|
+
- Compatibilidade de solo
|
|
12
|
+
- Compatibilidade de clima
|
|
13
|
+
- Compatibilidade de relevo
|
|
14
|
+
- Disponibilidade de água
|
|
15
|
+
- Lucro estimado
|
|
16
|
+
- Risco
|
|
17
|
+
"""
|
|
18
|
+
# Analisa todos os talhões
|
|
19
|
+
analises = analisar_todos_talhoes(talhoes, culturas, regras)
|
|
20
|
+
|
|
21
|
+
plano = []
|
|
22
|
+
|
|
23
|
+
for analise in analises:
|
|
24
|
+
talhao = analise['talhao']
|
|
25
|
+
melhor = analise['melhor_cultura']
|
|
26
|
+
ranking = analise['ranking']
|
|
27
|
+
justificativa = analise['justificativa']
|
|
28
|
+
|
|
29
|
+
if melhor:
|
|
30
|
+
plano.append({
|
|
31
|
+
'talhao': talhao['id'],
|
|
32
|
+
'area': talhao['area'],
|
|
33
|
+
'solo': talhao['solo'],
|
|
34
|
+
'clima': talhao['clima'],
|
|
35
|
+
'relevo': talhao['relevo'],
|
|
36
|
+
'agua': talhao['agua'],
|
|
37
|
+
'cultura_recomendada': melhor['cultura'],
|
|
38
|
+
'nota': melhor['nota'],
|
|
39
|
+
'lucro_estimado': melhor['lucro_total'],
|
|
40
|
+
'risco': melhor['risco'],
|
|
41
|
+
'tempo': melhor['tempo'],
|
|
42
|
+
'ranking': ranking,
|
|
43
|
+
'justificativa': justificativa
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
return plano
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def gerar_cenarios(culturas, talhoes, regras):
|
|
50
|
+
"""
|
|
51
|
+
Gera múltiplos cenários de planejamento para comparação
|
|
52
|
+
|
|
53
|
+
Retorna diferentes estratégias:
|
|
54
|
+
- Equilibrado
|
|
55
|
+
- Máximo Lucro
|
|
56
|
+
- Baixo Risco
|
|
57
|
+
- Sustentável
|
|
58
|
+
- Conservador
|
|
59
|
+
"""
|
|
60
|
+
return simular_cenarios(culturas, talhoes, regras)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def gerar_plano_genetico(culturas, talhoes, regras, objetivo='equilibrado', geracoes=100, populacao=50, seed=None):
|
|
64
|
+
"""
|
|
65
|
+
Gera plano otimizado usando Algoritmo Genético
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
culturas: DataFrame com culturas
|
|
69
|
+
talhoes: DataFrame com talhões
|
|
70
|
+
regras: DataFrame com regras
|
|
71
|
+
objetivo: 'equilibrado', 'lucro', 'risco' ou 'sustentavel'
|
|
72
|
+
geracoes: número de gerações do AG
|
|
73
|
+
populacao: tamanho da população
|
|
74
|
+
seed: seed para reprodutibilidade
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
Dicionário com plano otimizado
|
|
78
|
+
"""
|
|
79
|
+
return otimizar_plano_genetico(culturas, talhoes, regras, objetivo, geracoes, populacao, seed)
|