avant codex
This commit is contained in:
Binary file not shown.
@@ -0,0 +1,272 @@
|
||||
# consigne.md — Scraping / Collecte des prévisions météo (Open-Meteo)
|
||||
|
||||
## Objectif
|
||||
|
||||
Mettre en place une collecte **automatique** des données météo de **prévision** (et optionnellement d’historique) depuis **Open-Meteo** pour alimenter un **calendrier météo** dans l’application jardin.
|
||||
|
||||
Le système doit :
|
||||
- récupérer les **prévisions** (horizon configurable, ex. 7 à 16 jours)
|
||||
- stocker les données dans un format exploitable (JSON + SQLite conseillé)
|
||||
- pouvoir recalculer/mettre à jour chaque jour sans dupliquer inutilement
|
||||
- gérer correctement le **fuseau Europe/Paris**
|
||||
- fournir une structure de données stable pour l’UI (calendrier jour + détail horaire)
|
||||
|
||||
---
|
||||
|
||||
## Source de données
|
||||
|
||||
### API Open-Meteo (sans clé)
|
||||
- Endpoint prévisions : `https://api.open-meteo.com/v1/forecast`
|
||||
- Endpoint historique (optionnel) : `https://api.open-meteo.com/v1/archive`
|
||||
- Format : JSON
|
||||
|
||||
Variables recommandées (prévisions) :
|
||||
- **hourly** :
|
||||
- `temperature_2m`
|
||||
- `precipitation_probability`
|
||||
- `precipitation`
|
||||
- `relativehumidity_2m`
|
||||
- `windspeed_10m`
|
||||
- `winddirection_10m`
|
||||
- `cloudcover`
|
||||
- `weathercode`
|
||||
- **daily** :
|
||||
- `temperature_2m_max`
|
||||
- `temperature_2m_min`
|
||||
- `precipitation_sum`
|
||||
- `precipitation_probability_max`
|
||||
- `windspeed_10m_max`
|
||||
- `sunrise`
|
||||
- `sunset`
|
||||
- `weathercode`
|
||||
|
||||
Remarques :
|
||||
- Toujours inclure `timezone=Europe/Paris`
|
||||
- Toujours inclure `timeformat=iso8601` (par défaut)
|
||||
- Conserver l’élévation renvoyée (utile pour contexte jardin)
|
||||
|
||||
Documentation :
|
||||
- https://open-meteo.com/en/docs
|
||||
|
||||
---
|
||||
|
||||
## Paramètres de localisation
|
||||
|
||||
Localisation cible : **Messinhac – Bessamorel** (ou proche).
|
||||
|
||||
Le système doit permettre :
|
||||
- configuration par **latitude/longitude**
|
||||
- stockage de la localisation dans un fichier `config.yml` (ou `.env`)
|
||||
|
||||
Exemple :
|
||||
- `latitude: 45.05`
|
||||
- `longitude: 3.48`
|
||||
|
||||
---
|
||||
|
||||
## Requêtes à implémenter
|
||||
|
||||
### 1) Prévisions quotidiennes + horaires (une seule requête)
|
||||
|
||||
Exemple `curl` (référence) :
|
||||
|
||||
```bash
|
||||
curl "https://api.open-meteo.com/v1/forecast?latitude=45.05&longitude=3.48&hourly=temperature_2m,precipitation_probability,precipitation,relativehumidity_2m,windspeed_10m,winddirection_10m,cloudcover,weathercode&daily=temperature_2m_max,temperature_2m_min,precipitation_sum,precipitation_probability_max,windspeed_10m_max,sunrise,sunset,weathercode&timezone=Europe/Paris&forecast_days=16"
|
||||
|
||||
Règles :
|
||||
|
||||
forecast_days configurable (7/10/16)
|
||||
|
||||
si forecast_days absent, Open-Meteo peut renvoyer une valeur par défaut (éviter : toujours préciser)
|
||||
|
||||
2) Historique (optionnel) pour “ce qui s’est réellement passé”
|
||||
|
||||
Exemple curl :
|
||||
|
||||
curl "https://api.open-meteo.com/v1/archive?latitude=45.05&longitude=3.48&start_date=2026-01-01&end_date=2026-02-21&daily=temperature_2m_max,temperature_2m_min,precipitation_sum&timezone=Europe/Paris"
|
||||
|
||||
Règles :
|
||||
|
||||
requête par tranche (ex. mensuelle) pour éviter des payloads trop gros
|
||||
|
||||
historiser “source=archive” vs “source=forecast”
|
||||
|
||||
Exigences de stockage (recommandé)
|
||||
1) JSON brut (cache)
|
||||
|
||||
Conserver la réponse JSON brute pour audit/debug :
|
||||
|
||||
data/cache/openmeteo_forecast_<YYYY-MM-DD>.json
|
||||
|
||||
Conserver un historique minimal des runs (ex. 30 derniers) :
|
||||
|
||||
rotation ou purge automatique
|
||||
|
||||
2) SQLite (données normalisées)
|
||||
|
||||
Créer 2 tables :
|
||||
|
||||
Table meteo_daily
|
||||
|
||||
date (YYYY-MM-DD) PRIMARY KEY
|
||||
|
||||
tmin_c, tmax_c
|
||||
|
||||
precip_mm (cumul)
|
||||
|
||||
precip_prob_max_pct
|
||||
|
||||
wind_max_kmh
|
||||
|
||||
sunrise_local, sunset_local
|
||||
|
||||
weathercode
|
||||
|
||||
source (forecast|archive)
|
||||
|
||||
fetched_at (timestamp)
|
||||
|
||||
lat, lon, elevation
|
||||
|
||||
Table meteo_hourly
|
||||
|
||||
datetime_local (YYYY-MM-DDTHH:MM) PRIMARY KEY
|
||||
|
||||
temp_c
|
||||
|
||||
precip_mm
|
||||
|
||||
precip_prob_pct
|
||||
|
||||
humidity_pct
|
||||
|
||||
wind_kmh
|
||||
|
||||
wind_dir_deg
|
||||
|
||||
cloud_pct
|
||||
|
||||
weathercode
|
||||
|
||||
source (forecast|archive)
|
||||
|
||||
fetched_at
|
||||
|
||||
lat, lon, elevation
|
||||
|
||||
Règles d’upsert :
|
||||
|
||||
si source=forecast : écraser les mêmes timestamps lors d’un nouveau run (les prévisions changent)
|
||||
|
||||
si source=archive : ne pas écraser si déjà présent (ou écraser seulement si “qualité” supérieure)
|
||||
|
||||
Normalisation / conversions
|
||||
|
||||
Open-Meteo renvoie souvent les vitesses en km/h selon endpoint/paramètre, vérifier les unités :
|
||||
|
||||
lire hourly_units et daily_units dans la réponse
|
||||
|
||||
stocker les unités (au moins en logging)
|
||||
|
||||
conserver les dates en timezone Europe/Paris pour affichage calendrier
|
||||
|
||||
Plan de collecte (cron)
|
||||
|
||||
Prévisions :
|
||||
|
||||
fréquence : 1 fois par jour (ex. 06:10 Europe/Paris)
|
||||
|
||||
stocker fetched_at pour savoir quand la prévision a été téléchargée
|
||||
|
||||
Historique (optionnel) :
|
||||
|
||||
1 fois par jour pour la veille (ex. récupérer “hier” en archive) afin de figer le “réel”
|
||||
|
||||
ou batch mensuel si besoin de rattrapage
|
||||
|
||||
Contrôles qualité (obligatoires)
|
||||
|
||||
Après chaque run :
|
||||
|
||||
vérifier que daily.time contient au moins 7 jours
|
||||
|
||||
vérifier cohérence : tmin <= tmax
|
||||
|
||||
vérifier que les tableaux time et temperature_2m ont la même longueur
|
||||
|
||||
si champs manquants : log d’erreur + sauvegarde JSON brute + arrêt gracieux
|
||||
|
||||
Sorties attendues pour l’UI
|
||||
Vue calendrier (jour)
|
||||
|
||||
Pour chaque jour :
|
||||
|
||||
date
|
||||
|
||||
Tmin/Tmax
|
||||
|
||||
pluie cumulée
|
||||
|
||||
probabilité max pluie
|
||||
|
||||
icône/code météo (weathercode)
|
||||
|
||||
lever/coucher du soleil
|
||||
|
||||
badges : gel (tmin <= 0), pluie (precip_mm > 0), vent fort (wind_max_kmh > seuil)
|
||||
|
||||
Vue détail (horaire)
|
||||
|
||||
Pour une date sélectionnée :
|
||||
|
||||
liste des heures (locales)
|
||||
|
||||
température + probabilité pluie + pluie mm + vent + humidité + nuages
|
||||
|
||||
Notes sur weathercode
|
||||
|
||||
Open-Meteo utilise un weathercode (WMO).
|
||||
Le projet doit :
|
||||
|
||||
stocker le code brut
|
||||
|
||||
fournir une table de mapping côté UI (code -> libellé FR + icône)
|
||||
|
||||
Livrables
|
||||
|
||||
scripts/fetch_openmeteo_forecast.py
|
||||
|
||||
récupère prévisions + écrit cache JSON + upsert SQLite
|
||||
|
||||
scripts/fetch_openmeteo_archive.py (optionnel)
|
||||
|
||||
récupère archive sur période + écrit cache JSON + insert SQLite
|
||||
|
||||
db/schema.sql
|
||||
|
||||
schéma SQLite (tables + index)
|
||||
|
||||
config.yml.example
|
||||
|
||||
latitude/longitude, timezone, forecast_days, chemins
|
||||
|
||||
Commandes de test
|
||||
Test rapide (curl)
|
||||
curl -s "https://api.open-meteo.com/v1/forecast?latitude=45.05&longitude=3.48&daily=temperature_2m_max,temperature_2m_min,precipitation_sum&timezone=Europe/Paris&forecast_days=7" | head
|
||||
Test avec jq (si installé)
|
||||
curl -s "https://api.open-meteo.com/v1/forecast?latitude=45.05&longitude=3.48&daily=temperature_2m_max,temperature_2m_min,precipitation_sum&timezone=Europe/Paris&forecast_days=7" | jq '.daily'
|
||||
Critères d’acceptation
|
||||
|
||||
Une exécution quotidienne met à jour les prévisions (forecast) sans dupliquer en base.
|
||||
|
||||
Le calendrier UI peut afficher au minimum : Tmin/Tmax + pluie + probabilité pluie + code météo.
|
||||
|
||||
Le détail horaire d’une journée est affichable (température + probabilité pluie).
|
||||
|
||||
Les données sont en Europe/Paris (pas de décalage d’un jour).
|
||||
|
||||
Les fichiers JSON de cache existent pour debug.
|
||||
|
||||
https://api.open-meteo.com/v1/forecast?latitude=45.1412&longitude=4.0736&hourly=temperature_2m,weather_code,cloud_cover,evapotranspiration,precipitation,precipitation_probability&forecast_days=14
|
||||
|
||||
https://api.open-meteo.com/v1/forecast?latitude=45.1412&longitude=4.0736&hourly=temperature_2m,weather_code,cloud_cover,evapotranspiration,precipitation,precipitation_probability&forecast_days=14
|
||||
@@ -0,0 +1,217 @@
|
||||
# Consigne — Collecte météo Open‑Meteo (Prévisions + Historique)
|
||||
|
||||
## 1. Objectif
|
||||
|
||||
Mettre en place une collecte automatique des données Open‑Meteo pour alimenter :
|
||||
- une vue calendrier météo (agrégat journalier),
|
||||
- une vue détail horaire.
|
||||
|
||||
Le système doit :
|
||||
- récupérer les prévisions sur horizon configurable (`7`, `10`, `16` jours),
|
||||
- stocker un cache JSON brut (debug/audit),
|
||||
- upserter en SQLite sans duplication,
|
||||
- gérer `Europe/Paris` sans décalage de date,
|
||||
- permettre un mode historique (`archive`) optionnel.
|
||||
|
||||
---
|
||||
|
||||
## 2. Source de données
|
||||
|
||||
- API prévisions : `https://api.open-meteo.com/v1/forecast`
|
||||
- API historique : `https://api.open-meteo.com/v1/archive`
|
||||
- Doc : `https://open-meteo.com/en/docs`
|
||||
|
||||
Toujours envoyer :
|
||||
- `timezone=Europe/Paris`
|
||||
- `timeformat=iso8601`
|
||||
|
||||
---
|
||||
|
||||
## 3. Configuration
|
||||
|
||||
Créer `config.yml` (et `config.yml.example`) :
|
||||
|
||||
```yaml
|
||||
open_meteo:
|
||||
latitude: 45.05
|
||||
longitude: 3.48
|
||||
timezone: Europe/Paris
|
||||
forecast_days: 14
|
||||
cache_dir: data/cache
|
||||
db_path: data/jardin.db
|
||||
wind_strong_kmh: 40
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Requête prévisions (obligatoire)
|
||||
|
||||
Variables demandées :
|
||||
- `hourly`: `temperature_2m,precipitation_probability,precipitation,relative_humidity_2m,wind_speed_10m,wind_direction_10m,cloud_cover,weather_code`
|
||||
- `daily`: `temperature_2m_max,temperature_2m_min,precipitation_sum,precipitation_probability_max,wind_speed_10m_max,sunrise,sunset,weather_code`
|
||||
|
||||
Exemple :
|
||||
|
||||
```bash
|
||||
curl -s "https://api.open-meteo.com/v1/forecast?latitude=45.05&longitude=3.48&hourly=temperature_2m,precipitation_probability,precipitation,relative_humidity_2m,wind_speed_10m,wind_direction_10m,cloud_cover,weather_code&daily=temperature_2m_max,temperature_2m_min,precipitation_sum,precipitation_probability_max,wind_speed_10m_max,sunrise,sunset,weather_code&timezone=Europe/Paris&timeformat=iso8601&forecast_days=14"
|
||||
```
|
||||
|
||||
Règles :
|
||||
- `forecast_days` doit toujours être explicite.
|
||||
- Lire et logger `hourly_units` et `daily_units`.
|
||||
|
||||
---
|
||||
|
||||
## 5. Requête archive (optionnelle)
|
||||
|
||||
Exemple :
|
||||
|
||||
```bash
|
||||
curl -s "https://api.open-meteo.com/v1/archive?latitude=45.05&longitude=3.48&start_date=2026-01-01&end_date=2026-01-31&daily=temperature_2m_max,temperature_2m_min,precipitation_sum&hourly=temperature_2m,precipitation,weather_code&timezone=Europe/Paris&timeformat=iso8601"
|
||||
```
|
||||
|
||||
Règles :
|
||||
- exécuter par tranche (mensuelle recommandée),
|
||||
- source marquée `archive`.
|
||||
|
||||
---
|
||||
|
||||
## 6. Stockage
|
||||
|
||||
### 6.1 Cache JSON brut
|
||||
|
||||
Fichiers à écrire à chaque run :
|
||||
- `data/cache/openmeteo_forecast_<YYYY-MM-DDTHHMMSS>.json`
|
||||
- `data/cache/openmeteo_archive_<YYYY-MM-DDTHHMMSS>.json` (si archive)
|
||||
|
||||
Purge :
|
||||
- conserver les `30` derniers fichiers par type.
|
||||
|
||||
### 6.2 SQLite normalisée
|
||||
|
||||
Table `meteo_daily` :
|
||||
- `date TEXT NOT NULL`
|
||||
- `source TEXT NOT NULL` (`forecast` | `archive`)
|
||||
- `tmin_c REAL`
|
||||
- `tmax_c REAL`
|
||||
- `precip_mm REAL`
|
||||
- `precip_prob_max_pct REAL`
|
||||
- `wind_max_kmh REAL`
|
||||
- `sunrise_local TEXT`
|
||||
- `sunset_local TEXT`
|
||||
- `weather_code INTEGER`
|
||||
- `fetched_at TEXT NOT NULL`
|
||||
- `lat REAL NOT NULL`
|
||||
- `lon REAL NOT NULL`
|
||||
- `elevation REAL`
|
||||
- `PRIMARY KEY (date, source)`
|
||||
|
||||
Table `meteo_hourly` :
|
||||
- `datetime_local TEXT NOT NULL` (`YYYY-MM-DDTHH:MM`)
|
||||
- `source TEXT NOT NULL` (`forecast` | `archive`)
|
||||
- `temp_c REAL`
|
||||
- `precip_mm REAL`
|
||||
- `precip_prob_pct REAL`
|
||||
- `humidity_pct REAL`
|
||||
- `wind_kmh REAL`
|
||||
- `wind_dir_deg REAL`
|
||||
- `cloud_pct REAL`
|
||||
- `weather_code INTEGER`
|
||||
- `fetched_at TEXT NOT NULL`
|
||||
- `lat REAL NOT NULL`
|
||||
- `lon REAL NOT NULL`
|
||||
- `elevation REAL`
|
||||
- `PRIMARY KEY (datetime_local, source)`
|
||||
|
||||
Index recommandés :
|
||||
- `idx_meteo_daily_date ON meteo_daily(date)`
|
||||
- `idx_meteo_hourly_dt ON meteo_hourly(datetime_local)`
|
||||
|
||||
---
|
||||
|
||||
## 7. Règles d’upsert
|
||||
|
||||
- `forecast` : `INSERT ... ON CONFLICT (...) DO UPDATE` (les prévisions évoluent).
|
||||
- `archive` : `INSERT ... ON CONFLICT (...) DO NOTHING` (on fige l’observé).
|
||||
|
||||
Conflits :
|
||||
- `meteo_daily` : `(date, source)`
|
||||
- `meteo_hourly` : `(datetime_local, source)`
|
||||
|
||||
---
|
||||
|
||||
## 8. Contrôles qualité obligatoires
|
||||
|
||||
Après récupération JSON :
|
||||
- `daily.time` contient au moins `7` jours,
|
||||
- longueurs cohérentes des tableaux `hourly`,
|
||||
- pour chaque jour : `tmin_c <= tmax_c`,
|
||||
- présence de `timezone == Europe/Paris`.
|
||||
|
||||
En cas d’échec :
|
||||
- logger erreur explicite,
|
||||
- conserver le JSON brut,
|
||||
- sortie non fatale avec code d’erreur contrôlé.
|
||||
|
||||
---
|
||||
|
||||
## 9. Champs attendus pour l’UI
|
||||
|
||||
### Vue calendrier (jour)
|
||||
- `date`
|
||||
- `tmin_c`, `tmax_c`
|
||||
- `precip_mm`
|
||||
- `precip_prob_max_pct`
|
||||
- `weather_code`
|
||||
- `sunrise_local`, `sunset_local`
|
||||
- badges calculés :
|
||||
- `gel` si `tmin_c <= 0`
|
||||
- `pluie` si `precip_mm > 0`
|
||||
- `vent_fort` si `wind_max_kmh >= wind_strong_kmh`
|
||||
|
||||
### Vue détail (horaire)
|
||||
- `datetime_local`
|
||||
- `temp_c`
|
||||
- `precip_prob_pct`
|
||||
- `precip_mm`
|
||||
- `wind_kmh`, `wind_dir_deg`
|
||||
- `humidity_pct`
|
||||
- `cloud_pct`
|
||||
- `weather_code`
|
||||
|
||||
---
|
||||
|
||||
## 10. Livrables
|
||||
|
||||
- `prevision meteo/scripts/fetch_openmeteo_forecast.py`
|
||||
- `prevision meteo/scripts/fetch_openmeteo_archive.py` (optionnel)
|
||||
- `prevision meteo/db/schema.sql`
|
||||
- `prevision meteo/config.yml.example`
|
||||
|
||||
---
|
||||
|
||||
## 11. Exécution planifiée
|
||||
|
||||
- Prévisions : tous les jours à `06:10` Europe/Paris.
|
||||
- Archive (optionnel) : tous les jours à `06:20` pour `J-1`.
|
||||
|
||||
Exemple cron :
|
||||
|
||||
```cron
|
||||
10 6 * * * /usr/bin/python3 /path/prevision\ meteo/scripts/fetch_openmeteo_forecast.py
|
||||
20 6 * * * /usr/bin/python3 /path/prevision\ meteo/scripts/fetch_openmeteo_archive.py --yesterday
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 12. Critères d’acceptation
|
||||
|
||||
- Une exécution quotidienne met à jour `forecast` sans duplication.
|
||||
- Le calendrier UI affiche au minimum : Tmin/Tmax, pluie, probabilité pluie, weather code.
|
||||
- Le détail horaire est consultable pour un jour donné.
|
||||
- Les dates/heures sont correctes en `Europe/Paris`.
|
||||
- Les caches JSON sont présents et exploitables pour debug.
|
||||
|
||||
|
||||
|
||||
https://api.open-meteo.com/v1/forecast?latitude=45.1412&longitude=4.0736&hourly=temperature_2m,weather_code,cloud_cover,evapotranspiration,precipitation,precipitation_probability,rain,snowfall,wind_speed_10m,relative_humidity_2m,wind_direction_10m,soil_temperature_0cm,soil_temperature_6cm,soil_moisture_1_to_3cm,soil_moisture_3_to_9cm,sunshine_duration&forecast_days=14
|
||||
@@ -0,0 +1,301 @@
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import json
|
||||
from collections import defaultdict
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
from urllib.parse import urlencode
|
||||
|
||||
import openmeteo_requests
|
||||
import requests_cache
|
||||
from retry_requests import retry
|
||||
|
||||
WMO_LABELS = {
|
||||
0: "Clair", 1: "Plutôt clair", 2: "Partiellement nuageux", 3: "Couvert",
|
||||
45: "Brouillard", 48: "Brouillard givrant", 51: "Bruine légère", 53: "Bruine modérée",
|
||||
55: "Bruine dense", 61: "Pluie légère", 63: "Pluie modérée", 65: "Pluie forte",
|
||||
71: "Neige légère", 73: "Neige modérée", 75: "Neige forte", 80: "Averses légères",
|
||||
81: "Averses modérées", 82: "Averses fortes", 95: "Orage",
|
||||
}
|
||||
|
||||
HOURLY_FIELDS = [
|
||||
"temperature_2m", "weather_code", "cloud_cover", "evapotranspiration", "precipitation",
|
||||
"precipitation_probability", "rain", "snowfall", "wind_speed_10m", "relative_humidity_2m",
|
||||
"wind_direction_10m", "soil_temperature_0cm", "soil_temperature_6cm",
|
||||
"soil_moisture_1_to_3cm", "soil_moisture_3_to_9cm", "sunshine_duration", "freezing_level_height",
|
||||
]
|
||||
|
||||
CURRENT_FIELDS = [
|
||||
"temperature_2m", "is_day", "snowfall", "showers", "rain", "precipitation",
|
||||
"weather_code", "cloud_cover", "relative_humidity_2m", "apparent_temperature",
|
||||
]
|
||||
|
||||
|
||||
def build_url(lat: float, lon: float, days: int, past_days: int, tz: str) -> str:
|
||||
params = {
|
||||
"latitude": lat,
|
||||
"longitude": lon,
|
||||
"hourly": ",".join(HOURLY_FIELDS),
|
||||
"current": ",".join(CURRENT_FIELDS),
|
||||
"past_days": past_days,
|
||||
"forecast_days": days,
|
||||
"timezone": tz,
|
||||
}
|
||||
return f"https://api.open-meteo.com/v1/forecast?{urlencode(params)}"
|
||||
|
||||
|
||||
def _unit_of(variable) -> str:
|
||||
unit_fn = getattr(variable, "Unit", None)
|
||||
if callable(unit_fn):
|
||||
return unit_fn() or ""
|
||||
return ""
|
||||
|
||||
|
||||
def _to_json_safe(value):
|
||||
if isinstance(value, bytes):
|
||||
return value.decode("utf-8", errors="replace")
|
||||
return value
|
||||
|
||||
|
||||
def _iso_times(start_s: int, end_s: int, interval_s: int, utc_offset_s: int) -> list[str]:
|
||||
out: list[str] = []
|
||||
current = start_s
|
||||
while current < end_s:
|
||||
local_epoch = current + utc_offset_s
|
||||
dt = datetime.fromtimestamp(local_epoch, tz=timezone.utc)
|
||||
out.append(dt.strftime("%Y-%m-%dT%H:%M"))
|
||||
current += interval_s
|
||||
return out
|
||||
|
||||
|
||||
def fetch_data(lat: float, lon: float, days: int, past_days: int, tz: str) -> dict:
|
||||
cache_session = requests_cache.CachedSession(".cache", expire_after=3600)
|
||||
retry_session = retry(cache_session, retries=5, backoff_factor=0.2)
|
||||
client = openmeteo_requests.Client(session=retry_session)
|
||||
|
||||
params = {
|
||||
"latitude": lat,
|
||||
"longitude": lon,
|
||||
"hourly": HOURLY_FIELDS,
|
||||
"current": CURRENT_FIELDS,
|
||||
"past_days": past_days,
|
||||
"timezone": tz,
|
||||
"forecast_days": days,
|
||||
}
|
||||
responses = client.weather_api("https://api.open-meteo.com/v1/forecast", params=params)
|
||||
response = responses[0]
|
||||
|
||||
hourly = response.Hourly()
|
||||
hourly_time = _iso_times(
|
||||
hourly.Time(),
|
||||
hourly.TimeEnd(),
|
||||
hourly.Interval(),
|
||||
response.UtcOffsetSeconds(),
|
||||
)
|
||||
hourly_payload: dict[str, list] = {"time": hourly_time}
|
||||
hourly_units: dict[str, str] = {}
|
||||
for idx, field in enumerate(HOURLY_FIELDS):
|
||||
variable = hourly.Variables(idx)
|
||||
hourly_payload[field] = variable.ValuesAsNumpy().tolist()
|
||||
hourly_units[field] = _unit_of(variable)
|
||||
|
||||
current = response.Current()
|
||||
current_payload: dict[str, float | int | None] = {}
|
||||
current_units: dict[str, str] = {}
|
||||
for idx, field in enumerate(CURRENT_FIELDS):
|
||||
variable = current.Variables(idx)
|
||||
current_payload[field] = variable.Value()
|
||||
current_units[field] = _unit_of(variable)
|
||||
|
||||
current_payload["time"] = datetime.fromtimestamp(
|
||||
current.Time() + response.UtcOffsetSeconds(),
|
||||
tz=timezone.utc,
|
||||
).strftime("%Y-%m-%dT%H:%M")
|
||||
|
||||
metadata = {
|
||||
"latitude": response.Latitude(),
|
||||
"longitude": response.Longitude(),
|
||||
"elevation": response.Elevation(),
|
||||
"timezone": _to_json_safe(response.Timezone()),
|
||||
"timezone_abbr": _to_json_safe(response.TimezoneAbbreviation()),
|
||||
"utc_offset_seconds": response.UtcOffsetSeconds(),
|
||||
}
|
||||
|
||||
return {
|
||||
"hourly": hourly_payload,
|
||||
"hourly_units": hourly_units,
|
||||
"current": current_payload,
|
||||
"current_units": current_units,
|
||||
"metadata": metadata,
|
||||
}
|
||||
|
||||
|
||||
def _clean(vals):
|
||||
return [v for v in vals if v is not None]
|
||||
|
||||
|
||||
def _avg(vals, ndigits=1):
|
||||
c = _clean(vals)
|
||||
return round(sum(c) / len(c), ndigits) if c else None
|
||||
|
||||
|
||||
def _sum(vals, ndigits=1):
|
||||
c = _clean(vals)
|
||||
return round(sum(c), ndigits) if c else 0.0
|
||||
|
||||
|
||||
def summarize(data: dict) -> list[dict]:
|
||||
h = data["hourly"]
|
||||
days = defaultdict(lambda: defaultdict(list))
|
||||
|
||||
for i, iso in enumerate(h["time"]):
|
||||
day = iso.split("T", 1)[0]
|
||||
for k in HOURLY_FIELDS:
|
||||
days[day][k].append(h[k][i])
|
||||
|
||||
out = []
|
||||
for day in sorted(days.keys()):
|
||||
d = days[day]
|
||||
codes = d["weather_code"]
|
||||
dom_code = max(set(codes), key=codes.count)
|
||||
tvals = _clean(d["temperature_2m"])
|
||||
out.append({
|
||||
"date": day,
|
||||
"t_min": min(tvals) if tvals else None,
|
||||
"t_max": max(tvals) if tvals else None,
|
||||
"pluie_total_mm": _sum(d["precipitation"], 1),
|
||||
"pluie_mm": _sum(d["rain"], 1),
|
||||
"neige_cm": _sum(d["snowfall"], 1),
|
||||
"proba_pluie_max": max(_clean(d["precipitation_probability"])) if _clean(d["precipitation_probability"]) else None,
|
||||
"humidite_moy": _avg(d["relative_humidity_2m"], 0),
|
||||
"vent_moy_kmh": _avg(d["wind_speed_10m"], 1),
|
||||
"vent_dir_moy_deg": _avg(d["wind_direction_10m"], 0),
|
||||
"nuages_moy": _avg(d["cloud_cover"], 0),
|
||||
"sol_0cm_moy": _avg(d["soil_temperature_0cm"], 1),
|
||||
"sol_6cm_moy": _avg(d["soil_temperature_6cm"], 1),
|
||||
"hum_sol_1_3_moy": _avg(d["soil_moisture_1_to_3cm"], 3),
|
||||
"hum_sol_3_9_moy": _avg(d["soil_moisture_3_to_9cm"], 3),
|
||||
"ensoleillement_h": round((_sum(d["sunshine_duration"], 0)) / 3600, 1),
|
||||
"isotherme_0m_moy": _avg(d["freezing_level_height"], 0),
|
||||
"etp_mm": _sum(d["evapotranspiration"], 2),
|
||||
"wmo": dom_code,
|
||||
"meteo": WMO_LABELS.get(dom_code, f"Code {dom_code}"),
|
||||
})
|
||||
return out
|
||||
|
||||
|
||||
def _fmt(v, spec):
|
||||
if v is None:
|
||||
return " n/a"
|
||||
return format(v, spec)
|
||||
|
||||
|
||||
def print_table(rows: list[dict], units: dict[str, str]) -> None:
|
||||
t_unit = units.get("temperature_2m", "°C")
|
||||
precip_unit = units.get("precipitation", "mm")
|
||||
rain_unit = units.get("rain", "mm")
|
||||
snow_unit = units.get("snowfall", "cm")
|
||||
pprob_unit = units.get("precipitation_probability", "%")
|
||||
wind_unit = units.get("wind_speed_10m", "km/h")
|
||||
wdir_unit = units.get("wind_direction_10m", "°")
|
||||
hum_unit = units.get("relative_humidity_2m", "%")
|
||||
cloud_unit = units.get("cloud_cover", "%")
|
||||
soil0_unit = units.get("soil_temperature_0cm", "°C")
|
||||
soil6_unit = units.get("soil_temperature_6cm", "°C")
|
||||
soilm13_unit = units.get("soil_moisture_1_to_3cm", "m³/m³")
|
||||
soilm39_unit = units.get("soil_moisture_3_to_9cm", "m³/m³")
|
||||
etp_unit = units.get("evapotranspiration", "mm")
|
||||
freeze_unit = units.get("freezing_level_height", "m")
|
||||
|
||||
print(
|
||||
f"{'Date':<10} | {'Tmin':>6} | {'Tmax':>6} | {'Precip':>7} | {'Rain':>6} | {'Snow':>6} | "
|
||||
f"{'Pmax':>5} | {'Hum':>5} | {'Wind':>6} | {'Dir':>4} | {'Cloud':>5} | "
|
||||
f"{'Soil0':>6} | {'Soil6':>6} | {'SM1-3':>7} | {'SM3-9':>7} | {'Sun(h)':>6} | "
|
||||
f"{'ETP':>6} | {'Iso0':>6} | Meteo"
|
||||
)
|
||||
print(
|
||||
f"{'':<10} | {t_unit:>6} | {t_unit:>6} | {precip_unit:>7} | {rain_unit:>6} | {snow_unit:>6} | "
|
||||
f"{pprob_unit:>5} | {hum_unit:>5} | {wind_unit:>6} | {wdir_unit:>4} | {cloud_unit:>5} | "
|
||||
f"{soil0_unit:>6} | {soil6_unit:>6} | {soilm13_unit:>7} | {soilm39_unit:>7} | {'h':>6} | "
|
||||
f"{etp_unit:>6} | {freeze_unit:>6} |"
|
||||
)
|
||||
print("-" * 245)
|
||||
for r in rows:
|
||||
print(
|
||||
f"{r['date']:<10} | {_fmt(r['t_min'], '>6.1f')} | {_fmt(r['t_max'], '>6.1f')} | {_fmt(r['pluie_total_mm'], '>7.1f')} | "
|
||||
f"{_fmt(r['pluie_mm'], '>6.1f')} | {_fmt(r['neige_cm'], '>6.1f')} | {_fmt(r['proba_pluie_max'], '>5.0f')} | "
|
||||
f"{_fmt(r['humidite_moy'], '>5.0f')} | {_fmt(r['vent_moy_kmh'], '>6.1f')} | {_fmt(r['vent_dir_moy_deg'], '>4.0f')} | "
|
||||
f"{_fmt(r['nuages_moy'], '>5.0f')} | {_fmt(r['sol_0cm_moy'], '>6.1f')} | {_fmt(r['sol_6cm_moy'], '>6.1f')} | "
|
||||
f"{_fmt(r['hum_sol_1_3_moy'], '>7.3f')} | {_fmt(r['hum_sol_3_9_moy'], '>7.3f')} | {_fmt(r['ensoleillement_h'], '>6.1f')} | "
|
||||
f"{_fmt(r['etp_mm'], '>6.2f')} | {_fmt(r['isotherme_0m_moy'], '>6.0f')} | {r['meteo']}"
|
||||
)
|
||||
|
||||
|
||||
def print_current(current: dict, current_units: dict) -> None:
|
||||
print("\nCurrent")
|
||||
print("-------")
|
||||
order = [
|
||||
"time",
|
||||
"temperature_2m",
|
||||
"apparent_temperature",
|
||||
"is_day",
|
||||
"weather_code",
|
||||
"cloud_cover",
|
||||
"relative_humidity_2m",
|
||||
"precipitation",
|
||||
"rain",
|
||||
"showers",
|
||||
"snowfall",
|
||||
]
|
||||
for key in order:
|
||||
if key not in current:
|
||||
continue
|
||||
unit = current_units.get(key, "")
|
||||
suffix = f" {unit}" if unit else ""
|
||||
print(f"{key}: {current[key]}{suffix}")
|
||||
|
||||
|
||||
def main() -> int:
|
||||
ap = argparse.ArgumentParser(description="Résumé jardinier Open-Meteo (journalier)")
|
||||
ap.add_argument("--lat", type=float, default=45.1412)
|
||||
ap.add_argument("--lon", type=float, default=4.0736)
|
||||
ap.add_argument("--days", type=int, default=14)
|
||||
ap.add_argument("--past-days", type=int, default=7)
|
||||
ap.add_argument("--timezone", default="auto")
|
||||
ap.add_argument("--json", action="store_true", help="Sortie JSON")
|
||||
ap.add_argument("--json-out", help="Chemin de fichier JSON de sortie")
|
||||
args = ap.parse_args()
|
||||
|
||||
url = build_url(args.lat, args.lon, args.days, args.past_days, args.timezone)
|
||||
data = fetch_data(args.lat, args.lon, args.days, args.past_days, args.timezone)
|
||||
rows = summarize(data)
|
||||
units = data.get("hourly_units", {})
|
||||
payload = {
|
||||
"url": url,
|
||||
"metadata": data.get("metadata", {}),
|
||||
"units": units,
|
||||
"current_units": data.get("current_units", {}),
|
||||
"current": data.get("current", {}),
|
||||
"days": rows,
|
||||
}
|
||||
|
||||
if args.json_out:
|
||||
out_path = Path(args.json_out)
|
||||
out_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
out_path.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8")
|
||||
print(f"JSON écrit: {out_path}")
|
||||
|
||||
if args.json:
|
||||
print(json.dumps(payload, ensure_ascii=False, indent=2))
|
||||
else:
|
||||
print(f"Source: {url}")
|
||||
print(f"Location: {data.get('metadata', {}).get('latitude')}N {data.get('metadata', {}).get('longitude')}E | Elevation: {data.get('metadata', {}).get('elevation')} m")
|
||||
print(f"Timezone: {data.get('metadata', {}).get('timezone')} ({data.get('metadata', {}).get('timezone_abbr')})")
|
||||
print_current(data.get("current", {}), data.get("current_units", {}))
|
||||
print_table(rows, units)
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,519 @@
|
||||
{
|
||||
"url": "https://api.open-meteo.com/v1/forecast?latitude=45.1412&longitude=4.0736&hourly=temperature_2m%2Cweather_code%2Ccloud_cover%2Cevapotranspiration%2Cprecipitation%2Cprecipitation_probability%2Crain%2Csnowfall%2Cwind_speed_10m%2Crelative_humidity_2m%2Cwind_direction_10m%2Csoil_temperature_0cm%2Csoil_temperature_6cm%2Csoil_moisture_1_to_3cm%2Csoil_moisture_3_to_9cm%2Csunshine_duration%2Cfreezing_level_height¤t=temperature_2m%2Cis_day%2Csnowfall%2Cshowers%2Crain%2Cprecipitation%2Cweather_code%2Ccloud_cover%2Crelative_humidity_2m%2Capparent_temperature&past_days=7&forecast_days=14&timezone=auto",
|
||||
"metadata": {
|
||||
"latitude": 45.13999938964844,
|
||||
"longitude": 4.0799994468688965,
|
||||
"elevation": 891.0,
|
||||
"timezone": "Europe/Paris",
|
||||
"timezone_abbr": "GMT+1",
|
||||
"utc_offset_seconds": 3600
|
||||
},
|
||||
"units": {
|
||||
"temperature_2m": 1,
|
||||
"weather_code": 40,
|
||||
"cloud_cover": 35,
|
||||
"evapotranspiration": 32,
|
||||
"precipitation": 32,
|
||||
"precipitation_probability": 35,
|
||||
"rain": 32,
|
||||
"snowfall": 2,
|
||||
"wind_speed_10m": 24,
|
||||
"relative_humidity_2m": 35,
|
||||
"wind_direction_10m": 5,
|
||||
"soil_temperature_0cm": 1,
|
||||
"soil_temperature_6cm": 1,
|
||||
"soil_moisture_1_to_3cm": 3,
|
||||
"soil_moisture_3_to_9cm": 3,
|
||||
"sunshine_duration": 36,
|
||||
"freezing_level_height": 29
|
||||
},
|
||||
"current_units": {
|
||||
"temperature_2m": 1,
|
||||
"is_day": 6,
|
||||
"snowfall": 2,
|
||||
"showers": 32,
|
||||
"rain": 32,
|
||||
"precipitation": 32,
|
||||
"weather_code": 40,
|
||||
"cloud_cover": 35,
|
||||
"relative_humidity_2m": 35,
|
||||
"apparent_temperature": 1
|
||||
},
|
||||
"current": {
|
||||
"temperature_2m": 0.762499988079071,
|
||||
"is_day": 1.0,
|
||||
"snowfall": 0.0,
|
||||
"showers": 0.0,
|
||||
"rain": 0.0,
|
||||
"precipitation": 0.0,
|
||||
"weather_code": 2.0,
|
||||
"cloud_cover": 52.0,
|
||||
"relative_humidity_2m": 95.0,
|
||||
"apparent_temperature": -1.9485669136047363,
|
||||
"time": "2026-02-22T08:00"
|
||||
},
|
||||
"days": [
|
||||
{
|
||||
"date": "2026-02-15",
|
||||
"t_min": -5.1875,
|
||||
"t_max": 6.162499904632568,
|
||||
"pluie_total_mm": 1.6,
|
||||
"pluie_mm": 1.6,
|
||||
"neige_cm": 0.0,
|
||||
"proba_pluie_max": 95.0,
|
||||
"humidite_moy": 82.0,
|
||||
"vent_moy_kmh": 17.4,
|
||||
"vent_dir_moy_deg": 272.0,
|
||||
"nuages_moy": 91.0,
|
||||
"sol_0cm_moy": 0.5,
|
||||
"sol_6cm_moy": 0.9,
|
||||
"hum_sol_1_3_moy": 0.293,
|
||||
"hum_sol_3_9_moy": 0.291,
|
||||
"ensoleillement_h": 6.7,
|
||||
"isotherme_0m_moy": 1375.0,
|
||||
"etp_mm": 0.0,
|
||||
"wmo": 3.0,
|
||||
"meteo": "Couvert"
|
||||
},
|
||||
{
|
||||
"date": "2026-02-16",
|
||||
"t_min": 2.362499952316284,
|
||||
"t_max": 6.262499809265137,
|
||||
"pluie_total_mm": 16.5,
|
||||
"pluie_mm": 16.3,
|
||||
"neige_cm": 0.0,
|
||||
"proba_pluie_max": 100.0,
|
||||
"humidite_moy": 83.0,
|
||||
"vent_moy_kmh": 17.9,
|
||||
"vent_dir_moy_deg": 262.0,
|
||||
"nuages_moy": 98.0,
|
||||
"sol_0cm_moy": 4.0,
|
||||
"sol_6cm_moy": 3.9,
|
||||
"hum_sol_1_3_moy": 0.32,
|
||||
"hum_sol_3_9_moy": 0.319,
|
||||
"ensoleillement_h": 1.5,
|
||||
"isotherme_0m_moy": 1631.0,
|
||||
"etp_mm": 0.0,
|
||||
"wmo": 3.0,
|
||||
"meteo": "Couvert"
|
||||
},
|
||||
{
|
||||
"date": "2026-02-17",
|
||||
"t_min": 0.612500011920929,
|
||||
"t_max": 6.5625,
|
||||
"pluie_total_mm": 0.3,
|
||||
"pluie_mm": 0.3,
|
||||
"neige_cm": 0.0,
|
||||
"proba_pluie_max": 55.0,
|
||||
"humidite_moy": 74.0,
|
||||
"vent_moy_kmh": 11.3,
|
||||
"vent_dir_moy_deg": 264.0,
|
||||
"nuages_moy": 83.0,
|
||||
"sol_0cm_moy": 2.8,
|
||||
"sol_6cm_moy": 3.0,
|
||||
"hum_sol_1_3_moy": 0.291,
|
||||
"hum_sol_3_9_moy": 0.298,
|
||||
"ensoleillement_h": 7.1,
|
||||
"isotherme_0m_moy": 1234.0,
|
||||
"etp_mm": 0.0,
|
||||
"wmo": 3.0,
|
||||
"meteo": "Couvert"
|
||||
},
|
||||
{
|
||||
"date": "2026-02-18",
|
||||
"t_min": 1.2625000476837158,
|
||||
"t_max": 10.762499809265137,
|
||||
"pluie_total_mm": 2.8,
|
||||
"pluie_mm": 2.8,
|
||||
"neige_cm": 0.0,
|
||||
"proba_pluie_max": 100.0,
|
||||
"humidite_moy": 73.0,
|
||||
"vent_moy_kmh": 18.0,
|
||||
"vent_dir_moy_deg": 210.0,
|
||||
"nuages_moy": 96.0,
|
||||
"sol_0cm_moy": 5.4,
|
||||
"sol_6cm_moy": 4.8,
|
||||
"hum_sol_1_3_moy": 0.276,
|
||||
"hum_sol_3_9_moy": 0.281,
|
||||
"ensoleillement_h": 8.1,
|
||||
"isotherme_0m_moy": 2064.0,
|
||||
"etp_mm": 0.0,
|
||||
"wmo": 3.0,
|
||||
"meteo": "Couvert"
|
||||
},
|
||||
{
|
||||
"date": "2026-02-19",
|
||||
"t_min": 2.2125000953674316,
|
||||
"t_max": 6.0625,
|
||||
"pluie_total_mm": 8.3,
|
||||
"pluie_mm": 7.8,
|
||||
"neige_cm": 0.0,
|
||||
"proba_pluie_max": 95.0,
|
||||
"humidite_moy": 78.0,
|
||||
"vent_moy_kmh": 25.8,
|
||||
"vent_dir_moy_deg": 248.0,
|
||||
"nuages_moy": 95.0,
|
||||
"sol_0cm_moy": 4.0,
|
||||
"sol_6cm_moy": 4.3,
|
||||
"hum_sol_1_3_moy": 0.295,
|
||||
"hum_sol_3_9_moy": 0.291,
|
||||
"ensoleillement_h": 6.5,
|
||||
"isotherme_0m_moy": 1392.0,
|
||||
"etp_mm": 0.0,
|
||||
"wmo": 3.0,
|
||||
"meteo": "Couvert"
|
||||
},
|
||||
{
|
||||
"date": "2026-02-20",
|
||||
"t_min": 1.8624999523162842,
|
||||
"t_max": 5.962499618530273,
|
||||
"pluie_total_mm": 0.2,
|
||||
"pluie_mm": 0.2,
|
||||
"neige_cm": 0.0,
|
||||
"proba_pluie_max": 73.0,
|
||||
"humidite_moy": 80.0,
|
||||
"vent_moy_kmh": 14.4,
|
||||
"vent_dir_moy_deg": 318.0,
|
||||
"nuages_moy": 90.0,
|
||||
"sol_0cm_moy": 3.7,
|
||||
"sol_6cm_moy": 4.0,
|
||||
"hum_sol_1_3_moy": 0.292,
|
||||
"hum_sol_3_9_moy": 0.295,
|
||||
"ensoleillement_h": 5.4,
|
||||
"isotherme_0m_moy": 1236.0,
|
||||
"etp_mm": 0.0,
|
||||
"wmo": 3.0,
|
||||
"meteo": "Couvert"
|
||||
},
|
||||
{
|
||||
"date": "2026-02-21",
|
||||
"t_min": 2.1625001430511475,
|
||||
"t_max": 10.16249942779541,
|
||||
"pluie_total_mm": 0.0,
|
||||
"pluie_mm": 0.0,
|
||||
"neige_cm": 0.0,
|
||||
"proba_pluie_max": 0.0,
|
||||
"humidite_moy": 82.0,
|
||||
"vent_moy_kmh": 5.8,
|
||||
"vent_dir_moy_deg": 258.0,
|
||||
"nuages_moy": 58.0,
|
||||
"sol_0cm_moy": 4.9,
|
||||
"sol_6cm_moy": 4.6,
|
||||
"hum_sol_1_3_moy": 0.271,
|
||||
"hum_sol_3_9_moy": 0.276,
|
||||
"ensoleillement_h": 9.2,
|
||||
"isotherme_0m_moy": 2190.0,
|
||||
"etp_mm": 0.0,
|
||||
"wmo": 2.0,
|
||||
"meteo": "Partiellement nuageux"
|
||||
},
|
||||
{
|
||||
"date": "2026-02-22",
|
||||
"t_min": 0.762499988079071,
|
||||
"t_max": 13.012499809265137,
|
||||
"pluie_total_mm": 0.0,
|
||||
"pluie_mm": 0.0,
|
||||
"neige_cm": 0.0,
|
||||
"proba_pluie_max": 0.0,
|
||||
"humidite_moy": 80.0,
|
||||
"vent_moy_kmh": 4.3,
|
||||
"vent_dir_moy_deg": 235.0,
|
||||
"nuages_moy": 79.0,
|
||||
"sol_0cm_moy": 5.4,
|
||||
"sol_6cm_moy": 5.1,
|
||||
"hum_sol_1_3_moy": 0.261,
|
||||
"hum_sol_3_9_moy": 0.267,
|
||||
"ensoleillement_h": 9.2,
|
||||
"isotherme_0m_moy": 3118.0,
|
||||
"etp_mm": 0.0,
|
||||
"wmo": 3.0,
|
||||
"meteo": "Couvert"
|
||||
},
|
||||
{
|
||||
"date": "2026-02-23",
|
||||
"t_min": 4.362499713897705,
|
||||
"t_max": 11.762499809265137,
|
||||
"pluie_total_mm": 0.0,
|
||||
"pluie_mm": 0.0,
|
||||
"neige_cm": 0.0,
|
||||
"proba_pluie_max": 3.0,
|
||||
"humidite_moy": 75.0,
|
||||
"vent_moy_kmh": 6.4,
|
||||
"vent_dir_moy_deg": 214.0,
|
||||
"nuages_moy": 80.0,
|
||||
"sol_0cm_moy": 6.7,
|
||||
"sol_6cm_moy": 6.2,
|
||||
"hum_sol_1_3_moy": 0.254,
|
||||
"hum_sol_3_9_moy": 0.261,
|
||||
"ensoleillement_h": 7.5,
|
||||
"isotherme_0m_moy": 2597.0,
|
||||
"etp_mm": 0.0,
|
||||
"wmo": 2.0,
|
||||
"meteo": "Partiellement nuageux"
|
||||
},
|
||||
{
|
||||
"date": "2026-02-24",
|
||||
"t_min": 4.001999855041504,
|
||||
"t_max": 14.402000427246094,
|
||||
"pluie_total_mm": 0.0,
|
||||
"pluie_mm": 0.0,
|
||||
"neige_cm": 0.0,
|
||||
"proba_pluie_max": 0.0,
|
||||
"humidite_moy": 86.0,
|
||||
"vent_moy_kmh": 3.1,
|
||||
"vent_dir_moy_deg": 164.0,
|
||||
"nuages_moy": 57.0,
|
||||
"sol_0cm_moy": 7.1,
|
||||
"sol_6cm_moy": 6.6,
|
||||
"hum_sol_1_3_moy": 0.304,
|
||||
"hum_sol_3_9_moy": 0.306,
|
||||
"ensoleillement_h": 9.7,
|
||||
"isotherme_0m_moy": 3117.0,
|
||||
"etp_mm": 0.0,
|
||||
"wmo": 2.0,
|
||||
"meteo": "Partiellement nuageux"
|
||||
},
|
||||
{
|
||||
"date": "2026-02-25",
|
||||
"t_min": 5.202000141143799,
|
||||
"t_max": 14.85200023651123,
|
||||
"pluie_total_mm": 0.0,
|
||||
"pluie_mm": 0.0,
|
||||
"neige_cm": 0.0,
|
||||
"proba_pluie_max": 0.0,
|
||||
"humidite_moy": 67.0,
|
||||
"vent_moy_kmh": 7.6,
|
||||
"vent_dir_moy_deg": 194.0,
|
||||
"nuages_moy": 69.0,
|
||||
"sol_0cm_moy": 7.6,
|
||||
"sol_6cm_moy": 7.2,
|
||||
"hum_sol_1_3_moy": 0.312,
|
||||
"hum_sol_3_9_moy": 0.314,
|
||||
"ensoleillement_h": 10.0,
|
||||
"isotherme_0m_moy": 3335.0,
|
||||
"etp_mm": 0.0,
|
||||
"wmo": 3.0,
|
||||
"meteo": "Couvert"
|
||||
},
|
||||
{
|
||||
"date": "2026-02-26",
|
||||
"t_min": 5.052000045776367,
|
||||
"t_max": 17.00200080871582,
|
||||
"pluie_total_mm": 0.0,
|
||||
"pluie_mm": 0.0,
|
||||
"neige_cm": 0.0,
|
||||
"proba_pluie_max": 2.0,
|
||||
"humidite_moy": 56.0,
|
||||
"vent_moy_kmh": 5.4,
|
||||
"vent_dir_moy_deg": 207.0,
|
||||
"nuages_moy": 15.0,
|
||||
"sol_0cm_moy": 8.3,
|
||||
"sol_6cm_moy": 7.7,
|
||||
"hum_sol_1_3_moy": 0.31,
|
||||
"hum_sol_3_9_moy": 0.312,
|
||||
"ensoleillement_h": 10.1,
|
||||
"isotherme_0m_moy": 2972.0,
|
||||
"etp_mm": 0.0,
|
||||
"wmo": 0.0,
|
||||
"meteo": "Clair"
|
||||
},
|
||||
{
|
||||
"date": "2026-02-27",
|
||||
"t_min": 5.2860002517700195,
|
||||
"t_max": 17.38599967956543,
|
||||
"pluie_total_mm": 0.0,
|
||||
"pluie_mm": 0.0,
|
||||
"neige_cm": 0.0,
|
||||
"proba_pluie_max": 20.0,
|
||||
"humidite_moy": 66.0,
|
||||
"vent_moy_kmh": 7.4,
|
||||
"vent_dir_moy_deg": 171.0,
|
||||
"nuages_moy": 16.0,
|
||||
"sol_0cm_moy": 8.8,
|
||||
"sol_6cm_moy": 8.3,
|
||||
"hum_sol_1_3_moy": 0.294,
|
||||
"hum_sol_3_9_moy": 0.296,
|
||||
"ensoleillement_h": 10.1,
|
||||
"isotherme_0m_moy": 2904.0,
|
||||
"etp_mm": 0.0,
|
||||
"wmo": 0.0,
|
||||
"meteo": "Clair"
|
||||
},
|
||||
{
|
||||
"date": "2026-02-28",
|
||||
"t_min": 4.836000442504883,
|
||||
"t_max": 12.886000633239746,
|
||||
"pluie_total_mm": 0.0,
|
||||
"pluie_mm": 0.0,
|
||||
"neige_cm": 0.0,
|
||||
"proba_pluie_max": 31.0,
|
||||
"humidite_moy": 82.0,
|
||||
"vent_moy_kmh": 5.6,
|
||||
"vent_dir_moy_deg": 207.0,
|
||||
"nuages_moy": 73.0,
|
||||
"sol_0cm_moy": 7.7,
|
||||
"sol_6cm_moy": 7.6,
|
||||
"hum_sol_1_3_moy": 0.293,
|
||||
"hum_sol_3_9_moy": 0.295,
|
||||
"ensoleillement_h": 6.8,
|
||||
"isotherme_0m_moy": 2218.0,
|
||||
"etp_mm": 0.0,
|
||||
"wmo": 2.0,
|
||||
"meteo": "Partiellement nuageux"
|
||||
},
|
||||
{
|
||||
"date": "2026-03-01",
|
||||
"t_min": 6.5360002517700195,
|
||||
"t_max": 11.947500228881836,
|
||||
"pluie_total_mm": 0.0,
|
||||
"pluie_mm": 0.0,
|
||||
"neige_cm": 0.0,
|
||||
"proba_pluie_max": 32.0,
|
||||
"humidite_moy": 72.0,
|
||||
"vent_moy_kmh": 11.7,
|
||||
"vent_dir_moy_deg": 186.0,
|
||||
"nuages_moy": 73.0,
|
||||
"sol_0cm_moy": NaN,
|
||||
"sol_6cm_moy": NaN,
|
||||
"hum_sol_1_3_moy": NaN,
|
||||
"hum_sol_3_9_moy": NaN,
|
||||
"ensoleillement_h": 6.6,
|
||||
"isotherme_0m_moy": 2229.0,
|
||||
"etp_mm": 0.0,
|
||||
"wmo": 3.0,
|
||||
"meteo": "Couvert"
|
||||
},
|
||||
{
|
||||
"date": "2026-03-02",
|
||||
"t_min": 2.8975000381469727,
|
||||
"t_max": 12.697500228881836,
|
||||
"pluie_total_mm": 0.0,
|
||||
"pluie_mm": 0.0,
|
||||
"neige_cm": 0.0,
|
||||
"proba_pluie_max": 19.0,
|
||||
"humidite_moy": 64.0,
|
||||
"vent_moy_kmh": 11.6,
|
||||
"vent_dir_moy_deg": 161.0,
|
||||
"nuages_moy": 80.0,
|
||||
"sol_0cm_moy": NaN,
|
||||
"sol_6cm_moy": NaN,
|
||||
"hum_sol_1_3_moy": NaN,
|
||||
"hum_sol_3_9_moy": NaN,
|
||||
"ensoleillement_h": 10.2,
|
||||
"isotherme_0m_moy": 2206.0,
|
||||
"etp_mm": 0.0,
|
||||
"wmo": 3.0,
|
||||
"meteo": "Couvert"
|
||||
},
|
||||
{
|
||||
"date": "2026-03-03",
|
||||
"t_min": 6.047499656677246,
|
||||
"t_max": 11.697500228881836,
|
||||
"pluie_total_mm": 0.0,
|
||||
"pluie_mm": 0.0,
|
||||
"neige_cm": 0.0,
|
||||
"proba_pluie_max": 29.0,
|
||||
"humidite_moy": 69.0,
|
||||
"vent_moy_kmh": 14.1,
|
||||
"vent_dir_moy_deg": 152.0,
|
||||
"nuages_moy": 99.0,
|
||||
"sol_0cm_moy": NaN,
|
||||
"sol_6cm_moy": NaN,
|
||||
"hum_sol_1_3_moy": NaN,
|
||||
"hum_sol_3_9_moy": NaN,
|
||||
"ensoleillement_h": 9.3,
|
||||
"isotherme_0m_moy": 2141.0,
|
||||
"etp_mm": 0.0,
|
||||
"wmo": 3.0,
|
||||
"meteo": "Couvert"
|
||||
},
|
||||
{
|
||||
"date": "2026-03-04",
|
||||
"t_min": 4.147500038146973,
|
||||
"t_max": 13.297500610351562,
|
||||
"pluie_total_mm": 0.0,
|
||||
"pluie_mm": 0.0,
|
||||
"neige_cm": 0.0,
|
||||
"proba_pluie_max": 29.0,
|
||||
"humidite_moy": 80.0,
|
||||
"vent_moy_kmh": 4.4,
|
||||
"vent_dir_moy_deg": 108.0,
|
||||
"nuages_moy": 74.0,
|
||||
"sol_0cm_moy": NaN,
|
||||
"sol_6cm_moy": NaN,
|
||||
"hum_sol_1_3_moy": NaN,
|
||||
"hum_sol_3_9_moy": NaN,
|
||||
"ensoleillement_h": 9.1,
|
||||
"isotherme_0m_moy": 2200.0,
|
||||
"etp_mm": 0.0,
|
||||
"wmo": 3.0,
|
||||
"meteo": "Couvert"
|
||||
},
|
||||
{
|
||||
"date": "2026-03-05",
|
||||
"t_min": 3.3975000381469727,
|
||||
"t_max": 12.197500228881836,
|
||||
"pluie_total_mm": 0.0,
|
||||
"pluie_mm": 0.0,
|
||||
"neige_cm": 0.0,
|
||||
"proba_pluie_max": 29.0,
|
||||
"humidite_moy": 88.0,
|
||||
"vent_moy_kmh": 4.2,
|
||||
"vent_dir_moy_deg": 138.0,
|
||||
"nuages_moy": 79.0,
|
||||
"sol_0cm_moy": NaN,
|
||||
"sol_6cm_moy": NaN,
|
||||
"hum_sol_1_3_moy": NaN,
|
||||
"hum_sol_3_9_moy": NaN,
|
||||
"ensoleillement_h": 9.6,
|
||||
"isotherme_0m_moy": 2821.0,
|
||||
"etp_mm": 0.0,
|
||||
"wmo": 3.0,
|
||||
"meteo": "Couvert"
|
||||
},
|
||||
{
|
||||
"date": "2026-03-06",
|
||||
"t_min": 4.547499656677246,
|
||||
"t_max": 11.697500228881836,
|
||||
"pluie_total_mm": 0.0,
|
||||
"pluie_mm": 0.0,
|
||||
"neige_cm": 0.0,
|
||||
"proba_pluie_max": 42.0,
|
||||
"humidite_moy": 85.0,
|
||||
"vent_moy_kmh": 3.9,
|
||||
"vent_dir_moy_deg": 196.0,
|
||||
"nuages_moy": 98.0,
|
||||
"sol_0cm_moy": NaN,
|
||||
"sol_6cm_moy": NaN,
|
||||
"hum_sol_1_3_moy": NaN,
|
||||
"hum_sol_3_9_moy": NaN,
|
||||
"ensoleillement_h": 9.4,
|
||||
"isotherme_0m_moy": 2344.0,
|
||||
"etp_mm": 0.0,
|
||||
"wmo": 3.0,
|
||||
"meteo": "Couvert"
|
||||
},
|
||||
{
|
||||
"date": "2026-03-07",
|
||||
"t_min": 0.5475000143051147,
|
||||
"t_max": 11.09749984741211,
|
||||
"pluie_total_mm": 0.0,
|
||||
"pluie_mm": 0.0,
|
||||
"neige_cm": 0.0,
|
||||
"proba_pluie_max": 29.0,
|
||||
"humidite_moy": 83.0,
|
||||
"vent_moy_kmh": 5.2,
|
||||
"vent_dir_moy_deg": 177.0,
|
||||
"nuages_moy": 58.0,
|
||||
"sol_0cm_moy": NaN,
|
||||
"sol_6cm_moy": NaN,
|
||||
"hum_sol_1_3_moy": NaN,
|
||||
"hum_sol_3_9_moy": NaN,
|
||||
"ensoleillement_h": 10.3,
|
||||
"isotherme_0m_moy": 2071.0,
|
||||
"etp_mm": 0.0,
|
||||
"wmo": 1.0,
|
||||
"meteo": "Plutôt clair"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
{
|
||||
"WMO_Weather_Interpretation_Codes": [
|
||||
{
|
||||
"codes": [0],
|
||||
"description_fr": "Ciel clair"
|
||||
},
|
||||
{
|
||||
"codes": [1, 2, 3],
|
||||
"description_fr": "Principalement clair, partiellement nuageux ou couvert"
|
||||
},
|
||||
{
|
||||
"codes": [45, 48],
|
||||
"description_fr": "Brouillard et brouillard givrant"
|
||||
},
|
||||
{
|
||||
"codes": [51, 53, 55],
|
||||
"description_fr": "Bruine : légère, modérée ou dense"
|
||||
},
|
||||
{
|
||||
"codes": [56, 57],
|
||||
"description_fr": "Bruine verglaçante : légère ou dense"
|
||||
},
|
||||
{
|
||||
"codes": [61, 63, 65],
|
||||
"description_fr": "Pluie : légère, modérée ou forte"
|
||||
},
|
||||
{
|
||||
"codes": [66, 67],
|
||||
"description_fr": "Pluie verglaçante : légère ou forte"
|
||||
},
|
||||
{
|
||||
"codes": [71, 73, 75],
|
||||
"description_fr": "Chute de neige : faible, modérée ou forte"
|
||||
},
|
||||
{
|
||||
"codes": [77],
|
||||
"description_fr": "Neige en grains"
|
||||
},
|
||||
{
|
||||
"codes": [80, 81, 82],
|
||||
"description_fr": "Averses de pluie : légère, modérée ou violente"
|
||||
},
|
||||
{
|
||||
"codes": [85, 86],
|
||||
"description_fr": "Averses de neige : légère ou forte"
|
||||
},
|
||||
{
|
||||
"codes": [95],
|
||||
"description_fr": "Orage : léger ou modéré"
|
||||
},
|
||||
{
|
||||
"codes": [96, 99],
|
||||
"description_fr": "Orage avec grêle : légère ou forte"
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user