[EF-12] Migrations de la zone or : les sept tables et le référentiel des sites #105

Closed
opened 2026-09-03 08:32:51 +00:00 by lenaic · 1 comment
Owner

Exigence couverte

EF-12, ENF-04, ENF-07

Épreuve servie

EC05 · Data, ETL et BI

Charge estimée

1 j.h

Ce qu'on veut obtenir

Créer les tables de la zone or dans PostgreSQL, par des migrations versionnées. Elles n'existent nulle part : db/migrations/ ne contient que les six migrations d'authentification.

C'est un trou entre deux tickets. Le #29 demande de convertir les tables de mesures en hypertables alors qu'il n'y a rien à convertir. Le #35 écrit dans la zone or sans la créer. Ce ticket ne dépend ni du collecteur ni de personne, il peut partir tout de suite.

Le modèle est déjà arrêté : voir la section « Modèle de données de la zone or » du dossier d'architecture collectif, et docs/GLOSSAIRE.md pour les sept sites et leurs capacités.

Critères d'acceptation

  • Sept tables créées par migration : site, mesure, prevision, alerte, qualite_jour, recommandation, acces_site.
  • La clé (site_id, horodatage) est la même que celle des zones bronze et argent, c'est elle qui rend l'auditabilité vérifiable.
  • mesure porte la valeur retenue et la valeur brute côte à côte, plus la méthode d'imputation et l'indicateur de qualité : une valeur reconstituée ne remplace jamais la valeur mesurée.
  • recommandation porte l'identifiant et la version de la règle, plus les valeurs déclenchantes en jsonb.
  • La table site est peuplée depuis docs/GLOSSAIRE.md : sept lignes, identifiant, libellé, type, capacité déclarée et unité.
  • Les migrations se rejouent sans effet de bord, et le sens de retour est écrit pour chacune.
  • Le rôle grafana lit les tables nouvellement créées, la clause par défaut posée à l'installation le prévoit.

Comment on le vérifie

Commande python db/migrate.py puis \dt et \d mesure dans psql
Attendu les sept tables, la clé primaire composite sur mesure, sept lignes dans site
Preuve la sortie de \dt et celle d'un SELECT sur site, collées à la fermeture

Manuel d'exploitation à mettre à jour

docs/POSTGRESQL.md, section schéma, et db/migrations/README.md

Risque et retour arrière

Une table de la zone or portant une donnée à ne pas exposer serait lisible par grafana sans que personne ne l'ait décidé, la clause de droits par défaut couvrant tout le schéma public. À vérifier table par table. Retour arrière : chaque migration porte son DROP correspondant, le volume est vide à ce stade.

### Exigence couverte EF-12, ENF-04, ENF-07 ### Épreuve servie EC05 · Data, ETL et BI ### Charge estimée 1 j.h ### Ce qu'on veut obtenir Créer les tables de la zone or dans PostgreSQL, par des migrations versionnées. Elles n'existent nulle part : `db/migrations/` ne contient que les six migrations d'authentification. C'est un trou entre deux tickets. Le #29 demande de convertir les tables de mesures en hypertables alors qu'il n'y a rien à convertir. Le #35 écrit dans la zone or sans la créer. Ce ticket ne dépend ni du collecteur ni de personne, il peut partir tout de suite. Le modèle est déjà arrêté : voir la section « Modèle de données de la zone or » du dossier d'architecture collectif, et `docs/GLOSSAIRE.md` pour les sept sites et leurs capacités. ### Critères d'acceptation - [ ] Sept tables créées par migration : `site`, `mesure`, `prevision`, `alerte`, `qualite_jour`, `recommandation`, `acces_site`. - [ ] La clé `(site_id, horodatage)` est la même que celle des zones bronze et argent, c'est elle qui rend l'auditabilité vérifiable. - [ ] `mesure` porte la valeur retenue **et** la valeur brute côte à côte, plus la méthode d'imputation et l'indicateur de qualité : une valeur reconstituée ne remplace jamais la valeur mesurée. - [ ] `recommandation` porte l'identifiant et la version de la règle, plus les valeurs déclenchantes en `jsonb`. - [ ] La table `site` est peuplée depuis `docs/GLOSSAIRE.md` : sept lignes, identifiant, libellé, type, capacité déclarée et unité. - [ ] Les migrations se rejouent sans effet de bord, et le sens de retour est écrit pour chacune. - [ ] Le rôle `grafana` lit les tables nouvellement créées, la clause par défaut posée à l'installation le prévoit. ### Comment on le vérifie Commande python db/migrate.py puis \dt et \d mesure dans psql Attendu les sept tables, la clé primaire composite sur mesure, sept lignes dans site Preuve la sortie de \dt et celle d'un SELECT sur site, collées à la fermeture ### Manuel d'exploitation à mettre à jour `docs/POSTGRESQL.md`, section schéma, et `db/migrations/README.md` ### Risque et retour arrière Une table de la zone or portant une donnée à ne pas exposer serait lisible par `grafana` sans que personne ne l'ait décidé, la clause de droits par défaut couvrant tout le schéma `public`. À vérifier table par table. Retour arrière : chaque migration porte son `DROP` correspondant, le volume est vide à ce stade.
Member

État : le lot est écrit, la demande de fusion #110 est ouverte, l'application sur le serveur reste à faire

Huit migrations, 0008 à 0015, plus la fiche de décision et les manuels. Branche olivier/105-migrations-zone-or.

Le ticket a été rattrapé par le #29 en cours de route. Sa migration 0007_zone_or_mesure.sql (demande #106, ouverte) pose déjà site et mesure en forme réduite — sans table de mesures, il n'y avait rien à convertir en hypertable — et elle est déjà appliquée sur les deux bases. Plutôt que de la défaire, ce lot passe au-dessus : il complète ces deux tables et pose les cinq autres. Les fichiers 0008 et 0010 créent la forme complète sur une base neuve et ajoutent ce qui manque sur une base déjà posée, si bien que l'ordre de fusion des deux demandes n'a plus d'importance.

Les critères, un par un

  • Sept tables. site, mesure, prevision, alerte, qualite_jour, recommandation dans public ; acces_site dans ops.
  • Clé (site_id, horodatage) sur mesure et prevision, dans cet ordre, comme en bronze et en argent.
  • mesure porte la valeur retenue et la valeur brute, plus la méthode d'imputation et l'indicateur de qualité — et la clé bronze, sans laquelle le contrôle de l'ENF-07 se fait à la main, donc ne se fait jamais.
  • recommandation porte regle_id, regle_version et les valeurs déclenchantes en jsonb.
  • site peuplée depuis le glossaire : sept lignes, identifiant, libellé, type, localisation, capacité, unité. Un cas de test relit ces sept lignes dans docs/GLOSSAIRE.md — une capacité corrigée d'un côté et pas de l'autre fait rougir la chaîne.
  • Rejeu sans effet de bord et sens de retour écrit pour chaque fichier. Le lanceur n'a pas de mécanisme de retour : la convention est posée dans db/migrations/README.md et tenue par un cas de test.
  • grafana lit les tables — vérifiable seulement après application. Voir ci-dessous.

Ce que la mise en place a révélé, et qui vaut plus que le ticket

1. Les objets appartenaient à postgres, pas au rôle applicatif. Sur la production, public.site, public.mesure, le schéma ops et ops.schema_migrations étaient possédés par postgres : la 0007 y a été appliquée en superutilisateur. Deux conséquences :

  • bloquante — le rôle applicatif ne peut plus modifier ces tables, donc aucune migration ultérieure n'y passe ;
  • silencieuse — la clause ALTER DEFAULT PRIVILEGES FOR ROLE enervision_prod ne vaut que pour les objets créés par ce rôle. Ces deux tables ne sont lisibles par Grafana que grâce au grant explicite écrit dans la 0007. La table suivante posée de la même façon aurait été muette, sans le moindre message, et le symptôme serait apparu sur un tableau de bord vide des jours plus tard.

La reprise est un geste de superutilisateur — elle ne peut pas vivre dans une migration jouée par le rôle applicatif :

-- enervision_prod
alter schema ops owner to enervision_prod;
alter table ops.schema_migrations owner to enervision_prod;
alter table public.site   owner to enervision_prod;
alter table public.mesure owner to enervision_prod;
-- enervision_preprod
alter table public.site   owner to enervision_preprod;
alter table public.mesure owner to enervision_preprod;

tests/integration/test_zone_or.py vérifie désormais le propriétaire des tables, pour que la reprise ne se reperde pas.

2. Les six migrations d'authentification n'ont jamais été jouées sur la production. ops n'y contient que schema_migrations. Elles passeront avec ce lot, puisque 0015_ops_acces_site.sql référence ops.users — c'est attendu, mais ça n'a rien à voir avec la zone or, et il valait mieux le dire avant que de le découvrir dans un journal d'application.

3. La méthode d'imputation passe de trois régimes à quatre. La forme réduite fait porter à none deux sens distincts, « valeur mesurée » et « trou assumé ». Le §9 de docs/data/etl-pipeline.md et l'ADR 0006 nomment measured le régime de la valeur présente et valide ; les confondre interdit de compter les valeurs réellement mesurées, ce que la répartition par méthode de qualite_jour demande précisément. À valider par qui porte la #106.

4. Le §6 du dossier d'architecture collectif n'est pas sur develop. Le fichier docs/ARCHITECTURE.md que ce ticket cite comme référence du modèle n'existe que sur la branche lenaic/0-dossier-architecture : sa demande de fusion #54 a été fermée sans fusion. Le modèle a été suivi depuis cette branche. Deux écarts assumés par rapport à lui, motivés dans l'ADR 0008 : acces_site est dans ops et non dans public, et sa clé est ops.users (id) et non un sujet_oidc — le jeton est émis par notre API et Keycloak est reporté (ADR 0002), aucun annuaire ne sert de sujet aujourd'hui. À reprendre avec @lenaic : soit la demande #54 est rouverte, soit le §6 est corrigé, mais un ticket ne peut pas citer indéfiniment une référence absente de develop.

Ce qui reste, et pourquoi le ticket n'est pas fermé

L'application sur enervision_preprod puis enervision_prod — avec le rôle applicatif, jamais en postgres — et la sortie de \dt, \d mesure et du contrôle de droits, à coller ici. La reprise de propriété ci-dessus la précède ; sans elle, les alter table de 0008 et 0010 échouent sur un refus de droits.

Documentation

  • fiche ADR 0008public pour les six tables de données, ops pour la table des accès, avec ce qui a été écarté ;
  • docs/POSTGRESQL.md, section schéma, réécrite sur l'état posé, avec un tableau d'exposition Grafana table par table — c'est le « à vérifier table par table » du risque de ce ticket ;
  • db/migrations/README.md — la convention de retour arrière, le motif « crée ou complète » et son défaut silencieux, le geste de reprise de propriété ;
  • README.md racine — le vocabulaire raw / curated annoncé pour db/migrations/ et jamais implémenté est retiré, ce qui referme un point ouvert du §14 de docs/data/etl-pipeline.md.
### État : le lot est écrit, la demande de fusion #110 est ouverte, l'application sur le serveur reste à faire Huit migrations, `0008` à `0015`, plus la fiche de décision et les manuels. Branche `olivier/105-migrations-zone-or`. **Le ticket a été rattrapé par le #29 en cours de route.** Sa migration `0007_zone_or_mesure.sql` (demande #106, ouverte) pose déjà `site` et `mesure` en forme réduite — sans table de mesures, il n'y avait rien à convertir en hypertable — et elle est **déjà appliquée sur les deux bases**. Plutôt que de la défaire, ce lot passe au-dessus : il **complète** ces deux tables et pose les cinq autres. Les fichiers `0008` et `0010` créent la forme complète sur une base neuve *et* ajoutent ce qui manque sur une base déjà posée, si bien que l'ordre de fusion des deux demandes n'a plus d'importance. ### Les critères, un par un - [x] **Sept tables.** `site`, `mesure`, `prevision`, `alerte`, `qualite_jour`, `recommandation` dans `public` ; `acces_site` dans `ops`. - [x] **Clé `(site_id, horodatage)`** sur `mesure` et `prevision`, dans cet ordre, comme en bronze et en argent. - [x] **`mesure` porte la valeur retenue et la valeur brute**, plus la méthode d'imputation et l'indicateur de qualité — et la **clé bronze**, sans laquelle le contrôle de l'ENF-07 se fait à la main, donc ne se fait jamais. - [x] **`recommandation`** porte `regle_id`, `regle_version` et les valeurs déclenchantes en `jsonb`. - [x] **`site` peuplée depuis le glossaire** : sept lignes, identifiant, libellé, type, localisation, capacité, unité. Un cas de test relit ces sept lignes **dans** `docs/GLOSSAIRE.md` — une capacité corrigée d'un côté et pas de l'autre fait rougir la chaîne. - [x] **Rejeu sans effet de bord et sens de retour écrit** pour chaque fichier. Le lanceur n'a pas de mécanisme de retour : la convention est posée dans `db/migrations/README.md` et tenue par un cas de test. - [ ] **`grafana` lit les tables** — vérifiable seulement après application. Voir ci-dessous. ### Ce que la mise en place a révélé, et qui vaut plus que le ticket **1. Les objets appartenaient à `postgres`, pas au rôle applicatif.** Sur la production, `public.site`, `public.mesure`, le schéma `ops` et `ops.schema_migrations` étaient possédés par `postgres` : la `0007` y a été appliquée en superutilisateur. Deux conséquences : - *bloquante* — le rôle applicatif ne peut plus **modifier** ces tables, donc aucune migration ultérieure n'y passe ; - *silencieuse* — la clause `ALTER DEFAULT PRIVILEGES FOR ROLE enervision_prod` ne vaut que pour les objets créés **par** ce rôle. Ces deux tables ne sont lisibles par Grafana que grâce au `grant` explicite écrit dans la `0007`. La table suivante posée de la même façon aurait été muette, sans le moindre message, et le symptôme serait apparu sur un tableau de bord vide des jours plus tard. La reprise est un geste de superutilisateur — elle ne peut pas vivre dans une migration jouée par le rôle applicatif : ```sql -- enervision_prod alter schema ops owner to enervision_prod; alter table ops.schema_migrations owner to enervision_prod; alter table public.site owner to enervision_prod; alter table public.mesure owner to enervision_prod; -- enervision_preprod alter table public.site owner to enervision_preprod; alter table public.mesure owner to enervision_preprod; ``` `tests/integration/test_zone_or.py` vérifie désormais le propriétaire des tables, pour que la reprise ne se reperde pas. **2. Les six migrations d'authentification n'ont jamais été jouées sur la production.** `ops` n'y contient que `schema_migrations`. Elles passeront avec ce lot, puisque `0015_ops_acces_site.sql` référence `ops.users` — c'est attendu, mais ça n'a rien à voir avec la zone or, et il valait mieux le dire avant que de le découvrir dans un journal d'application. **3. La méthode d'imputation passe de trois régimes à quatre.** La forme réduite fait porter à `none` deux sens distincts, « valeur mesurée » et « trou assumé ». Le §9 de `docs/data/etl-pipeline.md` et l'ADR 0006 nomment `measured` le régime de la valeur présente et valide ; les confondre interdit de compter les valeurs réellement mesurées, ce que la répartition par méthode de `qualite_jour` demande précisément. À valider par qui porte la #106. **4. Le §6 du dossier d'architecture collectif n'est pas sur `develop`.** Le fichier `docs/ARCHITECTURE.md` que ce ticket cite comme référence du modèle n'existe que sur la branche `lenaic/0-dossier-architecture` : sa demande de fusion **#54 a été fermée sans fusion**. Le modèle a été suivi depuis cette branche. Deux écarts assumés par rapport à lui, motivés dans l'[ADR 0008](../src/branch/olivier/105-migrations-zone-or/docs/adr/0008-zone-or-dans-public-acces-site-dans-ops.md) : `acces_site` est dans `ops` et non dans `public`, et sa clé est `ops.users (id)` et non un `sujet_oidc` — le jeton est émis par notre API et Keycloak est reporté (ADR 0002), aucun annuaire ne sert de sujet aujourd'hui. **À reprendre avec @lenaic** : soit la demande #54 est rouverte, soit le §6 est corrigé, mais un ticket ne peut pas citer indéfiniment une référence absente de `develop`. ### Ce qui reste, et pourquoi le ticket n'est pas fermé L'application sur `enervision_preprod` puis `enervision_prod` — avec le rôle applicatif, jamais en `postgres` — et la sortie de `\dt`, `\d mesure` et du contrôle de droits, à coller ici. La reprise de propriété ci-dessus la précède ; sans elle, les `alter table` de `0008` et `0010` échouent sur un refus de droits. ### Documentation - fiche **ADR 0008** — `public` pour les six tables de données, `ops` pour la table des accès, avec ce qui a été écarté ; - `docs/POSTGRESQL.md`, section schéma, réécrite sur l'état posé, avec un tableau d'exposition Grafana **table par table** — c'est le « à vérifier table par table » du risque de ce ticket ; - `db/migrations/README.md` — la convention de retour arrière, le motif « crée ou complète » et son défaut silencieux, le geste de reprise de propriété ; - `README.md` racine — le vocabulaire `raw` / `curated` annoncé pour `db/migrations/` et jamais implémenté est retiré, ce qui referme un point ouvert du §14 de `docs/data/etl-pipeline.md`.
florian added this to the EnerVision project 2026-09-03 09:42:54 +00:00
Sign in to join this conversation.
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.

Reference
g2/enervision#105
No description provided.