# Données climatologiques Infoclimat — guide complet (llms-full.txt) > Version agent du guide utilisateur. Spec machine : > https://portail.chom.engineering/openapi.json — index : /llms.txt. > Préfiguration sans SLA sur *.chom.engineering. Guide utilisateur des trois accès aux données : **REST API climato**, **Live API** et **bulk Parquet/Iceberg**. ## Quel accès choisir | Votre besoin | Accès | Auth | |---|---|---| | Une série journalière ou mensuelle sur une station, quelques années | **REST API** `/v2`, `/legacy`, `/dataclimat`, `/ic`, `/statIC` | aucune | | Records, normales, indice thermique national, indicateurs canicule | **REST API** `/dataclimat`, `/v2/itn`, `/canicule` | aucune | | Dernière valeur connue d'une station, canicule en cours | **REST API** `?mode=live` · **Live API** pour le détail par paramètre | aucune / token | | Un dataset entier, des millions de lignes, de l'analytique | **bulk Parquet** via le catalogue Iceberg | credentials S3 | **Par défaut, prenez la REST API.** Elle est ouverte, sans clé, et couvre tous les cas hors analytique lourde. Au-delà de 20 000 lignes par requête, elle pagine (`suite.from`, cf. §2) ; le bulk ne se justifie que quand cette pagination devient déraisonnable — un dataset entier, des millions de lignes. Le portail expose la même API sous `/api/*` (même service, même origine) : `https://portail.chom.engineering/api/v2/journaliere?…` équivaut à `https://climato.chom.engineering/v2/journaliere?…`. ## Quickstart ```bash B=https://climato.chom.engineering # 1. trouver l'identifiant d'une station curl -s "$B/stations?q=toulouse" # 2. récupérer une série journalière curl -s "$B/v2/journaliere?station=07486&from=2025-07-01&to=2025-08-01" # 3. vérifier la fraîcheur du fonds curl -s "$B/coverage" ``` --- ## 1. Identifier une station **Trois** identifiants coexistent, et un seul fait foi. | Identifiant | Exemple | Ce que c'est | |---|---|---| | **`ic_id`** | `07486`, `FMEE`, `00006`, `STATIC0253` | canonique, fait foi. Hétérogène par conception : OACI pour les metar, OMM à 5 chiffres pour les synop, **aucune forme unique** pour StatIC (`00006`, `STATIC0253`, `MNWERO090`) — ne présumez d'aucun format | | `mfid` | `38384001` | NUM_POSTE Météo-France — **le même identifiant**, pas un second système. Absent hors périmètre MF | | `station_uid` | `648f5600-…` | `uuid5` dérivé de `ic_id` : pas plus stable que lui, change s'il change | `/stations` et `/ref/stations` renvoient le **même objet station** (mêmes clés). Le référentiel écarte par défaut les stations sans aucune observation ; `publiable=0` les rouvre. `/stations` rend au plus 20 résultats. Ordre contractuel : correspondance exacte DESC, libelle ASC, ic_id ASC, station_uid ASC. ```bash # recherche par nom OU par n'importe lequel des trois identifiants curl -s "$B/stations?q=toulouse" # référentiel GeoJSON, filtrable curl -s "$B/ref/stations?parametre=air_temperature&actif_depuis=2026-01-01" # quels paramètres une station mesure, et sur quelle profondeur temporelle curl -s "$B/ref/station-parametre?station=07486" ``` Ne présumez pas d'une profondeur homogène : certaines séries remontent à 1777, d'autres à 2015. `/ref/station-parametre` fait foi. --- ## 2. REST API climato Base : `https://climato.chom.engineering`. Ouverte, sans token, JSON UTF-8. Forme générale : `//?station=&from=YYYY-MM-DD&to=YYYY-MM-DD`, avec `from` inclus et `to` exclu. ### Les 7 familles | Famille | Contenu | Identifiant | Licence | |---|---|---|---| | `/v2/*` | **le choix par défaut** — climatologie recalculée depuis le socle canonique, schéma propre, contrôle qualité | `ic_id` / `mfid` | LO 2.0 + valeur ajoutée Infoclimat | | `/legacy/*` | même périmètre en iso-comportement avec le site actuel, colonnes Météo-France d'origine (`NUM_POSTE`, `AAAAMM`, `RR`, `QRR`…) | `mfid` (= NUM_POSTE) | LO 2.0 — Météo-France / Infoclimat | | `/dataclimat/*` | Météo-France seul : records absolus et battus, normales 1991-2020 | `mfid` (= NUM_POSTE) | LO 2.0 — Météo-France | | `/ic/*` | collecte Infoclimat hors flux Météo-France (synop, metar, bouées relayés, StatIC, Météo-à-l'École) | `ic_id` / `mfid` | opendata Infoclimat | | `/statIC/*` | réseau participatif StatIC seul, avec métriques de qualité (`n_obs`, `taux_qc_valide`) | `ic_id` | LO 2.0 (opendata Infoclimat) | | `/ref/*` | référentiel stations et couples station × paramètre | — | opendata Infoclimat | | `/canicule/*` | indicateurs d'été extrême par territoire | code INSEE / SIREN | LO 2.0 + valeur ajoutée Infoclimat | Prenez `/v2` sauf si vous migrez un code existant (`/legacy`), ou si vous avez besoin du périmètre Météo-France strict (`/dataclimat`). ### Datasets par famille #### legacy - `GET /legacy/mensuelle` — Le résumé de chaque mois par station — moyenne et extrêmes (jour le plus froid / le plus chaud) : pour des bilans mensuels, au format Météo-France d'origine. Ordre contractuel : AAAAMM ASC, NUM_POSTE ASC. Colonnes : NUM_POSTE (text), AAAAMM (timestamptz) - `GET /legacy/decadaire` — Cumuls et moyennes par décade (période de 10 jours) par station, au format Météo-France d'origine — utile pour l'agronomie et le suivi des cultures. Ordre contractuel : AAAAMM ASC, NUM_DECADE ASC, NUM_POSTE ASC. Colonnes : NUM_POSTE (text), AAAAMM (timestamptz), NUM_DECADE (integer — 3e composante de la clé naturelle (1/2/3, 3 décades par mois)) - `GET /legacy/decadaire_agro` — Cumuls et moyennes agrométéo par décade (période de 10 jours) par station, au format Météo-France d'origine — pensé pour le suivi des cultures. Ordre contractuel : AAAAMM ASC, NUM_DECADE ASC, NUM_POSTE ASC. Colonnes : NUM_POSTE (text), AAAAMM (timestamptz), NUM_DECADE (integer — 3e composante de la clé naturelle (1/2/3, 3 décades par mois)) - `GET /legacy/quotidienne` — Températures et précipitations journalières par station, au format Météo-France d'origine : pour tracer des courbes de température ou alimenter un tableau de bord compatible avec les outils existants. Ordre contractuel : AAAAMMJJ ASC, NUM_POSTE ASC. Colonnes : NUM_POSTE (text), AAAAMMJJ (timestamptz) - `GET /legacy/quotidienne_autres` — Les autres paramètres journaliers (pression, humidité, ensoleillement, vent, neige…) au-delà de la température et de la pluie, au format Météo-France d'origine. Ordre contractuel : AAAAMMJJ ASC, NUM_POSTE ASC. Colonnes : NUM_POSTE (text), AAAAMMJJ (timestamptz) #### v2 - `GET /v2/journaliere` — Températures journalières (min, max, moyenne) et cumul de précipitation par station, en °C et mm : pour tracer des courbes de température ou alimenter un tableau de bord. Recalculé avec un contrôle qualité qui écarte les valeurs aberrantes. Ordre contractuel : date ASC, ic_id ASC, mfid ASC. Colonnes : ic_id (text — Identifiant station Infoclimat (crosswalk dim_station)), mfid (text — NUM_POSTE Météo-France (crosswalk dim_station)), date (date), tn (double — Température min du jour (°C)), tx (double — Température max du jour (°C)), tm (double — (tn+tx)/2 (°C)), tntxm (double — alias de tm (°C)), rr (double — Cumul de précipitation du jour (mm)), n_obs (bigint — Nombre d'observations de température agrégées ce jour-là — toutes celles du jour, plausibles ou non, rejets du contrôle qualité compris. C'est la profondeur de preuve du tn/tx : un extrême calculé sur 24 observations et un extrême calculé sur 2 ne se valent pas, et l'écart est massif selon l'époque (médiane de 4 observations par jour en 1960, de 24 en 2025). Attention : n_obs - n_obs_rejetees est un MAJORANT du nombre d'observations effectivement retenues, pas ce nombre — une valeur hors bornes physiques n'est comptée dans aucun des deux et n'est pas retenue. Même définition que sur gold_statIC.journaliere.), n_obs_rejetees (bigint — Nombre d'observations de température écartées ce jour-là par le contrôle qualité des pics isolés — une valeur isolée au milieu de voisins normaux. Vaut 0 dans l'écrasante majorité des cas. La liste nominative des observations écartées est publiée sur /qc/pic_temperature.), statut_extremes (string — CALCULÉ PAR LE SERVING, pas stocké — ce que valent tn/tx compte tenu de la densité d'observation du jour. `tn`/`tx` sont le min/max des observations DU JOUR, pas les extrêmes DE LA JOURNÉE : quand la journée est mal échantillonnée, `tn` MAJORE le vrai minimum quotidien et `tx` MINORE le vrai maximum. · `quotidien` : au moins 8 observations retenues (`n_obs - n_obs_rejetees`) — le déficit médian d'amplitude tombe à 0,4 K et ne s'améliore plus au-delà. · `partiel` : 2 à 7 — amplitude amputée de 5,3 K (2 obs) à 0,6 K (7 obs) en médiane. · `releve_unique` : 1 seule — tn = tx = ce relevé, mesuré sur 100 % des 9 088 jours sondés ; ce n'est pas un extrême. · null : aucune observation retenue, rien à qualifier. Le compte est un MAJORANT (les valeurs hors bornes physiques ne sont dans aucun compteur) et il ignore la RÉPARTITION dans la journée : le statut oriente la lecture, il ne certifie pas l'extrême. Aucune valeur n'est filtrée ni nullifiée sur ce critère (issue #85).) - `GET /v2/mensuelle` — Le résumé de chaque mois par station — moyennes, extrêmes datés (jour le plus froid / le plus chaud), nombre de jours de gel : pour des bilans mensuels. Ordre contractuel : mois ASC, ic_id ASC, mfid ASC. Colonnes : ic_id (text), mfid (text), mois (timestamp — Premier jour du mois (date_trunc('month', date))), tnm (double — Moyenne mensuelle des tn (°C)), txm (double — Moyenne mensuelle des tx (°C)), tmm (double — Moyenne mensuelle des tm (°C)), rr (double — Cumul de précipitation du mois (mm)), tnn (double — Température min du mois (°C)), tnn_date (date), txx (double — Température max du mois (°C)), txx_date (date), nb_jours_gel (integer — Nombre de jours du mois avec tn < 0 °C), n_obs (bigint — Somme des n_obs journaliers du mois : nombre total d'observations de température agrégées, toutes celles du mois, plausibles ou non. Sert de poids pour comparer des mois dont la densité de mesure diffère. Même définition que sur gold_statIC.mensuelle.) - `GET /v2/records` — Les records de température (chaud/froid) de chaque station, par mois calendaire sur tout l'historique : pour afficher « record battu » et les extrêmes historiques. Ordre contractuel : mois_calendaire ASC, ic_id ASC, mfid ASC. Colonnes : ic_id (text), mfid (text), mois_calendaire (integer — Mois calendaire (1-12), 3e composante de la clé naturelle), txx_max (double — Record absolu de température max sur le mois calendaire (°C)), txx_max_date (date), txx_max_suivant (double — 2e valeur DISTINCTE la plus haute de tx sur le mois calendaire (°C) — suivant en VALEUR, pas dans le temps. Un écart important avec txx_max signale un extrême isolé du reste de la série. Vide si le mois calendaire ne porte qu'une seule valeur distincte.), tnn_min (double — Record absolu de température min sur le mois calendaire (°C)), tnn_min_date (date), tnn_min_suivant (double — 2e valeur DISTINCTE la plus basse de tn sur le mois calendaire (°C) — suivant en VALEUR, pas dans le temps. Un écart important avec tnn_min signale un extrême isolé du reste de la série. Vide si le mois calendaire ne porte qu'une seule valeur distincte.), n_jours_tx (bigint — Nombre de jours où tx est renseigné sur ce mois calendaire, tous millésimes confondus : le socle sur lequel txx_max est calculé.), n_jours_tn (bigint — Nombre de jours où tn est renseigné sur ce mois calendaire, tous millésimes confondus : le socle sur lequel tnn_min est calculé. Distinct de n_jours_tx — tn et tx ne manquent pas les mêmes jours.), premiere_obs (date — Premier jour mesuré de ce mois calendaire (tn ou tx renseigné)), derniere_obs (date — Dernier jour mesuré de ce mois calendaire (tn ou tx renseigné)), txx_max_n_obs (bigint — Nombre d'observations de température du jour où txx_max est atteint. Avec txx_max_n_obs_rejetees, permet de qualifier la densité du jour du record : au moins 8 observations retenues (n_obs - n_obs_rejetees) correspondent à un extrême quotidien.), tnn_min_n_obs (bigint — Nombre d'observations de température du jour où tnn_min est atteint. Avec tnn_min_n_obs_rejetees, permet de qualifier la densité du jour du record : au moins 8 observations retenues (n_obs - n_obs_rejetees) correspondent à un extrême quotidien.), txx_max_n_obs_rejetees (bigint — Observations du jour où txx_max est atteint, écartées par le contrôle qualité des pics de température. À soustraire de txx_max_n_obs pour lire la densité retenue du jour du record.), tnn_min_n_obs_rejetees (bigint — Observations du jour où tnn_min est atteint, écartées par le contrôle qualité des pics de température. À soustraire de tnn_min_n_obs pour lire la densité retenue du jour du record.), tnn_min_statut (string — CALCULÉ PAR LE SERVING, pas stocké — ce que valent tn/tx compte tenu de la densité d'observation du jour. `tn`/`tx` sont le min/max des observations DU JOUR, pas les extrêmes DE LA JOURNÉE : quand la journée est mal échantillonnée, `tn` MAJORE le vrai minimum quotidien et `tx` MINORE le vrai maximum. · `quotidien` : au moins 8 observations retenues (`n_obs - n_obs_rejetees`) — le déficit médian d'amplitude tombe à 0,4 K et ne s'améliore plus au-delà. · `partiel` : 2 à 7 — amplitude amputée de 5,3 K (2 obs) à 0,6 K (7 obs) en médiane. · `releve_unique` : 1 seule — tn = tx = ce relevé, mesuré sur 100 % des 9 088 jours sondés ; ce n'est pas un extrême. · null : aucune observation retenue, rien à qualifier. Le compte est un MAJORANT (les valeurs hors bornes physiques ne sont dans aucun compteur) et il ignore la RÉPARTITION dans la journée : le statut oriente la lecture, il ne certifie pas l'extrême. Aucune valeur n'est filtrée ni nullifiée sur ce critère (issue #85). Pour `tnn_min`, ce statut porte sur le jour du record, pas sur l'ensemble du mois calendaire.), txx_max_statut (string — CALCULÉ PAR LE SERVING, pas stocké — ce que valent tn/tx compte tenu de la densité d'observation du jour. `tn`/`tx` sont le min/max des observations DU JOUR, pas les extrêmes DE LA JOURNÉE : quand la journée est mal échantillonnée, `tn` MAJORE le vrai minimum quotidien et `tx` MINORE le vrai maximum. · `quotidien` : au moins 8 observations retenues (`n_obs - n_obs_rejetees`) — le déficit médian d'amplitude tombe à 0,4 K et ne s'améliore plus au-delà. · `partiel` : 2 à 7 — amplitude amputée de 5,3 K (2 obs) à 0,6 K (7 obs) en médiane. · `releve_unique` : 1 seule — tn = tx = ce relevé, mesuré sur 100 % des 9 088 jours sondés ; ce n'est pas un extrême. · null : aucune observation retenue, rien à qualifier. Le compte est un MAJORANT (les valeurs hors bornes physiques ne sont dans aucun compteur) et il ignore la RÉPARTITION dans la journée : le statut oriente la lecture, il ne certifie pas l'extrême. Aucune valeur n'est filtrée ni nullifiée sur ce critère (issue #85). Pour `txx_max`, ce statut porte sur le jour du record, pas sur l'ensemble du mois calendaire.) - `GET /v2/itn` — L'Indicateur Thermique National — la température moyenne quotidienne de la France (30 stations de référence) : pour suivre vagues de chaleur et de froid à l'échelle du pays. Ordre contractuel : date ASC. Colonnes : date (date), year (integer), month (integer), day_of_month (integer), is_fictive (boolean — 29/02 synthétique des années non-bissextiles (toujours FALSE dans ce build)), itn (double — Indicateur Thermique National du jour (°C)) #### dataclimat - `GET /dataclimat/quotidienne` — Températures journalières (min, max, moyenne) par station, valeurs officielles Météo-France, historique depuis 1816 : pour tracer des courbes de température ou alimenter un tableau de bord. Ordre contractuel : date ASC, station_code ASC. Colonnes : station_code (text — NUM_POSTE Météo-France), date (date), tntxm (double — Moyenne (tn+tx)/2 — midrange officiel MF (°C)), tn (double — Température minimale du jour climatologique (°C)), tx (double — Température maximale du jour climatologique (°C)) - `GET /dataclimat/mensuelle` — Le résumé de chaque mois par station — moyenne et extrêmes (jour le plus froid / le plus chaud) : pour des bilans mensuels. Ordre contractuel : date ASC, station_code ASC. Colonnes : station_code (text — NUM_POSTE Météo-France), date (date — Mois (premier jour du mois)), tnn (double — Tn minimale du mois (°C)), tnn_date (date — Jour du Tn minimal (tie-break date la plus ancienne)), txx (double — Tx maximale du mois (°C)), txx_date (date — Jour du Tx maximal), tmm (double — Moyenne mensuelle de tntxm, arrondie à 0,1 °C) - `GET /dataclimat/records_absolus` — Les records de température (chaud/froid) de chaque station, toute l'année / par mois / par saison : pour afficher « record battu » et les extrêmes historiques. Ordre contractuel : station_code ASC, period_type ASC, period_value ASC, record_type ASC. Colonnes : period_type (text — all_time | month | season), period_value (text — NULL (all_time) | '1'..'12' (mois) | spring|summer|autumn|winter), record_type (text — TX (chaud) | TN (froid)), station_code (text — NUM_POSTE Météo-France), station_name (text), department (text), record_value (double — Valeur du record (°C)), record_date (date — Date du record (tie-break date la plus ancienne)), lat (double), lon (double), alt (double), classe_recente (integer — Classe d'homogénéité MF de la station (1 = meilleure … 5), valeur courante (station_classe.date_fin IS NULL). Le dataset ne retient que classe_recente <= 3 (iso-comportement D4G v_station_records).) - `GET /dataclimat/records_battus` — L'HISTORIQUE des records progressivement battus par station : une ligne par date où la station a établi un nouvel extrême (chaud/froid), toute l'année / par mois / par saison. Distinct de records_absolus (extrême figé) — sert la timeline « records battus » de dataclimat.fr (API /records, alias /records/historical). Ordre contractuel : station_code ASC, period_type ASC, period_value ASC, record_type ASC, record_date ASC. Colonnes : period_type (text — all_time | month | season), period_value (text — NULL (all_time) | '1'..'12' (mois) | spring|summer|autumn|winter), record_type (text — TX (chaud) | TN (froid)), station_code (text — NUM_POSTE Météo-France), station_name (text), department (text), record_value (double — Valeur du record établi ce jour-là (°C)), record_date (date — Date à laquelle ce nouvel extrême a été atteint), lat (double), lon (double), alt (double), classe_recente (integer — Classe d'homogénéité MF (1 = meilleure … 5), valeur courante. Filtre classe_recente <= 3 (iso-comportement D4G v_records_battus).) - `GET /dataclimat/itn` — L'Indicateur Thermique National — la température moyenne quotidienne de la France (30 stations de référence) : pour suivre vagues de chaleur et de froid à l'échelle du pays. Ordre contractuel : date ASC. Colonnes : date (date), year (integer), month (integer), day_of_month (integer), is_fictive (boolean — TRUE si 29 février synthétique (année non bissextile)), itn (double — Indicateur Thermique National du jour (°C)) - `GET /dataclimat/itn_baseline` — La normale 1991-2020 de l'ITN, jour par jour : la référence pour dire si une journée est au-dessus ou en dessous de la normale nationale. Ordre contractuel : month ASC, day_of_month ASC. Colonnes : month (integer), day_of_month (integer), sample_size (integer), itn_mean (double), itn_stddev (double — Écart-type de population (STDDEV_POP)), itn_p20 (double), itn_p80 (double) - `GET /dataclimat/baseline_1991_2020` — La normale de température 1991-2020 de chaque station, jour par jour de l'année : pour calculer l'écart à la normale. Ordre contractuel : station_code ASC, month ASC, day ASC. Colonnes : station_code (text — NUM_POSTE Météo-France), month (integer), day (integer), sample_count (integer — Nombre d'années contributrices (>= 24)), baseline_mean_tntxm (double — Moyenne 1991-2020 de tntxm pour ce jour calendaire (°C)), classe_recente (integer — Classe d'homogénéité MF de la station (1 = meilleure … 5), valeur courante (station_classe.date_fin IS NULL). Le dataset ne retient que classe_recente <= 4 (iso-comportement D4G v_station_deviation).) - `GET /dataclimat/first_temperature_date` — Depuis quel mois chaque station mesure la température : pour connaître l'ancienneté d'une station. La date est un texte au format AAAAMM (année puis mois sur deux chiffres) — par exemple « 187206 » = juin 1872. Ordre contractuel : station_code ASC. Colonnes : station_code (text — NUM_POSTE Météo-France), first_temperature_date (text — Mois du premier relevé de température, texte au format AAAAMM (année + mois sur deux chiffres), ex. « 187206 » = juin 1872. Ni date ISO ni nombre.) #### ic - `GET /ic/journaliere` — Températures journalières (min, max, moyenne) et cumul de précipitation par station, en °C et mm, à partir des sources collectées par Infoclimat hors Météo-France (synop/metar/bouées officiels relayés + StatIC + Météo-à-l'École). Contrôle qualité qui écarte les valeurs aberrantes. Ordre contractuel : date ASC, ic_id ASC, mfid ASC. Colonnes : ic_id (text — Identifiant station Infoclimat (crosswalk dim_station)), mfid (text — NUM_POSTE Météo-France si la station est aussi référencée MF, sinon vide), date (date), tn (double — Température min du jour (°C)), tx (double — Température max du jour (°C)), tm (double — (tn+tx)/2 (°C)), tntxm (double — alias de tm (°C)), rr (double — Cumul de précipitation du jour (mm)), n_obs (bigint — Nombre d'observations de température agrégées ce jour-là — toutes celles du jour, plausibles ou non, rejets du contrôle qualité compris. C'est la profondeur de preuve du tn/tx : un extrême calculé sur 24 observations et un extrême calculé sur 2 ne se valent pas, et l'écart est massif selon l'époque (médiane de 4 observations par jour en 1960, de 24 en 2025). Attention : n_obs - n_obs_rejetees est un MAJORANT du nombre d'observations effectivement retenues, pas ce nombre — une valeur hors bornes physiques n'est comptée dans aucun des deux et n'est pas retenue. Même définition que sur gold_statIC.journaliere.), n_obs_rejetees (bigint — Nombre d'observations de température écartées ce jour-là par le contrôle qualité des pics isolés — une valeur isolée au milieu de voisins normaux. Vaut 0 dans l'écrasante majorité des cas. La liste nominative des observations écartées est publiée sur /qc/pic_temperature.), statut_extremes (string — CALCULÉ PAR LE SERVING, pas stocké — ce que valent tn/tx compte tenu de la densité d'observation du jour. `tn`/`tx` sont le min/max des observations DU JOUR, pas les extrêmes DE LA JOURNÉE : quand la journée est mal échantillonnée, `tn` MAJORE le vrai minimum quotidien et `tx` MINORE le vrai maximum. · `quotidien` : au moins 8 observations retenues (`n_obs - n_obs_rejetees`) — le déficit médian d'amplitude tombe à 0,4 K et ne s'améliore plus au-delà. · `partiel` : 2 à 7 — amplitude amputée de 5,3 K (2 obs) à 0,6 K (7 obs) en médiane. · `releve_unique` : 1 seule — tn = tx = ce relevé, mesuré sur 100 % des 9 088 jours sondés ; ce n'est pas un extrême. · null : aucune observation retenue, rien à qualifier. Le compte est un MAJORANT (les valeurs hors bornes physiques ne sont dans aucun compteur) et il ignore la RÉPARTITION dans la journée : le statut oriente la lecture, il ne certifie pas l'extrême. Aucune valeur n'est filtrée ni nullifiée sur ce critère (issue #85).) - `GET /ic/mensuelle` — Le résumé de chaque mois par station — moyennes, extrêmes datés (jour le plus froid / le plus chaud), nombre de jours de gel — sur les sources collectées par Infoclimat hors Météo-France. Ordre contractuel : mois ASC, ic_id ASC, mfid ASC. Colonnes : ic_id (text), mfid (text — NUM_POSTE Météo-France si la station est aussi référencée MF, sinon vide), mois (timestamp — Premier jour du mois (date_trunc('month', date))), tnm (double — Moyenne mensuelle des tn (°C)), txm (double — Moyenne mensuelle des tx (°C)), tmm (double — Moyenne mensuelle des tm (°C)), rr (double — Cumul de précipitation du mois (mm)), tnn (double — Température min du mois (°C)), tnn_date (date), txx (double — Température max du mois (°C)), txx_date (date), nb_jours_gel (integer — Nombre de jours du mois avec tn < 0 °C), n_obs (bigint — Somme des n_obs journaliers du mois : nombre total d'observations de température agrégées, toutes celles du mois, plausibles ou non. Sert de poids pour comparer des mois dont la densité de mesure diffère. Même définition que sur gold_statIC.mensuelle.) - `GET /ic/records` — Les records de température (chaud/froid) de chaque station, par mois calendaire sur tout l'historique, sur les sources collectées par Infoclimat hors Météo-France : pour afficher « record battu » et les extrêmes historiques. Ordre contractuel : mois_calendaire ASC, ic_id ASC, mfid ASC. Colonnes : ic_id (text), mfid (text — NUM_POSTE Météo-France si la station est aussi référencée MF, sinon vide), mois_calendaire (integer — Mois calendaire (1-12), 3e composante de la clé naturelle), txx_max (double — Record absolu de température max sur le mois calendaire (°C)), txx_max_date (date), txx_max_suivant (double — 2e valeur DISTINCTE la plus haute de tx sur le mois calendaire (°C) — suivant en VALEUR, pas dans le temps. Un écart important avec txx_max signale un extrême isolé du reste de la série. Vide si le mois calendaire ne porte qu'une seule valeur distincte.), tnn_min (double — Record absolu de température min sur le mois calendaire (°C)), tnn_min_date (date), tnn_min_suivant (double — 2e valeur DISTINCTE la plus basse de tn sur le mois calendaire (°C) — suivant en VALEUR, pas dans le temps. Un écart important avec tnn_min signale un extrême isolé du reste de la série. Vide si le mois calendaire ne porte qu'une seule valeur distincte.), n_jours_tx (bigint — Nombre de jours où tx est renseigné sur ce mois calendaire, tous millésimes confondus : le socle sur lequel txx_max est calculé.), n_jours_tn (bigint — Nombre de jours où tn est renseigné sur ce mois calendaire, tous millésimes confondus : le socle sur lequel tnn_min est calculé. Distinct de n_jours_tx — tn et tx ne manquent pas les mêmes jours.), premiere_obs (date — Premier jour mesuré de ce mois calendaire (tn ou tx renseigné)), derniere_obs (date — Dernier jour mesuré de ce mois calendaire (tn ou tx renseigné)), txx_max_n_obs (bigint — Nombre d'observations de température du jour où txx_max est atteint. Avec txx_max_n_obs_rejetees, permet de qualifier la densité du jour du record : au moins 8 observations retenues (n_obs - n_obs_rejetees) correspondent à un extrême quotidien.), tnn_min_n_obs (bigint — Nombre d'observations de température du jour où tnn_min est atteint. Avec tnn_min_n_obs_rejetees, permet de qualifier la densité du jour du record : au moins 8 observations retenues (n_obs - n_obs_rejetees) correspondent à un extrême quotidien.), txx_max_n_obs_rejetees (bigint — Observations du jour où txx_max est atteint, écartées par le contrôle qualité des pics de température. À soustraire de txx_max_n_obs pour lire la densité retenue du jour du record.), tnn_min_n_obs_rejetees (bigint — Observations du jour où tnn_min est atteint, écartées par le contrôle qualité des pics de température. À soustraire de tnn_min_n_obs pour lire la densité retenue du jour du record.), tnn_min_statut (string — CALCULÉ PAR LE SERVING, pas stocké — ce que valent tn/tx compte tenu de la densité d'observation du jour. `tn`/`tx` sont le min/max des observations DU JOUR, pas les extrêmes DE LA JOURNÉE : quand la journée est mal échantillonnée, `tn` MAJORE le vrai minimum quotidien et `tx` MINORE le vrai maximum. · `quotidien` : au moins 8 observations retenues (`n_obs - n_obs_rejetees`) — le déficit médian d'amplitude tombe à 0,4 K et ne s'améliore plus au-delà. · `partiel` : 2 à 7 — amplitude amputée de 5,3 K (2 obs) à 0,6 K (7 obs) en médiane. · `releve_unique` : 1 seule — tn = tx = ce relevé, mesuré sur 100 % des 9 088 jours sondés ; ce n'est pas un extrême. · null : aucune observation retenue, rien à qualifier. Le compte est un MAJORANT (les valeurs hors bornes physiques ne sont dans aucun compteur) et il ignore la RÉPARTITION dans la journée : le statut oriente la lecture, il ne certifie pas l'extrême. Aucune valeur n'est filtrée ni nullifiée sur ce critère (issue #85). Pour `tnn_min`, ce statut porte sur le jour du record, pas sur l'ensemble du mois calendaire.), txx_max_statut (string — CALCULÉ PAR LE SERVING, pas stocké — ce que valent tn/tx compte tenu de la densité d'observation du jour. `tn`/`tx` sont le min/max des observations DU JOUR, pas les extrêmes DE LA JOURNÉE : quand la journée est mal échantillonnée, `tn` MAJORE le vrai minimum quotidien et `tx` MINORE le vrai maximum. · `quotidien` : au moins 8 observations retenues (`n_obs - n_obs_rejetees`) — le déficit médian d'amplitude tombe à 0,4 K et ne s'améliore plus au-delà. · `partiel` : 2 à 7 — amplitude amputée de 5,3 K (2 obs) à 0,6 K (7 obs) en médiane. · `releve_unique` : 1 seule — tn = tx = ce relevé, mesuré sur 100 % des 9 088 jours sondés ; ce n'est pas un extrême. · null : aucune observation retenue, rien à qualifier. Le compte est un MAJORANT (les valeurs hors bornes physiques ne sont dans aucun compteur) et il ignore la RÉPARTITION dans la journée : le statut oriente la lecture, il ne certifie pas l'extrême. Aucune valeur n'est filtrée ni nullifiée sur ce critère (issue #85). Pour `txx_max`, ce statut porte sur le jour du record, pas sur l'ensemble du mois calendaire.) #### statIC - `GET /statIC/journaliere` — Températures journalières (min, max, moyenne) et cumul de précipitation par station StatIC, en °C et mm, avec la densité d'observations et le taux de mesures plausibles : pour exploiter le réseau participatif en connaissance de cause. Ordre contractuel : date ASC, ic_id ASC. Colonnes : ic_id (text — Identifiant station StatIC (Infoclimat)), date (date), tn (double — Température min du jour (°C)), tx (double — Température max du jour (°C)), tm (double — (tn+tx)/2 (°C)), tntxm (double — alias de tm (°C)), rr (double — Cumul de précipitation du jour (mm), nul si > 2000 mm (impossible)), n_obs (integer — Nombre d'observations de température agrégées ce jour (densité)), taux_qc_valide (double — Part des obs de température dans les bornes physiques [-90,+60] °C (0-1) — ratio, pas un pourcentage : 1 = 100 %, arrondi à 3 décimales), classe_station (integer — Classe siting StatIC (1-5) — NON RENSEIGNÉE (déviation, cf. limitations)), n_obs_rejetees (bigint — Nombre d'observations de température écartées ce jour-là par le contrôle qualité des pics isolés — une valeur isolée au milieu de voisins normaux. Vaut 0 dans l'écrasante majorité des cas. La liste nominative des observations écartées est publiée sur /qc/pic_temperature.), statut_extremes (string — CALCULÉ PAR LE SERVING, pas stocké — ce que valent tn/tx compte tenu de la densité d'observation du jour. `tn`/`tx` sont le min/max des observations DU JOUR, pas les extrêmes DE LA JOURNÉE : quand la journée est mal échantillonnée, `tn` MAJORE le vrai minimum quotidien et `tx` MINORE le vrai maximum. · `quotidien` : au moins 8 observations retenues (`n_obs - n_obs_rejetees`) — le déficit médian d'amplitude tombe à 0,4 K et ne s'améliore plus au-delà. · `partiel` : 2 à 7 — amplitude amputée de 5,3 K (2 obs) à 0,6 K (7 obs) en médiane. · `releve_unique` : 1 seule — tn = tx = ce relevé, mesuré sur 100 % des 9 088 jours sondés ; ce n'est pas un extrême. · null : aucune observation retenue, rien à qualifier. Le compte est un MAJORANT (les valeurs hors bornes physiques ne sont dans aucun compteur) et il ignore la RÉPARTITION dans la journée : le statut oriente la lecture, il ne certifie pas l'extrême. Aucune valeur n'est filtrée ni nullifiée sur ce critère (issue #85).) - `GET /statIC/mensuelle` — Le résumé de chaque mois par station StatIC — moyennes, extrêmes datés, jours de gel — avec la densité et le taux de mesures plausibles du mois. Ordre contractuel : mois ASC, ic_id ASC. Colonnes : ic_id (text), mois (timestamp — Premier jour du mois), tnm (double — Moyenne mensuelle des tn (°C)), txm (double — Moyenne mensuelle des tx (°C)), tmm (double — Moyenne mensuelle des tm (°C)), rr (double — Cumul de précipitation du mois (mm)), tnn (double — Température min du mois (°C)), tnn_date (date), txx (double — Température max du mois (°C)), txx_date (date), nb_jours_gel (integer — Nombre de jours du mois avec tn < 0 °C), n_obs (integer — Nombre d'observations de température du mois), taux_qc_valide (double — Taux d'obs plausibles du mois, pondéré par la densité (0-1) — ratio, pas un pourcentage : 1 = 100 %, arrondi à 3 décimales), classe_station (integer — Classe siting StatIC — NON RENSEIGNÉE (déviation)) - `GET /statIC/records` — Les records de température (chaud/froid) de chaque station StatIC, par mois calendaire sur tout l'historique : extrêmes bruts du réseau participatif (à lire avec la densité/qualité de la station). Ordre contractuel : mois_calendaire ASC, ic_id ASC. Colonnes : ic_id (text), mois_calendaire (integer — Mois calendaire (1-12)), txx_max (double — Record de température max sur le mois calendaire (°C)), txx_max_date (date), txx_max_suivant (double — 2e valeur DISTINCTE la plus haute de tx sur le mois calendaire (°C) — suivant en VALEUR, pas dans le temps. Un écart important avec txx_max signale un extrême isolé du reste de la série. Vide si le mois calendaire ne porte qu'une seule valeur distincte.), tnn_min (double — Record de température min sur le mois calendaire (°C)), tnn_min_date (date), tnn_min_suivant (double — 2e valeur DISTINCTE la plus basse de tn sur le mois calendaire (°C) — suivant en VALEUR, pas dans le temps. Un écart important avec tnn_min signale un extrême isolé du reste de la série. Vide si le mois calendaire ne porte qu'une seule valeur distincte.), n_jours_tx (bigint — Nombre de jours où tx est renseigné sur ce mois calendaire, tous millésimes confondus : le socle sur lequel txx_max est calculé.), n_jours_tn (bigint — Nombre de jours où tn est renseigné sur ce mois calendaire, tous millésimes confondus : le socle sur lequel tnn_min est calculé. Distinct de n_jours_tx — tn et tx ne manquent pas les mêmes jours.), premiere_obs (date — Premier jour mesuré de ce mois calendaire (tn ou tx renseigné)), derniere_obs (date — Dernier jour mesuré de ce mois calendaire (tn ou tx renseigné)), txx_max_n_obs (bigint — Nombre d'observations de température du jour où txx_max est atteint. Avec txx_max_n_obs_rejetees, permet de qualifier la densité du jour du record : au moins 8 observations retenues (n_obs - n_obs_rejetees) correspondent à un extrême quotidien.), tnn_min_n_obs (bigint — Nombre d'observations de température du jour où tnn_min est atteint. Avec tnn_min_n_obs_rejetees, permet de qualifier la densité du jour du record : au moins 8 observations retenues (n_obs - n_obs_rejetees) correspondent à un extrême quotidien.), txx_max_n_obs_rejetees (bigint — Observations du jour où txx_max est atteint, écartées par le contrôle qualité des pics de température. À soustraire de txx_max_n_obs pour lire la densité retenue du jour du record.), tnn_min_n_obs_rejetees (bigint — Observations du jour où tnn_min est atteint, écartées par le contrôle qualité des pics de température. À soustraire de tnn_min_n_obs pour lire la densité retenue du jour du record.), tnn_min_statut (string — CALCULÉ PAR LE SERVING, pas stocké — ce que valent tn/tx compte tenu de la densité d'observation du jour. `tn`/`tx` sont le min/max des observations DU JOUR, pas les extrêmes DE LA JOURNÉE : quand la journée est mal échantillonnée, `tn` MAJORE le vrai minimum quotidien et `tx` MINORE le vrai maximum. · `quotidien` : au moins 8 observations retenues (`n_obs - n_obs_rejetees`) — le déficit médian d'amplitude tombe à 0,4 K et ne s'améliore plus au-delà. · `partiel` : 2 à 7 — amplitude amputée de 5,3 K (2 obs) à 0,6 K (7 obs) en médiane. · `releve_unique` : 1 seule — tn = tx = ce relevé, mesuré sur 100 % des 9 088 jours sondés ; ce n'est pas un extrême. · null : aucune observation retenue, rien à qualifier. Le compte est un MAJORANT (les valeurs hors bornes physiques ne sont dans aucun compteur) et il ignore la RÉPARTITION dans la journée : le statut oriente la lecture, il ne certifie pas l'extrême. Aucune valeur n'est filtrée ni nullifiée sur ce critère (issue #85). Pour `tnn_min`, ce statut porte sur le jour du record, pas sur l'ensemble du mois calendaire.), txx_max_statut (string — CALCULÉ PAR LE SERVING, pas stocké — ce que valent tn/tx compte tenu de la densité d'observation du jour. `tn`/`tx` sont le min/max des observations DU JOUR, pas les extrêmes DE LA JOURNÉE : quand la journée est mal échantillonnée, `tn` MAJORE le vrai minimum quotidien et `tx` MINORE le vrai maximum. · `quotidien` : au moins 8 observations retenues (`n_obs - n_obs_rejetees`) — le déficit médian d'amplitude tombe à 0,4 K et ne s'améliore plus au-delà. · `partiel` : 2 à 7 — amplitude amputée de 5,3 K (2 obs) à 0,6 K (7 obs) en médiane. · `releve_unique` : 1 seule — tn = tx = ce relevé, mesuré sur 100 % des 9 088 jours sondés ; ce n'est pas un extrême. · null : aucune observation retenue, rien à qualifier. Le compte est un MAJORANT (les valeurs hors bornes physiques ne sont dans aucun compteur) et il ignore la RÉPARTITION dans la journée : le statut oriente la lecture, il ne certifie pas l'extrême. Aucune valeur n'est filtrée ni nullifiée sur ce critère (issue #85). Pour `txx_max`, ce statut porte sur le jour du record, pas sur l'ensemble du mois calendaire.) - `GET /statIC/indicateur_reseau` — L'indicateur thermique national du réseau StatIC — la température moyenne quotidienne de la France, calculée pour être SPATIALEMENT REPRÉSENTATIVE : moyenne par département puis moyenne des départements couverts (et non moyenne brute des stations, qui sur-pondérerait les zones denses). La couverture (nombre de stations, de départements) et la dispersion sont exposées pour la transparence. V1 national ; régional et anomalies vs normales = suite. Ordre contractuel : date ASC, echelle ASC. Colonnes : date (date), echelle (text — Échelle spatiale ('national' en V1)), itn_statIC (double — Indicateur thermique StatIC du jour, représentatif (°C)), tn_moy (double — Moyenne réseau des tn (par maille puis des mailles) (°C)), tx_moy (double — Moyenne réseau des tx (°C)), n_stations (integer — Nombre de stations StatIC contribuant ce jour (transparence)), n_mailles (integer — Nombre de départements couverts ce jour (représentativité)), ecart_type (double — Écart-type inter-stations des tm (dispersion, transparence)) - `GET /statIC/stations` — L'annuaire du réseau StatIC, triable par département : pour chaque station, tous les paramètres réellement mesurés et leur profondeur temporelle (première/dernière observation, volume). Grain : 1 ligne par (station, paramètre). Stations fermées incluses — les colonnes ouverte et derniere_obs permettent de filtrer. Dérivée de gold_ref (référentiel), refresh quotidien. Colonnes : ic_id (text — Identifiant station StatIC (Infoclimat)), libelle (text — Nom de la station), departement (text — Département (code, ex. « 31 ») — null hors France), pays (text — Code pays (ex. FR)), latitude (double), longitude (double), altitude (integer — Altitude (m)), ouverte (boolean — Station encore ouverte dans le référentiel), dh_ouverture (timestamp — Date d'ouverture de la station), parametre (text — Paramètre mesuré (vocabulaire canonique, cf. /params)), premiere_obs (timestamp — Première observation de ce paramètre sur cette station), derniere_obs (timestamp — Dernière observation de ce paramètre sur cette station), n_obs (bigint — Nombre total d'observations de ce paramètre) #### ref - `GET /ref/stations` — Une ligne par station : identité, géolocalisation, réseau, état d'ouverture, paramètres mesurés (tableau) et date de dernière activité. Colonnes : station_uid (text — Pivot de jointure interne. uuid5(ns, "ic:" + ic_id) — DÉRIVÉ de ic_id, donc pas plus stable que lui : il change si ic_id change. L'identifiant canonique est ic_id.), ic_id (text — Identifiant station Infoclimat — IDENTIFIANT CANONIQUE, celui qui fait foi. Hétérogène par conception : code OACI pour les metar (FMEE), indicatif OMM à 5 chiffres pour les synop (61980). Les stations StatIC n'ont AUCUNE forme unique (00006, STATIC0253, MNWERO090) : ne présumez d'aucun format, lisez la valeur.), aliases (array — Codes historiques internes résolus vers ic_id. Ne pas utiliser pour identifier une station : ic_id reste l'identifiant canonique.), mfid (text — NUM_POSTE Météo-France si la station est aussi référencée MF, sinon vide), genre (text — Réseau (static = StatIC participatif, synop = synoptique)), type_poste (text — Type de poste Météo-France (principale/secondaire), sinon vide), libelle (text), libelle_source (text — Libellé brut du référentiel amont, avant normalisation. Conservé pour traçabilité : 13 087 libellés portent un préfixe laissé par l'import CSV Météo-France (« TEST MF CSV … ») qui ne désigne PAS des stations de test — ce sont des postes Météo-France réels.), latitude (double), longitude (double), altitude (integer — Altitude (m)), departement (text), pays (text), ouverte (boolean — Station déclarée ouverte dans l'annuaire), dh_ouverture (timestamp), publiable (boolean — Indicateur d'activité : la station porte au moins une observation dans gold_ref.station_parametre. Ce n'est pas un critère de quarantaine ; les statuts non publiables ont déjà été exclus en amont. Les endpoints /stations et /ref/stations écartent les lignes à false par défaut (publiable=0 les inclut).), parametres_mesures (array — Clés canoniques des paramètres réellement observés), derniere_activite (timestamp — Dernière observation, tous paramètres confondus) - `GET /ref/station-parametre` — Pont station × paramètre : pour chaque paramètre observé par une station, première/dernière observation et volume. Fait autorité sur « quelles stations mesurent quoi ». Colonnes : station_uid (text), parametre (text — Clé canonique du paramètre (vocabulaire dim_parametre)), premiere_obs (timestamp), derniere_obs (timestamp), n_obs (bigint — Nombre d'observations du couple (station, paramètre)) > La famille `/canicule/*` n'apparaît pas dans cet inventaire : ses routes sont territoriales (commune/EPCI/département), décrites à la section « Canicule par territoire » ci-dessus et dans la spec OpenAPI. `/ref/stations-legacy` non plus : ce n'est pas un dataset des contrats ODCS mais un endpoint hors-datasets (tableau JSON nu, mêmes clés que `stations_xhr.php`), décrit dans la spec OpenAPI. Son `?perimetre=stations_xhr` sert le périmètre et les codes de licence de la source ; son `?display_closed=1` répond **451** — la source publie ses stations fermées sous une licence qui en interdit la redistribution. ### Formats de date Une granularité temporelle = une représentation, et la famille dit laquelle. Les familles **canoniques** portent la convention propre de l'API ; les familles de **compatibilité** portent le format de leur source d'origine. | Ce que la valeur désigne | Familles canoniques (`v2`, `ic`, `statIC`, `ref`) | Familles de compatibilité (`legacy`, `dataclimat`) | |---|---|---| | un mois entier | `mois` → `2025-01` | legacy `AAAAMM` → `202001` · dataclimat `date` → `2020-01-01` | | une journée | `date`, `tnn_date`, `txx_date`, `dh_ouverture` → `2025-01-14` | legacy `AAAAMMJJ` → `20200101` · dataclimat → `2020-01-01` | | un instant | `premiere_obs`, `derniere_obs`, `derniere_activite` → `2005-07-01T00:00:00Z` (RFC 3339, UTC) | `/ref/stations-legacy` : `last_report`, `derniere_activite` → `2005-07-01 00:00:00` (UTC, notation de `stations_xhr.php`) | Sous une colonne nommée `AAAAMM`, le nom **est** le format : `/legacy/*` sert les colonnes Météo-France telles que Météo-France les publie. Même règle pour `/ref/stations-legacy`, qui reproduit le feed `stations_xhr.php` : dates en `YYYY-MM-DD HH:MM:SS`, sans `T` ni `Z`, et sentinelle `0000-00-00 00:00:00` au lieu de `null` quand l'activité est inconnue. Ne parsez pas ce champ avec un lecteur RFC 3339 strict, et ne présumez pas qu'une date y est toujours valide. L'instant désigné est le même que sur `/ref/stations` ; seule l'écriture change. Sa variante `?format=geojson&perimetre=stations_xhr` reproduit l'autre graphie de la même source : `last_activity` en `2005-07-01T00:00:00Z`, et la sentinelle en `0000-00-00T00:00:00Z` — qui ressemble à du RFC 3339 sans en être. Même instant, troisième écriture ; c'est la source qui en publie deux, pas nous. Les paramètres de requête `from` et `to`, eux, sont toujours en `YYYY-MM-DD`, sur toutes les familles et toutes les granularités. ### Exemples ```bash # journalier recalculé — Grenoble-St-Geoirs, juillet 2025 curl -s "$B/v2/journaliere?station=07486&from=2025-07-01&to=2025-08-01" # → {"mesures":[{"ic_id":"07486","mfid":"38384001","date":"2025-07-01", # "tn":18.4,"tx":34.9,"tm":26.65,"tntxm":26.65,"rr":0.4}, …]} # mensuel legacy — Paris-Montsouris, schéma Météo-France d'origine curl -s "$B/legacy/mensuelle?station=75114002&from=1999-01-01&to=2000-01-01" # records absolus d'une station — pas de from/to, la clé est station × mois # (tous les postes n'en ont pas : une station sans record calculé renvoie n=0) curl -s "$B/dataclimat/records_absolus?station=31069001" # historique des records battus curl -s "$B/dataclimat/records_battus?station=25462001" # indice thermique national, canicule 2003 — dataset national, pas de station curl -s "$B/v2/itn?from=2003-08-01&to=2003-09-01" # réseau participatif curl -s "$B/statIC/journaliere?station=STATIC0253&from=2025-01-01&to=2025-04-01" ``` ### Champs qualité StatIC `/statIC/journaliere` et `/statIC/mensuelle` portent deux champs de contexte que les autres familles n'ont pas. - `n_obs` — nombre d'obs `air_temperature` agrégées sur la période, plausibles ou non. C'est la densité de mesure, pas un nombre de jours. En mensuel, c'est la somme des `n_obs` journaliers. - `taux_qc_valide` — **ratio [0, 1]** arrondi à 3 décimales, **pas un pourcentage** : `1` vaut 100 %. C'est la part de ces obs comprise dans les bornes de plausibilité physique [-90, +60] °C. En mensuel, moyenne des ratios journaliers pondérée par `n_obs`. Trois pièges : 1. **Ce n'est pas un QC de réseau.** Les obs StatIC arrivent sans contrôle qualité amont (`qc_flag = 'raw'`) : il n'y a pas de classes QC à agréger. Le champ ne dit qu'une chose — la mesure est-elle physiquement possible. 2. **Il ne discrimine rien.** Les bornes sont assez larges pour qu'il vaille `1,0` sur tout le fonds sondé — 26 870 jours sur 50 stations de 10 départements, 2024-01 → 2026-08, aucune valeur inférieure à 1. Garde-fou, pas critère de filtrage. 3. **Il n'est jamais `null`.** Une journée sans obs de température ne produit pas de ligne. Un `0` impliquerait `tn`/`tx`/`tm` à `null`. `classe_station` est **toujours `null`** sur les deux datasets : la classification de siting StatIC n'est pas sourçable aujourd'hui. ### Filtrer par réseau `?reseau=` est accepté sur les endpoints archive et live : `mf` · `ic` · `static` · `synop` · `metar` · `bouees`. ### Canicule par territoire ```bash # saison d'une commune — nuits tropicales, jours ≥ 30/35 °C, rang historique curl -s "$B/canicule/commune/31555?annee=2025" # records récents d'un département, en CSV curl -s "$B/canicule/records?dep=31&limit=50&format=csv" # saisons disponibles, pour alimenter un sélecteur d'année curl -s "$B/canicule/saisons" ``` `` ∈ `commune` (code INSEE) · `epci` (SIREN) · `departement`. `format=csv` est accepté sur `/canicule/records` et `/canicule//`. Ordre contractuel : record_date DESC, record_type ASC, station_code ASC, period_type ASC, period_value ASC. ### Volumétrie : plafond, troncature, page suivante **20 000 lignes par requête**, sur **toutes** les familles de série (`/legacy`, `/v2`, `/ic`, `/statIC`, `/dataclimat`). Chaque réponse le déclare : ```json { "n": 19983, "plafond": 20000, "tronque": true, "fenetre": {"debut": "1900-01-01", "fin": "1954-09-11"}, "suite": {"from": "1954-09-12"}, "mesures": ["…"] } ``` - `tronque: true` ⟹ **la série rendue est incomplète**. C'est le seul champ à tester avant d'utiliser des données : ignorer ce drapeau produit des séries historiques fausses sans erreur visible. - `suite` donne la page suivante : **réémettez la même URL** en remplaçant `from` par `suite.from`. `from` étant inclusif et la page s'arrêtant toujours sur une date complète, la concaténation des pages reconstitue la série sans perte ni doublon. - `fenetre` est l'extension **effective** des lignes rendues, bornes incluses. - `suite: null` avec `tronque: true` : il n'existe aucun point de reprise — soit le dataset n'a pas de colonne de date (`records`), soit une seule date remplit la page. Resserrez la requête, ou passez au bulk (§4). `fenetre` et `suite` sont en `YYYY-MM-DD`, l'espace des paramètres `from`/`to` — y compris pour les datasets mensuels, dont les colonnes se publient, elles, en `2025-01` ou `202001`. Boucle de pagination minimale : ```python url, params = f"{B}/v2/journaliere", {"station": "07486"} tout = [] while True: r = requests.get(url, params=params).json() tout += r["mesures"] if not r["tronque"] or not r["suite"]: break params["from"] = r["suite"]["from"] ``` Les **référentiels** (`/ref/stations`, `/ref/stations-legacy`, `/ref/station-parametre`, `/statIC/stations`) ne sont **pas** plafonnés : bornés par nature, ils ne portent ni `tronque` ni `suite`. `/stations` est un cas à part : il rend au plus 20 correspondances et ne porte pas de curseur. ### Limites - Pas d'API key, pas de quota. Le service tourne sur une seule machine : restez raisonnable. - **Démarrage** : il n'y a **pas** de « cold start » de 40 s. Ce chiffre, longtemps annoncé ici, décrivait une architecture antérieure où l'annuaire des stations était chargé en mémoire au démarrage. Mesuré le 02/08/2026 en production : chauffe du service **1,4 s**, première requête froide **0,4 à 2,9 s** selon la table, puis **0,02 à 0,3 s**. Pire chemin froid ≈ **4 s**. - **Contrat de disponibilité** : `/health` répond 200 dès que le processus est en vie, chauffe comprise ; `/ready` répond 200 quand le service peut servir, sinon **503 avec `Retry-After`**. Pendant la chauffe, les routes de données renvoient elles aussi 503 + `Retry-After: 2` — jamais un timeout muet. - **Timeout et retry conseillés** : timeout client **15 s**, un seul retry après 2 s sur 503, en respectant `Retry-After` quand il est présent. Ne provisionnez pas 60 s « pour le cold start » : à ce seuil, une vraie panne ne se voit plus. - **Cache HTTP** : les ressources stables — `/params`, `/coverage`, `/stations`, `/ref/*`, `/statIC/stations` et la documentation — portent `Cache-Control` et `ETag`. Renvoyez l'`ETag` en `If-None-Match` : vous obtenez un **304** sans corps. Les séries n'ont volontairement pas d'en-tête de cache. ### Erreurs **Depuis le 2 août 2026, l'API climato répond ses erreurs en RFC 9457 `application/problem+json`.** Rupture de format ; le tableau des changements est plus bas. ``` GET /ic/journaliere → 400 application/problem+json {"type":"https://portail.chom.engineering/errors#missing-parameter", "title":"Missing parameter","status":400,"code":"missing-parameter", "detail":"missing parameter: station","details":{"parameter":"station"}, "instance":"/ic/journaliere","request_id":"c1a43e92…"} ``` Toujours présents : `type`, `title`, `status`, `code`, `instance`, `request_id`. Optionnels : `detail` (phrase, tronquée à 200 caractères), `details` (objet structuré). **Branchez sur `code`**, stable et anglophone. `request_id` est aussi en en-tête `X-Request-Id`, y compris sur les succès : citez-le pour tout signalement. Catalogue **fermé**, quinze codes. Les trois prises (climato, live, archive) partagent la même enveloppe, sans forcément émettre tous les mêmes codes : `missing-parameter` (400), `invalid-parameter` (400), `malformed-request` (400/414/431/505), `station-not-found` (404, pour une station absente du référentiel canonique ; également émis par `/series`), `no-current-value` (404, Live API), `dataset-not-found` (404), `territory-not-found` (404), `route-not-found` (404), `document-not-found` (404), `method-not-supported` (501), `internal-error` (500), `service-warming-up` (503), `service-saturated` (503), `coverage-unavailable` (503), `schema-not-ready` (503, `/stations` : le Gold de référence n'a pas encore la colonne `aliases`; reconstruisez `gold_ref.station`, puis réessayez). Explications publiées : https://portail.chom.engineering/errors **Station inconnue vs fenêtre vide.** Sur les routes de séries qui déclarent `station-not-found`, un identifiant absent du référentiel canonique (aliases publiés inclus) renvoie **404 `station-not-found`**. Une station canonique connue sans donnée dans la fenêtre demandée renvoie **200 avec `n = 0`**. Un paramètre `station` absent ou vide renvoie **400 `missing-parameter`**. Les identifiants explicitement quarantined ou classés hors référentiel ne sont pas canonisés par l'API et restent donc des 404. **Annonce consommateur — 11 août 2026 (changement en attente de déploiement).** Au prochain déploiement, les quinze routes qui déclarent `station-not-found` passeront d'un `200`/`n = 0` à `404 station-not-found` pour une station inconnue ; une station canonique sans mesure gardera `200`/`n = 0`. Sur `/legacy/*`, une canonique sans `mfid` publiera `station.num_poste: null`, jamais son `ic_id` présenté comme NUM_POSTE. Changements du 2 août 2026 (production, 18:48 UTC) : | | Avant | Depuis | |---|---|---| | `Content-Type` | `application/json` | `application/problem+json` | | Corps, données et référentiel | `{"erreur": "…"}` (français) | l'enveloppe ci-dessus | | Corps, famille canicule | `{"error": …, "code": …, "niveau": …}` | idem, forme unique | | Corrélateur | aucun | `request_id`, corps et en-tête | | `/coverage` sans fichier précalculé | **200** portant `{"erreur": …}` | **503** `coverage-unavailable` | Depuis le 5 août 2026 l'enveloppe est unique au pied de la lettre : le Live API (`/last`, `/series`) l'a adoptée (issue #42) et un verbe non géré reçoit un 501 en problem+json (issue #41). Limite assumée : sur une ligne de requête illisible, la bibliothèque standard répond en mode HTTP/0.9 — corps JSON, mais sans ligne de statut ni en-têtes. **Inchangé** : l'absence de donnée est un **200** avec `n = 0` et `mesures: []`. Ce n'est pas une erreur. --- ## 3. Live API Contrat de démarrage : ces deux prises prennent leur port AVANT d'être prêtes. Pendant la chauffe — une cinquantaine de secondes sur l'archive — elles répondent 503 `application/problem+json` avec `Retry-After`, jamais un silence. `details.uptime_s` dit depuis combien de temps ça chauffe, ce qui distingue « ça démarre » de « ça n'a jamais fini de démarrer ». Respectez `Retry-After`. Le tier chaud est une **rolling window de 24 mois**, rafraîchie quotidiennement. La dernière valeur connue par station × paramètre est servie en point lookup (~0,2 ms). ### Sans authentification ```bash # fraîcheur du fonds, archive et live curl -s "$B/coverage" # {"archive":{"oldest":"1777-01-01T00:00:00Z","newest":"2026-07-27T02:30:00Z"}, …} # canicule en cours sur une commune : dernières Tn/Tx et streaks curl -s "$B/canicule/commune/31555?mode=live" # → {"live":{"as_of":"2026-07-27","derniere_tn":19.3,"derniere_tx":20.2, # "nuit_tropicale":false,"streak_nuits_tropicales":0, …}} # vocabulaire des 131 paramètres canoniques et leurs unités curl -s "$B/params" ``` ### Sur token Deux endpoints exposent les mesures brutes au pas de mesure et demandent un en-tête `Authorization: Bearer `, délivré sur demande. ```bash # dernières valeurs d'une station, paramètre par paramètre curl -s -H "Authorization: Bearer $TOKEN" \ "https://live.chom.engineering/last?station=07156¶m=air_temperature" # série brute sur le fonds canonique complet curl -s -H "Authorization: Bearer $TOKEN" \ "https://archive.chom.engineering/series?station=07156¶m=air_temperature&from=2025-06-01&to=2025-07-01" ``` > **Unités.** Ces deux endpoints renvoient le canonique **SI** : températures en > kelvin (`292.65`), pressions en pascals. Les datasets Gold (`/v2`, `/legacy`, > `/dataclimat`, `/ic`, `/statIC`) sont convertis en unités conventionnelles > (°C, hPa, mm). `/params` donne l'unité de chaque clé. --- ## 4. Bulk : Parquet et Iceberg Les 30 datasets Gold sont des **tables Iceberg** (Parquet + métadonnées) dans un bucket S3 OVH région GRA, organisées en 7 namespaces : `gold_v2`, `gold_legacy`, `gold_dataclimat`, `gold_ic`, `gold_statIC`, `gold_ref`, `gold_canicule`. **L'accès demande des credentials S3 et un accès au catalogue Iceberg**, tous deux délivrés sur demande. Deux règles avant de commencer : 1. **Passez toujours par le catalogue**, jamais par une URL S3 en dur. Les chemins des data files sont des UUID régénérés à chaque refresh : un chemin noté la veille est mort le lendemain. 2. **Filtrez au scan.** Iceberg élague les fichiers à partir des prédicats ; sans filtre, `gold_v2.journaliere` fait 145 M de lignes. ### Lire une table avec PyIceberg ```python import os from pyiceberg.catalog.rest import RestCatalog import pyarrow.parquet as pq cat = RestCatalog("chom", uri=os.environ["ICEBERG_CATALOG_URI"], warehouse="diffusion", **{ "py-io-impl": "pyiceberg.io.fsspec.FsspecFileIO", # requis : le FileIO pyarrow ne gère pas cet endpoint S3 "s3.endpoint": os.environ["AWS_ENDPOINT_URL"], "s3.access-key-id": os.environ["AWS_ACCESS_KEY_ID"], "s3.secret-access-key": os.environ["AWS_SECRET_ACCESS_KEY"], "s3.region": "gra", "s3.path-style-access": "true", }) print([n[0] for n in cat.list_namespaces()]) print([t[1] for t in cat.list_tables("gold_dataclimat")]) t = cat.load_table("gold_dataclimat.records_absolus") paris = t.scan(row_filter="department = '75'").to_arrow() # predicate pushdown pq.write_table(paris, "records_paris.parquet") ``` `scan()` accepte aussi `selected_fields=(…)` pour la projection de colonnes. ### Analytique : DuckDB sur les métadonnées Pour agréger sans matérialiser en mémoire, branchez DuckDB directement sur le `metadata_location` de la table. ```python import duckdb con = duckdb.connect() con.execute("INSTALL iceberg; LOAD iceberg; INSTALL httpfs; LOAD httpfs") endpoint = os.environ["AWS_ENDPOINT_URL"].removeprefix("https://").rstrip("/") con.execute(f"""CREATE SECRET (TYPE S3, KEY_ID '{os.environ["AWS_ACCESS_KEY_ID"].strip()}', SECRET '{os.environ["AWS_SECRET_ACCESS_KEY"].strip()}', REGION 'gra', ENDPOINT '{endpoint}', URL_STYLE 'path')""") con.sql(f"SELECT * FROM iceberg_scan('{t.metadata_location}') WHERE department = '75'") # ou : COPY (…) TO 'extrait.parquet' (FORMAT PARQUET) ``` ### Limites actuelles Le bucket n'est pas lisible anonymement et il n'existe pas encore d'URL de téléchargement stables par dataset. Pour un besoin ponctuel ou un tiers sans client Iceberg, demandez une **URL présignée** — ou restez sur la REST API (§2). --- ## 5. Fraîcheur | Données | Rafraîchissement | Disponibilité | |---|---|---| | Datasets climato (`/v2`, `/legacy`, `/dataclimat`, `/ic`, `/statIC`, `/canicule`) | quotidien | J-1, à partir de 08:00 UTC | | Tier chaud / live | quotidien | quelques heures de latence | | Référentiel stations (`/ref`) | quotidien | J-1 | `/coverage` donne la vérité à l'instant t : ne codez pas en dur une hypothèse de fraîcheur, interrogez-la. Il porte **deux** fraîcheurs distinctes : ```json { "archive": {"oldest": "1777-01-01T00:00:00Z", "newest": "2026-08-04T02:30:00Z"}, "computed_at": "2026-08-04T05:16:27Z", "serving": {"instantane_resolu_a": "2026-08-04T19:36:27Z", "age_s": 42.3} } ``` `archive`, `live` et `computed_at` décrivent le **fonds** ; `serving` décrit le **processus qui répond** — quand il a résolu pour la dernière fois l'instantané Iceberg qu'il sert. `age_s` reste normalement sous 300 s ; un âge qui grandit sans retomber signale une boucle de rafraîchissement arrêtée, donc des données servies figées à cette date pendant qu'`archive` avance. --- ## 6. Pièges connus - **Précipitations.** `precip_1h` est un cumul *glissant* : les stations infra-horaires le rapportent ~4×/h. Les `rr` journaliers Gold appliquent une correction de phase dominante (une mesure par heure). Si vous recalculez un cumul depuis le canonique brut, vous sur-compterez. - **Records.** Les records `dataclimat` sont filtrés sur la **classe de siting** (≤ 3 pour les records absolus, ≤ 4 pour les normales). D'où Pontarlier −32,0 °C plutôt que Mouthe −36,7 °C. - **`classe_station` StatIC est toujours `null`** : il n'existe pas de classification de siting sur un réseau participatif. - **Datasets nationaux.** `/v2/itn` et `/statIC/indicateur_reseau` ignorent le paramètre `station`. - **Datasets sans axe temps.** Sur `records`, la clé est station × mois calendaire : `from` et `to` sont sans effet. - **Périmètre géographique.** Les DOM-TOM sont exclus de l'indicateur national StatIC ; `gold_ic` inclut des stations étrangères (réseau MNW). --- ## 7. Licence et citation Chaque réponse porte ses champs `licence` et `attribution` : **ce sont eux qui font foi**, ils diffèrent d'une famille à l'autre. En résumé : les données Météo-France sont sous **Licence Ouverte (Etalab) 2.0**, les agrégats et réseaux Infoclimat sous la **politique opendata Infoclimat**, avec citation de la source d'origine.