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,286 @@
1
+ """
2
+ Simulador de cenários para comparar diferentes estratégias de planejamento agrícola
3
+ """
4
+
5
+ from core.terrain_analyzer import analisar_todos_talhoes
6
+
7
+
8
+ def calcular_metricas_plano(plano):
9
+ """Calcula métricas agregadas de um plano"""
10
+ lucro_total = sum(p['lucro_estimado'] for p in plano)
11
+ area_total = sum(p['area'] for p in plano)
12
+ risco_ponderado = sum(p['risco'] * p['area'] for p in plano)
13
+ risco_medio = risco_ponderado / area_total if area_total > 0 else 0
14
+
15
+ return {
16
+ 'lucro_total': lucro_total,
17
+ 'area_total': area_total,
18
+ 'risco_medio': risco_medio
19
+ }
20
+
21
+
22
+ def gerar_cenario_equilibrado(analises):
23
+ """
24
+ Cenário Equilibrado: Escolhe a cultura com maior nota_final
25
+ Melhor equilíbrio entre lucro, compatibilidade e risco
26
+ """
27
+ plano = []
28
+
29
+ for analise in analises:
30
+ talhao = analise['talhao']
31
+ melhor = analise['melhor_cultura'] # Já é a de maior nota
32
+
33
+ if melhor:
34
+ plano.append({
35
+ 'talhao': talhao['id'],
36
+ 'area': talhao['area'],
37
+ 'cultura': melhor['cultura'],
38
+ 'lucro_estimado': melhor['lucro_total'],
39
+ 'risco': melhor['risco'],
40
+ 'nota': melhor['nota']
41
+ })
42
+
43
+ metricas = calcular_metricas_plano(plano)
44
+
45
+ return {
46
+ 'nome': 'Equilibrado',
47
+ 'descricao': 'Melhor equilíbrio entre lucro, compatibilidade e risco.',
48
+ 'plano': plano,
49
+ **metricas
50
+ }
51
+
52
+
53
+ def gerar_cenario_maximo_lucro(analises):
54
+ """
55
+ Cenário Máximo Lucro: Prioriza lucro_estimado
56
+ O risco pode ser maior, mas ainda aparece no relatório
57
+ """
58
+ plano = []
59
+
60
+ for analise in analises:
61
+ talhao = analise['talhao']
62
+ ranking = analise['ranking']
63
+
64
+ # Escolhe a cultura com maior lucro
65
+ melhor_lucro = max(ranking, key=lambda x: x['lucro_total'])
66
+
67
+ plano.append({
68
+ 'talhao': talhao['id'],
69
+ 'area': talhao['area'],
70
+ 'cultura': melhor_lucro['cultura'],
71
+ 'lucro_estimado': melhor_lucro['lucro_total'],
72
+ 'risco': melhor_lucro['risco'],
73
+ 'nota': melhor_lucro['nota']
74
+ })
75
+
76
+ metricas = calcular_metricas_plano(plano)
77
+
78
+ return {
79
+ 'nome': 'Máximo Lucro',
80
+ 'descricao': 'Prioriza o maior retorno financeiro estimado.',
81
+ 'plano': plano,
82
+ **metricas
83
+ }
84
+
85
+
86
+ def gerar_cenario_baixo_risco(analises):
87
+ """
88
+ Cenário Baixo Risco: Prioriza culturas com menor risco
89
+ Em caso de empate, escolhe maior nota_final
90
+ """
91
+ plano = []
92
+
93
+ for analise in analises:
94
+ talhao = analise['talhao']
95
+ ranking = analise['ranking']
96
+
97
+ # Ordena por risco (crescente) e depois por nota (decrescente)
98
+ ranking_ordenado = sorted(ranking, key=lambda x: (x['risco'], -x['nota']))
99
+ melhor_baixo_risco = ranking_ordenado[0]
100
+
101
+ plano.append({
102
+ 'talhao': talhao['id'],
103
+ 'area': talhao['area'],
104
+ 'cultura': melhor_baixo_risco['cultura'],
105
+ 'lucro_estimado': melhor_baixo_risco['lucro_total'],
106
+ 'risco': melhor_baixo_risco['risco'],
107
+ 'nota': melhor_baixo_risco['nota']
108
+ })
109
+
110
+ metricas = calcular_metricas_plano(plano)
111
+
112
+ return {
113
+ 'nome': 'Baixo Risco',
114
+ 'descricao': 'Prioriza segurança e menor exposição a perdas.',
115
+ 'plano': plano,
116
+ **metricas
117
+ }
118
+
119
+
120
+ def gerar_cenario_sustentavel(analises):
121
+ """
122
+ Cenário Sustentável: Prioriza boa compatibilidade com solo, água e risco menor
123
+ Não escolhe apenas pelo lucro
124
+ """
125
+ plano = []
126
+
127
+ for analise in analises:
128
+ talhao = analise['talhao']
129
+ ranking = analise['ranking']
130
+
131
+ # Calcula score de sustentabilidade
132
+ # Prioriza compatibilidades altas e risco baixo
133
+ def score_sustentavel(cultura):
134
+ compatibilidade = (
135
+ cultura['compatibilidade_solo'] +
136
+ cultura['compatibilidade_clima'] +
137
+ cultura['compatibilidade_agua']
138
+ )
139
+ # Penaliza risco mais fortemente
140
+ return compatibilidade - (cultura['risco'] / 5)
141
+
142
+ melhor_sustentavel = max(ranking, key=score_sustentavel)
143
+
144
+ plano.append({
145
+ 'talhao': talhao['id'],
146
+ 'area': talhao['area'],
147
+ 'cultura': melhor_sustentavel['cultura'],
148
+ 'lucro_estimado': melhor_sustentavel['lucro_total'],
149
+ 'risco': melhor_sustentavel['risco'],
150
+ 'nota': melhor_sustentavel['nota']
151
+ })
152
+
153
+ metricas = calcular_metricas_plano(plano)
154
+
155
+ return {
156
+ 'nome': 'Sustentável',
157
+ 'descricao': 'Prioriza compatibilidade ambiental e uso eficiente de recursos.',
158
+ 'plano': plano,
159
+ **metricas
160
+ }
161
+
162
+
163
+ def gerar_cenario_conservador(analises):
164
+ """
165
+ Cenário Conservador: Evita culturas com risco alto
166
+ Escolhe opções com boa nota e risco controlado
167
+ """
168
+ plano = []
169
+
170
+ for analise in analises:
171
+ talhao = analise['talhao']
172
+ ranking = analise['ranking']
173
+
174
+ # Filtra culturas com risco <= 30%, se possível
175
+ ranking_baixo_risco = [c for c in ranking if c['risco'] <= 30]
176
+
177
+ # Se não houver opções de baixo risco, usa todas
178
+ if not ranking_baixo_risco:
179
+ ranking_baixo_risco = ranking
180
+
181
+ # Entre as de baixo risco, escolhe a de maior nota
182
+ melhor_conservador = max(ranking_baixo_risco, key=lambda x: x['nota'])
183
+
184
+ plano.append({
185
+ 'talhao': talhao['id'],
186
+ 'area': talhao['area'],
187
+ 'cultura': melhor_conservador['cultura'],
188
+ 'lucro_estimado': melhor_conservador['lucro_total'],
189
+ 'risco': melhor_conservador['risco'],
190
+ 'nota': melhor_conservador['nota']
191
+ })
192
+
193
+ metricas = calcular_metricas_plano(plano)
194
+
195
+ return {
196
+ 'nome': 'Conservador',
197
+ 'descricao': 'Evita riscos altos, priorizando segurança e estabilidade.',
198
+ 'plano': plano,
199
+ **metricas
200
+ }
201
+
202
+
203
+ def simular_cenarios(culturas, talhoes, regras):
204
+ """
205
+ Simula diferentes cenários de planejamento agrícola
206
+
207
+ Retorna um dicionário com todos os cenários e suas métricas
208
+ """
209
+ # Analisa todos os talhões uma única vez
210
+ analises = analisar_todos_talhoes(talhoes, culturas, regras)
211
+
212
+ # Gera todos os cenários
213
+ cenarios = {
214
+ 'equilibrado': gerar_cenario_equilibrado(analises),
215
+ 'maximo_lucro': gerar_cenario_maximo_lucro(analises),
216
+ 'baixo_risco': gerar_cenario_baixo_risco(analises),
217
+ 'sustentavel': gerar_cenario_sustentavel(analises),
218
+ 'conservador': gerar_cenario_conservador(analises)
219
+ }
220
+
221
+ return cenarios
222
+
223
+
224
+ def recomendar_melhor_cenario(cenarios):
225
+ """
226
+ Recomenda o melhor cenário baseado em critérios balanceados
227
+
228
+ Considera:
229
+ - Lucro razoável (não necessariamente o máximo)
230
+ - Risco controlado
231
+ - Equilíbrio geral
232
+ """
233
+ # Por padrão, recomenda o equilibrado
234
+ melhor = 'equilibrado'
235
+
236
+ # Mas verifica se há cenários melhores
237
+ equilibrado = cenarios['equilibrado']
238
+ baixo_risco = cenarios['baixo_risco']
239
+
240
+ # Se o baixo risco tem lucro muito próximo (>90%) e risco menor, prefere ele
241
+ if baixo_risco['lucro_total'] >= equilibrado['lucro_total'] * 0.9:
242
+ if baixo_risco['risco_medio'] < equilibrado['risco_medio']:
243
+ melhor = 'baixo_risco'
244
+
245
+ justificativa = gerar_justificativa_recomendacao(cenarios, melhor)
246
+
247
+ return {
248
+ 'cenario': melhor,
249
+ 'justificativa': justificativa
250
+ }
251
+
252
+
253
+ def gerar_justificativa_recomendacao(cenarios, cenario_escolhido):
254
+ """Gera justificativa para a recomendação do cenário"""
255
+ cenario = cenarios[cenario_escolhido]
256
+
257
+ justificativas = {
258
+ 'equilibrado': (
259
+ f"O cenário {cenario['nome']} foi recomendado porque apresenta "
260
+ f"bom lucro estimado (R$ {cenario['lucro_total']:,.2f}), "
261
+ f"risco controlado ({cenario['risco_medio']:.1f}%) e "
262
+ f"alta compatibilidade entre culturas e características dos talhões."
263
+ ),
264
+ 'baixo_risco': (
265
+ f"O cenário {cenario['nome']} foi recomendado porque oferece "
266
+ f"excelente segurança com risco médio de apenas {cenario['risco_medio']:.1f}%, "
267
+ f"mantendo lucro razoável de R$ {cenario['lucro_total']:,.2f}."
268
+ ),
269
+ 'maximo_lucro': (
270
+ f"O cenário {cenario['nome']} foi recomendado porque maximiza "
271
+ f"o retorno financeiro (R$ {cenario['lucro_total']:,.2f}), "
272
+ f"com risco aceitável de {cenario['risco_medio']:.1f}%."
273
+ ),
274
+ 'sustentavel': (
275
+ f"O cenário {cenario['nome']} foi recomendado porque prioriza "
276
+ f"compatibilidade ambiental e uso eficiente de recursos, "
277
+ f"com lucro de R$ {cenario['lucro_total']:,.2f} e risco de {cenario['risco_medio']:.1f}%."
278
+ ),
279
+ 'conservador': (
280
+ f"O cenário {cenario['nome']} foi recomendado porque evita "
281
+ f"riscos altos, mantendo risco médio de {cenario['risco_medio']:.1f}% "
282
+ f"e lucro de R$ {cenario['lucro_total']:,.2f}."
283
+ )
284
+ }
285
+
286
+ return justificativas.get(cenario_escolhido, "Cenário recomendado pelo sistema.")
@@ -0,0 +1,101 @@
1
+ """
2
+ Sistema de pontuação para avaliar compatibilidade entre culturas e talhões
3
+ """
4
+
5
+ def calcular_compatibilidade_solo(solo_talhao, solos_ideais):
6
+ """Calcula compatibilidade do solo (0-25 pontos)"""
7
+ solos = [s.strip() for s in solos_ideais.split(';')]
8
+ return 25 if solo_talhao in solos else 0
9
+
10
+
11
+ def calcular_compatibilidade_clima(clima_talhao, climas_ideais):
12
+ """Calcula compatibilidade do clima (0-25 pontos)"""
13
+ climas = [c.strip() for c in climas_ideais.split(';')]
14
+ return 25 if clima_talhao in climas else 0
15
+
16
+
17
+ def calcular_compatibilidade_relevo(relevo_talhao, relevos_ideais):
18
+ """Calcula compatibilidade do relevo (0-15 pontos)"""
19
+ relevos = [r.strip() for r in relevos_ideais.split(';')]
20
+ return 15 if relevo_talhao in relevos else 0
21
+
22
+
23
+ def calcular_compatibilidade_agua(agua_talhao, agua_necessaria):
24
+ """Calcula compatibilidade da disponibilidade de água (0-15 pontos)"""
25
+ # Mapeamento de níveis
26
+ niveis = {'baixa': 1, 'media': 2, 'alta': 3}
27
+
28
+ nivel_talhao = niveis.get(agua_talhao, 0)
29
+ nivel_necessario = niveis.get(agua_necessaria, 0)
30
+
31
+ # Pontuação máxima se atende ou excede a necessidade
32
+ if nivel_talhao >= nivel_necessario:
33
+ return 15
34
+ # Penalidade se não atende
35
+ elif nivel_talhao == nivel_necessario - 1:
36
+ return 8
37
+ else:
38
+ return 0
39
+
40
+
41
+ def calcular_lucro_por_hectare(cultura):
42
+ """Calcula lucro estimado por hectare"""
43
+ return (cultura['preco'] * cultura['produtividade']) - cultura['custo']
44
+
45
+
46
+ def normalizar_lucro(lucro, lucro_min, lucro_max):
47
+ """Normaliza lucro para escala 0-10"""
48
+ if lucro_max == lucro_min:
49
+ return 5
50
+ return 10 * (lucro - lucro_min) / (lucro_max - lucro_min)
51
+
52
+
53
+ def calcular_nota_final(talhao, cultura, regra, lucro_normalizado):
54
+ """
55
+ Calcula nota final da cultura para o talhão
56
+
57
+ Componentes:
58
+ - Solo: 0-25 pontos
59
+ - Clima: 0-25 pontos
60
+ - Relevo: 0-15 pontos
61
+ - Água: 0-15 pontos
62
+ - Lucro: 0-10 pontos
63
+ - Risco: -risco_base pontos
64
+
65
+ Total máximo: 90 pontos (antes do risco)
66
+ """
67
+ nota = 0
68
+
69
+ # Compatibilidades
70
+ nota += calcular_compatibilidade_solo(talhao['solo'], regra['solos_ideais'])
71
+ nota += calcular_compatibilidade_clima(talhao['clima'], regra['climas_ideais'])
72
+ nota += calcular_compatibilidade_relevo(talhao['relevo'], regra['relevo_ideal'])
73
+ nota += calcular_compatibilidade_agua(talhao['agua'], regra['agua_necessaria'])
74
+
75
+ # Lucro normalizado
76
+ nota += lucro_normalizado
77
+
78
+ # Penalidade por risco
79
+ nota -= (regra['risco_base'] / 10)
80
+
81
+ return round(nota, 2)
82
+
83
+
84
+ def avaliar_cultura_para_talhao(talhao, cultura, regra, lucro_normalizado):
85
+ """Avalia uma cultura específica para um talhão e retorna detalhes completos"""
86
+ lucro_por_ha = calcular_lucro_por_hectare(cultura)
87
+ lucro_total = lucro_por_ha * talhao['area']
88
+ nota = calcular_nota_final(talhao, cultura, regra, lucro_normalizado)
89
+
90
+ return {
91
+ 'cultura': cultura['nome'],
92
+ 'nota': nota,
93
+ 'lucro_por_ha': lucro_por_ha,
94
+ 'lucro_total': lucro_total,
95
+ 'risco': regra['risco_base'],
96
+ 'tempo': cultura['tempo'],
97
+ 'compatibilidade_solo': calcular_compatibilidade_solo(talhao['solo'], regra['solos_ideais']),
98
+ 'compatibilidade_clima': calcular_compatibilidade_clima(talhao['clima'], regra['climas_ideais']),
99
+ 'compatibilidade_relevo': calcular_compatibilidade_relevo(talhao['relevo'], regra['relevo_ideal']),
100
+ 'compatibilidade_agua': calcular_compatibilidade_agua(talhao['agua'], regra['agua_necessaria'])
101
+ }
@@ -0,0 +1,123 @@
1
+ """
2
+ Analisador de terreno que avalia e recomenda culturas para cada talhão
3
+ """
4
+
5
+ from core.scorer import calcular_lucro_por_hectare, normalizar_lucro, avaliar_cultura_para_talhao
6
+
7
+
8
+ def analisar_talhao(talhao, culturas, regras):
9
+ """
10
+ Analisa um talhão e retorna ranking de culturas recomendadas
11
+
12
+ Retorna:
13
+ - ranking: lista de culturas ordenadas por nota
14
+ - melhor_cultura: cultura com maior nota
15
+ - justificativa: texto explicando a recomendação
16
+ """
17
+ avaliacoes = []
18
+
19
+ # Calcula lucro de todas as culturas para normalização
20
+ lucros = []
21
+ for _, cultura in culturas.iterrows():
22
+ lucro = calcular_lucro_por_hectare(cultura)
23
+ lucros.append(lucro)
24
+
25
+ lucro_min = min(lucros)
26
+ lucro_max = max(lucros)
27
+
28
+ # Avalia cada cultura para este talhão
29
+ for _, cultura in culturas.iterrows():
30
+ # Busca regras da cultura
31
+ regra = regras[regras['cultura'] == cultura['nome']]
32
+
33
+ if regra.empty:
34
+ continue
35
+
36
+ regra = regra.iloc[0]
37
+
38
+ # Calcula lucro normalizado
39
+ lucro = calcular_lucro_por_hectare(cultura)
40
+ lucro_normalizado = normalizar_lucro(lucro, lucro_min, lucro_max)
41
+
42
+ # Avalia cultura
43
+ avaliacao = avaliar_cultura_para_talhao(talhao, cultura, regra, lucro_normalizado)
44
+ avaliacoes.append(avaliacao)
45
+
46
+ # Ordena por nota (decrescente)
47
+ ranking = sorted(avaliacoes, key=lambda x: x['nota'], reverse=True)
48
+
49
+ # Melhor cultura
50
+ melhor = ranking[0] if ranking else None
51
+
52
+ # Gera justificativa
53
+ justificativa = gerar_justificativa(talhao, melhor)
54
+
55
+ return {
56
+ 'ranking': ranking,
57
+ 'melhor_cultura': melhor,
58
+ 'justificativa': justificativa
59
+ }
60
+
61
+
62
+ def gerar_justificativa(talhao, avaliacao):
63
+ """Gera texto explicando por que a cultura foi recomendada"""
64
+ if not avaliacao:
65
+ return "Nenhuma cultura compatível encontrada."
66
+
67
+ cultura = avaliacao['cultura']
68
+ pontos_fortes = []
69
+ pontos_fracos = []
70
+
71
+ # Analisa compatibilidades
72
+ if avaliacao['compatibilidade_solo'] == 25:
73
+ pontos_fortes.append(f"solo {talhao['solo']}")
74
+ elif avaliacao['compatibilidade_solo'] == 0:
75
+ pontos_fracos.append(f"solo {talhao['solo']} não é ideal")
76
+
77
+ if avaliacao['compatibilidade_clima'] == 25:
78
+ pontos_fortes.append(f"clima {talhao['clima']}")
79
+ elif avaliacao['compatibilidade_clima'] == 0:
80
+ pontos_fracos.append(f"clima {talhao['clima']} não é ideal")
81
+
82
+ if avaliacao['compatibilidade_relevo'] == 15:
83
+ pontos_fortes.append(f"relevo {talhao['relevo']}")
84
+ elif avaliacao['compatibilidade_relevo'] == 0:
85
+ pontos_fracos.append(f"relevo {talhao['relevo']} não é ideal")
86
+
87
+ if avaliacao['compatibilidade_agua'] == 15:
88
+ pontos_fortes.append(f"disponibilidade {talhao['agua']} de água")
89
+ elif avaliacao['compatibilidade_agua'] < 15:
90
+ if avaliacao['compatibilidade_agua'] == 0:
91
+ pontos_fracos.append("disponibilidade de água insuficiente")
92
+ else:
93
+ pontos_fracos.append("disponibilidade de água abaixo do ideal")
94
+
95
+ # Monta justificativa
96
+ texto = f"A cultura {cultura} foi recomendada porque "
97
+
98
+ if pontos_fortes:
99
+ texto += f"apresenta alta compatibilidade com {', '.join(pontos_fortes)}"
100
+
101
+ if pontos_fracos:
102
+ if pontos_fortes:
103
+ texto += f", apesar de {', '.join(pontos_fracos)}"
104
+ else:
105
+ texto += f"é a melhor opção disponível, embora {', '.join(pontos_fracos)}"
106
+
107
+ # Adiciona informação sobre lucro e risco
108
+ texto += f". Oferece lucro estimado de R$ {avaliacao['lucro_total']:,.2f} "
109
+ texto += f"com risco de {avaliacao['risco']}% e ciclo de {avaliacao['tempo']} dias."
110
+
111
+ return texto
112
+
113
+
114
+ def analisar_todos_talhoes(talhoes, culturas, regras):
115
+ """Analisa todos os talhões e retorna recomendações completas"""
116
+ analises = []
117
+
118
+ for _, talhao in talhoes.iterrows():
119
+ analise = analisar_talhao(talhao, culturas, regras)
120
+ analise['talhao'] = talhao
121
+ analises.append(analise)
122
+
123
+ return analises
@@ -0,0 +1,11 @@
1
+ nome,custo,preco,produtividade,tempo
2
+ soja,1500,3000,3.2,120
3
+ milho,1200,2500,4.5,100
4
+ feijao,1000,2200,2.8,90
5
+ trigo,1300,2400,3.5,110
6
+ algodao,1800,3500,2.5,140
7
+ cafe,2200,4500,2.0,180
8
+ cana,1400,2800,5.5,150
9
+ sorgo,900,1800,3.8,85
10
+ mandioca,800,1600,4.2,240
11
+ arroz,1100,2300,3.6,130
@@ -0,0 +1,11 @@
1
+ cultura,solos_ideais,climas_ideais,relevo_ideal,agua_necessaria,risco_base
2
+ soja,argiloso;misto,quente;ameno,plano;leve,media,30
3
+ milho,argiloso;misto;arenoso,quente;ameno,plano;leve,alta,35
4
+ feijao,misto;arenoso,ameno;quente,plano,media,25
5
+ trigo,argiloso;misto,frio;ameno,plano;leve,media,40
6
+ algodao,argiloso;arenoso,quente,plano;leve,alta,45
7
+ cafe,argiloso;siltoso,ameno;quente,leve;moderado,alta,50
8
+ cana,argiloso;misto,quente,plano,alta,38
9
+ sorgo,arenoso;siltoso,quente;ameno,plano;leve,baixa,20
10
+ mandioca,arenoso;misto,quente;ameno,plano;leve,baixa,18
11
+ arroz,argiloso;siltoso,ameno;frio,plano,alta,42
@@ -0,0 +1,11 @@
1
+ id,area,solo,clima,relevo,agua
2
+ 1,10,argiloso,quente,plano,media
3
+ 2,15,arenoso,quente,leve,baixa
4
+ 3,8,misto,ameno,plano,alta
5
+ 4,12,siltoso,ameno,leve,media
6
+ 5,18,argiloso,frio,plano,alta
7
+ 6,9,arenoso,quente,moderado,baixa
8
+ 7,14,misto,ameno,plano,media
9
+ 8,11,argiloso,quente,leve,alta
10
+ 9,7,siltoso,frio,moderado,media
11
+ 10,13,misto,ameno,ingreme,baixa