agroplan-ai-cli 1.0.15 → 1.0.18

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,256 @@
1
+ """
2
+ Script para construir índice ZARC compacto
3
+
4
+ Processa o CSV oficial ZARC e gera um índice JSON pequeno
5
+ contendo apenas as regiões e culturas de interesse do AgroPlan.
6
+
7
+ Uso:
8
+ python scripts/build_zarc_index.py
9
+ """
10
+ import sys
11
+ import os
12
+ import json
13
+ from datetime import datetime
14
+
15
+ # Adicionar diretório pai ao path para importar providers
16
+ sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
17
+
18
+ from providers.zarc_provider import (
19
+ ensure_zarc_file,
20
+ iter_zarc_records,
21
+ normalizar_cultura,
22
+ normalizar_municipio,
23
+ normalizar_uf,
24
+ normalizar_solo,
25
+ mapear_codigo_solo,
26
+ extrair_janelas_plantio,
27
+ escolher_melhor_janela,
28
+ ZARC_CACHE_DIR
29
+ )
30
+
31
+ # Regiões de interesse
32
+ REGIOES_INTERESSE = [
33
+ {"uf": "SP", "municipio": "Clementina"},
34
+ {"uf": "SP", "municipio": "São Paulo"},
35
+ {"uf": "SP", "municipio": "Ribeirão Preto"},
36
+ {"uf": "MS", "municipio": "Campo Grande"},
37
+ {"uf": "PR", "municipio": "Londrina"},
38
+ {"uf": "DF", "municipio": "Brasília"},
39
+ ]
40
+
41
+ # Culturas de interesse
42
+ CULTURAS_INTERESSE = [
43
+ "soja",
44
+ "milho",
45
+ "feijao",
46
+ "trigo",
47
+ "algodao",
48
+ "cafe",
49
+ "cana",
50
+ "arroz",
51
+ "sorgo",
52
+ "mandioca"
53
+ ]
54
+
55
+ # Solos de interesse
56
+ SOLOS_INTERESSE = ["arenoso", "medio", "argiloso", "misto"]
57
+
58
+ def build_zarc_index(safra: str = "2025/2026"):
59
+ """
60
+ Constrói índice ZARC compacto
61
+
62
+ Args:
63
+ safra: Safra para processar
64
+ """
65
+ print(f"🌾 Construindo índice ZARC para safra {safra}...")
66
+ print()
67
+
68
+ # Garantir que arquivo ZARC existe
69
+ file_info = ensure_zarc_file(safra)
70
+
71
+ if not file_info:
72
+ print("❌ Erro: Arquivo ZARC não disponível")
73
+ return False
74
+
75
+ print(f"✅ Arquivo ZARC: {file_info['file_path']}")
76
+ print(f" Fonte: {file_info['source']}")
77
+ print()
78
+
79
+ # Normalizar regiões de interesse
80
+ regioes_norm = []
81
+ for regiao in REGIOES_INTERESSE:
82
+ regioes_norm.append({
83
+ "uf": normalizar_uf(regiao["uf"]),
84
+ "municipio": normalizar_municipio(regiao["municipio"]),
85
+ "municipio_original": regiao["municipio"]
86
+ })
87
+
88
+ # Normalizar culturas de interesse
89
+ culturas_norm = [normalizar_cultura(c) for c in CULTURAS_INTERESSE]
90
+
91
+ print("📍 Regiões de interesse:")
92
+ for r in regioes_norm:
93
+ print(f" - {r['municipio_original']}/{r['uf']}")
94
+ print()
95
+
96
+ print("🌱 Culturas de interesse:")
97
+ for c in CULTURAS_INTERESSE:
98
+ print(f" - {c}")
99
+ print()
100
+
101
+ # Processar CSV em streaming
102
+ print("🔄 Processando CSV oficial...")
103
+
104
+ index_records = {}
105
+ registros_processados = 0
106
+ registros_incluidos = 0
107
+
108
+ for registro in iter_zarc_records(file_info['file_path']):
109
+ registros_processados += 1
110
+
111
+ # Mostrar progresso a cada 100k registros
112
+ if registros_processados % 100000 == 0:
113
+ print(f" Processados: {registros_processados:,} registros...")
114
+
115
+ # Verificar se é cultura de interesse
116
+ cultura_csv = normalizar_cultura(registro.get("Nome_cultura", ""))
117
+ if cultura_csv not in culturas_norm:
118
+ continue
119
+
120
+ # Verificar se é região de interesse
121
+ uf_csv = normalizar_uf(registro.get("UF", ""))
122
+ municipio_csv = normalizar_municipio(registro.get("municipio", ""))
123
+
124
+ regiao_match = None
125
+ for regiao in regioes_norm:
126
+ if uf_csv == regiao["uf"] and municipio_csv == regiao["municipio"]:
127
+ regiao_match = regiao
128
+ break
129
+
130
+ if not regiao_match:
131
+ continue
132
+
133
+ # Solo
134
+ solo_codigo = registro.get("Cod_Solo", "")
135
+ solo_nome = mapear_codigo_solo(solo_codigo)
136
+ solo_norm = normalizar_solo(solo_nome)
137
+
138
+ if solo_norm not in SOLOS_INTERESSE and solo_norm != "desconhecido":
139
+ continue
140
+
141
+ # Extrair janelas de plantio
142
+ janelas = extrair_janelas_plantio(registro)
143
+ melhor_janela = escolher_melhor_janela(janelas)
144
+
145
+ if not melhor_janela:
146
+ # Sem janelas válidas, pular
147
+ continue
148
+
149
+ # Criar chave: UF|municipio|cultura|solo
150
+ chave = f"{uf_csv}|{municipio_csv}|{cultura_csv}|{solo_norm}"
151
+
152
+ # Se já existe, manter o de menor risco
153
+ if chave in index_records:
154
+ risco_atual = index_records[chave]["risco"]
155
+ risco_novo = melhor_janela["risco_predominante"]
156
+
157
+ ordem_risco = {"baixo": 1, "medio": 2, "alto": 3}
158
+
159
+ if ordem_risco.get(risco_novo, 999) < ordem_risco.get(risco_atual, 999):
160
+ # Novo tem risco menor, substituir
161
+ pass
162
+ else:
163
+ # Manter o atual
164
+ continue
165
+
166
+ # Adicionar ao índice
167
+ index_records[chave] = {
168
+ "source": "zarc-oficial-derived",
169
+ "fallback": False,
170
+ "cultura": registro.get("Nome_cultura"),
171
+ "uf": uf_csv.upper(),
172
+ "municipio": regiao_match["municipio_original"],
173
+ "solo": solo_nome,
174
+ "safra": safra,
175
+ "janela_plantio": {
176
+ "inicio": melhor_janela["inicio"],
177
+ "fim": melhor_janela["fim"]
178
+ },
179
+ "risco": melhor_janela["risco_predominante"],
180
+ "decendios_recomendados": melhor_janela["decendios"],
181
+ "geocodigo": registro.get("geocodigo", ""),
182
+ "encontrado": True,
183
+ "observacao": "Dados derivados da Tábua de Risco oficial do ZARC."
184
+ }
185
+
186
+ registros_incluidos += 1
187
+
188
+ print(f"✅ Processamento concluído!")
189
+ print(f" Total processado: {registros_processados:,} registros")
190
+ print(f" Incluídos no índice: {registros_incluidos} registros")
191
+ print()
192
+
193
+ # Criar estrutura do índice
194
+ index = {
195
+ "metadata": {
196
+ "source": "zarc-oficial-derived",
197
+ "safra": safra,
198
+ "generated_at": datetime.now().isoformat(),
199
+ "generated_from": file_info["source"],
200
+ "regions": [f"{r['municipio_original']}/{r['uf']}" for r in regioes_norm],
201
+ "cultures": CULTURAS_INTERESSE,
202
+ "soils": SOLOS_INTERESSE,
203
+ "total_records": registros_incluidos
204
+ },
205
+ "records": index_records
206
+ }
207
+
208
+ # Salvar índice
209
+ safra_filename = safra.replace("/", "-")
210
+ index_path = os.path.join(ZARC_CACHE_DIR, f"zarc_index_{safra_filename}.json")
211
+
212
+ with open(index_path, 'w', encoding='utf-8') as f:
213
+ json.dump(index, f, ensure_ascii=False, indent=2)
214
+
215
+ # Calcular tamanho
216
+ size_bytes = os.path.getsize(index_path)
217
+ size_kb = size_bytes / 1024
218
+
219
+ print(f"💾 Índice salvo em: {index_path}")
220
+ print(f" Tamanho: {size_kb:.2f} KB")
221
+ print()
222
+
223
+ # Estatísticas por região
224
+ print("📊 Estatísticas por região:")
225
+ for regiao in regioes_norm:
226
+ count = sum(1 for k in index_records.keys()
227
+ if k.startswith(f"{regiao['uf']}|{regiao['municipio']}|"))
228
+ print(f" {regiao['municipio_original']}/{regiao['uf']}: {count} registros")
229
+ print()
230
+
231
+ # Estatísticas por cultura
232
+ print("📊 Estatísticas por cultura:")
233
+ for cultura in culturas_norm:
234
+ count = sum(1 for k in index_records.keys()
235
+ if f"|{cultura}|" in k)
236
+ print(f" {cultura}: {count} registros")
237
+ print()
238
+
239
+ print("✅ Índice ZARC construído com sucesso!")
240
+ return True
241
+
242
+ if __name__ == "__main__":
243
+ import argparse
244
+
245
+ parser = argparse.ArgumentParser(description="Construir índice ZARC compacto")
246
+ parser.add_argument(
247
+ "--safra",
248
+ default="2025/2026",
249
+ help="Safra para processar (padrão: 2025/2026)"
250
+ )
251
+
252
+ args = parser.parse_args()
253
+
254
+ success = build_zarc_index(args.safra)
255
+
256
+ sys.exit(0 if success else 1)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agroplan-ai-cli",
3
- "version": "1.0.15",
3
+ "version": "1.0.18",
4
4
  "description": "CLI global para AgroPlan AI - modo local acelerado",
5
5
  "type": "module",
6
6
  "bin": {