agroplan-ai-cli 1.0.36 → 1.0.37
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/backend-template/VERSION.json +5 -4
- package/backend-template/api.py +33 -0
- package/backend-template/core/calendar_weather_adapter.py +1 -1
- package/backend-template/providers/calendar_weather_provider.py +4 -3
- package/backend-template/providers/nasa_power_provider.py +186 -22
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"cli_version": "1.0.
|
|
3
|
-
"backend_template_version": "1.0.
|
|
2
|
+
"cli_version": "1.0.37",
|
|
3
|
+
"backend_template_version": "1.0.37",
|
|
4
4
|
"zarc_index_version": "2025-2026-fast-index-v2",
|
|
5
5
|
"price_index_version": "2025-05-reference-v1",
|
|
6
6
|
"features": [
|
|
@@ -25,7 +25,8 @@
|
|
|
25
25
|
"calendar_date_safety",
|
|
26
26
|
"calendar_weather_integration",
|
|
27
27
|
"calendar_weather_dependency_fix",
|
|
28
|
-
"nasa_power_climatology"
|
|
28
|
+
"nasa_power_climatology",
|
|
29
|
+
"nasa_power_parser_fix"
|
|
29
30
|
],
|
|
30
|
-
"generated_at": "2026-05-10T23:
|
|
31
|
+
"generated_at": "2026-05-10T23:30:00Z"
|
|
31
32
|
}
|
package/backend-template/api.py
CHANGED
|
@@ -415,6 +415,39 @@ def get_clima_nasa_power(
|
|
|
415
415
|
except Exception as e:
|
|
416
416
|
raise HTTPException(status_code=500, detail=str(e))
|
|
417
417
|
|
|
418
|
+
@app.get("/dados/clima/nasa-power/debug")
|
|
419
|
+
def get_clima_nasa_power_debug(
|
|
420
|
+
lat: Optional[float] = Query(None, description="Latitude"),
|
|
421
|
+
lon: Optional[float] = Query(None, description="Longitude"),
|
|
422
|
+
month: int = Query(None, description="Mês (1-12)")
|
|
423
|
+
):
|
|
424
|
+
"""Debug endpoint - mostra resposta bruta da NASA POWER para diagnóstico"""
|
|
425
|
+
try:
|
|
426
|
+
from providers.nasa_power_provider import buscar_climatologia_nasa_power_debug
|
|
427
|
+
|
|
428
|
+
# Se lat ou lon não foram fornecidos, retornar informações
|
|
429
|
+
if lat is None or lon is None or month is None:
|
|
430
|
+
return {
|
|
431
|
+
"message": "Informe latitude, longitude e mês para debug NASA POWER.",
|
|
432
|
+
"exemplo": "/dados/clima/nasa-power/debug?lat=-21.56&lon=-50.45&month=5",
|
|
433
|
+
"parametros": {
|
|
434
|
+
"lat": "Latitude da localização",
|
|
435
|
+
"lon": "Longitude da localização",
|
|
436
|
+
"month": "Mês (1-12)"
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
if month < 1 or month > 12:
|
|
441
|
+
raise HTTPException(status_code=400, detail="Month deve estar entre 1 e 12")
|
|
442
|
+
|
|
443
|
+
# Buscar debug info
|
|
444
|
+
debug_info = buscar_climatologia_nasa_power_debug(lat, lon, month)
|
|
445
|
+
|
|
446
|
+
return debug_info
|
|
447
|
+
|
|
448
|
+
except Exception as e:
|
|
449
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
450
|
+
|
|
418
451
|
@app.get("/dados/zarc")
|
|
419
452
|
def get_zarc(
|
|
420
453
|
cultura: Optional[str] = Query(None, description="Nome da cultura"),
|
|
@@ -136,7 +136,7 @@ def enriquecer_calendario_com_clima(
|
|
|
136
136
|
"source": weather_data.get("source", "unknown"),
|
|
137
137
|
"forecast_type": forecast_type,
|
|
138
138
|
"summary": summary,
|
|
139
|
-
"precipitation_mm": weather_data.get("precipitation_sum") or weather_data.get("
|
|
139
|
+
"precipitation_mm": weather_data.get("precipitation_sum") or weather_data.get("precipitation_monthly_total") or weather_data.get("precipitation_daily_avg"),
|
|
140
140
|
"precipitation_probability": weather_data.get("precipitation_probability"),
|
|
141
141
|
"temperature_min": weather_data.get("temperature_min"),
|
|
142
142
|
"temperature_max": weather_data.get("temperature_max"),
|
|
@@ -139,7 +139,8 @@ def buscar_climatologia_longo_prazo(
|
|
|
139
139
|
"temperature_min": nasa_data["temperature_min"],
|
|
140
140
|
"temperature_avg": nasa_data["temperature_avg"],
|
|
141
141
|
"precipitation_expected": nasa_data["precipitation_expected"],
|
|
142
|
-
"
|
|
142
|
+
"precipitation_daily_avg": nasa_data["precipitation_daily_avg"],
|
|
143
|
+
"precipitation_monthly_total": nasa_data["precipitation_monthly_total"],
|
|
143
144
|
"confidence": "media",
|
|
144
145
|
"note": "Climatologia NASA POWER, não previsão exata"
|
|
145
146
|
})
|
|
@@ -153,7 +154,7 @@ def buscar_climatologia_longo_prazo(
|
|
|
153
154
|
"temperature_max": climatology["temp_max"],
|
|
154
155
|
"temperature_min": climatology["temp_min"],
|
|
155
156
|
"precipitation_expected": climatology["precip_desc"],
|
|
156
|
-
"
|
|
157
|
+
"precipitation_daily_avg": climatology["precip_mm"],
|
|
157
158
|
"confidence": "baixa",
|
|
158
159
|
"note": "Climatologia simplificada, NASA POWER indisponível"
|
|
159
160
|
})
|
|
@@ -251,7 +252,7 @@ def gerar_recomendacao_climatica(
|
|
|
251
252
|
"""
|
|
252
253
|
|
|
253
254
|
forecast_type = weather_data.get("forecast_type", "climatology")
|
|
254
|
-
precip = weather_data.get("precipitation_sum") or weather_data.get("
|
|
255
|
+
precip = weather_data.get("precipitation_sum") or weather_data.get("precipitation_monthly_total") or weather_data.get("precipitation_daily_avg", 0)
|
|
255
256
|
precip_prob = weather_data.get("precipitation_probability", 0)
|
|
256
257
|
temp_max = weather_data.get("temperature_max", 25)
|
|
257
258
|
temp_min = weather_data.get("temperature_min", 15)
|
|
@@ -12,6 +12,58 @@ import requests
|
|
|
12
12
|
from .cache import get_cache, set_cache
|
|
13
13
|
|
|
14
14
|
|
|
15
|
+
# Mapeamento de mês numérico para chave NASA POWER
|
|
16
|
+
MONTH_KEYS = {
|
|
17
|
+
1: "JAN",
|
|
18
|
+
2: "FEB",
|
|
19
|
+
3: "MAR",
|
|
20
|
+
4: "APR",
|
|
21
|
+
5: "MAY",
|
|
22
|
+
6: "JUN",
|
|
23
|
+
7: "JUL",
|
|
24
|
+
8: "AUG",
|
|
25
|
+
9: "SEP",
|
|
26
|
+
10: "OCT",
|
|
27
|
+
11: "NOV",
|
|
28
|
+
12: "DEC",
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def get_month_value(parameter_data: Dict, month: int) -> Optional[float]:
|
|
33
|
+
"""
|
|
34
|
+
Extrai valor mensal de parâmetro NASA POWER.
|
|
35
|
+
|
|
36
|
+
Tenta múltiplos formatos de chave:
|
|
37
|
+
1. Chave alfabética (MAY, JUN, etc.) - formato padrão NASA POWER
|
|
38
|
+
2. Chave numérica string ("5", "6", etc.)
|
|
39
|
+
3. Chave numérica com zero ("05", "06", etc.)
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
parameter_data: Dicionário com dados do parâmetro
|
|
43
|
+
month: Mês numérico (1-12)
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
Valor do parâmetro ou None se não encontrado
|
|
47
|
+
"""
|
|
48
|
+
if not parameter_data:
|
|
49
|
+
return None
|
|
50
|
+
|
|
51
|
+
# Tentativa 1: Chave alfabética (formato padrão NASA POWER)
|
|
52
|
+
month_key = MONTH_KEYS.get(month)
|
|
53
|
+
if month_key and month_key in parameter_data:
|
|
54
|
+
return parameter_data.get(month_key)
|
|
55
|
+
|
|
56
|
+
# Tentativa 2: Chave numérica string
|
|
57
|
+
if str(month) in parameter_data:
|
|
58
|
+
return parameter_data.get(str(month))
|
|
59
|
+
|
|
60
|
+
# Tentativa 3: Chave numérica com zero
|
|
61
|
+
if f"{month:02d}" in parameter_data:
|
|
62
|
+
return parameter_data.get(f"{month:02d}")
|
|
63
|
+
|
|
64
|
+
return None
|
|
65
|
+
|
|
66
|
+
|
|
15
67
|
def buscar_climatologia_nasa_power(
|
|
16
68
|
lat: float,
|
|
17
69
|
lon: float,
|
|
@@ -41,10 +93,11 @@ def buscar_climatologia_nasa_power(
|
|
|
41
93
|
|
|
42
94
|
# Parâmetros climatológicos
|
|
43
95
|
parameters = [
|
|
44
|
-
"T2M",
|
|
45
|
-
"T2M_MAX",
|
|
46
|
-
"T2M_MIN",
|
|
47
|
-
"PRECTOTCORR",
|
|
96
|
+
"T2M", # Temperatura média a 2m
|
|
97
|
+
"T2M_MAX", # Temperatura máxima a 2m
|
|
98
|
+
"T2M_MIN", # Temperatura mínima a 2m
|
|
99
|
+
"PRECTOTCORR", # Precipitação diária média corrigida
|
|
100
|
+
"PRECTOTCORR_SUM", # Precipitação total mensal corrigida
|
|
48
101
|
]
|
|
49
102
|
|
|
50
103
|
# Endpoint NASA POWER Climatology
|
|
@@ -58,33 +111,46 @@ def buscar_climatologia_nasa_power(
|
|
|
58
111
|
"format": "JSON"
|
|
59
112
|
}
|
|
60
113
|
|
|
61
|
-
response = requests.get(url, params=params, timeout=
|
|
114
|
+
response = requests.get(url, params=params, timeout=30)
|
|
62
115
|
response.raise_for_status()
|
|
63
116
|
data = response.json()
|
|
64
117
|
|
|
65
118
|
# Extrair dados do mês específico
|
|
66
119
|
properties = data.get("properties", {}).get("parameter", {})
|
|
67
120
|
|
|
68
|
-
#
|
|
69
|
-
|
|
121
|
+
# Usar helper para extrair valores com fallback robusto
|
|
122
|
+
month_key = MONTH_KEYS.get(month)
|
|
123
|
+
|
|
124
|
+
temp_avg = get_month_value(properties.get("T2M"), month)
|
|
125
|
+
temp_max = get_month_value(properties.get("T2M_MAX"), month)
|
|
126
|
+
temp_min = get_month_value(properties.get("T2M_MIN"), month)
|
|
70
127
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
precip = properties.get("PRECTOTCORR", {}).get(month_str)
|
|
128
|
+
# Precipitação: priorizar PRECTOTCORR_SUM (total mensal), fallback para PRECTOTCORR (média diária)
|
|
129
|
+
precip_sum = get_month_value(properties.get("PRECTOTCORR_SUM"), month)
|
|
130
|
+
precip_daily = get_month_value(properties.get("PRECTOTCORR"), month)
|
|
75
131
|
|
|
76
|
-
# Validar dados
|
|
77
|
-
if temp_avg is None
|
|
132
|
+
# Validar dados essenciais
|
|
133
|
+
if temp_avg is None:
|
|
134
|
+
return None
|
|
135
|
+
|
|
136
|
+
# Calcular precipitação mensal
|
|
137
|
+
if precip_sum is not None:
|
|
138
|
+
precip_monthly = precip_sum
|
|
139
|
+
precip_daily_avg = precip_sum / 30 # Estimativa
|
|
140
|
+
elif precip_daily is not None:
|
|
141
|
+
precip_daily_avg = precip_daily
|
|
142
|
+
precip_monthly = precip_daily * 30 # Estimativa
|
|
143
|
+
else:
|
|
78
144
|
return None
|
|
79
145
|
|
|
80
146
|
# Classificar precipitação
|
|
81
|
-
if
|
|
147
|
+
if precip_monthly < 50:
|
|
82
148
|
precip_desc = "Período seco"
|
|
83
|
-
elif
|
|
149
|
+
elif precip_monthly < 100:
|
|
84
150
|
precip_desc = "Chuvas ocasionais"
|
|
85
|
-
elif
|
|
151
|
+
elif precip_monthly < 150:
|
|
86
152
|
precip_desc = "Chuvas moderadas"
|
|
87
|
-
elif
|
|
153
|
+
elif precip_monthly < 200:
|
|
88
154
|
precip_desc = "Chuvas frequentes"
|
|
89
155
|
else:
|
|
90
156
|
precip_desc = "Estação chuvosa"
|
|
@@ -93,11 +159,13 @@ def buscar_climatologia_nasa_power(
|
|
|
93
159
|
"source": "nasa-power",
|
|
94
160
|
"forecast_type": "climatology",
|
|
95
161
|
"month": month,
|
|
162
|
+
"month_key": month_key,
|
|
96
163
|
"temperature_avg": round(temp_avg, 1),
|
|
97
164
|
"temperature_max": round(temp_max, 1) if temp_max else None,
|
|
98
165
|
"temperature_min": round(temp_min, 1) if temp_min else None,
|
|
99
166
|
"precipitation_expected": precip_desc,
|
|
100
|
-
"
|
|
167
|
+
"precipitation_daily_avg": round(precip_daily_avg, 1),
|
|
168
|
+
"precipitation_monthly_total": round(precip_monthly, 1),
|
|
101
169
|
"confidence": "media",
|
|
102
170
|
"note": "Dados climatológicos/históricos NASA POWER, não previsão exata."
|
|
103
171
|
}
|
|
@@ -108,13 +176,16 @@ def buscar_climatologia_nasa_power(
|
|
|
108
176
|
return result
|
|
109
177
|
|
|
110
178
|
except requests.exceptions.Timeout:
|
|
111
|
-
print(f"Timeout ao buscar
|
|
179
|
+
print(f"[NASA POWER] Timeout ao buscar dados para lat={lat}, lon={lon}, month={month}")
|
|
112
180
|
return None
|
|
113
181
|
except requests.exceptions.RequestException as e:
|
|
114
|
-
print(f"
|
|
182
|
+
print(f"[NASA POWER] Erro de requisição: {e}")
|
|
183
|
+
return None
|
|
184
|
+
except KeyError as e:
|
|
185
|
+
print(f"[NASA POWER] Erro ao parsear resposta - chave ausente: {e}")
|
|
115
186
|
return None
|
|
116
187
|
except Exception as e:
|
|
117
|
-
print(f"Erro inesperado
|
|
188
|
+
print(f"[NASA POWER] Erro inesperado: {e}")
|
|
118
189
|
return None
|
|
119
190
|
|
|
120
191
|
|
|
@@ -129,8 +200,101 @@ def get_nasa_power_status() -> Dict:
|
|
|
129
200
|
"provider": "NASA POWER",
|
|
130
201
|
"type": "climatology",
|
|
131
202
|
"source": "https://power.larc.nasa.gov/",
|
|
132
|
-
"parameters": ["T2M", "T2M_MAX", "T2M_MIN", "PRECTOTCORR"],
|
|
203
|
+
"parameters": ["T2M", "T2M_MAX", "T2M_MIN", "PRECTOTCORR", "PRECTOTCORR_SUM"],
|
|
133
204
|
"community": "AG (Agricultural)",
|
|
134
205
|
"cache_ttl": "7 days",
|
|
135
206
|
"note": "Climatologia/histórico, não previsão exata"
|
|
136
207
|
}
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def buscar_climatologia_nasa_power_debug(
|
|
211
|
+
lat: float,
|
|
212
|
+
lon: float,
|
|
213
|
+
month: int
|
|
214
|
+
) -> Dict:
|
|
215
|
+
"""
|
|
216
|
+
Versão debug que retorna resposta bruta da NASA POWER para diagnóstico.
|
|
217
|
+
|
|
218
|
+
Args:
|
|
219
|
+
lat: Latitude
|
|
220
|
+
lon: Longitude
|
|
221
|
+
month: Mês (1-12)
|
|
222
|
+
|
|
223
|
+
Returns:
|
|
224
|
+
Dicionário com resposta bruta e diagnóstico
|
|
225
|
+
"""
|
|
226
|
+
try:
|
|
227
|
+
# Parâmetros climatológicos
|
|
228
|
+
parameters = [
|
|
229
|
+
"T2M",
|
|
230
|
+
"T2M_MAX",
|
|
231
|
+
"T2M_MIN",
|
|
232
|
+
"PRECTOTCORR",
|
|
233
|
+
"PRECTOTCORR_SUM",
|
|
234
|
+
]
|
|
235
|
+
|
|
236
|
+
# Endpoint NASA POWER Climatology
|
|
237
|
+
url = "https://power.larc.nasa.gov/api/temporal/climatology/point"
|
|
238
|
+
|
|
239
|
+
params = {
|
|
240
|
+
"parameters": ",".join(parameters),
|
|
241
|
+
"community": "AG",
|
|
242
|
+
"longitude": lon,
|
|
243
|
+
"latitude": lat,
|
|
244
|
+
"format": "JSON"
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
response = requests.get(url, params=params, timeout=30)
|
|
248
|
+
response.raise_for_status()
|
|
249
|
+
data = response.json()
|
|
250
|
+
|
|
251
|
+
# Extrair informações para diagnóstico
|
|
252
|
+
properties = data.get("properties", {}).get("parameter", {})
|
|
253
|
+
|
|
254
|
+
# Coletar chaves de cada parâmetro
|
|
255
|
+
parameter_keys = {}
|
|
256
|
+
for param in parameters:
|
|
257
|
+
param_data = properties.get(param, {})
|
|
258
|
+
if isinstance(param_data, dict):
|
|
259
|
+
parameter_keys[param] = list(param_data.keys())
|
|
260
|
+
else:
|
|
261
|
+
parameter_keys[param] = f"Not a dict: {type(param_data)}"
|
|
262
|
+
|
|
263
|
+
# Tentar extrair valores para o mês solicitado
|
|
264
|
+
month_key = MONTH_KEYS.get(month)
|
|
265
|
+
extracted_values = {}
|
|
266
|
+
for param in parameters:
|
|
267
|
+
extracted_values[param] = get_month_value(properties.get(param), month)
|
|
268
|
+
|
|
269
|
+
return {
|
|
270
|
+
"status": "success",
|
|
271
|
+
"url": url,
|
|
272
|
+
"params": params,
|
|
273
|
+
"status_code": response.status_code,
|
|
274
|
+
"month_requested": month,
|
|
275
|
+
"month_key": month_key,
|
|
276
|
+
"parameter_keys": parameter_keys,
|
|
277
|
+
"extracted_values": extracted_values,
|
|
278
|
+
"raw_response_keys": list(data.keys()),
|
|
279
|
+
"properties_keys": list(data.get("properties", {}).keys()),
|
|
280
|
+
"note": "Debug endpoint - mostra estrutura bruta da resposta NASA POWER"
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
except requests.exceptions.Timeout:
|
|
284
|
+
return {
|
|
285
|
+
"status": "error",
|
|
286
|
+
"error_type": "timeout",
|
|
287
|
+
"message": "Timeout ao buscar NASA POWER"
|
|
288
|
+
}
|
|
289
|
+
except requests.exceptions.RequestException as e:
|
|
290
|
+
return {
|
|
291
|
+
"status": "error",
|
|
292
|
+
"error_type": "request_error",
|
|
293
|
+
"message": str(e)
|
|
294
|
+
}
|
|
295
|
+
except Exception as e:
|
|
296
|
+
return {
|
|
297
|
+
"status": "error",
|
|
298
|
+
"error_type": "unexpected_error",
|
|
299
|
+
"message": str(e)
|
|
300
|
+
}
|