[EF-05] Les alertes de la source entrent en zone argent (#165) #167
No reviewers
Labels
No labels
Compat/Breaking
EC01
EC02
EC03
EC04
EC05
EC06
Kind/BDD
Kind/Back
Kind/Bug
Kind/CICD
Kind/Cloud
Kind/Contenu
Kind/Data
Kind/Documentation
Kind/Enhancement
Kind/Feature
Kind/Front
Kind/IA
Kind/Infra
Kind/Monitoring
Kind/Security
Kind/Testing
Portée/Post-jury
Priority
Critical
Priority
High
Priority
Low
Priority
Medium
Reviewed
Confirmed
Reviewed
Duplicate
Reviewed
Invalid
Reviewed
Won't Fix
Status
Abandoned
Status
Blocked
Status
Need More Info
ops/alerte
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
g2/enervision!167
Loading…
Reference in a new issue
No description provided.
Delete branch "marvin/165-alertes-zone-argent"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Ce que ça change
Les alertes de la source s'arrêtaient en zone bronze : 11 546 objets collectés au fil de l'eau depuis le 3 septembre, que rien ne transformait. Cette demande ajoute
silver.alerte, quatrième table de la zone argent, et l'écrit à chaque passe du jobsilver_daily.Preuve
La chaine complete du job sur bronze reel, en lecture seule, sans rien ecrire dans MinIO :
2 984, soit exactement le nombre d'objets d'alerte que bronze porte pour ce jour-la. Aucune perte, aucun doublon.
Relecture
Ce qui suit le code
docs/runbooks/mis à jour —etl.mdannonce quatre objets par journée au lieu de trois, et dit qu'une partition d'alertes vide n'est pas une panne. C'est ce qu'un exploitant voit en premier avecmc ls.Où regarder en priorité
1. Le choix d'architecture : pourquoi la zone argent et pas directement la zone or. C'est le point qui mérite le plus votre avis. Le §4 du pipeline définissait la zone argent comme une « série régulière », et une alerte est un événement : ni grille, ni imputation, ni validation physique. L'argument qui a tranché n'est pas la symétrie du médaillon, c'est que le collecteur délègue déjà un traitement à cette zone —
collector/alertes.pyrange sousdt=inconnuune alerte qu'il n'a pas su dater, et son docstring annonce que « la zone argent la replacera d'après son contenu ». Personne ne l'avait écrit.charger_alerteslit donc deux partitions par site et replace d'après le payload.Le §4, le §10, le §11 et le §14 du pipeline sont réécrits en conséquence. Si vous préférez un chemin bronze → or direct, c'est maintenant qu'il faut le dire : la table est isolée, la défaire coûte peu.
2. Le nom du fichier de test.
test_alertes_silver.pyet nontest_alertes.py, parce quetests/unit/collector/test_alertes.pyporte déjà ce nom et que pytest refuse deux modules homonymes — la collecte de la suite entière échoue, alors que le fichier passe isolément. Les deux correctifs globaux, poser des__init__.pyou passer en--import-mode=importlib, casseraient l'import des aides_echantillonset_doublespartout. Le suffixe coûte moins, mais si vous voyez mieux, je prends.3. L'horodatage n'est pas tronqué à la minute, contrairement à celui d'une mesure.
public.alerteest unique sur(site_id, horodatage, type, source): tronquer ferait de deux alertes du même type à douze secondes d'écart une seule alerte, et la seconde disparaîtrait en silence.Deux choses relevées en chemin, hors de ce lot
Un tiers à la moitié des alertes de la source ont une valeur en dessous de leur seuil. Sur 500 échantillonnées :
threshold34/102,spike48/114,outage38/96,anomaly44/86,sensor53/102. Une alerte « Seuil de consommation dépassé » avecvalue = 70.34etthreshold = 135.0se verra sur l'écran Qualité. C'est la donnée que la source sert, et l'historiser telle quelle est ce que demande l'EF-05 — mais ça vaut un coup d'oeil avant la démo.docs/dataest ignoré par.gitignore. La règledata/matche à tous les niveaux, doncgit add docs/data/etl-pipeline.mdsort un avertissement. Le fichier existant reste suivi, mais tout nouveau document déposé là serait invisible sans le moindre message. Déjà signalé par @olivier en fin de commentaire du #35. Le correctif tient en un caractère —/data/— et ne relève pas de cette demande.Relu en entier. J'approuve : la CI est verte (4/4), le module est net, les 30 cas sont bien nommés et le schéma est justifié colonne par colonne. Rien de ce qui suit ne bloque la fusion — le chemin qui s'exécute réellement est correct. Mais deux points méritent une suite, dont un qui touche l'argument d'architecture que tu mets en avant.
1. Le replacement depuis
dt=inconnune peut rien replacerC'est le point sur lequel tu demandes un avis, et il se démontre sans exécuter le code.
Le collecteur range sous
dt=inconnusi et seulement sihorodatage_de_la_sourcerendNone(collector/alertes.py:109-111). Or ce prédicat est le même, à l'octet près, que celui de la zone argent :Et
collecter_alertesécritpayload=alerte, la charge d'origine non modifiée (collector/alertes.py:156) : letimestampillisible dans l'objet est celui-là même qui a envoyé l'alerte sousdt=inconnu.Donc toute alerte présente sous
dt=inconnuporte untimestampque_en_utcrejette →alerte_depuis_payloadrendNone(ts is None) →lire_alertesl'écarte, avant quecharger_alertesn'ait l'occasion de la replacer. La partition est lue à chaque passe, jamais moissonnée.Le test qui semble l'établir s'appuie sur un état que le collecteur ne peut pas produire :
alerte()porte untimestampvalide sous une cléinconnu— soit l'inverse exact de la condition de rangement.Coût réel : 7 lectures de préfixe de plus par passe, toutes les heures, sur une partition qui ne fait que s'accumuler et dont chaque objet est retéléchargé et reparsé indéfiniment pour être jeté. Ça explique aussi que ton contrôle sur bronze réel tombe exactement sur 2 984 : la seconde partition n'apporte rien.
À noter que
test_la_partition_sentinelle_est_la_meme_des_deux_cotesgarde la moitié cosmétique de l'accord avec le collecteur — la valeur"inconnu"— et laisse sans garde la moitié porteuse : la relation entre les deux analyseurs. C'est la duplication de_en_utcqui est le point de rupture, pas celle de la sentinelle.Sur le placement lui-même, je ne reviens pas en arrière. Je garderais
alerteen zone argent, mais pour la raison qu'écrit déjà ton propre docstring de_colonnes_alerte:site_typeetcapacity_kwy sont dénormalisés pour que la zone or obtienne letaux_de_chargedepublic.alertesans joindre le référentiel. Argument suffisant et vérifiable. Ce sont les §4, §10, §11 et §14 du pipeline qu'il faut réécrire sur cette base plutôt que sur la promesse déléguée — et vu que c'est une décision structurante, la « définition de terminé » dedocs/CONVENTIONS.mddemanderait plutôt une fiche dansdocs/adr/.2. Une alerte hors énumération bloque aussi les mesures
executerappelleverifier_enumerations_alertesavantverifier_taux_de_trouset avantecrire. Une seule alerte autypeou à laseverityinattendus fait donc échouer la passe entière : nimesure, nijournal_capteur, nidisponibilite_journe sont écrites, et ça se reproduit à chaque heure jusqu'à intervention humaine. C'est un couplage nouveau — jusqu'ici un problème d'alerte ne pouvait pas arrêter les mesures, qui n'ont rien à voir avec elles.La source étant collectée au fil de l'eau, et vu que tu relèves toi-même qu'elle sert des données incohérentes (un tiers à la moitié des valeurs sous leur seuil), une sixième valeur de
typeun matin arrête toute la zone argent. Deux sorties : écarter les alertes fautives en les journalisant, ou déplacer le contrôle après l'écriture des trois autres tables. La première me paraît plus fidèle àcode_de_sortiedu collecteur, qui refuse déjà de faire échouer cron « pour une donnée qu'on ne maîtrise pas ».Point annexe qui trompera un relecteur : le docstring annonce « même parti pris que
gold.agregation._controler_enumerations». Ce n'est pas le même — gold écarte explicitement les nuls (WHERE {colonne} IS NOT NULL AND {colonne} NOT IN (...)), iciNoneest refusé. RefuserNoneest défendable, les colonnes étantnot nullen 0012, et ton test le documente ; c'est la comparaison à la zone or qui est fausse et la divergence qui n'est pas dite.3. Points mineurs
ecrire(..., alertes=()): le défaut vide efface silencieusement la partition d'alertes pour tout appelant qui l'oublie, ettest_les_quatre_tables_sont_ecrites_dans_la_partition_du_jours'appuie précisément sur ce défaut — c'est la forme exacte de l'accident. En argument nommé obligatoire, ça coûte unalertes=[]dans deux tests et supprime le piège._en_utcdit « à la seconde près » alors que les microsecondes sont conservées — ettest_l_horodatage_n_est_pas_tronque_a_la_minuteassènemicrosecond == 987654. Dérive de docstring sur le seul comportement dont le module fait un point d'honneur.docs/runbooks/etl.md: la ligneLitannonce toujours « préfixesendpoint=currentetendpoint=readings». Le job lit maintenant aussiendpoint=alerts, et c'est la ligne que l'exploitant lit en premier.executeravec des alertes non vides :alertes_ecrites, la compréhensionligne_alerteet l'accèssites[alerte.site_id]ne sont couverts qu'isolément. C'est vraisemblablement les 7 % manquants dejob.py.Tes deux autres questions
Le nom de fichier :
test_alertes_silver.pyme va. La contrainte pytest est réelle, les deux correctifs globaux coûtent effectivement plus cher, et le suffixe se lit.Ne pas tronquer : d'accord, et bien argumenté.
unique (site_id, horodatage, type, source)en 0012 le confirme — tronquer perdrait la seconde alerte en silence.Méthode
Je n'ai pas rejoué la suite : le venv du poste n'a aucune dépendance applicative (
duckdbabsent), donc je m'appuie sur la CI verte du commit572a081et sur ta preuve pour les 628 tests. Le point 1 est une lecture de code, pas une observation : si tu as sousdt=inconnudes objets réels dont letimestampse parse, montre-les, ça invaliderait mon raisonnement.Je fusionne. Les points 1 et 2 valent un ticket de suite — le 2 avant la démo.
New commits pushed, approval review dismissed automatically according to repository settings