Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
d5ce027
adr: 0003-0007 — schéma canonique (lakehouse Iceberg, vocabulaire, id…
citarf Jul 11, 2026
f6d3e64
contracts: gold-climato-legacy (mensuelle)
citarf Jul 16, 2026
78409f9
contracts: gold-climato-legacy (+ decadaire, decadaire_agro)
citarf Jul 16, 2026
c9fa5e7
contracts: gold-climato-v2 (4 datasets recalcul canonique)
citarf Jul 16, 2026
fb92027
contracts: gold-climato-legacy (+ quotidienne, quotidienne_autres)
citarf Jul 16, 2026
fcb281a
contracts: gold-dataclimat (catégorie dédiée dataclimat.fr/D4G — 7 da…
citarf Jul 18, 2026
7f69caa
contracts: descriptions orientées usage (public) au lieu de la logiqu…
citarf Jul 18, 2026
a431845
contracts(gold-dataclimat): préciser le format AAAAMM de first_temper…
citarf Jul 18, 2026
8d1db3c
contracts(gold-ic): nouveau contrat climato réseau IC pur
citarf Jul 18, 2026
e305edb
contracts(gold-statIC): climato du réseau participatif StatIC
citarf Jul 19, 2026
4688ac1
contracts(gold-statIC): + indicateur_reseau (ITN-StatIC représentatif…
citarf Jul 19, 2026
e347538
contracts(gold-ic): clarifie 'collecte Infoclimat hors MF' (pas un ré…
citarf Jul 19, 2026
63acf5f
contract gold-dataclimat: classe_recente sourcée + exposée (déviation…
citarf Jul 19, 2026
c574431
contract gold-dataclimat: dataset records_battus (historique progress…
citarf Jul 19, 2026
b22eaeb
feat(contracts): gold-ref (référentiel stations Gold, 2 datasets)
citarf Jul 22, 2026
3e719eb
contract(gold-canicule): socle observatoire canicule (station_insee, …
citarf Jul 24, 2026
165a80c
contract(gold-canicule): support en liste (conforme ODCS v3 + contrat…
citarf Jul 24, 2026
39ff356
contract(gold-canicule): 4e table commune_couverture (statut couvertu…
citarf Jul 26, 2026
7a13308
contract(gold-canicule): limitations à jour (commune_couverture préca…
citarf Jul 26, 2026
f4831f2
feat(contracts): gold_statIC.stations — annuaire StatIC station × par…
citarf Jul 30, 2026
238215e
docs(gold-ref): publiable, libelle_source, et ic_id désigné canonique
citarf Aug 1, 2026
0775b3b
fix(contracts): corrige la description de ic_id dans gold-ref (StatIC…
citarf Aug 1, 2026
8a5a6e1
chore: ignore .DS_Store et audits/bdd (volet commercial, dépôt public)
citarf Aug 1, 2026
d045021
docs(adr-0004): amendement NOTE-A — l'historique MF est en unités rée…
citarf Aug 1, 2026
7373330
docs(adr-0007): amendement — obs_last.source_prio (préséance à l'upsert)
citarf Aug 1, 2026
011b0f2
feat(rgpd): registre art. 30 v2 — en-tête responsable/socle + champs …
citarf Aug 1, 2026
0dec714
feat(catalog): preseance.yaml v1.0.0 — règles de fusion des sources (…
citarf Aug 1, 2026
d0bbe4c
feat(catalog): parametres.yaml — vocabulaire contrôlé (lot A3, BROUIL…
citarf Aug 1, 2026
9f0d176
docs(adr-0008): prévisions numériques — dimensions partagées, table s…
citarf Aug 1, 2026
f82b6d1
chore(tools): uv.lock (verrouillage des dépendances de l'outillage)
citarf Aug 1, 2026
a86cfb0
fix(contracts): serveur des 7 contrats gold conforme ODCS v3 (s3 + fo…
citarf Aug 1, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,10 @@ __pycache__/
# IDE
.idea/
.vscode/

# macOS
.DS_Store

# Réponse à l'appel d'offres BDD (BPU/DQE, mémoire technique, résumés CA) —
# volet commercial CONFIDENTIEL : ce dépôt est public, ces pièces n'y entrent pas.
audits/bdd/
112 changes: 112 additions & 0 deletions adr/0003-format-table-iceberg-lakehouse.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
# ADR-0003 — Apache Iceberg comme format de table du lakehouse d'observations

- Statut : acceptée (cadre R&D)
- Date : 2026-07-11
- Décideurs : data engineer (pam)

> Issue de l'étude R&D « évolution de schéma » du 2026-07-11 (hub sémantique en lakehouse,
> couches Bronze/Silver/Gold, table canonique `observation`). Cette ADR fixe le format de
> table ; le vocabulaire des paramètres et l'identité des stations sont traités par
> ADR-0004 et ADR-0005. Cadre : R&D open-source, sans contrainte d'infra ni de budget.

## Contexte

Le patrimoine d'observations vit dans deux systèmes aux modèles divergents : MariaDB
`V5_data_*` (~794 GiB, 4,4 Md lignes, sharding par nommage — 20 292 tables) et
TimescaleDB (~780 GiB, wide Météo-France). L'étude R&D conclut à un lakehouse ouvert :
Parquet sur stockage objet + format de table transactionnel + catalogue, moteurs de
requête interchangeables.

Un pilote existe déjà : `climato-froid` a migré le synop pré-2000 (392,7 M lignes) en
**Delta Lake** sur R2 — compression 6× index compris, requêtes sub-secondes en DuckDB,
principes acquis (tri `(station, date)` + zstd + partition pruning, fidélité d'archive,
overwrite prédiqué par partition, réconciliation d'historiques divergents).

Trois candidats pour généraliser :

- **Delta Lake** (continuité du pilote) : delta-rs mûr en Python/Rust, mais catalogue
ouvert moins standardisé hors écosystème Databricks, et features récentes (variant,
row lineage) tirées par un seul éditeur.
- **Apache Iceberg** : le format le plus neutre — spec v3 (type `variant` pour le
semi-structuré, deletion vectors, row lineage), catalogues REST open-source
interchangeables (Lakekeeper, Apache Polaris, Nessie), lu/écrit par DuckDB, Trino,
Spark, ClickHouse, pyiceberg/iceberg-rust.
- **DuckLake** : catalogue = un simple Postgres, très peu de pièces mobiles — séduisant
pour une équipe mono-steward, mais jeune et couplé à l'écosystème DuckDB.

## Décision

**Apache Iceberg (spec v3) est le format de table des nouvelles tables du lakehouse**
(Bronze, Silver, Gold de l'étude). Modalités :

1. **Catalogue REST léger.** Un catalogue Iceberg REST open-source auto-hébergé ;
le choix de l'implémentation (Lakekeeper vs Polaris) se fait par un **spike mesuré**
(critères : coût opérationnel mono-steward, auth simple, backup trivial) — c'est un
détail d'exécution, pas une décision d'architecture, il ne bloque rien : pyiceberg
sait démarrer sur un catalogue SQL minimal.

*Précision du 2026-07-11 (décision d'hébergement full OVH, critère de
souveraineté européenne)* : les catalogues Iceberg **managés** hors UE
(ex. Cloudflare R2 Data Catalog, qui inclut aussi la compaction managée)
sont **exclus par principe**, bien qu'économiquement attractifs —
l'auto-hébergé est confirmé. Conséquence assumée : la **maintenance des
tables (expire_snapshots + compaction) reste à notre charge**, job
hebdomadaire non optionnel (leçon du POC : 1 227 snapshots =
+400 ms/requête en lecture par métadonnées).
2. **Conventions physiques héritées du pilote** : partition par `annee` (+ bucket sur
le paramètre pour la table canonique long, cf. étude), tri `(station_uid, dh_utc)`,
compression zstd, schémas Arrow explicites dérivés des contrats — famille sans
contrat = refus d'écrire.
3. **`variant` pour les payloads bruts.** `raw_msg` (synop, bouées), `donnees`
(ACARS, profils) vont en Bronze dans une colonne `variant` typée — fin du `text`
non requêtable, sans inventer un schéma pour du semi-structuré.
4. **Le pilote Delta reste tel quel.** `climatologie-cold` (R2) n'est pas re-commité :
DuckDB lit les deux formats, l'API climato-froid ne change pas. Migration éventuelle
vers Iceberg = décision d'exécution future, sur besoin réel (ex. unification du
catalogue), jamais par principe.
5. **Chaque table Iceberg = un contrat ODCS** (`physicalType: iceberg-table`), versionnée
selon ADR-0002 (bump semver + `changelog:` + RunEvent OpenLineage). Le namespace
lineage s'étend : `iceberg://<catalogue>/<table>` à ajouter aux conventions
(`lineage/namespaces.md`).

## Justification

- **Neutralité moteur = la garantie R&D.** Le même jeu de tables doit être requêtable
par DuckDB (notebooks, API), Trino (SQL fédéré), Spark (ML distribué) et polars sans
copie. Iceberg est le seul des trois candidats dont le catalogue REST est un standard
multi-implémentations.
- **v3 couvre deux besoins précis du dossier** : `variant` (payloads bruts en Bronze)
et row lineage (traçabilité au grain ligne, complète OpenLineage au grain job).
- **L'expérience acquise transfère.** Les concepts delta-rs (log, snapshots, overwrite
prédiqué, vacuum) ont leurs équivalents directs Iceberg ; les pièges documentés du
pilote (historiques divergents, sync fichiers interdite) s'appliquent à l'identique.
- **DuckLake écarté comme choix par défaut, pas enterré** : la frugalité opérationnelle
est réelle, mais parier le schéma canonique sur un format jeune et mono-écosystème
contredit l'objectif d'interopérabilité. Voir critère de réouverture.

## Conséquences

- (+) Toute table du lakehouse est lisible par l'écosystème entier sans export ; les
datasets publics (phase 3 de l'étude) sont des tables Iceberg exposées telles quelles.
- (+) Schéma évolutif sans réécriture (add column, rename sûrs par ID de colonne) —
cohérent avec le versioning ODCS.
- (−) **Une pièce d'infra en plus** (le catalogue REST) à opérer, sauvegarder,
superviser — le point faible assumé face à DuckLake. Mitigation : spike de choix
orienté « coût mono-steward », et démarrage possible sur catalogue SQL pyiceberg.
- (−) Deux formats coexistent (Delta du pilote + Iceberg) tant que `climatologie-cold`
n'est pas migrée — acceptable car lecteurs communs, mais à documenter au catalogue.
- (−) L'écriture Python passe de delta-rs à pyiceberg : re-valider les débits
d'extraction du pilote (counts exacts, reprise idempotente) sur une année témoin.

**Critère de réouverture** : si après 6 mois le catalogue REST s'avère être le poste
de coût opérationnel dominant du chantier (incidents, temps d'admin), ré-évaluer
DuckLake — la donnée reste en Parquet, le coût de conversion est un re-commit de
métadonnées, pas une réécriture.

## Références

- Étude R&D « évolution de schéma data » 2026-07-11 (workspace, hors repo — à ranger)
- Pilote : repo local `climato-froid/` (README — conventions physiques, pièges Delta/R2)
- ADR-0002 (versioning de schéma = fait de lineage — s'applique aux tables Iceberg)
- `lineage/namespaces.md` (namespace `iceberg://` à ajouter)
- Apache Iceberg spec v3 ; pyiceberg / iceberg-rust ; Lakekeeper, Apache Polaris (spike)
178 changes: 178 additions & 0 deletions adr/0004-vocabulaire-parametres-observation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
# ADR-0004 — Vocabulaire contrôlé des paramètres d'observation (`dim_parametre`)

- Statut : proposée
- Date : 2026-07-11
- Décideurs : data engineer (pam)

> Phase 0 de l'étude R&D « évolution de schéma » du 2026-07-11, volet 1/2 (le volet
> identité des stations est ADR-0005). Principe directeur hérité d'ADR-0002 :
> **git fait foi, les tables et les tools sont des vues.**

> **Amendée le 2026-07-11** (audit de traduisibilité legacy,
> `audit-traduisibilite-legacy-2026-07.md`) : ajout des conventions §7
> (dimensions verticales/couches dans la clé, marqueur `porte_occurrence`,
> frontière profils verticaux) et recadrage du volume cible (~140 entrées —
> l'audit a inventorié les 94 colonnes wide MF réellement consommées, les 34
> sous-champs packés de `static.complements` et les codes `_H` de `mf_data`
> lus par mobile-api).

## Contexte

Il n'existe aucune définition partagée de « ce qu'est un paramètre mesuré ». La même
grandeur physique vit sous des formes incompatibles selon le système :

- **Noms** : `temperature` (familles IC), `T` (wide MF historique), `t` (temps réel MF,
en Kelvin dans l'API source), codes `_H` MF (`TSV_H`, `GLO2_H`…), éléments ECA&D
(`TX`, `TN`, `RR`), colonnes climato (`tn`, `tx`, `rr`, `ens`).
- **Types** : `temperature` en `float` (synop/static), `tinyint` (metar — troncature
au degré entier), `double` (bouées, mf_data) ; `nebulosite` en `enum('0'..'8')` ici,
`int(2)` ou `tinyint(4)` là.
- **Unités** : unités RÉELLES (°C, hPa, mm) dans l'historique Timescale — corrigé par
l'amendement du 2026-07-13 ci-dessous : le contrat annonçait une convention au 1/10,
l'échantillonnage prod l'a réfutée — unités usuelles converties (°C, hPa, m/s) dans
les tables temps réel, SI (K, Pa) dans l'API brute ; le piège K vs °C est déjà
documenté comme incohérence entre contrats (`climato-mf-timescale` vs
`horaire-mf-timescale`).
- **Support temporel encodé dans les noms de colonnes** : `pluie_1h`/`pluie_3h`/
`pluie_6h`/`pluie_12h`/`pluie_24h`/`pluie_cumul_0h`, `temperature_min`/`_max` sur des
fenêtres implicites, `vent_rafales` vs `vent_rafales_10min` — la durée est une
dimension, pas une grandeur, mais le schéma actuel les confond.
- **Dictionnaires hors modèle** : `static_qualite.id_parametre` et
`static_instruments.id_instrument` définis dans des `COMMENT` SQL (0=Température,
1=Vent…), non requêtables, non versionnés.

Toute fusion multi-source, tout export harmonisé, toute réponse LLM sur la donnée bute
sur cette absence de vocabulaire. Le repo a déjà l'embryon : `catalog/glossary.md`
(prose) et les contrats sources MF qui documentent champ par champ unités et types.

## Décision

Créer un **vocabulaire contrôlé des paramètres**, artefact versionné du repo, source
unique de vérité dont tables et outils dérivent.

1. **Source de vérité : `catalog/parametres.yaml`.** Une entrée par grandeur, clés :
- `parametre` (clé stable snake_case, ex. `air_temperature`, `precipitation_amount`,
`wind_speed_of_gust`) ;
- `cf_standard_name` (conventions CF quand il existe, sinon `null` + justification) ;
- `unite_si` (unité de stockage canonique : K, Pa, m/s, kg m-2, %…) ;
- `domaine` (bornes physiques plausibles min/max — socle des contrôles qualité) ;
- `description_fr` ;
- `sources` : le **mapping par système** — colonne famille IC (`synop.temperature`),
code MF historique (`T`, facteur 1 — °C réels, amendement 2026-07-13), colonne
temps réel (`t`, K), code `_H`,
élément ECA&D, id du dictionnaire `static_qualite` — avec pour chacun le facteur
et l'offset de conversion vers l'unité SI.
Ordre de grandeur : ~150 entrées ; la matière existe déjà (contrats
`source-meteofrance-*`, DDL MariaDB, glossaire) — c'est une transcription outillée,
pas une découverte.
2. **Le support temporel est une dimension, pas un paramètre.** Le couple
`(parametre, duree_s)` remplace les familles de colonnes : `precipitation_amount` ×
{3600, 10800, 21600, 43200, 86400} couvre `pluie_1h`…`pluie_24h` ;
`air_temperature_min` disparaît au profit de (`air_temperature`, agrégat min,
`duree_s`). Le vocabulaire liste pour chaque paramètre les supports **attendus** par
source (sert de test de complétude à l'ingestion).
3. **Convention de nommage : CF d'abord.** Quand CF définit un standard name, la clé le
suit ; les spécificités Infoclimat (ex. qualité déclarée StatIC) prennent un préfixe
`ic_`. Ni français ni codes MF dans les clés — le français vit dans `description_fr`,
les codes MF dans les mappings.
4. **Matérialisations dérivées** (jamais éditées à la main) :
- table Iceberg `dim_parametre` (ADR-0003) régénérée depuis le YAML ;
- blocs `schema.properties` des futurs contrats ODCS des tables canoniques,
**générés** depuis le vocabulaire (fin de la double saisie contrat/vocab) ;
- vue MCP/bot (précédent ADR-0001/0002 : le tool lit git) — le text-to-SQL et les
réponses du bot s'appuient sur le vocabulaire, pas sur une tradition orale.
5. **Évolution selon ADR-0002** : ajout de paramètre = MR + entrée changelog
(`non-breaking`) ; changement d'unité ou de sémantique d'une clé existante =
`breaking` (et donc, en pratique, création d'une nouvelle clé + dépréciation —
patron `replace` d'ADR-0002, qui s'applique tel quel).
6. **Validation en CI** : lint du YAML (unicité des clés, unités reconnues, bornes
cohérentes, mappings sans collision) + test croisé contre les contrats sources
(tout champ actif d'un contrat `source-meteofrance-*` doit être mappé ou
explicitement listé `non_retenu`).
7. **Conventions d'extension** (amendement 2026-07-11, issues de l'audit de
traduisibilité) :
- **Dimensions verticales et couches encodées dans la clé** quand la
cardinalité est **bornée et fixe** : `soil_temperature_10cm/_20cm/_50cm/
_100cm`, `cloud_layer_1_area_fraction`…`cloud_layer_4_*` — précédent
GHCN-Daily (profondeur et couverture encodées dans le code élément).
**Frontière explicite** : cette convention ne s'étend PAS aux niveaux
nombreux ou variables — les profils verticaux (radiosondages, ACARS)
exigeront une vraie colonne de niveau ou une modélisation `profile`
dédiée (featureType CF), jamais une explosion de clés.
- **Marqueur `porte_occurrence: true`** sur les paramètres agrégés dont la
source fournit l'heure/date d'occurrence (TN/HTN, TX/HTX, rafales/HXY,
records de normales…) : déclare que la mesure alimente la colonne
`dh_occurrence` du modèle canonique (cf. ADR-0007 amendée). Le couple
(valeur, occurrence) reste atomique — c'est le motif retenu contre le
« paramètre apparié » (`time_of_maximum` séparé), qui expose des couples
orphelins au QC et au filtrage.
- **Mesures techniques** (tension batterie, facteur qualité SR50 — issues de
`static.complements`) : entrées de plein droit avec `domaine` technique,
taguées `technique: true` pour exclusion par défaut des produits météo.

## Justification

- **Les conversions d'unités deviennent structurellement uniques.** Un seul endroit
(le mapping) porte par exemple « uv StatIC = centi-index, facteur 1/100 » (vérifié
par échantillon prod, revue A3) ; la conversion vers l'affichage vit dans les vues
Gold, une fois, testée. Le bug K vs °C cesse d'être possible par construction.
- **Évolution additive sans ALTER.** Un nouveau capteur StatIC ou un nouveau champ MF
= une entrée YAML + une ligne de mapping — pas de migration sur des tables de
centaines de GiB, pas de bump majeur en cascade.
- **C'est le prérequis du format long.** La table canonique `observation` de l'étude
n'a de sens que si `parametre` référence un vocabulaire fermé et versionné.
- **Interopérabilité gratuite** : les clés CF alignent les exports sur ce que xarray,
MetPy, les catalogues climat et les équipes recherche attendent.

## Conséquences

- (+) Fin des dictionnaires en COMMENT SQL : `static_qualite`/`static_instruments`
se raccordent au vocabulaire par mapping explicite.
- (+) Les ~150 entrées documentent au passage les décisions d'harmonisation
(ex. metar tronqué au degré : mappé avec `qc_detail` de précision — la perte est
tracée, pas silencieuse).
- (+) Le MCP répond « quelles variables de vent existe-t-il et dans quelles sources ? »
depuis git, offline.
- (−) Coût initial de transcription et d'arbitrage (~150 entrées, quelques cas
ambigus : grandeurs MF sans équivalent CF, cumuls à heure d'occurrence en `char(5)`) ;
mitigé en démarrant par le noyau commun aux 3 modèles (~30 paramètres couvrent >95 %
des lignes).
- (−) Une dépendance de plus pour les pipelines (le vocabulaire devient bloquant à
l'ingestion canonique) — c'est voulu : paramètre inconnu = refus d'écrire en Silver,
la donnée reste en Bronze en attendant la MR de vocabulaire.

**Critère de réouverture** : si le YAML unique devient ingérable (>500 entrées,
conflits de MR fréquents), éclater par domaine (`parametres/{thermo,vent,precip}.yaml`)
sans changer le contrat d'interface des vues.

## Amendement 2026-07-13 — unités de l'historique Timescale MF (NOTE-A)

La version initiale de cet ADR affirmait, sur la foi du contrat
`source-meteofrance-*`, que l'historique Timescale MF suivait les « conventions
MF au 1/10 » (°C, mm stockés en dixièmes). **C'est faux, et corrigé** :
l'échantillonnage prod du 2026-07-12 (SELECT sur `Horaire`, années 1980/1989/
2024 : T min −30,3 / max 36,9 / moyenne 13,1 en 1980 ; PMER en hPa réels) est
sans ambiguïté — **les valeurs sont en unités réelles, `facteur = 1`** dans
tous les mappings concernés du vocabulaire.

Conséquences :
1. le contrat ODCS source est à corriger (il documentait une convention que la
donnée ne suit pas) ;
2. règle de méthode ajoutée à la revue A3 : **toute unité douteuse se tranche
par échantillon prod, jamais par le seul contrat ou la seule doc** (patron
`sample_unites`) — le contrat décrit une intention, l'échantillon décrit la
donnée ;
3. les 20 mappings encore `unite_incertaine` restent exclus de l'unpivot
(garde `load_vocab`) tant qu'ils n'ont pas leur échantillon.

## Références

- Étude R&D « évolution de schéma data » 2026-07-11 (§4.4, §8 mapping de convergence)
- `catalog/glossary.md` (embryon prose), `contracts/source-meteofrance-*.odcs.yaml`
(unités/champs bruts déjà transcrits)
- Dump `schemas/mariadb/schema.sql.gz` (dictionnaires en COMMENT, familles de colonnes)
- ADR-0002 (changelog, patron `replace`, sévérités) ; ADR-0003 (matérialisation Iceberg)
- Audit de traduisibilité legacy 2026-07-11 (`audit-traduisibilite-legacy-2026-07.md`,
workspace — inventaire des colonnes réellement consommées, source du §7)
- CF Standard Names (conventions Climate & Forecast) ; vocabulaire OMM/WIGOS ;
GHCN-Daily (précédent de l'encodage profondeur/couche dans le code élément)
Loading
Loading