Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
181 changes: 181 additions & 0 deletions contracts/silver.observation_v2.odcs.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
apiVersion: v3.0.2
kind: DataContract
id: urn:infoclimat:contract:silver-observation-v2
name: Silver observation v2 (dataset Iceberg réellement servi)
version: 0.1.0
status: active
domain: observations
dataProduct: silver-observation-v2

description:
purpose: >-
Table Iceberg canonique des observations (IC + Météo-France), unpivotée depuis
Bronze et servie aux pipelines Gold. infoclimat-labs/chom-poc-data#79 l'étend
de façon additive avec un jour civil de station et son fuseau IANA versionné ;
aucune table observation_v3 ni reconstruction complète n'est autorisée.
usage: >-
Consommer dh_utc (instant UTC réel) pour toute agrégation ; jour_climatologique_local
et tz_iana ne sont exploitables que par paire atomique, pour les usages qui ont besoin
du jour civil de la station plutôt que du jour UTC.
limitations: >-
Une station sans fuseau contrôlé (sans_controle) n'est jamais écrite ici avec un
fuseau deviné : ses observations sont retirées de ce dataset et publiées dans
silver.observation_v2_quarantine (infoclimat-labs/chom-poc-data#176), sans bloquer
la migration du reste du lot. jour_climatologique_local et tz_iana restent null pour
toute ligne antérieure au rétro-remplissage #79.

tags:
- meteo
- observations
- meteofrance
- silver
- iceberg
- schema-canonique

servers:
- server: lakekeeper-warehouse-ic
type: custom
environment: production
catalog: chom
warehouse: ic
format: iceberg
description: >-
Catalogue REST Lakekeeper (pyiceberg RestCatalog "chom", warehouse "ic" —
infoclimat-labs/chom-poc-data@d0be025c, scripts/remote_catalog.py).
Endpoint S3 et bucket physiques injectés par l'environnement d'exécution
(AWS_ENDPOINT_URL/AWS_ACCESS_KEY_ID), non fixés dans le code et donc non
vérifiables depuis ce dépôt gouverné : aucun serveur "warehouse-iceberg" ni
bucket S3 concret n'existe. "iceberg://warehouse" (lineage/jobs.yaml) est
l'identité de namespace OpenLineage de ce catalogue, pas une URI physique.

schema:
- name: observation
physicalName: silver.observation_v2
physicalType: table
description: >-
Observation unpivotée (une ligne par station × instant × paramètre), triée et
partitionnée par mois de dh_utc.
properties:
- name: station_uid
physicalType: TEXT
required: true
description: UID interne de la station observée.
- name: dh_utc
physicalType: TIMESTAMP
required: true
description: Instant UTC réel ; les producteurs MF convertissent leur heure locale contrôlée.
- name: parametre
physicalType: TEXT
required: true
description: Clé du vocabulaire de paramètre (catalog/parametres.yaml).
- name: valeur
physicalType: DOUBLE
description: Valeur mesurée, en unité canonique du vocabulaire.
- name: duree_s
physicalType: INTEGER
description: Durée d'intégration de la mesure, en secondes.
- name: qc_flag
physicalType: TEXT
description: Drapeau de contrôle qualité amont.
- name: source
physicalType: TEXT
required: true
description: >-
Identifiant de source (ex. mf-h, mf-i, mf-q, synop-ic, static-ic, metar-ic,
bouees-ic).
- name: version_obs
physicalType: INTEGER
required: true
description: >-
Version de la ligne d'observation. Les writers vus à d0be025c (ex.
catchup_silver_delta.py:586-607) assignent uniquement version_obs=1 : rien
côté producteur ne prouve qu'il disambigue effectivement des révisions
coexistantes d'une même (station_uid, dh_utc, parametre, source). Le canonique
append-only porte déjà des doublons connus, explicitement signalés par des
consommateurs Gold (gold_v2_journaliere.py:46-54).
- name: ingest_run_id
physicalType: TEXT
required: true
description: Run ayant produit ou fait progresser cette ligne.
- name: jour_climatologique_local
physicalType: DATE
required: false
description: >-
Jour civil de station matérialisé avec tz_iana. Null seulement avant le
rétro-remplissage #79 ; jamais calculé par défaut pour une station en
quarantaine (#176).
- name: tz_iana
physicalType: TEXT
required: false
description: >-
Fuseau IANA versionné ayant servi au jour local ; paire atomique avec
jour_climatologique_local.
quality:
- rule: temporal_pair_is_atomic
description: jour_climatologique_local et tz_iana sont tous deux null ou tous deux renseignés.
severity: error
- rule: controlled_timezone_only
description: >-
Les stations sans_controle ne sont jamais écrites avec un fuseau deviné :
leurs observations sont retirées de ce dataset et publiées dans
silver.observation_v2_quarantine (#176), sans bloquer la migration du reste
du lot.
severity: error
- rule: dst_is_diagnostic
description: Une heure MF ambiguë ou inexistante déclenche un arrêt fail-closed du lot amont.
severity: error
- rule: identity_and_uniqueness_not_established
description: >-
Ce contrat NE déclare AUCUN primaryKey et n'affirme aucune clé de ligne
unique : à d0be025c, aucun writer producteur ne prouve l'unicité de
(station_uid, dh_utc, parametre, source), avec ou sans version_obs — les
writers vus assignent seulement version_obs=1 (catchup_silver_delta.py:586-607),
et le canonique append-only porte déjà des doublons connus, explicitement
signalés par des consommateurs Gold (gold_v2_journaliere.py:46-54). Iceberg
n'impose de toute façon aucune contrainte d'unicité. C'est un POC append-only :
l'identité/unicité de ligne est une question ouverte, pas une garantie de ce
contrat.
severity: warning

team:
- username: pam
role: owner

support:
- channel: data
tool: email
url: mailto:pamahe@proton.me

customProperties:
- property: source
value: >-
bronze.synop (synop-ic) + bronze.static (static-ic) + bronze.metar (metar-ic) +
bronze.bouees (bouees-ic) + bronze.mf_horaire (mf-h) + bronze.mf_infrahoraire
(mf-i) + bronze.mf_quotidienne (mf-q) + dim.station, lus exclusivement par
batch.catchup_silver_delta.
batch.silver_v2_temporal_backfill ne lit aucun Bronze : il rejoue silver.observation_v2
lui-même (cf. lineage/jobs.yaml).
- property: lineage
value: >-
batch.catchup_silver_delta : bronze.synop (synop-ic) + bronze.static (static-ic) +
bronze.metar (metar-ic) + bronze.bouees (bouees-ic) + bronze.mf_horaire (mf-h) +
bronze.mf_infrahoraire (mf-i) + bronze.mf_quotidienne (mf-q) + dim.station +
auto-lecture silver.observation_v2 (curseur par station) + auto-lecture silver.observation_v2_quarantine
(curseur_quarantine_mf) -> silver.observation_v2 (station à fuseau contrôlé) ou
silver.observation_v2_quarantine (station sans_controle, #176), append. |
batch.silver_v2_temporal_backfill : auto-lecture silver.observation_v2 (coeur
légataire, jamais Bronze) + auto-lecture silver.observation_v2_quarantine
(recompte de preuve) -> silver.observation_v2 (overwrite borné) et
silver.observation_v2_quarantine (écrite en premier).
- property: lineageJob
value: >-
{"datasets":{"silver.observation_v2":{"producers":["batch.silver_v2_temporal_backfill","batch.catchup_silver_delta"],"consumers":["batch.gold_ref_station_parametre","batch.gold_dataclimat_quotidienne","batch.gold_v2_journaliere","batch.gold_ic_journaliere","batch.gold_statIC_journaliere","batch.gold_canicule_station_saison","batch.gold_qc_pics"]}}}
- property: dataLicense
value: >-
Données Météo-France (Licence Ouverte Etalab 2.0) et sources IC internes
(synop/static/metar/bouées), politique opendata IC ; cf. contrats sources
(infrahoraire-mf, static-stations-obs, synop-metar-reseau-etranger).
- property: retroCompatLayer
value: N/A — pipeline Iceberg POC (infoclimat-labs/chom-poc-data), pas de couche PHP dual-source.
- property: issues
value: infoclimat-labs/chom-poc-data#79, #176, #178
186 changes: 186 additions & 0 deletions contracts/silver.observation_v2_quarantine.odcs.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
apiVersion: v3.0.2
kind: DataContract
id: urn:infoclimat:contract:silver-observation-v2-quarantine
name: Silver observation v2 — quarantaine fuseau non contrôlé
version: 0.1.0
status: active
domain: observations
dataProduct: silver-observation-v2

description:
purpose: >-
Quarantaine explicite, traçable et rejouable des observations dont la station est
sans_controle au moment du rétro-remplissage borné (infoclimat-labs/chom-poc-data#79)
ou du writer incrémental (#176). Documente ce qui a été RETIRÉ de
silver.observation_v2, pas une source active — cohérente avec la politique
quarantined_unresolved déjà appliquée par #83 (batch.station_observation_coverage).
usage: >-
Dataset interne de traçabilité (quarantine_reason, decision_id, quarantined_run_id,
quarantined_at) : jamais lu par le serving ni par un pipeline Gold. Il EST en
revanche relu opérationnellement par les deux mêmes jobs Silver qui l'écrivent
(infoclimat-labs/chom-poc-data@d0be025c) : batch.catchup_silver_delta y lit le
curseur par station déjà quarantinée (curseur_quarantine_mf, fusionné au curseur
Bronze pour éviter une re-quarantaine en double) et batch.silver_v2_temporal_backfill
y recompte sa preuve avant=temporalisées+quarantinées après chaque écriture — deux
auto-lectures internes, distinctes d'une consommation Gold/serving.
limitations: >-
Jamais lu par le serving ni par Gold — aucun consommateur Gold/serving n'est déclaré
pour ce dataset (cf. lineageJob.consumers pour les deux auto-lectures Silver
opérationnelles réelles). Une station qui redevient contrôlée après sa quarantaine
n'est pas rejouée automatiquement depuis cette table (limite assumée #176) ; sa
réintégration dans silver.observation_v2 est un geste explicite hors périmètre,
comme tout UPDATE/MERGE/DELETE sur silver.observation_v2.

tags:
- meteo
- observations
- meteofrance
- silver
- iceberg
- quarantaine

servers:
- server: lakekeeper-warehouse-ic
type: custom
environment: production
catalog: chom
warehouse: ic
format: iceberg
description: >-
Catalogue REST Lakekeeper (pyiceberg RestCatalog "chom", warehouse "ic" —
infoclimat-labs/chom-poc-data@d0be025c, scripts/remote_catalog.py).
Endpoint S3 et bucket physiques injectés par l'environnement d'exécution
(AWS_ENDPOINT_URL/AWS_ACCESS_KEY_ID), non fixés dans le code et donc non
vérifiables depuis ce dépôt gouverné : aucun serveur "warehouse-iceberg" ni
bucket S3 concret n'existe. "iceberg://warehouse" (lineage/jobs.yaml) est
l'identité de namespace OpenLineage de ce catalogue, pas une URI physique.

schema:
- name: observation_quarantine
physicalName: silver.observation_v2_quarantine
physicalType: table
description: >-
Une ligne par observation retirée de silver.observation_v2 parce que sa station
est sans_controle (#78) au moment de l'écriture, additive uniquement.
properties:
- name: station_uid
physicalType: TEXT
required: true
description: UID interne de la station observée.
- name: dh_source_local
physicalType: TIMESTAMP
required: true
description: >-
Heure civile locale telle que portée par la source (Bronze) ; jamais convertie
en UTC, faute de fuseau IANA versionné pour cette station.
- name: parametre
physicalType: TEXT
required: true
description: Clé du vocabulaire de paramètre (catalog/parametres.yaml).
- name: valeur
physicalType: DOUBLE
description: Valeur mesurée, en unité canonique du vocabulaire.
- name: duree_s
physicalType: INTEGER
description: Durée d'intégration de la mesure, en secondes.
- name: qc_flag
physicalType: TEXT
description: Drapeau de contrôle qualité amont.
- name: source
physicalType: TEXT
required: true
description: Identifiant de source MF (mf-h, mf-i, mf-q).
- name: version_obs
physicalType: INTEGER
required: true
description: Version de la ligne d'observation.
- name: ingest_run_id
physicalType: TEXT
required: true
description: ingest_run_id d'origine de l'observation legacy, jamais réémis.
- name: quarantine_reason
physicalType: TEXT
required: true
description: Motif de la mise en quarantaine (station_sans_controle_timezone).
- name: decision_id
physicalType: TEXT
required: true
description: >-
"policy:mf_timezone_quarantine/v1" — décision de politique, pas une décision
station par station comme le registre sourcé de #83.
- name: quarantined_run_id
physicalType: TEXT
required: true
description: Identifie le run (#79 borné ou #176 incrémental) qui a écrit la ligne.
- name: quarantined_at
physicalType: TIMESTAMP
required: true
description: Instant UTC d'écriture de la ligne en quarantaine.
quality:
- rule: never_served
description: >-
Aucun contrat serving/Gold ne lit cette table (cf. lineageJob.consumers :
seuls batch.catchup_silver_delta et batch.silver_v2_temporal_backfill —
les deux mêmes producteurs Silver — la relisent, pour leur curseur/preuve
opérationnels, jamais pour du serving) ; une ligne présente ici correspond à
une observation RETIRÉE de silver.observation_v2 (station sans_controle) pour
(station_uid, parametre, source) et son instant source dh_source_local — le
seul champ d'horodatage réel de ce schéma, non converti en UTC faute de fuseau
IANA versionné pour cette station.
severity: error
- rule: replayable_by_predicate
description: >-
Le rétro-remplissage borné écrit par overwrite(source, coeur année×famille) ;
le writer incrémental complète par append filtré sur un curseur par station
dans le domaine source (jamais dh_utc Silver) — même garantie d'absence de
doublon des deux côtés.
severity: error
- rule: no_automatic_reintegration
description: >-
Une station qui devient contrôlée après sa quarantaine n'est PAS rejouée
automatiquement depuis cette table (limite assumée #176).
severity: warning
- rule: identity_and_uniqueness_not_established
description: >-
Ce contrat NE déclare AUCUN primaryKey, à l'image de silver.observation_v2.
Aucune clé de ligne unique n'est établie côté producteur à d0be025c : ni
(station_uid, dh_source_local, parametre, source) ni une variante incluant
version_obs (les writers vus assignent seulement version_obs=1). C'est un POC
append-only : l'identité/unicité de ligne est une question ouverte, pas une
garantie de ce contrat.
severity: warning

team:
- username: pam
role: owner

support:
- channel: data
tool: email
url: mailto:pamahe@proton.me

customProperties:
- property: source
value: >-
bronze.mf_horaire (mf-h) + bronze.mf_infrahoraire (mf-i) + bronze.mf_quotidienne
(mf-q) + dim.station, lus exclusivement par batch.catchup_silver_delta ;
lignes retirées de silver.observation_v2, jamais une source active.
- property: lineage
value: >-
batch.catchup_silver_delta : bronze.mf_horaire (mf-h) + bronze.mf_infrahoraire (mf-i)
+ bronze.mf_quotidienne (mf-q) + dim.station -> silver.observation_v2_quarantine
(station sans_controle, #176), append ; relit aussi cette table (curseur_quarantine_mf)
pour ne pas re-quarantiner deux fois la même ligne. |
batch.silver_v2_temporal_backfill : écrit silver.observation_v2_quarantine EN PREMIER
(overwrite borné, aucun Bronze lu) puis relit son propre écrit pour recompter sa preuve.
- property: lineageJob
value: >-
{"datasets":{"silver.observation_v2_quarantine":{"producers":["batch.silver_v2_temporal_backfill","batch.catchup_silver_delta"],"consumers":["batch.silver_v2_temporal_backfill","batch.catchup_silver_delta"]}}}
- property: dataLicense
value: >-
Données Météo-France (Licence Ouverte Etalab 2.0) ; dataset interne de
traçabilité, jamais redistribué (never_served).
- property: retroCompatLayer
value: N/A — pipeline Iceberg POC (infoclimat-labs/chom-poc-data), pas de couche PHP dual-source.
- property: issues
value: infoclimat-labs/chom-poc-data#79, #83, #176, #178
Loading
Loading