[36] Entraînement du modèle de prévision H+1 et promotion #166
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
3 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
g2/enervision!166
Loading…
Reference in a new issue
No description provided.
Delete branch "olivier/36-entrainement-modele"
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?
Ferme #36. Prévision de puissance à H+1 par site, avec sa référence de comparaison publiée, entraînée en local et promue dans le registre MLflow.
Le modèle est entraîné et promu, la preuve est en commentaire du #36.
enervision-prevision-h1version 3 porte l'aliasproduction— MAE 2,018 kW contre 21,443 kW pour la persistance sur 504 heures de test, et le modèle bat la référence sur les sept sites.Ce que la branche apporte
services/inference/model/, le paquet d'entraînement, ettests/unit/model/, ses 95 tests. Sept modules :config.pydonnees.pyreference.pymetriques.pymodele.pyregistre.pyentrainement.pyPlus
bin/entrainer.sh, le README du service, etservices/inferenceajouté aupythonpath.Par où commencer la relecture
1.
donnees.py— l'absence de fuite. Un exemple prévoit l'instantpà partir des heuresp−1…p−24, toutes révolues à l'émission. Aucune statistique n'est calculée avant le découpage, qui est chronologique et non aléatoire. Deux tests le gardent explicitement (test_la_cible_n_apparait_jamais_dans_ses_propres_entrees,test_les_retards_sont_les_heures_qui_precedent_l_instant_prevu).La source est
public.mesure_horaire, l'agrégat continu de la 0016, et paspublic.mesureréagrégée ici : la règle qui exclut les relevéscriticalest déjà écrite une fois, en SQL, et c'est la vue que l'API et Grafana serviront.2.
modele.py— le réglage qui compte. Ce n'est pas le régresseur, c'estearly_stopping=False. Laissé sur « auto », scikit-learn met de côté 10 % des exemples tirés au hasard dès dix mille lignes : à la fois une fuite sur une série temporelle et un découpage qui n'est pas le nôtre.3.
registre.py— la règle de promotion.decider_promotionest une fonction pure de deux nombres, le seul endroit du dépôt qui dise quand un modèle passe en ligne : strictement mieux que la persistance, ou l'alias ne bouge pas. À égalité on garde la version en place.4. Le garde-fou de base. Le lanceur choisit
prod(défaut) oupreprodpar l'environnement de lancement. La promotion est refusée horsenervision_prodsauf--promouvoir-hors-prod, parce que l'aliasproductionest ce que le service du #37 chargera au démarrage. C'est le nom de base relu dans l'URL qui décide, jamais le réglage — un réglage peut mentir.Les quatre critères
ml-stagiaire-02, baseenervision_preprodmae_<site>par site dans l'exécutionproduction, exécution382e4058b234Le calcul GPU est sorti du périmètre
Décision du 07/09 :
HistGradientBoostingRegressor, conformément à l'ADR 0012 — « le T4 n'est pas requis ». Le ticket #36 a été modifié (titre, premier critère, preuve attendue). L'écart avecEXIGENCES-collectives.md§1, qui porte « en local, sur le Tesla T4 du serveur, arbitré le 01/09 », est consigné et non tranché : la ligne se lit désormais « en local, sur le serveur qui porte le T4 », et cette PR ne modifie pas le fichier collectif. À acter en point du matin si le groupe veut aligner le texte.À savoir avant de fusionner
public.mesurey est vide, le chargement de la zone or n'étant pas planifié — c'est #136, non fusionnée. Dès qu'elle passe,entrainer.sh --depuis ...vise la production sans autre option et trouve son URL dansENERVISION_ETL_DATABASE_URL.pytest.inisera en conflit d'une ligne avec la PR #154, qui ajouteservices/recommendationsen bout de la même ligne. Les deux ajouts se gardent, la résolution prend dix secondes.ci.ymln'est pas touché : la tâche Python découvre seule les tests et les manifestes. Si l'on veut inscriremodeldans les zones sensibles à 85 %, ce sera après #154, en une ligne.scikit-learn,numpyetmlflowaux dépendances applicatives. C'est le point quepip-auditva exercer pour la première fois sur des paquets de cette taille — s'il rougit, l'échappatoire est une CVE à la fois, justifiée en commentaire, jamais un interrupteur global.Contrôles joués localement
ruff et mypy strict propres sur
servicesetpackages· 692 tests unitaires au vert, 1 sauté hors chaîne (il exige scikit-learn, que la chaîne installe) · couverture globale 89 %, paquet 83 %, seuil bloquant 70 % ·developfusionné dans la branche, sans conflit.Relecture souhaitée par @justine, qui porte EC06 et le #37 :
model.registre.chargerest la couture qu'elle empruntera, et les trois noms du registre (enervision-prevision-h1, aliasproduction, expérienceprevision-h1) sont fixés parconfig.pycomme le manuel §4 le demandait au #36.Deux défauts relevés à la première exécution réelle, le 7 septembre, et que seul un vrai modèle pouvait montrer. `register_model("runs:/<id>/modele")` ne visait rien. MLflow 3 ne range plus un modèle sous les artefacts de son exécution mais comme une entité à lui, d'URI `models:/m-<id>` : le chemin n'existait pas, et l'enregistrement ne réussissait que par le repli du serveur — « Run with id ... has no artifacts at artifact path 'modele', registering model based on models:/m-... instead ». Un repli qui avertit aujourd'hui est un échec demain. L'enregistrement passe désormais par `registered_model_name` dans `log_model`, dont le retour porte la version ; le repli sur `info.model_uri` couvre une version de MLflow qui ne la porterait pas, et c'est l'URI que le serveur vient de créer, pas un chemin reconstruit. La signature et l'exemple d'entrée décrivaient le même objet de deux façons. Signature déduite d'une liste de listes, exemple donné en tableau NumPy : MLflow refusait de valider l'exemple qu'il venait d'écrire — « Invalid input. Invalid object type at position 0 ». Les deux viennent maintenant du même tableau. NumPy est déclaré au manifeste pour cette ligne, même si scikit-learn l'installerait de toute façon : on déclare ce qu'on importe, comme `pytz` dans l'ETL. ÉPROUVÉ SUR LE SERVEUR, trois exécutions successives sur `enervision_preprod`, bornes 2026-07-30 → 2026-09-04 : plus aucun avertissement, versions 1 puis 2 puis 3 enregistrées, alias `production` déplacé à chaque fois — et la lecture de la version en place avant déplacement donne bien « 2 -> 3 », ce que la première exécution ne pouvait pas montrer faute de version précédente. MAE identique au millième aux trois exécutions, 2,018 kW contre 21,443 kW pour la persistance : la rejouabilité du quatrième critère est constatée, pas supposée. Le chargement par alias est éprouvé pour la première fois du projet : `models:/enervision-prevision-h1@production` rend un PyFuncModel et sa version. C'est la seule commande de `docs/runbooks/mlflow.md` §5 qui n'avait jamais pu être jouée — « elle le sera au premier modèle du #36 » — et c'est la couture que le #37 empruntera. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>@olivier Ok pour moi une fois le conflit sur pytest.ini réglé