Jalon : la chaîne IA en production, prévision et recommandations en cron #188

Merged
lenaic merged 33 commits from develop into main 2026-09-08 09:37:41 +00:00
Owner

33 commits, 78 fichiers. La chaîne IA entre en production : le job de prévision et celui des recommandations tournent pour la première fois sur ce serveur.

Ce que ça pose

migration        0019 seule, le cycle de vie des recommandations (#164)
clés de coffre   aucune nouvelle, l'assertion d'entrée du rôle app est inchangée
piles recréées   api et supervision SEULEMENT, plus postgres
crons ajoutés    prevoir.sh à :35, recommandations-hourly.sh à :45
dépendances      déjà dans le venv du serveur, l'étape pip ne téléchargera rien

Vérifié plutôt que supposé, parce que ce sont les trois choses qui cassent un déploiement :

  • les huit paquets que deps-services.py réclame sont déjà installés sur le serveur, mlflow 3.16 et scikit-learn 1.9 compris ;
  • la 0019 s'applique sur public.recommandation qui contient une ligne, et son default '' puis drop default est écrit pour ce cas ;
  • postgres n'est plus recréé, contrairement au déploiement d'hier. C'est le retour de Gabriel sur le #176 : la base ne tombe plus à chaque fusion, seules api et supervision sont recréées parce qu'elles seules portent des montages de fichiers.

Ce qui entre

La chaîne IA. Prévision H+1 branchée sur le front (#185), job des recommandations sur la zone or (#175), cycle de vie en base (#164, #180), contrôle de rejouabilité sur le serveur (#181).

L'exploitation. Les conteneurs relisent enfin les fichiers du dépôt (#176) : Prometheus tournait depuis quatre jours avec la configuration du 3 septembre. La sauvegarde PostgreSQL redevient observable (#172), et son alerte cessera de se déclencher sur une absence de donnée. Tableau de bord Grafana de la chaîne de données (#112).

L'outillage. Un banc pouvait rendre un rouge sur du code sain (#174) : producteur | grep -q sous pipefail rend 141 et le contrôle conclut « absent » sur ce qui est présent.

Ce que ça ne fait pas

La #182 de Marvin n'entre pas : en conflit et rouge. Le tableau de bord servi reste celui d'hier, ce qui est cohérent puisque sa demande fait justement lire la zone or au front. Elle suivra dans un second déploiement.

À quoi s'attendre

Cinq à dix minutes, la pile api est recréée donc npm ci et vite build repassent. app_compose_wait_timeout est à 600 s pour ça.

Deux tâches n'ont jamais tourné sur ce serveur : prevoir.sh à :35 et recommandations-hourly.sh à :45. La première passe complète sera donc à 12h35 puis 12h45, et c'est là qu'on saura si la chaîne IA tient de bout en bout.

Critères de sortie

  • Le déploiement sort en failed=0, la pile api n'est pas sautée
  • https://app.g2.enervision/api/v1/sites répond 200 et le tableau de bord se charge
  • La migration 0019 est appliquée, statut, resolue_a et action existent
  • Les sept lignes de crontab sont posées, prevoir et recommandations comprises
  • probe_success rend une série pour l'API, la forge et Grafana
  • ev_ops_sauvegarde_pg_dernier_resultat remonte dans Prometheus
  • La relève de la minute n'a pas de trou pendant la bascule
  • À 12h45, public.recommandation et public.prevision ont bougé

Si ça tourne mal

main revient à 4689705 et le déploiement se rejoue. Le socle est reconstructible, ça a été fait trois fois hier.

33 commits, 78 fichiers. La chaîne IA entre en production : le job de prévision et celui des recommandations tournent pour la première fois sur ce serveur. ## Ce que ça pose ``` migration 0019 seule, le cycle de vie des recommandations (#164) clés de coffre aucune nouvelle, l'assertion d'entrée du rôle app est inchangée piles recréées api et supervision SEULEMENT, plus postgres crons ajoutés prevoir.sh à :35, recommandations-hourly.sh à :45 dépendances déjà dans le venv du serveur, l'étape pip ne téléchargera rien ``` Vérifié plutôt que supposé, parce que ce sont les trois choses qui cassent un déploiement : - les huit paquets que `deps-services.py` réclame sont **déjà installés** sur le serveur, mlflow 3.16 et scikit-learn 1.9 compris ; - la 0019 s'applique sur `public.recommandation` qui contient **une** ligne, et son `default ''` puis `drop default` est écrit pour ce cas ; - `postgres` n'est **plus** recréé, contrairement au déploiement d'hier. C'est le retour de Gabriel sur le #176 : la base ne tombe plus à chaque fusion, seules `api` et `supervision` sont recréées parce qu'elles seules portent des montages de fichiers. ## Ce qui entre **La chaîne IA.** Prévision H+1 branchée sur le front (#185), job des recommandations sur la zone or (#175), cycle de vie en base (#164, #180), contrôle de rejouabilité sur le serveur (#181). **L'exploitation.** Les conteneurs relisent enfin les fichiers du dépôt (#176) : Prometheus tournait depuis quatre jours avec la configuration du 3 septembre. La sauvegarde PostgreSQL redevient observable (#172), et son alerte cessera de se déclencher sur une absence de donnée. Tableau de bord Grafana de la chaîne de données (#112). **L'outillage.** Un banc pouvait rendre un rouge sur du code sain (#174) : `producteur | grep -q` sous `pipefail` rend 141 et le contrôle conclut « absent » sur ce qui est présent. ## Ce que ça ne fait pas La #182 de Marvin n'entre pas : en conflit et rouge. Le tableau de bord servi reste celui d'hier, ce qui est cohérent puisque sa demande fait justement lire la zone or au front. Elle suivra dans un second déploiement. ## À quoi s'attendre **Cinq à dix minutes**, la pile `api` est recréée donc `npm ci` et `vite build` repassent. `app_compose_wait_timeout` est à 600 s pour ça. Deux tâches n'ont **jamais** tourné sur ce serveur : `prevoir.sh` à :35 et `recommandations-hourly.sh` à :45. La première passe complète sera donc à 12h35 puis 12h45, et c'est là qu'on saura si la chaîne IA tient de bout en bout. ## Critères de sortie - [ ] Le déploiement sort en `failed=0`, la pile `api` n'est pas sautée - [ ] `https://app.g2.enervision/api/v1/sites` répond 200 et le tableau de bord se charge - [ ] La migration 0019 est appliquée, `statut`, `resolue_a` et `action` existent - [ ] Les sept lignes de crontab sont posées, `prevoir` et `recommandations` comprises - [ ] `probe_success` rend une série pour l'API, la forge et Grafana - [ ] `ev_ops_sauvegarde_pg_dernier_resultat` remonte dans Prometheus - [ ] La relève de la minute n'a pas de trou pendant la bascule - [ ] À 12h45, `public.recommandation` et `public.prevision` ont bougé ## Si ça tourne mal `main` revient à `4689705` et le déploiement se rejoue. Le socle est reconstructible, ça a été fait trois fois hier.
lenaic self-assigned this 2026-09-08 09:31:18 +00:00
outillage: un banc pouvait rendre un rouge sur du code sain
Some checks failed
Intégration / Contrôles statiques du dépôt (pull_request) Successful in 8s
Intégration / Workflows — lint et audit de sécurité (pull_request) Successful in 22s
Intégration / Tableau de bord — dépendances, tests et construction (pull_request) Successful in 38s
Intégration / Python — qualité, tests et dépendances (pull_request) Has been cancelled
f534f75d6e
La #173 est sortie ROUGE sur « web.listen-address=127.0.0.1:9100 », alors que
la ligne est bien dans infra/compose/supervision/docker-compose.yml. Dans la
MÊME boucle, le 9090 passait au vert.

La cause n'est pas le fichier :

  grep -q sort dès la PREMIÈRE correspondance et ferme le tuyau ;
  le producteur en amont reçoit un SIGPIPE et meurt en 141 ;
  set -o pipefail fait valoir 141 au pipeline entier ;
  le « if » prend la branche « absent » sur une chaîne PRÉSENTE.

Reproduit :

  set -uo pipefail
  gros=$(seq 1 200000)
  printf '%s\n' "$gros" | grep -qF -- '1'   ->  141

Seules les correspondances PRÉCOCES sont touchées, celles où le producteur
écrit encore quand grep s'arrête. Le 9100 est ligne 70, le 9090 ligne 180 :
d'où un banc intermittent, dépendant de la taille du fichier et de l'ordre des
motifs. C'est le pire des défauts pour une chaîne, parce qu'un rouge cesse
d'être une information.

Six pipelines remplacés par une chaîne ici, qui n'a pas de tuyau donc pas de
SIGPIPE, dans test-supervision.sh, test-deploiement-continu.sh et
test-chaine-tableau-de-bord.sh.

Et un banc de plus, tests/ci/test-hygiene-bancs.sh, qui reproduit d'abord le
défaut puis interdit la forme dans tout script sous pipefail. Il s'exclut
lui-même, à dessein : il porte la démonstration.

Au passage, un second piège rencontré en réécrivant : « <<<"" » envoie UNE
LIGNE VIDE, pas zéro octet, et grep -v la fait correspondre. Le contrôle du
jeton de forge rougissait sur zéro occurrence. La garde de vacuité est posée,
avec la raison écrite au-dessus.

Onze bancs verts, shellcheck propre, ruff propre.
recommandations: le job qui fait tourner les trois règles sur la zone or (#39)
All checks were successful
Intégration / Contrôles statiques du dépôt (pull_request) Successful in 8s
Intégration / Workflows — lint et audit de sécurité (pull_request) Successful in 23s
Intégration / Tableau de bord — dépendances, tests et construction (pull_request) Successful in 38s
Infra Ansible / Playbooks Ansible valides (pull_request) Successful in 1m45s
Intégration / Python — qualité, tests et dépendances (pull_request) Successful in 5m34s
1d0ab31181
CE QUI MANQUAIT. La #154 a livré les règles, le moteur et le rapprochement,
tous éprouvés sans base. Personne ne les appelait : public.recommandation est
restée vide, et la vue Site du tableau de bord n'avait rien à afficher. Le
critère de vérification du #39 demande « trois recommandations produites avec
leur justification » ; c'est ce module qui les produit.

Trois pièces : entrepot.py, seul module du paquet à connaître PostgreSQL et à
importer psycopg tardivement, pour que les 123 tests du paquet tournent sans
pilote ; job.py, la passe et ses codes de sortie ; et le lanceur cron, planifié
à la minute 37, dix minutes derrière load-postgres qui écrit ce que les règles
lisent.

PREUVE SUR LES DONNÉES RÉELLES, le 08/09 à 07h30 :

  passe terminée : 1 écrite(s), 0 maintenue(s), 0 résolue(s),
                   13 silence(s), 7 indécision(s), 0 défaillance(s)

  site      SITE003
  regle     r2 v1.0.0
  libelle   Site Data Center Marseille : la charge moyenne dépasse 85 % de la
            capacité depuis 3 heures (94 %, 94 %, 94 %).

DEUX DÉFAUTS TROUVÉS EN LE BRANCHANT, ET C'EST LE POINT PRINCIPAL.

1. R2 était STRUCTURELLEMENT MUETTE. Sa fenêtre de fraîcheur valait trois
   heures. Or mesure_horaire est un agrégat continu dont la politique porte
   end_offset => 1 hour : TimescaleDB ne matérialise qu'un seau entièrement
   contenu dans la fenêtre, et la politique se rejoue toutes les heures, donc
   le seau le plus récent a DÉJÀ entre deux et trois heures. Mesuré à 3 h 29 le
   08/09 à 07h29, sur une politique pourtant saine, dernier passage à 06h57
   sans échec. Sur les 434 lignes de qualite_jour et 10 283 moyennes horaires,
   la règle n'aurait jamais rien dit.
   La fenêtre passe à six heures, chiffre mesuré et non choisi, et deux tests
   l'épinglent : le cas de production redevient jugeable, la borne reste.

2. Collision de noms de fichiers de test. tests/unit/silver porte déjà
   test_job.py et test_entrepot.py ; sans __init__.py, pytest refusait de
   charger les deux paires. Les miens portent le suffixe du paquet.

CE QUE LA BASE NE SAIT PAS PORTER, et qui reste ouvert côté #153 :

- qualite_jour n'a pas de colonne releves_imputes. Elle est dérivée de
  repartition_methode, forward_fill plus interpolated. Trois tests gardent la
  dérivation, dont celui qui refuse de rendre 0 quand la répartition manque :
  « zéro imputé » et « on ne sait pas » ne sont pas la même chose.
- public.recommandation n'a ni gravite ni action ni statut. Les deux premières
  rejoignent valeurs_declenchantes faute de colonne ; le cycle de vie avec
  resolved_at que le #153 spécifie n'a toujours aucun emplacement.

R1 reste indécise tant que public.prevision est vide : elle attend le job
d'inférence du #37. R3 se tait, et c'est une bonne nouvelle : le taux de
disponibilité va de 0,9812 à 1,0000 sur les 434 lignes, aucune sous son seuil
de 0,80. Le moteur enregistre ça comme un Silence, distinct d'une indécision.

123 tests sur le paquet, 91 % de couverture, 724 sur la suite complète hors
API. ruff, mypy strict, ansible-lint profil production, yamllint, shellcheck :
propres. Banc du rôle app : 15 contrôles, aucun échec.
infra: les conteneurs relisent les fichiers du dépôt à chaque déploiement (#176)
All checks were successful
Intégration / Contrôles statiques du dépôt (pull_request) Successful in 9s
Intégration / Workflows — lint et audit de sécurité (pull_request) Successful in 28s
Intégration / Tableau de bord — dépendances, tests et construction (pull_request) Successful in 39s
Infra Ansible / Playbooks Ansible valides (pull_request) Successful in 1m8s
Intégration / Python — qualité, tests et dépendances (pull_request) Successful in 5m12s
801017a43e
Un montage de fichier unique suit l'INODE, pas le chemin. Git n'édite jamais
un fichier en place : il en écrit un neuf et le renomme. Après un déploiement,
le conteneur garde donc l'ancien contenu, et RIEN ne le signale.

Deux pannes en deux jours, même cause :

- Prometheus a tourné QUATRE JOURS avec la configuration du 3 septembre. Le
  job blackbox était dans le fichier côté hôte, absent côté conteneur, et la
  cinquième alerte du #42 n'avait aucune donnée sur laquelle se déclencher.
- Le tableau de bord a rendu 404 après le déploiement de la #173 : le Caddy de
  la forge ne voyait pas le montage du front.

Trois choses ici.

1. La boucle des piles passe recreate: always. Les piles à build: true étaient
   déjà recréées par effet de bord de la reconstruction ; le réglage rend la
   règle explicite et uniforme. Coût mesuré : quelques secondes par pile, les
   volumes nommés survivent.

2. Notre propre API entre dans les cibles blackbox. Elle en était absente parce
   que prometheus.yml est antérieur à la pile api, alors que c'est la latence
   que l'ENF-04 chiffre à 400 ms p95. Sondée sur son écoute directe, pas par
   Caddy, pour mesurer le service et non le proxy.

3. Le manuel de supervision dit comment reconnaître le cas en deux commandes :
   le compte diffère entre le fichier de l'hôte et celui que le conteneur lit.

Le banc du rôle garde le réglage, éprouvé en rouge sur sa suppression.

ansible-lint profil production, yamllint propre, trois playbooks valides,
banc du rôle 16 contrôles, dix bancs verts.
ci: relance la chaîne, la tâche Python avait été annulée
All checks were successful
Intégration / Contrôles statiques du dépôt (pull_request) Successful in 8s
Intégration / Workflows — lint et audit de sécurité (pull_request) Successful in 23s
Intégration / Tableau de bord — dépendances, tests et construction (pull_request) Successful in 38s
Intégration / Python — qualité, tests et dépendances (pull_request) Successful in 5m39s
7950471d66
Aucun changement de contenu. La tâche « Python — qualité, tests et
dépendances » de cette demande porte le statut « cancelled », pas « failure » :
elle a été coupée le 07/09 à 14h19 pendant qu'une autre exécution était
interrompue à la main. Les quatre autres contrôles étaient verts.

L'API de la forge n'expose pas de relance de tâche ; un commit vide redéclenche
la chaîne, et c'est la seule façon de distinguer un vrai refus d'une annulation
sans laisser la demande rouge.
infra: la sauvegarde PostgreSQL redevient observable, l'alerte du #42 disait faux (#172)
All checks were successful
Intégration / Tableau de bord — dépendances, tests et construction (pull_request) Successful in 41s
Intégration / Contrôles statiques du dépôt (pull_request) Successful in 7s
Intégration / Workflows — lint et audit de sécurité (pull_request) Successful in 19s
Infra Ansible / Playbooks Ansible valides (pull_request) Successful in 57s
Intégration / Python — qualité, tests et dépendances (pull_request) Successful in 5m59s
97fdb65239
L'alerte « Sauvegarde PostgreSQL en échec » a ouvert une issue le 07/09 à
14h10, alors que les vidages passent tous les jours. Le dernier état dit
« 2026-09-08T02:30:04 OK 3 base(s) », 8,5 Mo pour enervision_prod, disque à
51 %. L'alerte se déclenchait sur une ABSENCE de donnée, ce qui est le pire
cas : une alerte critique qui a tort désarme celui qui la lit.

Deux causes, indépendantes, toutes les deux corrigées ici.

1. Le rôle backup n'a AUCUNE étiquette dans site.yml.
   Le déploiement continu joue --tags app,proxy : un rôle sans étiquette n'est
   jamais joué. /usr/local/bin/pg-backup.sh est donc resté figé à sa version
   d'amorçage du 2 septembre, celle d'avant le #42, qui ne publie aucune
   métrique. Zéro occurrence de TEXTFILE dans le script installé, six dans
   celui du dépôt. Le rôle prend l'étiquette « backup » et entre dans les
   étiquettes du déploiement. Il est idempotent et ne touche pas au socle :
   un répertoire, deux scripts, une ligne de cron.

2. Les fichiers de métriques sortaient en 0640 root:root.
   pg-backup.sh tourne en root sous une umask restrictive et n'imposait pas de
   mode. node-exporter, lui, tourne sans privilège : il rendait
   node_textfile_scrape_error 1 et n'exposait rien. Les fichiers du collecteur,
   eux, sont en 0664 et remontaient bien — d'où une panne qui ne touchait que
   la sauvegarde. Le chmod porte sur le TEMPORAIRE, avant le renommage, donc
   l'écriture reste atomique. 0644 et pas 0640 : trois compteurs, aucun secret.

Vérifié sur le serveur avant d'écrire : script réinstallé à la main, vidage
rejoué, ev_ops_sauvegarde_pg_dernier_resultat 0 et les deux horodatages
publiés. Cette demande fait en sorte que ça n'ait plus à être fait à la main.

Le banc de supervision garde le mode 0644, éprouvé en rouge sur sa suppression.

ansible-lint profil production, yamllint propre, trois playbooks valides,
shellcheck propre, dix bancs verts.
db: le cycle de vie d'une recommandation entre en base (#164)
All checks were successful
Intégration / Contrôles statiques du dépôt (pull_request) Successful in 8s
Intégration / Workflows — lint et audit de sécurité (pull_request) Successful in 25s
Intégration / Tableau de bord — dépendances, tests et construction (pull_request) Successful in 36s
Intégration / Python — qualité, tests et dépendances (pull_request) Successful in 5m21s
ab69e1344e
`cycle.rapprocher`, écrit au #154, attendait de savoir où écrire. La 0019
ajoute à `public.recommandation` les trois colonnes du #153 : `statut`
(active/résolue, défaut active), `resolue_a`, et `action` — la consigne, qui
sépare une recommandation d'une alerte et que la 0014 avait oubliée. La 0014
n'est pas retouchée.

L'unicité (site, horodatage, règle, version) tombe : elle laissait coexister
deux lignes actives pour le même site et la même règle, et faisait de la
version une part de l'identité, alors que le #153 pose l'inverse — recaler un
seuil ne ferme ni ne rouvre ce qui est en cours. À sa place, un index unique
partiel sur (site_id, regle_id) où statut = 'active'.

`action text not null` devant des lignes déjà écrites (critère 5) : `default ''`
puis `drop default`. Le défaut rend l'ajout sûr quelle que soit l'histoire de
la base — sans lui, la migration échoue dès la première ligne présente, au
démarrage du serveur ; son retrait fait qu'une insertion qui oublie l'action
échoue au lieu d'écrire une consigne vide. La table est vide partout
aujourd'hui : le chargement de la zone or ne remplit que `mesure`,
`qualite_jour` et `alerte`, et l'API sert des fixtures.

Une contrainte de plus que le ticket ne demandait : `(statut = 'resolue') =
(resolue_a is not null)`. L'état et son instant ne peuvent pas se contredire,
et c'est l'invariant que la colonne existe pour tenir.

0017 était pris par le #36 et 0018 par le #168 pendant que le #164 attendait :
le fichier est donc la 0019, et non la 0017 du ticket.

Le fichier rejoint la liste `NOUVEAUX` de test_migrations_zone_or.py
(critère 4) et porte ses propres cas dans
tests/unit/db/test_migration_recommandation_cycle.py, dont la garde de dérive
avec `cycle.Resolution` et `Recommandation` du moteur.
inference: --deux-passes tourne aussi sur GNU coreutils (#36)
All checks were successful
Intégration / Tableau de bord — dépendances, tests et construction (pull_request) Successful in 35s
Intégration / Contrôles statiques du dépôt (pull_request) Successful in 6s
Intégration / Workflows — lint et audit de sécurité (pull_request) Successful in 19s
Infra Ansible / Playbooks Ansible valides (pull_request) Successful in 1m5s
Intégration / Python — qualité, tests et dépendances (pull_request) Successful in 5m12s
40ae579ae6
`mktemp -t passe1-36` échoue sur GNU coreutils — « too few X's in
template » — alors que le mktemp de macOS complète le motif tout seul. Le
chemin `--deux-passes` ne pouvait donc jamais tourner là où le ticket
demande qu'il tourne : sur le serveur de la salle, en Ubuntu.

Le contrôle de rejouabilité du quatrième critère sortait en échec avant
d'avoir appris quoi que ce soit, avec un message qui parle de mktemp et
non du modèle.

Aucun test ne couvre `entrainer.sh` : c'est ce qui a laissé passer
l'écart entre les deux mktemp, et ça reste vrai après ce correctif.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
recommandations: le job s'aligne sur le cycle de vie en base (#39, #164)
All checks were successful
Intégration / Contrôles statiques du dépôt (pull_request) Successful in 9s
Intégration / Tableau de bord — dépendances, tests et construction (pull_request) Successful in 36s
Intégration / Workflows — lint et audit de sécurité (pull_request) Successful in 20s
Infra Ansible / Playbooks Ansible valides (pull_request) Successful in 1m7s
Intégration / Python — qualité, tests et dépendances (pull_request) Successful in 5m23s
65f3312607
La migration 0019 du #164 change le schéma sous ce module, et deux points
auraient cassé la passe horaire en production sans qu'aucun test ne bronche.

1. `on conflict (site_id, horodatage, regle_id, regle_version)` visait
   l'unicité de la 0014, que la 0019 SUPPRIME. PostgreSQL rend 42P10, « no
   unique or exclusion constraint matching the ON CONFLICT specification », et
   l'insertion entière échoue. Le on conflict vise maintenant l'index unique
   partiel, PRÉDICAT COMPRIS : sans le `where statut = 'active'`, l'index
   partiel n'est pas reconnu.

2. `action` est devenue une colonne `not null` sans défaut. L'insertion ne
   la renseignait pas — elle la rangeait dans valeurs_declenchantes faute de
   colonne — ce qui vaut 23502. Elle sort du JSON et va où elle doit.
   `gravite` y reste, elle n'a toujours pas de colonne.

Et deux conséquences que la migration rend enfin possibles :

3. `actives()` lit `statut` au lieu d'une fenêtre de 26 heures. L'heuristique
   oubliait une recommandation encore ouverte mais plus vieille que la fenêtre,
   la faisait ré-émettre, et l'index unique partiel aurait refusé l'insertion.

4. Les résolutions sont ÉCRITES, plus seulement comptées. Sans statut,
   a_resoudre était journalisé puis perdu : une recommandation restait ouverte
   pour toujours, et avec l'index partiel la règle ne pouvait plus jamais
   ré-émettre pour ce site.

Le SQL n'était couvert par rien : les tests du job passent par un double, ceux
de l'entrepôt ne couvraient que _imputes et dsn. Trois contrôles croisent
maintenant le texte du SQL avec celui de la migration, comme le contrat de
prévision croise son format d'identifiant avec la 0008.

Éprouvé en rouge sur quatre défauts réintroduits un par un : l'ancienne cible
du on conflict, la disparition d'action, et l'appel à resoudre retiré du job.

126 tests, ruff et mypy --strict propres.
Merge pull request 'db : le cycle de vie d'une recommandation entre en base (#164)' (#180) from olivier/164-recommandation-cycle into develop
All checks were successful
Intégration / Tableau de bord — dépendances, tests et construction (push) Successful in 40s
Intégration / Contrôles statiques du dépôt (push) Successful in 7s
Intégration / Workflows — lint et audit de sécurité (push) Successful in 19s
Intégration / Python — qualité, tests et dépendances (push) Successful in 5m31s
f010b1d5ce
Reviewed-on: https://10.105.200.41/g2/enervision/pulls/180
Reviewed-by: lenaic <lenaic@noreply.10.105.200.41>
infra: la recréation se décide pile par pile, pas globalement (#176)
All checks were successful
Intégration / Tableau de bord — dépendances, tests et construction (pull_request) Successful in 37s
Intégration / Contrôles statiques du dépôt (pull_request) Successful in 6s
Intégration / Workflows — lint et audit de sécurité (pull_request) Successful in 20s
Infra Ansible / Playbooks Ansible valides (pull_request) Successful in 1m2s
Intégration / Python — qualité, tests et dépendances (pull_request) Successful in 5m33s
06d0d6f7f7
Retour de Gabriel en relecture, et il a raison sur le point qui compte.

recreate: always sur les cinq piles recréait le conteneur PostgreSQL DE
PRODUCTION à chaque fusion. Or postgres ne porte que des montages de
répertoire et de volume : sa composition le dit déjà en capitales, avec les
deux inodes mesurés. Il n'a pas le défaut qu'on répare, et le couper pendant
que la relève tourne à la minute, la zone argent à :00, la zone or à :17
et :27 et les recommandations à :37, c'est tomber au milieu d'une passe à
chaque déploiement.

Le drapeau vit maintenant dans app_stacks, à côté de build et env :

  api           vrai  monte services/api en lecture seule et lance uvicorn
                      sans rechargement : sans recréation, un déploiement ne
                      mettait jamais le nouveau code d'API en service
  supervision   vrai  monte le fichier de règles d'alerte de Grafana
  postgres      faux  montages de répertoire, déjà à l'abri
  minio         faux  monte init-buckets.sh sur un conteneur restart: no ;
                      le recréer le RELANCE à chaque déploiement
  mlflow        faux  reconstruite de toute façon par build: true

Et le remède d'un mot, que Gabriel a relevé et qui était dans le dépôt depuis
avant cette demande : prometheus/ et blackbox/ sont montés en RÉPERTOIRES.
Chacun ne contient qu'un fichier, le montage est strictement équivalent, la
cause disparaît sans recréer quoi que ce soit. --web.enable-lifecycle redevient
vrai : son commentaire promettait un rechargement à chaud qui relisait
l'ancien inode.

Le contrôle du banc porte sur le drapeau et non sur un littéral : il cherchait
« recreate: always » et aurait rougi sur « recreate: "always" », qui est le
même réglage. C'est la faute même que la #174 corrige à côté.

Éprouvé en rouge sur trois défauts réintroduits un par un : postgres remis en
recréation, l'API privée de la sienne, et la tâche qui ignore le drapeau.

ansible-lint profil production, yamllint propre, trois playbooks valides,
banc du rôle 16 contrôles, dix bancs verts.
outillage: le garde-fou couvre la classe du défaut, et un vrai script le portait (#174)
All checks were successful
Intégration / Tableau de bord — dépendances, tests et construction (pull_request) Successful in 40s
Intégration / Contrôles statiques du dépôt (pull_request) Successful in 7s
Intégration / Workflows — lint et audit de sécurité (pull_request) Successful in 17s
Intégration / Python — qualité, tests et dépendances (pull_request) Successful in 5m21s
283a06c789
Trois retours de Gabriel, dont un qui trouve le défaut hors des bancs.

1. infra/terraform/bootstrap.sh porte la forme, deux fois.
   Le balayage ne lisait que tests/ci et .forgejo/scripts. Ces deux-là sont
   dans un script d'exploitation Azure, où un « la politique n'a pas refusé »
   erroné envoie chercher au mauvais endroit. Corrigés, et le balayage lit
   maintenant git ls-files '*.sh'.

2. Le motif ne couvrait qu'une orthographe d'un seul consommateur.
   La classe n'est pas « grep -q » : c'est un consommateur qui sort avant la
   fin du producteur. head, grep -m1, grep -F -q ont le même effet.

   Ma première correction a élargi SANS BORNER, et le banc s'est mis à rougir
   sur du code sain : x=$(... | head -1 | cut -d= -f2), où personne ne teste
   le statut, et jusqu'à un awk -F'|' dont le tube est dans une chaîne. Un
   banc qui crie au loup se fait désarmer, ce qui est le reproche que cette
   demande fait aux autres. Le motif est donc borné à ce qui est vraiment
   dangereux : un tuyau dont le statut SERT DE CONDITION, derrière if, elif,
   while, until ou une négation. C'est la forme des deux cas de bootstrap.sh.

3. La démonstration n'est plus comptée comme un cas d'essai.
   Ses deux branches appelaient ok : on lisait deux verts là où il y en a un.
   Elle s'affiche « note » et sort du décompte.

Éprouvé en rouge sur le vrai défaut : la forme réintroduite dans bootstrap.sh
fait rougir le banc, en le nommant.

shellcheck propre, onze bancs verts.
recommandations: la fenêtre de lecture borne la règle, et un seau nul ne tue plus la passe (#39)
All checks were successful
Intégration / Tableau de bord — dépendances, tests et construction (pull_request) Successful in 39s
Intégration / Contrôles statiques du dépôt (pull_request) Successful in 6s
Intégration / Workflows — lint et audit de sécurité (pull_request) Successful in 20s
Infra Ansible / Playbooks Ansible valides (pull_request) Successful in 1m17s
Intégration / Python — qualité, tests et dépendances (pull_request) Successful in 5m28s
418cc14510
Deux retours de Gabriel, tous deux fondés, et le premier rendait mon propre
correctif inopérant.

1. FRAICHEUR_MAX à 6 h ne servait à rien avec HEURES_REMONTEES à 6.
   C'est la fenêtre de LECTURE qui borne la règle, pas celle de fraîcheur : à
   3 h de retard la requête ne rend que trois seaux, à 4 h elle n'en rend que
   deux, et R2 se tait sur « 2 moyennes disponibles » au lieu de « hors de la
   fenêtre glissante ». Le retard mesuré le 08/09 était de 3 h 29 : je tenais
   avec ZÉRO marge, et un cycle de rafraîchissement manqué faisait taire la
   règle — exactement ce que mon commentaire prétendait avoir couvert.

   La valeur se DÉRIVE maintenant des deux constantes de la règle,
   FRAICHEUR_MAX + HEURES, soit 9. Recaler l'une sans l'autre est la faute
   qu'on répare : elle ne peut plus se refaire.

2. Un seau moyenne_kw à NULL faisait tomber toute la passe.
   mesure_horaire calcule avg(...) filter (where indicateur_qualite <>
   'critical') : une heure dont TOUS les relevés sont critiques rend NULL.
   C'est un état normal, c'est même ce que R3 sert à rendre visible.
   float(None) levait un TypeError hors de tout try : sept sites perdaient
   leurs recommandations parce qu'une heure d'un seul site était trouée.

   Le seau est écarté, pas remplacé par zéro : une heure sans mesure
   exploitable n'est pas une heure à zéro kW. R2 voit un trou et se dit
   indécise, ce qui est la vérité.

Le regroupement des seaux sort dans _moyennes_par_site() pour être éprouvable :
le garde ne s'observait pas tant qu'il vivait dans une fonction qui ouvre une
connexion. On éprouve le comportement, pas le texte.

Quatre contrôles ajoutés, éprouvés en rouge sur les deux défauts réintroduits.

130 tests, ruff et mypy --strict propres.
Merge branch 'develop' into olivier/36-entrainement-modele
All checks were successful
Intégration / Tableau de bord — dépendances, tests et construction (pull_request) Successful in 39s
Intégration / Contrôles statiques du dépôt (pull_request) Successful in 7s
Intégration / Workflows — lint et audit de sécurité (pull_request) Successful in 18s
Intégration / Python — qualité, tests et dépendances (pull_request) Successful in 5m43s
89b5ae0298
outillage: le contrôle du mode des métriques dit vrai (#172)
All checks were successful
Intégration / Tableau de bord — dépendances, tests et construction (pull_request) Successful in 39s
Intégration / Contrôles statiques du dépôt (pull_request) Successful in 6s
Intégration / Workflows — lint et audit de sécurité (pull_request) Successful in 18s
Infra Ansible / Playbooks Ansible valides (pull_request) Successful in 1m9s
Intégration / Python — qualité, tests et dépendances (pull_request) Successful in 5m12s
6414118356
Retour de Gabriel, et deux versions successives se sont trompées dans les
deux sens avant d'arriver à celle-ci.

1. `grep -c ... || echo 0` produisait DEUX lignes. grep -c imprime déjà 0
   quand il ne trouve rien, et sort en 1 : le repli ajoutait une seconde
   ligne, le test crachait « integer expression expected » et le message
   annonçait « 0\n0 sur 2 ». Le verdict restait rouge, mais un rouge qui se
   présente mal se fait ignorer.

2. Ma première correction a élargi le motif à `umask`, et le banc est devenu
   VERT sur un script dont les deux chmod avaient disparu — le script porte un
   umask ailleurs. Un contrôle qui accepte tout ne garde rien, ce qui est
   exactement le reproche que la #174 fait aux autres, et que je viens de
   refaire en le corrigeant.

Le contrôle compte maintenant le mode LÀ OÙ IL SE POSE : sur les lignes qui
manipulent le fichier temporaire, et il en veut deux. `chmod` et
`install -m` sont acceptés, un umask global ne l'est pas — c'est justement
lui qui produisait le 0640 illisible.

Éprouvé dans les trois sens : les deux chmod retirés donnent « 0 sur 2 », un
seul retiré donne « 1 sur 2 », et un `install -m 0644` équivalent reste vert.

shellcheck propre, dix bancs verts.
infra: tableau de bord Grafana chaîne-donnée (#112)
All checks were successful
Intégration / Contrôles statiques du dépôt (pull_request) Successful in 8s
Intégration / Workflows — lint et audit de sécurité (pull_request) Successful in 23s
Intégration / Tableau de bord — dépendances, tests et construction (pull_request) Successful in 39s
Intégration / Python — qualité, tests et dépendances (pull_request) Successful in 5m19s
514995ac7a
Ajoute le tableau de bord chaine-donnee (fraîcheur par site, volume
ingéré par heure, complétude par site, répartition par régime
d'imputation), branché sur la source postgres-metier maintenant que
#33/#34/#35 écrivent en base. modele et sauvegardes restent hors de ce
ticket : ils dépendent respectivement de #36/#37 et #71, non livrés.

Étend tests/ci/test-supervision.sh pour couvrir ce nouveau tableau de
bord.
Merge branch 'develop' into lenaic/bancs-faux-negatif
All checks were successful
Intégration / Tableau de bord — dépendances, tests et construction (pull_request) Successful in 40s
Intégration / Contrôles statiques du dépôt (pull_request) Successful in 8s
Intégration / Workflows — lint et audit de sécurité (pull_request) Successful in 17s
Intégration / Python — qualité, tests et dépendances (pull_request) Successful in 5m10s
96b0ee9545
[37] Ajout prévision et branchement front
All checks were successful
Intégration / Contrôles statiques du dépôt (pull_request) Successful in 6s
Intégration / Workflows — lint et audit de sécurité (pull_request) Successful in 24s
Intégration / Tableau de bord — dépendances, tests et construction (pull_request) Successful in 38s
Infra Ansible / Playbooks Ansible valides (pull_request) Successful in 1m7s
Intégration / Python — qualité, tests et dépendances (pull_request) Successful in 6m10s
52d16f8376
supervision: le panneau dit la zone or, pas la zone bronze (#112)
All checks were successful
Intégration / Contrôles statiques du dépôt (pull_request) Successful in 6s
Intégration / Workflows — lint et audit de sécurité (pull_request) Successful in 22s
Intégration / Tableau de bord — dépendances, tests et construction (pull_request) Successful in 36s
Intégration / Python — qualité, tests et dépendances (pull_request) Successful in 5m29s
b7dbbaee77
Le panneau s'intitulait « Volume ingéré par heure (zone bronze) » et interroge
public.mesure, qui est la zone OR (migration 0007_zone_or_mesure.sql). Le corps
de la demande dit d'ailleurs « zone or » : c'est le titre qui se trompe.

La zone bronze, ce sont les objets JSON de MinIO, que rien ici ne lit. Un jury
qui demande à voir la zone bronze et à qui on montre une requête PostgreSQL,
sur une épreuve dont le médaillon est le sujet, c'est un mauvais moment pour
rien.

Le titre dit maintenant ce que le panneau mesure : le volume en zone or dont
la valeur brute a été conservée.
Merge pull request 'outillage : un banc pouvait rendre un rouge sur du code sain' (#174) from lenaic/bancs-faux-negatif into develop
All checks were successful
Intégration / Tableau de bord — dépendances, tests et construction (push) Successful in 39s
Intégration / Contrôles statiques du dépôt (push) Successful in 7s
Intégration / Workflows — lint et audit de sécurité (push) Successful in 17s
Intégration / Python — qualité, tests et dépendances (push) Successful in 5m32s
b41918bd21
Merge branch 'develop' into lenaic/39-job-recommandations
All checks were successful
Intégration / Tableau de bord — dépendances, tests et construction (pull_request) Successful in 41s
Intégration / Contrôles statiques du dépôt (pull_request) Successful in 6s
Intégration / Workflows — lint et audit de sécurité (pull_request) Successful in 19s
Infra Ansible / Playbooks Ansible valides (pull_request) Successful in 1m3s
Intégration / Python — qualité, tests et dépendances (pull_request) Successful in 5m28s
e796fb5f93
Merge branch 'develop' into justine/37-inference-prevision-servie
All checks were successful
Intégration / Tableau de bord — dépendances, tests et construction (pull_request) Successful in 39s
Intégration / Contrôles statiques du dépôt (pull_request) Successful in 7s
Intégration / Workflows — lint et audit de sécurité (pull_request) Successful in 18s
Infra Ansible / Playbooks Ansible valides (pull_request) Successful in 1m4s
Intégration / Python — qualité, tests et dépendances (pull_request) Successful in 5m57s
cbe04ac43f
Merge pull request 'recommandations : le job qui fait tourner les trois règles sur la zone or (#39)' (#175) from lenaic/39-job-recommandations into develop
Some checks failed
Infra Ansible / Playbooks Ansible valides (push) Waiting to run
Intégration / Tableau de bord — dépendances, tests et construction (push) Successful in 41s
Intégration / Contrôles statiques du dépôt (push) Successful in 7s
Intégration / Workflows — lint et audit de sécurité (push) Has been cancelled
Intégration / Python — qualité, tests et dépendances (push) Has been cancelled
cac3ec7367
Reviewed-on: https://10.105.200.41/g2/enervision/pulls/175
Reviewed-by: gabriel <gabriel@noreply.10.105.200.41>
Merge branch 'develop' into lenaic/176-montages-fichiers
All checks were successful
Intégration / Contrôles statiques du dépôt (pull_request) Successful in 7s
Intégration / Tableau de bord — dépendances, tests et construction (pull_request) Successful in 35s
Intégration / Workflows — lint et audit de sécurité (pull_request) Successful in 34s
Infra Ansible / Playbooks Ansible valides (pull_request) Successful in 2m58s
Intégration / Python — qualité, tests et dépendances (pull_request) Successful in 5m26s
6d09cdbd4e
[37] fix path packages/contract
Some checks failed
Intégration / Tableau de bord — dépendances, tests et construction (pull_request) Successful in 40s
Intégration / Contrôles statiques du dépôt (pull_request) Successful in 7s
Intégration / Workflows — lint et audit de sécurité (pull_request) Successful in 18s
Intégration / Python — qualité, tests et dépendances (pull_request) Has been cancelled
Infra Ansible / Playbooks Ansible valides (pull_request) Has been cancelled
96e0a8c71a
Merge pull request 'infra : la sauvegarde PostgreSQL redevient observable, l'alerte disait faux (#172)' (#178) from lenaic/172-sauvegarde-observable into develop
Some checks failed
Intégration / Python — qualité, tests et dépendances (push) Waiting to run
Intégration / Tableau de bord — dépendances, tests et construction (push) Waiting to run
Intégration / Contrôles statiques du dépôt (push) Waiting to run
Intégration / Workflows — lint et audit de sécurité (push) Waiting to run
Infra Ansible / Playbooks Ansible valides (push) Has been cancelled
6ccf25f516
Merge pull request '[36] Le contrôle de rejouabilité tourne aussi sur le serveur' (#181) from olivier/36-entrainement-modele into develop
Some checks are pending
Intégration / Python — qualité, tests et dépendances (push) Waiting to run
Intégration / Tableau de bord — dépendances, tests et construction (push) Waiting to run
Intégration / Contrôles statiques du dépôt (push) Waiting to run
Intégration / Workflows — lint et audit de sécurité (push) Waiting to run
0a1c3ea7b7
Merge pull request 'infra : tableau de bord Grafana chaîne-donnée (#112)' (#183) from gabriel/112-dashboard-chaine-donnee into develop
Some checks failed
Intégration / Contrôles statiques du dépôt (push) Waiting to run
Intégration / Workflows — lint et audit de sécurité (push) Waiting to run
Intégration / Tableau de bord — dépendances, tests et construction (push) Has been cancelled
Intégration / Python — qualité, tests et dépendances (push) Has been cancelled
fef285078b
Merge remote-tracking branch 'origin/develop' into justine/37-inference-prevision-servie
Some checks failed
Intégration / Python — qualité, tests et dépendances (pull_request) Has been cancelled
Intégration / Tableau de bord — dépendances, tests et construction (pull_request) Has been cancelled
Intégration / Contrôles statiques du dépôt (pull_request) Has been cancelled
Intégration / Workflows — lint et audit de sécurité (pull_request) Has been cancelled
Infra Ansible / Playbooks Ansible valides (pull_request) Has been cancelled
661d8c1451
# Conflicts:
#	infra/ansible/group_vars/all/vars.yml
Merge branch 'develop' into justine/37-inference-prevision-servie
All checks were successful
Intégration / Contrôles statiques du dépôt (pull_request) Successful in 9s
Intégration / Tableau de bord — dépendances, tests et construction (pull_request) Successful in 36s
Intégration / Workflows — lint et audit de sécurité (pull_request) Successful in 18s
Infra Ansible / Playbooks Ansible valides (pull_request) Successful in 1m31s
Intégration / Python — qualité, tests et dépendances (pull_request) Successful in 6m2s
557fce3554
Merge pull request 'infra : les conteneurs relisent les fichiers du dépôt à chaque déploiement (#176)' (#177) from lenaic/176-montages-fichiers into develop
All checks were successful
Intégration / Tableau de bord — dépendances, tests et construction (push) Successful in 40s
Intégration / Contrôles statiques du dépôt (push) Successful in 7s
Intégration / Workflows — lint et audit de sécurité (push) Successful in 19s
Infra Ansible / Playbooks Ansible valides (push) Successful in 1m7s
Intégration / Python — qualité, tests et dépendances (push) Successful in 5m38s
94e8f9b4fa
Merge branch 'develop' into justine/37-inference-prevision-servie
All checks were successful
Intégration / Tableau de bord — dépendances, tests et construction (pull_request) Successful in 38s
Intégration / Contrôles statiques du dépôt (pull_request) Successful in 9s
Intégration / Workflows — lint et audit de sécurité (pull_request) Successful in 20s
Infra Ansible / Playbooks Ansible valides (pull_request) Successful in 1m37s
Intégration / Python — qualité, tests et dépendances (pull_request) Successful in 5m38s
1c8a967bb6
Merge pull request '[37] Ajout prévision et branchement front' (#185) from justine/37-inference-prevision-servie into develop
All checks were successful
Intégration / Tableau de bord — dépendances, tests et construction (push) Successful in 40s
Intégration / Contrôles statiques du dépôt (push) Successful in 6s
Intégration / Workflows — lint et audit de sécurité (push) Successful in 19s
Infra Ansible / Playbooks Ansible valides (push) Successful in 1m31s
Intégration / Python — qualité, tests et dépendances (push) Successful in 5m37s
Intégration / Contrôles statiques du dépôt (pull_request) Successful in 8s
Intégration / Workflows — lint et audit de sécurité (pull_request) Successful in 22s
Intégration / Tableau de bord — dépendances, tests et construction (pull_request) Successful in 36s
Infra Ansible / Playbooks Ansible valides (pull_request) Successful in 1m34s
Intégration / Python — qualité, tests et dépendances (pull_request) Successful in 5m51s
5c8a3b752c
Reviewed-on: https://10.105.200.41/g2/enervision/pulls/185
Reviewed-by: marvin <marvin@noreply.10.105.200.41>
lenaic requested review from gabriel 2026-09-08 09:31:19 +00:00
gabriel approved these changes 2026-09-08 09:33:31 +00:00
lenaic merged commit cff745d453 into main 2026-09-08 09:37:41 +00:00
Sign in to join this conversation.
No reviewers
No milestone
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!188
No description provided.