agroplan-ai-cli 1.0.21 → 1.0.23
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,13 +1,14 @@
|
|
|
1
1
|
{
|
|
2
|
-
"cli_version": "1.0.
|
|
3
|
-
"backend_template_version": "1.0.
|
|
2
|
+
"cli_version": "1.0.23",
|
|
3
|
+
"backend_template_version": "1.0.23",
|
|
4
4
|
"zarc_index_version": "2025-2026-fast-index-v2",
|
|
5
5
|
"features": [
|
|
6
6
|
"zarc_fast_index",
|
|
7
7
|
"zarc_fallback_sorgo_mandioca",
|
|
8
8
|
"soil_normalization_misto_siltoso",
|
|
9
9
|
"climate_real_data",
|
|
10
|
-
"hybrid_mode"
|
|
10
|
+
"hybrid_mode",
|
|
11
|
+
"report_generator_zarc_support"
|
|
11
12
|
],
|
|
12
|
-
"generated_at": "2026-05-
|
|
13
|
+
"generated_at": "2026-05-09T19:00:00Z"
|
|
13
14
|
}
|
package/backend-template/api.py
CHANGED
|
@@ -151,7 +151,15 @@ def health():
|
|
|
151
151
|
from providers.zarc_provider import get_zarc_status
|
|
152
152
|
zarc_status = get_zarc_status()
|
|
153
153
|
|
|
154
|
-
|
|
154
|
+
# Carregar VERSION.json se existir
|
|
155
|
+
version_info = {}
|
|
156
|
+
version_path = os.path.join(os.path.dirname(__file__), "VERSION.json")
|
|
157
|
+
if os.path.exists(version_path):
|
|
158
|
+
import json
|
|
159
|
+
with open(version_path, 'r') as f:
|
|
160
|
+
version_info = json.load(f)
|
|
161
|
+
|
|
162
|
+
response = {
|
|
155
163
|
"status": "healthy",
|
|
156
164
|
"culturas": len(culturas),
|
|
157
165
|
"talhoes": len(talhoes),
|
|
@@ -164,9 +172,169 @@ def health():
|
|
|
164
172
|
},
|
|
165
173
|
"provider_cache": provider_cache_stats
|
|
166
174
|
}
|
|
175
|
+
|
|
176
|
+
# Adicionar info de versão se disponível
|
|
177
|
+
if version_info:
|
|
178
|
+
response["backend_template_version"] = version_info.get("backend_template_version")
|
|
179
|
+
response["zarc_index_version"] = version_info.get("zarc_index_version")
|
|
180
|
+
|
|
181
|
+
return response
|
|
167
182
|
except Exception as e:
|
|
168
183
|
raise HTTPException(status_code=500, detail=str(e))
|
|
169
184
|
|
|
185
|
+
@app.get("/debug/version")
|
|
186
|
+
def debug_version():
|
|
187
|
+
"""Retorna informações detalhadas de versão e configuração do backend"""
|
|
188
|
+
try:
|
|
189
|
+
import json
|
|
190
|
+
from providers.zarc_provider import (
|
|
191
|
+
ZARC_FAST_INDEX_ENABLED,
|
|
192
|
+
ZARC_ALLOW_FULL_SCAN,
|
|
193
|
+
load_zarc_index,
|
|
194
|
+
get_zarc_fallback
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
# Carregar VERSION.json
|
|
198
|
+
version_info = {}
|
|
199
|
+
version_path = os.path.join(os.path.dirname(__file__), "VERSION.json")
|
|
200
|
+
if os.path.exists(version_path):
|
|
201
|
+
with open(version_path, 'r') as f:
|
|
202
|
+
version_info = json.load(f)
|
|
203
|
+
|
|
204
|
+
# Verificar índice ZARC
|
|
205
|
+
zarc_index = load_zarc_index()
|
|
206
|
+
zarc_index_info = {}
|
|
207
|
+
zarc_index_keys_sample = []
|
|
208
|
+
|
|
209
|
+
if zarc_index:
|
|
210
|
+
records = zarc_index.get("records", {})
|
|
211
|
+
zarc_index_info = {
|
|
212
|
+
"exists": True,
|
|
213
|
+
"total_records": len(records),
|
|
214
|
+
"generated_at": zarc_index.get("generated_at"),
|
|
215
|
+
"source": zarc_index.get("source")
|
|
216
|
+
}
|
|
217
|
+
# Sample de 10 primeiras chaves
|
|
218
|
+
zarc_index_keys_sample = list(records.keys())[:10]
|
|
219
|
+
else:
|
|
220
|
+
zarc_index_info = {"exists": False}
|
|
221
|
+
|
|
222
|
+
# Verificar fallbacks
|
|
223
|
+
fallback_data = get_zarc_fallback()
|
|
224
|
+
culturas_fallback = set(item.get("cultura") for item in fallback_data)
|
|
225
|
+
|
|
226
|
+
return {
|
|
227
|
+
"api_version": "5.0.0",
|
|
228
|
+
"backend_file": __file__,
|
|
229
|
+
"backend_template_version": version_info.get("backend_template_version", "unknown"),
|
|
230
|
+
"cli_version": version_info.get("cli_version", "unknown"),
|
|
231
|
+
"zarc_index_version": version_info.get("zarc_index_version", "unknown"),
|
|
232
|
+
"features": version_info.get("features", []),
|
|
233
|
+
"generated_at": version_info.get("generated_at"),
|
|
234
|
+
"zarc_config": {
|
|
235
|
+
"fast_index_enabled": ZARC_FAST_INDEX_ENABLED,
|
|
236
|
+
"allow_full_scan": ZARC_ALLOW_FULL_SCAN,
|
|
237
|
+
"index": zarc_index_info,
|
|
238
|
+
"index_keys_sample": zarc_index_keys_sample
|
|
239
|
+
},
|
|
240
|
+
"zarc_fallback": {
|
|
241
|
+
"total_records": len(fallback_data),
|
|
242
|
+
"culturas": sorted(list(culturas_fallback)),
|
|
243
|
+
"has_sorgo": "sorgo" in culturas_fallback,
|
|
244
|
+
"has_mandioca": "mandioca" in culturas_fallback
|
|
245
|
+
},
|
|
246
|
+
"data_mode": DATA_MODE,
|
|
247
|
+
"weather_provider": WEATHER_PROVIDER
|
|
248
|
+
}
|
|
249
|
+
except Exception as e:
|
|
250
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
251
|
+
|
|
252
|
+
@app.get("/debug/zarc-coverage")
|
|
253
|
+
def debug_zarc_coverage(
|
|
254
|
+
uf: Optional[str] = Query(None, description="UF"),
|
|
255
|
+
municipio: Optional[str] = Query(None, description="Município"),
|
|
256
|
+
safra: str = Query("2025/2026", description="Safra")
|
|
257
|
+
):
|
|
258
|
+
"""Retorna diagnóstico detalhado de cobertura ZARC"""
|
|
259
|
+
try:
|
|
260
|
+
import json
|
|
261
|
+
from core.zarc_adapter import enriquecer_plano_com_zarc
|
|
262
|
+
|
|
263
|
+
# Usar o mesmo plano do dashboard
|
|
264
|
+
resultado_ag = get_ag_cacheado(objetivo="equilibrado")
|
|
265
|
+
|
|
266
|
+
# Enriquecer com ZARC
|
|
267
|
+
resultado_enriquecido = enriquecer_plano_com_zarc(
|
|
268
|
+
resultado_ag,
|
|
269
|
+
uf=uf,
|
|
270
|
+
municipio=municipio,
|
|
271
|
+
safra=safra
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
# Analisar cobertura
|
|
275
|
+
detalhes = []
|
|
276
|
+
culturas_com_zarc = 0
|
|
277
|
+
culturas_fallback = 0
|
|
278
|
+
culturas_unavailable = 0
|
|
279
|
+
|
|
280
|
+
for item in resultado_enriquecido["plano"]:
|
|
281
|
+
zarc = item.get("zarc", {})
|
|
282
|
+
|
|
283
|
+
detalhes.append({
|
|
284
|
+
"talhao": item.get("talhao"),
|
|
285
|
+
"cultura": item.get("cultura"),
|
|
286
|
+
"solo_original": item.get("solo"),
|
|
287
|
+
"zarc_ativo": zarc.get("ativo", False),
|
|
288
|
+
"zarc_source": zarc.get("source"),
|
|
289
|
+
"zarc_fallback": zarc.get("fallback", False),
|
|
290
|
+
"zarc_message": zarc.get("message"),
|
|
291
|
+
"zarc_janela": zarc.get("janela_plantio")
|
|
292
|
+
})
|
|
293
|
+
|
|
294
|
+
if zarc.get("ativo"):
|
|
295
|
+
culturas_com_zarc += 1
|
|
296
|
+
if zarc.get("fallback"):
|
|
297
|
+
culturas_fallback += 1
|
|
298
|
+
else:
|
|
299
|
+
culturas_unavailable += 1
|
|
300
|
+
|
|
301
|
+
total_culturas = len(resultado_enriquecido["plano"])
|
|
302
|
+
coverage_percent = (culturas_com_zarc / total_culturas * 100) if total_culturas > 0 else 0
|
|
303
|
+
|
|
304
|
+
# Carregar VERSION.json
|
|
305
|
+
version_info = {}
|
|
306
|
+
version_path = os.path.join(os.path.dirname(__file__), "VERSION.json")
|
|
307
|
+
if os.path.exists(version_path):
|
|
308
|
+
with open(version_path, 'r') as f:
|
|
309
|
+
version_info = json.load(f)
|
|
310
|
+
|
|
311
|
+
# Verificar índice ZARC
|
|
312
|
+
from providers.zarc_provider import load_zarc_index
|
|
313
|
+
zarc_index = load_zarc_index()
|
|
314
|
+
zarc_index_total = len(zarc_index.get("records", {})) if zarc_index else 0
|
|
315
|
+
|
|
316
|
+
return {
|
|
317
|
+
"uf": uf,
|
|
318
|
+
"municipio": municipio,
|
|
319
|
+
"safra": safra,
|
|
320
|
+
"summary": {
|
|
321
|
+
"culturas_com_zarc": culturas_com_zarc,
|
|
322
|
+
"culturas_fallback": culturas_fallback,
|
|
323
|
+
"culturas_unavailable": culturas_unavailable,
|
|
324
|
+
"total_culturas": total_culturas,
|
|
325
|
+
"coverage_percent": round(coverage_percent, 1)
|
|
326
|
+
},
|
|
327
|
+
"backend_info": {
|
|
328
|
+
"backend_template_version": version_info.get("backend_template_version", "unknown"),
|
|
329
|
+
"zarc_index_version": version_info.get("zarc_index_version", "unknown"),
|
|
330
|
+
"zarc_index_total_records": zarc_index_total
|
|
331
|
+
},
|
|
332
|
+
"details": detalhes
|
|
333
|
+
}
|
|
334
|
+
except Exception as e:
|
|
335
|
+
import traceback
|
|
336
|
+
raise HTTPException(status_code=500, detail=f"{str(e)}\n{traceback.format_exc()}")
|
|
337
|
+
|
|
170
338
|
@app.get("/dados/clima")
|
|
171
339
|
def get_clima(
|
|
172
340
|
lat: Optional[float] = Query(None, description="Latitude"),
|
|
@@ -850,14 +1018,37 @@ def limpar_cache(request: Request):
|
|
|
850
1018
|
detail="Token de administração inválido ou ausente. Use header X-Cache-Token."
|
|
851
1019
|
)
|
|
852
1020
|
|
|
853
|
-
# Limpa cache
|
|
1021
|
+
# Limpa cache de resultados
|
|
854
1022
|
global _resultados_cache
|
|
855
1023
|
items_removidos = len(_resultados_cache)
|
|
856
1024
|
_resultados_cache.clear()
|
|
857
1025
|
|
|
1026
|
+
# Limpa cache de provedores (weather, etc)
|
|
1027
|
+
provider_items_removidos = 0
|
|
1028
|
+
try:
|
|
1029
|
+
from providers.cache import clear_provider_cache
|
|
1030
|
+
provider_items_removidos = clear_provider_cache()
|
|
1031
|
+
except Exception:
|
|
1032
|
+
pass
|
|
1033
|
+
|
|
1034
|
+
# Limpa cache do índice ZARC em memória
|
|
1035
|
+
zarc_cache_cleared = False
|
|
1036
|
+
try:
|
|
1037
|
+
from providers import zarc_provider
|
|
1038
|
+
if hasattr(zarc_provider, '_zarc_index_cache'):
|
|
1039
|
+
zarc_provider._zarc_index_cache.clear()
|
|
1040
|
+
zarc_cache_cleared = True
|
|
1041
|
+
except Exception:
|
|
1042
|
+
pass
|
|
1043
|
+
|
|
858
1044
|
return {
|
|
859
1045
|
"status": "ok",
|
|
860
|
-
"message": f"Cache limpo
|
|
1046
|
+
"message": f"Cache limpo completamente.",
|
|
1047
|
+
"details": {
|
|
1048
|
+
"resultados_cache": items_removidos,
|
|
1049
|
+
"provider_cache": provider_items_removidos,
|
|
1050
|
+
"zarc_index_cache": "cleared" if zarc_cache_cleared else "not_found"
|
|
1051
|
+
},
|
|
861
1052
|
"protected": bool(cache_admin_token)
|
|
862
1053
|
}
|
|
863
1054
|
|
|
@@ -98,7 +98,7 @@ def get_objetivo_description(objetivo):
|
|
|
98
98
|
return descriptions.get(objetivo, "otimizou múltiplos critérios")
|
|
99
99
|
|
|
100
100
|
|
|
101
|
-
def gerar_relatorio_completo(culturas, talhoes, regras, objetivo='equilibrado', formato='md', contexto_climatico=None):
|
|
101
|
+
def gerar_relatorio_completo(culturas, talhoes, regras, objetivo='equilibrado', formato='md', contexto_climatico=None, uf=None, municipio=None, safra="2025/2026"):
|
|
102
102
|
"""
|
|
103
103
|
Gera relatório completo do sistema
|
|
104
104
|
|
|
@@ -109,6 +109,9 @@ def gerar_relatorio_completo(culturas, talhoes, regras, objetivo='equilibrado',
|
|
|
109
109
|
objetivo: objetivo do AG
|
|
110
110
|
formato: 'md' ou 'txt'
|
|
111
111
|
contexto_climatico: Dicionário com dados climáticos reais (opcional)
|
|
112
|
+
uf: Unidade Federativa para ZARC (opcional)
|
|
113
|
+
municipio: Município para ZARC (opcional)
|
|
114
|
+
safra: Safra ZARC (padrão: 2025/2026)
|
|
112
115
|
|
|
113
116
|
Returns:
|
|
114
117
|
Caminho do arquivo gerado
|
|
@@ -151,6 +154,14 @@ def gerar_relatorio_completo(culturas, talhoes, regras, objetivo='equilibrado',
|
|
|
151
154
|
secao_clima = gerar_secao_climatica(contexto_climatico, formato)
|
|
152
155
|
conteudo += "\n\n" + secao_clima
|
|
153
156
|
|
|
157
|
+
# Adicionar seção ZARC se disponível
|
|
158
|
+
if uf:
|
|
159
|
+
from core.zarc_adapter import enriquecer_plano_com_zarc, gerar_secao_zarc_relatorio
|
|
160
|
+
resultado_temp = {"plano": resultado_ag["plano"]}
|
|
161
|
+
resultado_temp = enriquecer_plano_com_zarc(resultado_temp, uf, municipio, safra)
|
|
162
|
+
secao_zarc = gerar_secao_zarc_relatorio(resultado_temp["plano"], uf, municipio, safra, formato)
|
|
163
|
+
conteudo += "\n\n" + secao_zarc
|
|
164
|
+
|
|
154
165
|
# Salva arquivo
|
|
155
166
|
os.makedirs('reports', exist_ok=True)
|
|
156
167
|
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
|
|
@@ -78,10 +78,10 @@ def enriquecer_plano_com_zarc(
|
|
|
78
78
|
if zarc_data.get("fallback"):
|
|
79
79
|
tem_fallback = True
|
|
80
80
|
else:
|
|
81
|
-
# ZARC não encontrado
|
|
81
|
+
# ZARC não encontrado - usar mensagem honesta
|
|
82
82
|
item["zarc"] = {
|
|
83
83
|
"ativo": False,
|
|
84
|
-
"message": zarc_data.get("message"
|
|
84
|
+
"message": zarc_data.get("message", "ZARC consultado, mas sem recomendação disponível para esta cultura/região.")
|
|
85
85
|
}
|
|
86
86
|
|
|
87
87
|
# Determinar source geral
|
|
@@ -304,6 +304,36 @@ def normalizar_solo(solo: str) -> str:
|
|
|
304
304
|
"""Normaliza tipo de solo"""
|
|
305
305
|
return normalizar_texto(solo)
|
|
306
306
|
|
|
307
|
+
def normalizar_solo_zarc(solo: str) -> str:
|
|
308
|
+
"""
|
|
309
|
+
Normaliza tipo de solo para busca ZARC
|
|
310
|
+
|
|
311
|
+
Mapeia variações de solo para os tipos reconhecidos pelo ZARC:
|
|
312
|
+
- misto -> medio
|
|
313
|
+
- siltoso -> medio
|
|
314
|
+
|
|
315
|
+
Args:
|
|
316
|
+
solo: Tipo de solo original
|
|
317
|
+
|
|
318
|
+
Returns:
|
|
319
|
+
Tipo de solo normalizado para ZARC
|
|
320
|
+
"""
|
|
321
|
+
if not solo:
|
|
322
|
+
return ""
|
|
323
|
+
|
|
324
|
+
solo_norm = normalizar_solo(solo)
|
|
325
|
+
|
|
326
|
+
# Mapeamento de solos para ZARC
|
|
327
|
+
mapa_zarc = {
|
|
328
|
+
"arenoso": "arenoso",
|
|
329
|
+
"medio": "medio",
|
|
330
|
+
"misto": "medio", # misto -> medio
|
|
331
|
+
"siltoso": "medio", # siltoso -> medio
|
|
332
|
+
"argiloso": "argiloso"
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
return mapa_zarc.get(solo_norm, solo_norm)
|
|
336
|
+
|
|
307
337
|
def get_cache_path(safra: str) -> str:
|
|
308
338
|
"""Retorna caminho do arquivo de cache para a safra"""
|
|
309
339
|
os.makedirs(ZARC_CACHE_DIR, exist_ok=True)
|
|
@@ -427,15 +457,15 @@ def buscar_zarc_indexado(
|
|
|
427
457
|
# Tentar diferentes combinações de solo
|
|
428
458
|
solos_tentar = []
|
|
429
459
|
if solo:
|
|
430
|
-
solo_norm =
|
|
460
|
+
solo_norm = normalizar_solo_zarc(solo) # Usa normalização ZARC (misto->medio, siltoso->medio)
|
|
431
461
|
# Tentar o solo especificado primeiro, depois outros como fallback
|
|
432
|
-
solos_tentar = [solo_norm, "medio", "arenoso", "argiloso"
|
|
462
|
+
solos_tentar = [solo_norm, "medio", "arenoso", "argiloso"]
|
|
433
463
|
# Remover duplicatas mantendo ordem
|
|
434
464
|
seen = set()
|
|
435
465
|
solos_tentar = [s for s in solos_tentar if not (s in seen or seen.add(s))]
|
|
436
466
|
else:
|
|
437
467
|
# Se não especificou solo, tentar todos (preferir medio/argiloso)
|
|
438
|
-
solos_tentar = ["medio", "argiloso", "arenoso"
|
|
468
|
+
solos_tentar = ["medio", "argiloso", "arenoso"]
|
|
439
469
|
|
|
440
470
|
# Buscar no índice
|
|
441
471
|
for solo_test in solos_tentar:
|
|
@@ -680,6 +710,48 @@ def get_zarc_fallback() -> List[Dict[str, Any]]:
|
|
|
680
710
|
"janela_fim": "31/03",
|
|
681
711
|
"risco": "baixo",
|
|
682
712
|
"safra": "2025/2026"
|
|
713
|
+
},
|
|
714
|
+
# Sorgo
|
|
715
|
+
{
|
|
716
|
+
"cultura": "sorgo",
|
|
717
|
+
"uf": "SP",
|
|
718
|
+
"municipio": "ribeirao preto",
|
|
719
|
+
"solo": "medio",
|
|
720
|
+
"janela_inicio": "15/10",
|
|
721
|
+
"janela_fim": "15/12",
|
|
722
|
+
"risco": "medio",
|
|
723
|
+
"safra": "2025/2026"
|
|
724
|
+
},
|
|
725
|
+
{
|
|
726
|
+
"cultura": "sorgo",
|
|
727
|
+
"uf": "MG",
|
|
728
|
+
"municipio": "uberlandia",
|
|
729
|
+
"solo": "medio",
|
|
730
|
+
"janela_inicio": "01/10",
|
|
731
|
+
"janela_fim": "30/11",
|
|
732
|
+
"risco": "medio",
|
|
733
|
+
"safra": "2025/2026"
|
|
734
|
+
},
|
|
735
|
+
# Mandioca
|
|
736
|
+
{
|
|
737
|
+
"cultura": "mandioca",
|
|
738
|
+
"uf": "SP",
|
|
739
|
+
"municipio": "sao paulo",
|
|
740
|
+
"solo": "medio",
|
|
741
|
+
"janela_inicio": "01/09",
|
|
742
|
+
"janela_fim": "31/03",
|
|
743
|
+
"risco": "baixo",
|
|
744
|
+
"safra": "2025/2026"
|
|
745
|
+
},
|
|
746
|
+
{
|
|
747
|
+
"cultura": "mandioca",
|
|
748
|
+
"uf": "PR",
|
|
749
|
+
"municipio": "londrina",
|
|
750
|
+
"solo": "medio",
|
|
751
|
+
"janela_inicio": "15/08",
|
|
752
|
+
"janela_fim": "31/03",
|
|
753
|
+
"risco": "baixo",
|
|
754
|
+
"safra": "2025/2026"
|
|
683
755
|
}
|
|
684
756
|
]
|
|
685
757
|
|
|
@@ -689,12 +761,14 @@ def buscar_zarc(
|
|
|
689
761
|
municipio: Optional[str] = None,
|
|
690
762
|
solo: Optional[str] = None,
|
|
691
763
|
safra: str = ZARC_SAFRA_DEFAULT
|
|
692
|
-
) ->
|
|
764
|
+
) -> Dict[str, Any]:
|
|
693
765
|
"""
|
|
694
766
|
Busca dados ZARC para cultura/região específica
|
|
695
767
|
|
|
696
768
|
PERFORMANCE: Tenta índice primeiro (rápido), depois streaming (lento)
|
|
697
769
|
|
|
770
|
+
SEMPRE retorna um dicionário, nunca None
|
|
771
|
+
|
|
698
772
|
Args:
|
|
699
773
|
cultura: Nome da cultura
|
|
700
774
|
uf: Unidade Federativa (opcional)
|
|
@@ -703,7 +777,7 @@ def buscar_zarc(
|
|
|
703
777
|
safra: Safra (padrão: 2025/2026)
|
|
704
778
|
|
|
705
779
|
Returns:
|
|
706
|
-
Dicionário com dados ZARC
|
|
780
|
+
Dicionário com dados ZARC (sempre retorna, nunca None)
|
|
707
781
|
"""
|
|
708
782
|
# FAST PATH: Tentar índice primeiro (O(1) lookup)
|
|
709
783
|
if ZARC_FAST_INDEX_ENABLED:
|
|
@@ -718,7 +792,14 @@ def buscar_zarc(
|
|
|
718
792
|
return buscar_zarc_fallback(cultura, uf, municipio, solo, safra)
|
|
719
793
|
|
|
720
794
|
# Full scan permitido (desenvolvimento local)
|
|
721
|
-
|
|
795
|
+
resultado_streaming = buscar_zarc_streaming(cultura, uf, municipio, solo, safra)
|
|
796
|
+
|
|
797
|
+
# buscar_zarc_streaming pode retornar None se arquivo não disponível
|
|
798
|
+
# Nesse caso, usar fallback
|
|
799
|
+
if resultado_streaming is None:
|
|
800
|
+
return buscar_zarc_fallback(cultura, uf, municipio, solo, safra)
|
|
801
|
+
|
|
802
|
+
return resultado_streaming
|
|
722
803
|
|
|
723
804
|
def buscar_zarc_streaming(
|
|
724
805
|
cultura: str,
|
|
@@ -737,7 +818,7 @@ def buscar_zarc_streaming(
|
|
|
737
818
|
cultura_norm = normalizar_cultura(cultura)
|
|
738
819
|
uf_norm = normalizar_uf(uf) if uf else None
|
|
739
820
|
municipio_norm = normalizar_municipio(municipio) if municipio else None
|
|
740
|
-
solo_norm =
|
|
821
|
+
solo_norm = normalizar_solo_zarc(solo) if solo else None # Usa normalização ZARC
|
|
741
822
|
|
|
742
823
|
# Tentar obter arquivo ZARC
|
|
743
824
|
file_info = ensure_zarc_file(safra)
|
|
@@ -768,7 +849,7 @@ def buscar_zarc_streaming(
|
|
|
768
849
|
# Solo (se fornecido)
|
|
769
850
|
if solo_norm:
|
|
770
851
|
solo_registro = mapear_codigo_solo(registro.get("Cod_Solo", ""))
|
|
771
|
-
if
|
|
852
|
+
if normalizar_solo_zarc(solo_registro) == solo_norm: # Usa normalização ZARC
|
|
772
853
|
score += 2
|
|
773
854
|
|
|
774
855
|
# Manter apenas o melhor match (não acumula lista)
|
|
@@ -837,15 +918,17 @@ def buscar_zarc_fallback(
|
|
|
837
918
|
municipio: Optional[str] = None,
|
|
838
919
|
solo: Optional[str] = None,
|
|
839
920
|
safra: str = ZARC_SAFRA_DEFAULT
|
|
840
|
-
) ->
|
|
921
|
+
) -> Dict[str, Any]:
|
|
841
922
|
"""
|
|
842
923
|
Busca ZARC em dados simplificados (fallback)
|
|
924
|
+
|
|
925
|
+
SEMPRE retorna um dicionário, nunca None
|
|
843
926
|
"""
|
|
844
927
|
# Normalizar parâmetros
|
|
845
928
|
cultura_norm = normalizar_cultura(cultura)
|
|
846
929
|
uf_norm = normalizar_uf(uf) if uf else None
|
|
847
930
|
municipio_norm = normalizar_municipio(municipio) if municipio else None
|
|
848
|
-
solo_norm =
|
|
931
|
+
solo_norm = normalizar_solo_zarc(solo) if solo else None # Usa normalização ZARC
|
|
849
932
|
|
|
850
933
|
# Fallback: usar dados simplificados (lista pequena em memória)
|
|
851
934
|
fallback_data = get_zarc_fallback()
|
|
@@ -869,7 +952,7 @@ def buscar_zarc_fallback(
|
|
|
869
952
|
score += 3
|
|
870
953
|
|
|
871
954
|
# Solo (se fornecido)
|
|
872
|
-
if solo_norm and
|
|
955
|
+
if solo_norm and normalizar_solo_zarc(registro.get("solo", "")) == solo_norm: # Usa normalização ZARC
|
|
873
956
|
score += 2
|
|
874
957
|
|
|
875
958
|
if score > melhor_score:
|
|
@@ -894,4 +977,11 @@ def buscar_zarc_fallback(
|
|
|
894
977
|
"observacao": "Dados simplificados locais usados porque o CSV oficial não estava disponível."
|
|
895
978
|
}
|
|
896
979
|
|
|
897
|
-
|
|
980
|
+
# Nenhum match encontrado - retornar estado "unavailable" em vez de None
|
|
981
|
+
return {
|
|
982
|
+
"encontrado": False,
|
|
983
|
+
"source": "zarc-unavailable",
|
|
984
|
+
"fallback": False,
|
|
985
|
+
"message": "ZARC consultado, mas nenhuma recomendação foi encontrada para esta cultura, solo e região.",
|
|
986
|
+
"observacao": "A cultura pode não estar disponível no índice ZARC compacto para a região selecionada."
|
|
987
|
+
}
|