agroplan-ai-cli 1.0.35 → 1.0.36
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 +43 -0
- package/backend-template/core/calendar_weather_adapter.py +4 -0
- package/backend-template/providers/calendar_weather_provider.py +39 -16
- package/backend-template/providers/nasa_power_provider.py +136 -0
- 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.36",
|
|
3
|
+
"backend_template_version": "1.0.36",
|
|
4
4
|
"zarc_index_version": "2025-2026-fast-index-v2",
|
|
5
5
|
"price_index_version": "2025-05-reference-v1",
|
|
6
6
|
"features": [
|
|
@@ -24,7 +24,8 @@
|
|
|
24
24
|
"expanded_crop_calendar_10_cultures",
|
|
25
25
|
"calendar_date_safety",
|
|
26
26
|
"calendar_weather_integration",
|
|
27
|
-
"calendar_weather_dependency_fix"
|
|
27
|
+
"calendar_weather_dependency_fix",
|
|
28
|
+
"nasa_power_climatology"
|
|
28
29
|
],
|
|
29
|
-
"generated_at": "2026-05-
|
|
30
|
+
"generated_at": "2026-05-10T23:00:00Z"
|
|
30
31
|
}
|
package/backend-template/api.py
CHANGED
|
@@ -372,6 +372,49 @@ def get_clima(
|
|
|
372
372
|
except Exception as e:
|
|
373
373
|
raise HTTPException(status_code=500, detail=str(e))
|
|
374
374
|
|
|
375
|
+
@app.get("/dados/clima/nasa-power")
|
|
376
|
+
def get_clima_nasa_power(
|
|
377
|
+
lat: Optional[float] = Query(None, description="Latitude"),
|
|
378
|
+
lon: Optional[float] = Query(None, description="Longitude"),
|
|
379
|
+
month: int = Query(None, description="Mês (1-12)")
|
|
380
|
+
):
|
|
381
|
+
"""Obtém climatologia NASA POWER para um mês específico"""
|
|
382
|
+
try:
|
|
383
|
+
from providers.nasa_power_provider import buscar_climatologia_nasa_power, get_nasa_power_status
|
|
384
|
+
|
|
385
|
+
# Se lat ou lon não foram fornecidos, retornar informações
|
|
386
|
+
if lat is None or lon is None or month is None:
|
|
387
|
+
return {
|
|
388
|
+
"message": "Informe latitude, longitude e mês para consultar climatologia NASA POWER.",
|
|
389
|
+
"exemplo": "/dados/clima/nasa-power?lat=-21.56&lon=-50.45&month=5",
|
|
390
|
+
"parametros": {
|
|
391
|
+
"lat": "Latitude da localização",
|
|
392
|
+
"lon": "Longitude da localização",
|
|
393
|
+
"month": "Mês (1-12)"
|
|
394
|
+
},
|
|
395
|
+
"provider_info": get_nasa_power_status()
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
if month < 1 or month > 12:
|
|
399
|
+
raise HTTPException(status_code=400, detail="Month deve estar entre 1 e 12")
|
|
400
|
+
|
|
401
|
+
# Buscar climatologia
|
|
402
|
+
climatology = buscar_climatologia_nasa_power(lat, lon, month)
|
|
403
|
+
|
|
404
|
+
if climatology:
|
|
405
|
+
return climatology
|
|
406
|
+
else:
|
|
407
|
+
return {
|
|
408
|
+
"message": "Não foi possível obter dados NASA POWER para esta localização.",
|
|
409
|
+
"lat": lat,
|
|
410
|
+
"lon": lon,
|
|
411
|
+
"month": month,
|
|
412
|
+
"note": "NASA POWER pode estar temporariamente indisponível ou a localização pode estar fora da cobertura."
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
except Exception as e:
|
|
416
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
417
|
+
|
|
375
418
|
@app.get("/dados/zarc")
|
|
376
419
|
def get_zarc(
|
|
377
420
|
cultura: Optional[str] = Query(None, description="Nome da cultura"),
|
|
@@ -121,8 +121,12 @@ def enriquecer_calendario_com_clima(
|
|
|
121
121
|
|
|
122
122
|
# Gerar resumo
|
|
123
123
|
forecast_type = weather_data.get("forecast_type", "climatology")
|
|
124
|
+
source = weather_data.get("source", "unknown")
|
|
125
|
+
|
|
124
126
|
if forecast_type == "forecast":
|
|
125
127
|
summary = f"Previsão: {weather_data.get('precipitation_sum', 0):.1f}mm de chuva, {weather_data.get('temperature_min', 0):.0f}°C a {weather_data.get('temperature_max', 0):.0f}°C"
|
|
128
|
+
elif source == "nasa-power":
|
|
129
|
+
summary = f"Climatologia NASA POWER: temperatura média {weather_data.get('temperature_avg', 0):.0f}°C, {weather_data.get('precipitation_expected', 'condições típicas')}"
|
|
126
130
|
else:
|
|
127
131
|
summary = f"Climatologia: {weather_data.get('precipitation_expected', 'Condições típicas')}"
|
|
128
132
|
|
|
@@ -99,8 +99,7 @@ def buscar_climatologia_longo_prazo(
|
|
|
99
99
|
"""
|
|
100
100
|
Busca climatologia/histórico para períodos longos (17+ dias).
|
|
101
101
|
|
|
102
|
-
|
|
103
|
-
Futuramente pode integrar NASA POWER.
|
|
102
|
+
Tenta usar NASA POWER primeiro, fallback local se falhar.
|
|
104
103
|
|
|
105
104
|
Args:
|
|
106
105
|
lat: Latitude
|
|
@@ -112,28 +111,52 @@ def buscar_climatologia_longo_prazo(
|
|
|
112
111
|
Lista de dicionários com climatologia por data/mês
|
|
113
112
|
"""
|
|
114
113
|
|
|
115
|
-
|
|
116
|
-
# Futuramente: integrar NASA POWER Climatology API
|
|
114
|
+
from .nasa_power_provider import buscar_climatologia_nasa_power
|
|
117
115
|
|
|
118
116
|
result = []
|
|
119
117
|
current = start_date
|
|
120
118
|
|
|
119
|
+
# Cache de dados NASA POWER por mês
|
|
120
|
+
nasa_cache = {}
|
|
121
|
+
|
|
121
122
|
while current <= end_date:
|
|
122
123
|
month = current.month
|
|
123
124
|
|
|
124
|
-
#
|
|
125
|
-
|
|
125
|
+
# Tentar NASA POWER primeiro
|
|
126
|
+
if month not in nasa_cache:
|
|
127
|
+
nasa_data = buscar_climatologia_nasa_power(lat, lon, month)
|
|
128
|
+
nasa_cache[month] = nasa_data
|
|
129
|
+
|
|
130
|
+
nasa_data = nasa_cache[month]
|
|
126
131
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
132
|
+
if nasa_data:
|
|
133
|
+
# Usar dados NASA POWER
|
|
134
|
+
result.append({
|
|
135
|
+
"date": current.isoformat(),
|
|
136
|
+
"source": "nasa-power",
|
|
137
|
+
"forecast_type": "climatology",
|
|
138
|
+
"temperature_max": nasa_data["temperature_max"],
|
|
139
|
+
"temperature_min": nasa_data["temperature_min"],
|
|
140
|
+
"temperature_avg": nasa_data["temperature_avg"],
|
|
141
|
+
"precipitation_expected": nasa_data["precipitation_expected"],
|
|
142
|
+
"precipitation_mm_avg": nasa_data["precipitation_mm_avg"],
|
|
143
|
+
"confidence": "media",
|
|
144
|
+
"note": "Climatologia NASA POWER, não previsão exata"
|
|
145
|
+
})
|
|
146
|
+
else:
|
|
147
|
+
# Fallback local se NASA POWER falhar
|
|
148
|
+
climatology = _get_monthly_climatology(month, lat, lon)
|
|
149
|
+
result.append({
|
|
150
|
+
"date": current.isoformat(),
|
|
151
|
+
"source": "climate-fallback",
|
|
152
|
+
"forecast_type": "climatology",
|
|
153
|
+
"temperature_max": climatology["temp_max"],
|
|
154
|
+
"temperature_min": climatology["temp_min"],
|
|
155
|
+
"precipitation_expected": climatology["precip_desc"],
|
|
156
|
+
"precipitation_mm_avg": climatology["precip_mm"],
|
|
157
|
+
"confidence": "baixa",
|
|
158
|
+
"note": "Climatologia simplificada, NASA POWER indisponível"
|
|
159
|
+
})
|
|
137
160
|
|
|
138
161
|
current += timedelta(days=1)
|
|
139
162
|
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Provider NASA POWER para Climatologia
|
|
3
|
+
|
|
4
|
+
Usa NASA POWER Climatology API para obter dados históricos/climatológicos
|
|
5
|
+
para períodos além da janela de previsão meteorológica (17+ dias).
|
|
6
|
+
|
|
7
|
+
NASA POWER não é previsão exata, é climatologia/histórico.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from typing import Dict, Optional
|
|
11
|
+
import requests
|
|
12
|
+
from .cache import get_cache, set_cache
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def buscar_climatologia_nasa_power(
|
|
16
|
+
lat: float,
|
|
17
|
+
lon: float,
|
|
18
|
+
month: int
|
|
19
|
+
) -> Optional[Dict]:
|
|
20
|
+
"""
|
|
21
|
+
Busca climatologia NASA POWER para um mês específico.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
lat: Latitude
|
|
25
|
+
lon: Longitude
|
|
26
|
+
month: Mês (1-12)
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
Dicionário com dados climatológicos ou None se falhar
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
# Cache key
|
|
33
|
+
cache_key = f"nasa_power:{lat}:{lon}:{month}"
|
|
34
|
+
cached = get_cache(cache_key)
|
|
35
|
+
if cached:
|
|
36
|
+
return cached
|
|
37
|
+
|
|
38
|
+
try:
|
|
39
|
+
# NASA POWER Climatology API
|
|
40
|
+
# Documentação: https://power.larc.nasa.gov/docs/services/api/
|
|
41
|
+
|
|
42
|
+
# Parâmetros climatológicos
|
|
43
|
+
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
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
# Endpoint NASA POWER Climatology
|
|
51
|
+
url = "https://power.larc.nasa.gov/api/temporal/climatology/point"
|
|
52
|
+
|
|
53
|
+
params = {
|
|
54
|
+
"parameters": ",".join(parameters),
|
|
55
|
+
"community": "AG", # Agricultural community
|
|
56
|
+
"longitude": lon,
|
|
57
|
+
"latitude": lat,
|
|
58
|
+
"format": "JSON"
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
response = requests.get(url, params=params, timeout=15)
|
|
62
|
+
response.raise_for_status()
|
|
63
|
+
data = response.json()
|
|
64
|
+
|
|
65
|
+
# Extrair dados do mês específico
|
|
66
|
+
properties = data.get("properties", {}).get("parameter", {})
|
|
67
|
+
|
|
68
|
+
# NASA POWER retorna dados mensais (1-12)
|
|
69
|
+
month_str = str(month)
|
|
70
|
+
|
|
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)
|
|
75
|
+
|
|
76
|
+
# Validar dados
|
|
77
|
+
if temp_avg is None or precip is None:
|
|
78
|
+
return None
|
|
79
|
+
|
|
80
|
+
# Classificar precipitação
|
|
81
|
+
if precip < 50:
|
|
82
|
+
precip_desc = "Período seco"
|
|
83
|
+
elif precip < 100:
|
|
84
|
+
precip_desc = "Chuvas ocasionais"
|
|
85
|
+
elif precip < 150:
|
|
86
|
+
precip_desc = "Chuvas moderadas"
|
|
87
|
+
elif precip < 200:
|
|
88
|
+
precip_desc = "Chuvas frequentes"
|
|
89
|
+
else:
|
|
90
|
+
precip_desc = "Estação chuvosa"
|
|
91
|
+
|
|
92
|
+
result = {
|
|
93
|
+
"source": "nasa-power",
|
|
94
|
+
"forecast_type": "climatology",
|
|
95
|
+
"month": month,
|
|
96
|
+
"temperature_avg": round(temp_avg, 1),
|
|
97
|
+
"temperature_max": round(temp_max, 1) if temp_max else None,
|
|
98
|
+
"temperature_min": round(temp_min, 1) if temp_min else None,
|
|
99
|
+
"precipitation_expected": precip_desc,
|
|
100
|
+
"precipitation_mm_avg": round(precip, 1),
|
|
101
|
+
"confidence": "media",
|
|
102
|
+
"note": "Dados climatológicos/históricos NASA POWER, não previsão exata."
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
# Cache por 7 dias (climatologia muda pouco)
|
|
106
|
+
set_cache(cache_key, result, ttl_seconds=604800)
|
|
107
|
+
|
|
108
|
+
return result
|
|
109
|
+
|
|
110
|
+
except requests.exceptions.Timeout:
|
|
111
|
+
print(f"Timeout ao buscar NASA POWER para lat={lat}, lon={lon}, month={month}")
|
|
112
|
+
return None
|
|
113
|
+
except requests.exceptions.RequestException as e:
|
|
114
|
+
print(f"Erro ao buscar NASA POWER: {e}")
|
|
115
|
+
return None
|
|
116
|
+
except Exception as e:
|
|
117
|
+
print(f"Erro inesperado ao processar NASA POWER: {e}")
|
|
118
|
+
return None
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def get_nasa_power_status() -> Dict:
|
|
122
|
+
"""
|
|
123
|
+
Retorna status do provider NASA POWER.
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
Dicionário com informações de status
|
|
127
|
+
"""
|
|
128
|
+
return {
|
|
129
|
+
"provider": "NASA POWER",
|
|
130
|
+
"type": "climatology",
|
|
131
|
+
"source": "https://power.larc.nasa.gov/",
|
|
132
|
+
"parameters": ["T2M", "T2M_MAX", "T2M_MIN", "PRECTOTCORR"],
|
|
133
|
+
"community": "AG (Agricultural)",
|
|
134
|
+
"cache_ttl": "7 days",
|
|
135
|
+
"note": "Climatologia/histórico, não previsão exata"
|
|
136
|
+
}
|