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.
@@ -1,6 +1,6 @@
1
1
  {
2
- "cli_version": "1.0.36",
3
- "backend_template_version": "1.0.36",
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:00:00Z"
31
+ "generated_at": "2026-05-10T23:30:00Z"
31
32
  }
@@ -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("precipitation_mm_avg"),
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
- "precipitation_mm_avg": nasa_data["precipitation_mm_avg"],
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
- "precipitation_mm_avg": climatology["precip_mm"],
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("precipitation_mm_avg", 0)
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", # Temperatura média a 2m
45
- "T2M_MAX", # Temperatura máxima a 2m
46
- "T2M_MIN", # Temperatura mínima a 2m
47
- "PRECTOTCORR", # Precipitação total corrigida
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=15)
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
- # NASA POWER retorna dados mensais (1-12)
69
- month_str = str(month)
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
- temp_avg = properties.get("T2M", {}).get(month_str)
72
- temp_max = properties.get("T2M_MAX", {}).get(month_str)
73
- temp_min = properties.get("T2M_MIN", {}).get(month_str)
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 or precip 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 precip < 50:
147
+ if precip_monthly < 50:
82
148
  precip_desc = "Período seco"
83
- elif precip < 100:
149
+ elif precip_monthly < 100:
84
150
  precip_desc = "Chuvas ocasionais"
85
- elif precip < 150:
151
+ elif precip_monthly < 150:
86
152
  precip_desc = "Chuvas moderadas"
87
- elif precip < 200:
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
- "precipitation_mm_avg": round(precip, 1),
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 NASA POWER para lat={lat}, lon={lon}, month={month}")
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"Erro ao buscar NASA POWER: {e}")
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 ao processar NASA POWER: {e}")
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
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agroplan-ai-cli",
3
- "version": "1.0.36",
3
+ "version": "1.0.37",
4
4
  "description": "CLI global para AgroPlan AI - modo local acelerado",
5
5
  "type": "module",
6
6
  "bin": {