[EF-05] Les alertes traversent la zone or jusqu'à public.alerte (#168) #169

Merged
olivier merged 6 commits from marvin/168-alertes-zone-or into develop 2026-09-07 12:43:27 +00:00
Member

Ce que ça change

Le #165 fait entrer les alertes de la source en zone argent. Cette demande termine le chemin : silver.alertegold/table=alertepublic.alerte, la seule couche que l'API et Grafana lisent.

Avec elle, l'écran Qualité et le pavé « alertes ouvertes » de l'écran Parc cessent de dépendre de fixtures.

Closes #168

⚠️ Demande empilée, à recibler avant la fusion

Base : marvin/165-alertes-zone-argent, pas develop. Le #168 lit silver.alerte, qui n'existe que sur la branche du #165. Empilée, cette demande ne montre que son propre diff ; ouverte sur develop, elle aurait porté les quatre commits du #165 en plus et personne n'aurait relu l'un sans l'autre.

À faire quand la #167 sera fusionnée : recibler celle-ci sur develop. Aucun conflit attendu, les deux lots ne touchent pas les mêmes fichiers hormis docs/data/etl-pipeline.md, où ils écrivent dans des sections différentes.

Preuve

$ pytest tests/unit -q
665 passed

$ pytest tests/unit/gold tests/unit/db --cov=services/etl/etl/gold
services/etl/etl/gold/alertes.py       28    0   100%
services/etl/etl/gold/chargement.py    97    6    94%

$ ruff check services packages && ruff format --check services packages
All checks passed!

$ mypy --config-file etl/pyproject.toml etl      # strict
Success: no issues found in 23 source files

Et la chaîne des trois zones sur bronze réel, jouée en local, sans rien écrire dans MinIO ni en base :

2 984 alertes bronze -> 2 984 lignes argent -> 2 984 lignes or
source      'api_simulation'   (uniforme)
etat        'ouverte'          (uniforme)
libelle     2984/2984 non nuls
cle_bronze  2984/2984 non nuls
types       anomaly, outage, sensor, spike, threshold

Ce qui suit le code

  • docs/ : le §12 du pipeline décrit les alertes en zone or, le §15 fait remonter l'EF-05 jusqu'en base, le README de l'ETL gagne sa troisième ligne.
  • docs/runbooks/etl.md n'est pas étendu à la zone or ici, délibérément : la #159 le fait déjà, et deux demandes qui réécrivent la même section se battraient pour rien.

Relecture

  • Un pair a relu et laissé un commentaire, même court
  • Ses remarques sont traitées, ou une réponse explique pourquoi elles ne le sont pas

@lenaic la relecture est obligatoire : db/ est à toi dans CODEOWNERS, et cette demande porte la migration 0017.

Où regarder en priorité

1. La migration 0017, et surtout ce qu'elle n'ajoute pas. public.alerte ne pouvait pas servir AlerteOut : il manquait de quoi dire ce que l'alerte raconte, si elle est ouverte, et d'où elle vient. Trois colonnes — libelle, etat, cle_bronze — plus un index partiel sur les seules ouvertes.

titre, description et exigence ne sont pas stockés, et c'est le point à contester si vous n'êtes pas d'accord. Ce sont des mises en forme, et la 0012 pose déjà la règle à propos du taux de charge : « le pourcentage est une mise en forme, elle appartient à l'affichage ». Les stocker ferait de chaque changement de formulation une migration. resolue_a non plus : rien ne résout d'alerte aujourd'hui, et une colonne toujours nulle est une promesse que personne ne tient — elle viendra avec ce qui les résoudra, comme au #164.

2. etat est dans le do update de l'upsert, ce qui est sans effet tant que la zone or ne porte que « ouverte ». Le jour où quelque chose résoudra les alertes, cette colonne devra en SORTIR, sinon un rejeu rouvrirait toutes les alertes fermées de la journée. Un cas de test porte la remarque à l'endroit où elle se lira.

3. La limite de COPY … PARTITION_BY (dt) sur zéro ligne. Sans valeur de dt, DuckDB ne crée pas de répertoire : une journée qui passerait de N alertes à zéro garderait sa partition or précédente. Le cas est théorique — une alerte ne disparaît de bronze qu'avec la rétention de 180 jours, qui emporte la journée entière — mais il est réel, et la zone argent ne l'a pas, elle écrit à un chemin nommé. Écrit dans le module et gardé par un cas, plutôt que découvert un soir de démonstration.

4. Le contrôle des énumérations fait doublon avec celui de la zone argent, délibérément, comme agregation._controler_enumerations le fait pour la mesure que la zone argent produit pourtant. Une partition argent peut venir d'une version antérieure du job ou d'un rattrapage à la main.

Ce que cette demande ne fait pas

public.prevision et public.recommandation restent vides : ce sont le #37 et l'écriture en base des règles du #154. Et rien ne tourne encore en cron — c'est la #159.

## Ce que ça change Le #165 fait entrer les alertes de la source en zone argent. Cette demande termine le chemin : `silver.alerte` → `gold/table=alerte` → `public.alerte`, la seule couche que l'API et Grafana lisent. Avec elle, l'écran Qualité et le pavé « alertes ouvertes » de l'écran Parc cessent de dépendre de fixtures. Closes #168 ## ⚠️ Demande empilée, à recibler avant la fusion **Base : `marvin/165-alertes-zone-argent`, pas `develop`.** Le #168 lit `silver.alerte`, qui n'existe que sur la branche du #165. Empilée, cette demande ne montre que son propre diff ; ouverte sur `develop`, elle aurait porté les quatre commits du #165 en plus et personne n'aurait relu l'un sans l'autre. **À faire quand la #167 sera fusionnée** : recibler celle-ci sur `develop`. Aucun conflit attendu, les deux lots ne touchent pas les mêmes fichiers hormis `docs/data/etl-pipeline.md`, où ils écrivent dans des sections différentes. ## Preuve ``` $ pytest tests/unit -q 665 passed $ pytest tests/unit/gold tests/unit/db --cov=services/etl/etl/gold services/etl/etl/gold/alertes.py 28 0 100% services/etl/etl/gold/chargement.py 97 6 94% $ ruff check services packages && ruff format --check services packages All checks passed! $ mypy --config-file etl/pyproject.toml etl # strict Success: no issues found in 23 source files ``` Et la chaîne des trois zones sur bronze réel, jouée en local, sans rien écrire dans MinIO ni en base : ``` 2 984 alertes bronze -> 2 984 lignes argent -> 2 984 lignes or source 'api_simulation' (uniforme) etat 'ouverte' (uniforme) libelle 2984/2984 non nuls cle_bronze 2984/2984 non nuls types anomaly, outage, sensor, spike, threshold ``` ## Ce qui suit le code - [x] `docs/` : le §12 du pipeline décrit les alertes en zone or, le §15 fait remonter l'EF-05 jusqu'en base, le README de l'ETL gagne sa troisième ligne. - [ ] `docs/runbooks/etl.md` **n'est pas** étendu à la zone or ici, délibérément : la #159 le fait déjà, et deux demandes qui réécrivent la même section se battraient pour rien. ## Relecture - [ ] Un pair a relu et laissé un commentaire, même court - [ ] Ses remarques sont traitées, ou une réponse explique pourquoi elles ne le sont pas **@lenaic la relecture est obligatoire** : `db/` est à toi dans `CODEOWNERS`, et cette demande porte la migration `0017`. ## Où regarder en priorité **1. La migration `0017`, et surtout ce qu'elle n'ajoute pas.** `public.alerte` ne pouvait pas servir `AlerteOut` : il manquait de quoi dire ce que l'alerte raconte, si elle est ouverte, et d'où elle vient. Trois colonnes — `libelle`, `etat`, `cle_bronze` — plus un index partiel sur les seules ouvertes. `titre`, `description` et `exigence` ne sont **pas** stockés, et c'est le point à contester si vous n'êtes pas d'accord. Ce sont des mises en forme, et la `0012` pose déjà la règle à propos du taux de charge : « le pourcentage est une mise en forme, elle appartient à l'affichage ». Les stocker ferait de chaque changement de formulation une migration. `resolue_a` non plus : rien ne résout d'alerte aujourd'hui, et une colonne toujours nulle est une promesse que personne ne tient — elle viendra avec ce qui les résoudra, comme au #164. **2. `etat` est dans le `do update` de l'upsert**, ce qui est sans effet tant que la zone or ne porte que « ouverte ». Le jour où quelque chose résoudra les alertes, cette colonne devra en SORTIR, sinon un rejeu rouvrirait toutes les alertes fermées de la journée. Un cas de test porte la remarque à l'endroit où elle se lira. **3. La limite de `COPY … PARTITION_BY (dt)` sur zéro ligne.** Sans valeur de `dt`, DuckDB ne crée pas de répertoire : une journée qui passerait de N alertes à zéro garderait sa partition or précédente. Le cas est théorique — une alerte ne disparaît de bronze qu'avec la rétention de 180 jours, qui emporte la journée entière — mais il est réel, et la zone argent ne l'a pas, elle écrit à un chemin nommé. Écrit dans le module et gardé par un cas, plutôt que découvert un soir de démonstration. **4. Le contrôle des énumérations fait doublon avec celui de la zone argent**, délibérément, comme `agregation._controler_enumerations` le fait pour la mesure que la zone argent produit pourtant. Une partition argent peut venir d'une version antérieure du job ou d'un rattrapage à la main. ## Ce que cette demande ne fait pas `public.prevision` et `public.recommandation` restent vides : ce sont le #37 et l'écriture en base des règles du #154. Et rien ne tourne encore en cron — c'est la #159.
marvin self-assigned this 2026-09-07 12:06:59 +00:00
La table de la 0012 a été posée avant que les écrans ne soient
contractualisés. Confrontée à `AlerteOut`, le modèle de sortie que le tableau de
bord consomme, il lui manquait de quoi répondre à trois questions que l'écran
Qualité pose : que dit l'alerte, est-elle encore ouverte, et d'où vient-elle.

- `libelle` : la seule phrase que la source produise. Sans elle, elle s'arrête à
  la zone argent et l'écran reconstruit un libellé générique depuis le type —
  plus pauvre, et faux dès que la source précise ce que le type ne dit pas.
- `etat`, `not null default 'ouverte'` : `/api/v1/alerts` rend les alertes
  COURANTES, pas un historique. Le défaut vaut donc pour les lignes déjà
  chargées comme pour celles à venir.
- `cle_bronze` : l'ENF-07, comme `public.mesure` la porte depuis la 0010. Rien
  ne justifiait qu'elle s'applique à la mesure et pas à l'alerte. La clé porte
  l'`alert_id` de la source dans son dernier segment, ce qui rend cet
  identifiant retrouvable sans lui donner sa colonne.

Plus un index partiel sur les seules alertes ouvertes : une fois l'historique
accumulé, elles sont la minorité, et c'est ce que l'écran Qualité et la synthèse
du parc interrogent.

CE QUI N'EST PAS AJOUTÉ. `titre`, `description` et `exigence` sont des mises en
forme, et la 0012 pose déjà la règle à propos du taux de charge : « le
pourcentage est une mise en forme, elle appartient à l'affichage ». Les stocker
ferait de chaque changement de formulation une migration. `resolue_a` non plus :
rien ne résout d'alerte aujourd'hui, la colonne viendra avec ce qui les
résoudra, comme au #164 pour les recommandations. Une colonne toujours nulle est
une promesse que personne ne tient. Deux cas gardent ces deux absences.

La contrainte `check` est posée dans un bloc séparé et non dans l'`add column` :
sur une base où la colonne existe déjà, la clause du `add column if not exists`
n'est pas rejouée et la contrainte manquerait en silence. Même motif que le bloc
de la 0010.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014e7YA7QikrPg6wU3U1ur9M
Le #165 fait entrer les alertes en zone argent. Ce commit termine le chemin :
`silver.alerte` -> `gold/table=alerte` -> `public.alerte`, la seule couche que
l'API et Grafana lisent.

`gold/alertes.py` ne recalcule rien, il projette. La zone argent a déjà lu
l'enveloppe bronze, replacé la journée d'après le contenu et écarté les alertes
inexploitables. Ici on traduit un vocabulaire — `alert_type` devient `type`,
`severity` devient `severite` — et c'est le seul endroit du dépôt où cette
traduction a lieu ; on divise une fois pour obtenir `taux_de_charge`, sans
jointure, la ligne argent portant déjà `capacity_kw` recopiée du référentiel
exprès pour ça ; et on pose `source = 'api_simulation'`, que la 0012 sépare de
ce que nos règles produiront.

Trois décisions que les cas gardent :

- `taux_de_charge` vaut NULL et non zéro quand la capacité manque ou vaut zéro.
  Un taux indéfini n'est pas un taux nul, et une division par zéro ferait
  échouer la journée entière pour une ligne de référentiel incomplète.
- Le contrôle des énumérations fait doublon avec celui de la zone argent,
  délibérément, comme `agregation._controler_enumerations` le fait pour la
  mesure que la zone argent produit pourtant. Une partition argent peut venir
  d'une version antérieure du job ou d'un rattrapage à la main.
- L'absence de partition d'alertes n'est pas une faute, ni à la projection ni
  au chargement : `silver.alerte` n'existe que depuis le #165, et rejouer une
  journée antérieure doit réussir plutôt que d'échouer sur une chaîne qui, elle,
  va bien. La série se charge quand même.

L'upsert porte sur `(site_id, horodatage, type, source)`, la clé unique de la
0012, et non sur `alerte_id`, qui est `generated always as identity` : la base
la produit, et un insert qui la fournirait serait refusé. Les alertes partent
dans la MEME transaction que la mesure et la qualité.

UNE LIMITE CONNUE, ÉCRITE PLUTÔT QUE DÉCOUVERTE PLUS TARD. `ecrire_partition`
passe par `COPY ... PARTITION_BY (dt)`, et sans valeur de `dt` DuckDB ne crée
pas de répertoire : une journée qui passerait de N alertes à zéro garderait sa
partition or précédente. Le cas est théorique — une alerte ne disparaît de
bronze qu'avec la rétention de 180 jours, qui emporte la journée entière — mais
il est réel, et la zone argent ne l'a pas, elle écrit à un chemin nommé.

Vérifié bout en bout sur bronze réel, en local, sans rien écrire dans MinIO :
2 984 alertes du 2026-09-06 traversent les trois zones sans perte ni doublon,
`source` et `etat` uniformes, `libelle` et `cle_bronze` renseignés sur les
2 984 lignes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014e7YA7QikrPg6wU3U1ur9M
docs: la zone or porte les alertes, et la 0017 n'est plus libre (#168)
All checks were successful
Intégration / Tableau de bord — dépendances, tests et construction (pull_request) Successful in 32s
Intégration / Contrôles statiques du dépôt (pull_request) Successful in 5s
Intégration / Aucun secret commité (pull_request) Successful in 3s
Intégration / Python — qualité, tests et dépendances (pull_request) Successful in 3m50s
77b4a34fae
`docs/data/etl-pipeline.md` §12 : une section pour les alertes en zone or — les
trois choses qui s'y décident et une seule fois (le vocabulaire, le taux de
charge sans jointure, l'origine `api_simulation`), la clé de l'upsert et
pourquoi elle n'est pas `alerte_id`, le fait qu'une partition d'alertes absente
n'empêche pas la série de se charger, et la limite de `COPY ... PARTITION_BY`
sur zéro ligne.

Les deux entrées de commande annoncent leurs sorties réelles : `agregation`
écrit aussi `alerte`, `chargement` porte aussi `public.alerte`.

§15 : l'EF-05 va désormais jusqu'en base, avec la clé de l'upsert.

Une correction au passage. Le §12 annonçait « ce serait une migration 0017 »
à propos d'un éventuel drapeau `journee_complete` en base. Le numéro est pris
par les alertes depuis ce lot : la phrase le dit plutôt que de laisser un
lecteur chercher une migration qui parle d'autre chose.

`services/etl/README.md` : la troisième ligne du tableau des objets de la zone
or.

Le manuel `docs/runbooks/etl.md` n'est PAS étendu à la zone or ici : la #159 le
fait déjà, et deux PR qui réécrivent la même section se battraient pour rien.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014e7YA7QikrPg6wU3U1ur9M
marvin changed target branch from marvin/165-alertes-zone-argent to develop 2026-09-07 12:13:38 +00:00
Merge branch 'develop' into marvin/165-alertes-zone-argent (#165)
All checks were successful
Intégration / Contrôles statiques du dépôt (pull_request) Successful in 6s
Intégration / Aucun secret commité (pull_request) Successful in 4s
Intégration / Tableau de bord — dépendances, tests et construction (pull_request) Successful in 33s
Intégration / Python — qualité, tests et dépendances (pull_request) Successful in 3m41s
70dbfc0761
La #159 a réécrit `docs/runbooks/etl.md` pendant la relecture. Les deux
intentions sont gardées, aucune n'écrase l'autre :

- le tableau de la #159 couvre les trois passes, on le prend, en y ajoutant
  `endpoint=alerts` que la zone argent lit maintenant aussi ;
- le compte d'objets de la zone argent passe à quatre là où la #159 l'avait
  déplacé, dans le commentaire de l'étape 1 ;
- les deux paragraphes disent deux choses différentes — le piège du chargement
  et la partition d'alertes vide — donc les deux restent.

Résolution écrite par Olivier en relecture de la #167.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
db: la migration des alertes devient la 0018, la 0017 est prise (#168)
All checks were successful
Intégration / Tableau de bord — dépendances, tests et construction (pull_request) Successful in 31s
Intégration / Contrôles statiques du dépôt (pull_request) Successful in 4s
Intégration / Aucun secret commité (pull_request) Successful in 2s
Intégration / Python — qualité, tests et dépendances (pull_request) Successful in 3m44s
591ba13781
`develop` a reçu `0017_zone_or_prevision_reference.sql` avec le contrat de
prévision (#36, PR #161) pendant que cette branche écrivait sa propre 0017. Deux
fichiers de même numéro s'appliqueraient dans l'ordre de leur nom et non dans
celui de leurs dépendances : c'est le défaut que
`test_la_serie_des_migrations_ne_porte_ni_doublon_ni_desordre` interdit depuis
le #105.

Le garde-fou a fait son travail — les deux cas ont rougi au merge, celui du
dépôt et celui que ce lot avait ajouté. Le renommage suit partout : le fichier,
son test, le tableau du README des migrations, le §12 du pipeline, le README de
l'ETL et le docstring de `gold/alertes.py`.

Au passage, le tableau du README des migrations ne citait pas la 0017 de la
prévision : elle y est ajoutée. Un tableau incomplet est pire qu'absent, on
croit y lire la série entière.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014e7YA7QikrPg6wU3U1ur9M
marvin requested review from olivier 2026-09-07 12:21:40 +00:00
db: le numéro de la 0018 se garde par sa dépendance, pas par la fin de série (#168)
All checks were successful
Intégration / Contrôles statiques du dépôt (pull_request) Successful in 6s
Intégration / Aucun secret commité (pull_request) Successful in 4s
Intégration / Tableau de bord — dépendances, tests et construction (pull_request) Successful in 33s
Infra Ansible / Playbooks Ansible valides (pull_request) Successful in 1m57s
Intégration / Python — qualité, tests et dépendances (pull_request) Successful in 3m49s
c5ff5bca91
`assert numeros[-1] == 18` aurait fait rougir la chaîne à la prochaine
migration du dépôt, sur un autre ticket, sans que rien ne soit cassé — et
le runner est unique pour tout le groupe. Le cas garde désormais la
dépendance réelle : la 0018 complète la table de la 0012, son numéro doit
lui être supérieur. Les doublons de numéro restent gardés par
`test_migrations_zone_or`, qui exprime déjà sa borne de la même façon.

Au passage, le docstring du fichier annonçait « La 0017 », numéro que le
dernier commit de cette demande a déjà rendu à la #36.
olivier approved these changes 2026-09-07 12:40:15 +00:00
olivier left a comment

Relu sur c5ff5bc. Chaîne verte (5/5), et rejoué ici pour ne pas croire la seule chaîne : pytest tests/unit → 693 passés, ruff check et ruff format --check sur services packages propres, mypy --config-file etl/pyproject.toml etl en strict sans erreur.

Approuvé. Les quatre points que tu mets en avant tiennent. Un cinquième était à corriger, il l'est.

Ce que j'ai poussé — c5ff5bc

test_le_numero_suit_le_dernier_applique finissait sur assert numeros[-1] == 18. Ce cas-là ne gardait pas la 0018 : il aurait rougi à la première 0019 du dépôt, sur un autre ticket, sans que rien ne soit cassé — et le runner est unique pour tout le groupe, donc la panne se paie en file d'attente pour tout le monde, en semaine de livraison.

Remplacé par la dépendance réelle : la 0018 complète la table de la 0012, son numéro doit lui être supérieur (int(MIGRATION.name[:4]) > 12). Les doublons de numéro restaient de toute façon gardés par test_migrations_zone_or.test_la_serie_des_migrations_ne_porte_ni_doublon_ni_desordre, qui exprime déjà sa borne comme une dépendance (> 7) et pas comme une fin de série. Vérifié en posant une 0019_bidon.sql à côté : le cas passe encore.

Au passage, le docstring du fichier annonçait « La 0017 », numéro que ton dernier commit a rendu à la #36.

Sur tes quatre points

1. La migration, et ce qu'elle n'ajoute pas. D'accord, y compris sur le refus de titre, description et exigence : la 0012 pose la règle et AlerteOut les compose. resolue_a de même — la contrainte check (etat in ('ouverte', 'resolue')) porte déjà le futur cycle sans colonne toujours nulle. La contrainte posée hors de l'add column est le motif de la 0010, et un cas le garde explicitement : bien vu, c'est le genre d'oubli qui ne se voit jamais.

2. etat dans le do update. D'accord, et test_l_etat_est_mis_a_jour_par_le_rejeu porte la remarque au bon endroit : le jour où quelque chose résoudra les alertes, c'est ce cas qui rougira, pas la production.

3. COPY … PARTITION_BY (dt) sur zéro ligne. La limite est réelle, et la distinction que tu tiens entre « pas de partition argent » (#165 antérieur) et « partition argent vide » est celle qui compte — les deux cas de test la portent séparément. Rien à ajouter.

4. Le doublon du contrôle d'énumérations. Justifié : la zone or ne charge pas en base ce que la base rejetterait, quelle que soit la version du job qui a écrit la partition argent. Même raison qu'agregation._controler_enumerations.

Vérifié aussi, puisque le chargement entre dans la transaction de la série : COLONNES_ALERTE couvre exactement les colonnes de la 0012 complétée par la 0018, sans alerte_id — et public.alerte n'a pas d'execution_id, donc rien ne manque. L'upsert porte bien la clé unique. La contrainte de clé étrangère sur site_id n'ouvre pas de nouveau risque : silver/job.py construit ses lignes par sites[alerte.site_id], donc les alertes chargées viennent des sites du référentiel, comme les mesures.

Deux remarques non bloquantes, pour la suite

  • La description promet un cran de trop. « L'écran Qualité et le pavé alertes ouvertes cessent de dépendre de fixtures » : pas encore. dashboard/repository.py:67 rend toujours fixtures.ALERTES, et ce lot ne touche pas services/api. Il rend la donnée disponible dans public.alerte — le branchement reste à faire. À ne pas cocher l'EF-05 de bout en bout sur cette base au moment du bilan.
  • libelle est nullable, AlerteOut.description ne l'est pas (schemas.py:212, models.py:206). Le branchement devra donc composer un repli quand la source n'a pas de phrase — ce que ton commentaire de migration annonce déjà (« l'écran affiche un libellé générique reconstruit à partir du type »). Autant que le ticket du branchement le dise, plutôt que de le redécouvrir devant un ValidationError.

Un point de procédure, pas de code

Ta case « @lenaic la relecture est obligatoire : db/ est à toi dans CODEOWNERS » n'est pas cochée, et la forge ne l'a pas ajouté comme relecteur — la demande n'a que moi. CODEOWNERS le dit lui-même : le fichier seul ne bloque rien tant que « relecture des Code Owners requise » n'est pas mise dans la protection de develop, et elle ne l'est pas. La forge laissera donc passer.

Mon approbation porte sur la zone or, le chargement et la migration, que j'ai relus et rejoués. Elle ne remplace pas l'aval du propriétaire de db/ voulu par la réunion du 31/08 (§7) : c'est à trancher avant la fusion, pas après.

Relu sur `c5ff5bc`. Chaîne verte (5/5), et rejoué ici pour ne pas croire la seule chaîne : `pytest tests/unit` → 693 passés, `ruff check` et `ruff format --check` sur `services packages` propres, `mypy --config-file etl/pyproject.toml etl` en strict sans erreur. **Approuvé.** Les quatre points que tu mets en avant tiennent. Un cinquième était à corriger, il l'est. ## Ce que j'ai poussé — `c5ff5bc` `test_le_numero_suit_le_dernier_applique` finissait sur `assert numeros[-1] == 18`. Ce cas-là ne gardait pas la `0018` : il aurait rougi à la première `0019` du dépôt, sur un autre ticket, sans que rien ne soit cassé — et le runner est unique pour tout le groupe, donc la panne se paie en file d'attente pour tout le monde, en semaine de livraison. Remplacé par la dépendance réelle : la `0018` complète la table de la `0012`, son numéro doit lui être supérieur (`int(MIGRATION.name[:4]) > 12`). Les doublons de numéro restaient de toute façon gardés par `test_migrations_zone_or.test_la_serie_des_migrations_ne_porte_ni_doublon_ni_desordre`, qui exprime déjà sa borne comme une dépendance (`> 7`) et pas comme une fin de série. Vérifié en posant une `0019_bidon.sql` à côté : le cas passe encore. Au passage, le docstring du fichier annonçait « La 0017 », numéro que ton dernier commit a rendu à la #36. ## Sur tes quatre points **1. La migration, et ce qu'elle n'ajoute pas.** D'accord, y compris sur le refus de `titre`, `description` et `exigence` : la `0012` pose la règle et `AlerteOut` les compose. `resolue_a` de même — la contrainte `check (etat in ('ouverte', 'resolue'))` porte déjà le futur cycle sans colonne toujours nulle. La contrainte posée hors de l'`add column` est le motif de la `0010`, et un cas le garde explicitement : bien vu, c'est le genre d'oubli qui ne se voit jamais. **2. `etat` dans le `do update`.** D'accord, et `test_l_etat_est_mis_a_jour_par_le_rejeu` porte la remarque au bon endroit : le jour où quelque chose résoudra les alertes, c'est ce cas qui rougira, pas la production. **3. `COPY … PARTITION_BY (dt)` sur zéro ligne.** La limite est réelle, et la distinction que tu tiens entre « pas de partition argent » (#165 antérieur) et « partition argent vide » est celle qui compte — les deux cas de test la portent séparément. Rien à ajouter. **4. Le doublon du contrôle d'énumérations.** Justifié : la zone or ne charge pas en base ce que la base rejetterait, quelle que soit la version du job qui a écrit la partition argent. Même raison qu'`agregation._controler_enumerations`. Vérifié aussi, puisque le chargement entre dans la transaction de la série : `COLONNES_ALERTE` couvre exactement les colonnes de la `0012` complétée par la `0018`, sans `alerte_id` — et `public.alerte` n'a pas d'`execution_id`, donc rien ne manque. L'upsert porte bien la clé unique. La contrainte de clé étrangère sur `site_id` n'ouvre pas de nouveau risque : `silver/job.py` construit ses lignes par `sites[alerte.site_id]`, donc les alertes chargées viennent des sites du référentiel, comme les mesures. ## Deux remarques non bloquantes, pour la suite - **La description promet un cran de trop.** « L'écran Qualité et le pavé alertes ouvertes cessent de dépendre de fixtures » : pas encore. `dashboard/repository.py:67` rend toujours `fixtures.ALERTES`, et ce lot ne touche pas `services/api`. Il rend la donnée *disponible* dans `public.alerte` — le branchement reste à faire. À ne pas cocher l'EF-05 de bout en bout sur cette base au moment du bilan. - **`libelle` est nullable, `AlerteOut.description` ne l'est pas** (`schemas.py:212`, `models.py:206`). Le branchement devra donc composer un repli quand la source n'a pas de phrase — ce que ton commentaire de migration annonce déjà (« l'écran affiche un libellé générique reconstruit à partir du type »). Autant que le ticket du branchement le dise, plutôt que de le redécouvrir devant un `ValidationError`. ## Un point de procédure, pas de code Ta case « @lenaic la relecture est obligatoire : `db/` est à toi dans `CODEOWNERS` » n'est pas cochée, et la forge ne l'a pas ajouté comme relecteur — la demande n'a que moi. `CODEOWNERS` le dit lui-même : le fichier seul ne bloque rien tant que « relecture des Code Owners requise » n'est pas mise dans la protection de `develop`, et elle ne l'est pas. La forge laissera donc passer. Mon approbation porte sur la zone or, le chargement et la migration, que j'ai relus et rejoués. Elle ne remplace pas l'aval du propriétaire de `db/` voulu par la réunion du 31/08 (§7) : c'est à trancher avant la fusion, pas après.
olivier merged commit 920d5a84b8 into develop 2026-09-07 12:43:27 +00:00
olivier deleted branch marvin/168-alertes-zone-or 2026-09-07 12:43:28 +00:00
Sign in to join this conversation.
No reviewers
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
g2/enervision!169
No description provided.