@pierre-gilles,
Tout d’abord merci pour cette réponse, qui a le mérite d’être complète et honnête sur tes contraintes.
Pour pouvoir te répondre pleinement, et pas juste « je suis sûr de mon code », j’ai pris le temps de mener un audit rigoureux de la PR cet après-midi. J’ai cadré la demande avec une consigne explicite : vérifier que chaque changement est nécessaire, solide, bien pensé, sans régression, et surtout que les tests ne sont pas dénaturés — y compris en cassant volontairement le code pour vérifier que les tests détectent bien les régressions (mutation testing réel). C’est précisément le doute légitime que tu soulèves sur l’IA et les tests, et je voulais y apporter une réponse vérifiable, pas une affirmation.
Je te livre ci-dessous le résultat de cet audit, factuel et reproductible chez toi.
1. Sur ton objection centrale : « la PR modifie en profondeur la logique de calcul »
Je maintiens ma réponse précédente, mais cette fois avec la vérification ligne à ligne. Les deux fichiers où la logique métier vit sont :
server/services/energy-monitoring/lib/energy-monitoring.calculateConsumptionFromIndex.js
server/services/energy-monitoring/lib/energy-monitoring.calculateCostFrom.js
J’ai relu chaque ligne modifiée. Ce qui est strictement inchangé :
convertEnergyUnit(...) — conversions d’unité
contracts[contract](...) — toutes les formules de coût (base, HP/HC, Tempo)
- Calcul du delta d’index et gestion du counter reset
- Filtrage des prix par date + EDF Tempo +
subtract(30, 'minutes')
saveMultipleHistoricalStates / saveHistoricalState
Les seuls ajouts dans calculateConsumptionFromIndex.js sont encapsulés dans un if (selectorSet.size > 0) (filtrage par whitelist) : désactivés en l’absence de whitelist, donc comportement legacy strictement préservé pour le calcul incrémental live qui tourne toutes les 30 minutes.
Dans calculateCostFrom.js, les ajouts sont :
- Parsing des dates de début/fin (nouvelle entrée pour le mode range)
- Filtrage par selectors (encapsulé dans
if (selectorSet.size > 0))
destroyStatesBetween si endAt fourni, sinon destroyStatesFrom (comportement legacy)
- Un
try/catch par feature de coût (robustesse : une feature qui plante ne bloque pas les autres)
La formule de coût elle-même — celle qui t’a pris des mois à fiabiliser sur base/HP/HC/Tempo — n’a pas bougé d’un caractère. C’est vérifiable en quelques minutes avec git diff master HEAD -- server/services/energy-monitoring/lib/energy-monitoring.calculateCostFrom.js et un Ctrl+F sur contracts[contract], convertEnergyUnit, energyPricesForDate.
2. Sur ton objection sur les tests : « l’IA peut introduire des régressions et adapter les tests pour les valider »
C’est une crainte parfaitement légitime, et c’est précisément pour ça que j’ai fait du mutation testing réel : j’ai cassé temporairement 3 points critiques du code et relancé toute la suite. Le but est de vérifier que les tests détectent la régression, pas qu’ils passent avec la régression.
Voici les 3 mutations testées et le résultat :
| Mutation appliquée |
Tests qui cassent |
| Validation des dates retirée du controller |
reject invalid start date format + reject invalid end date format (2 tests) |
Filtre whitelist du moteur live calculateConsumptionFromIndex retiré |
skip consumption features not in whitelist selectors (1 test) |
Bloc finally de restauration du curseur ENERGY_INDEX_LAST_PROCESSED retiré |
restore last processed value on selector-based recalculation + restore last processed value on window error (2 tests) |
Total : 5 tests cassent précisément sur les bonnes assertions. Les tests ne sont pas du green-washing IA, ils détectent vraiment les régressions sur les protections critiques. Reproductible chez toi : il suffit de commenter ces 3 blocs et relancer npm run test-service --service=energy-monitoring. Fichiers restaurés après vérification, j’ai confirmé avec git status qu’il n’y a aucune modification résiduelle.
J’ai aussi vérifié la non-régression des tests existants :
- Les tests legacy modifiés (sur
calculateConsumptionFromIndex.test.js) ont uniquement un ajout de undefined en deuxième paramètre (pour s’aligner sur la nouvelle signature). Aucune assertion n’a été affaiblie ou supprimée.
calculateCostFromYesterday.test.js est en réalité renforcé : avant il vérifiait juste calledOnce, maintenant il vérifie la signature exacte des arguments passés.
- Tests Controller : le pattern
try/catch + expect.fail est remplacé par next(error) — c’est le pattern Express correct.
3. Sur ta proposition « PR front-only avec presets 1/3/6/12 mois »
Je comprends la logique : éviter la phase de validation lourde sur le métier en ne touchant pas au backend. Mais cette proposition ne couvre pas le besoin réel des utilisateurs, et c’est important que je l’explique :
Cas d’usage n°1 — Le recalcul historique sur appareils existants ajoutés au suivi a posteriori
C’est la promesse du suivi de l’énergie dans Gladys. Beaucoup d’utilisateurs (dont moi) ont des prises Tasmota ou Zigbee2mqtt depuis plusieurs années dans leur instance Gladys, avec parfois 4 ans de données d’index stockées. Quand on ajoute l’intégration suivi d’énergie sur ces appareils, on doit pouvoir recalculer la consommation et le coût sur toute l’historique disponible, et pas seulement sur les 12 derniers mois.
Aujourd’hui, le seul moyen est le from-beginning global, qui :
- recalcule pour tous les appareils en même temps (et pas seulement celui qu’on vient d’ajouter)
- ne va pas au bout sur les installations conséquentes (cas que je vis et que d’autres remontent)
- bloque la requête HTTP côté front pendant toute la durée du calcul (probablement la régression silencieuse qui fait qu’on dit « ça ne marche pas » pour les grosses installs)
Un preset « 12 derniers mois » ne résout aucun de ces trois problèmes. Une sélection par feature + plage de dates les résout tous les trois.
Cas d’usage n°2 — Le tarif rétroactif
Un utilisateur qui s’aperçoit qu’il a changé de tarif il y a 18 mois et qui veut recalculer uniquement cette plage est bloqué avec un preset 12 mois.
Cas d’usage n°3 — La granularité
Quand on a 30 appareils suivis et qu’on veut juste corriger une donnée sur un seul appareil sur une semaine, lancer un recalcul global sur 1/3/6/12 mois est disproportionné et risqué pour les autres appareils.
4. Sur les améliorations de robustesse apportées par la PR
Je voudrais aussi mentionner deux points où la PR rend le code plus sûr que master :
wrapperDetached (nouvelle primitive job) découple la requête HTTP du calcul long. Avant, le from-beginning bloquait la requête HTTP jusqu’à la fin du calcul, ce qui faisait timeout sur les grosses installs. C’est probablement la cause silencieuse des « ça ne marche pas » qu’on remonte régulièrement.
- Le
try/finally autour de la restauration du curseur ENERGY_INDEX_LAST_PROCESSED protège l’instance d’un état corrompu si une window échoue en plein recalcul. Sur master, si une window plante, le curseur reste détruit et le calcul incrémental live démarre depuis zéro à la prochaine itération.
5. Points honnêtes que je vais corriger avant push
Pour ne pas te raconter que tout est parfait, l’audit a aussi remonté 3 cleanups mineurs (non bloquants, sans risque de régression) :
- Un fragment de backward-compat dans
calculateCostFrom qui n’a plus d’utilité (tous les callers internes passent déjà la nouvelle signature).
- Une duplication ~70% entre
FromBeginning et Range que je peux factoriser.
- 2-3 tests « passants par hasard » dans
calculateCostFrom.test.js qui ne testent pas vraiment ce qu’ils prétendent (à durcir ou retirer — les vrais tests métier restent dans les tests originaux non modifiés).
Je m’engage à corriger ces 3 points avant le découpage.
6. Ma proposition concrète
Compte tenu de ce qui précède, je te propose toujours le découpage en sous-PRs, mais en aménageant ton souci de « phase de validation incompressible » :
- PR1 :
destroyStatesBetween + wrapperDetached + leurs tests. ~150-200 lignes. Aucune logique métier, juste des utilitaires. Mergeable rapidement, low-risk.
- PR2 : recalcul par sélection de features depuis le début (sans plage de dates). Le filtre whitelist est encapsulé dans
if (selectorSet.size > 0), donc le comportement legacy reste strictement préservé.
- PR3 : recalcul par plage de dates +
shouldRestoreLastProcessed. C’est là que se concentre la « vraie » review métier.
- PR4 : ajustements UI.
Sur PR3 — celle qui te demande le plus de validation — je suis prêt à :
- Te fournir des jeux de données de test reproductibles à partir de ma prod (sortie SQL anonymisée) que tu pourrais rejouer chez toi
- Ajouter de la doc dans le code sur
shouldRestoreLastProcessed
- Te faire une démonstration vidéo du comportement avant/après si ça aide
Si tu valides ce découpage, je commence par PR1 cette semaine. Si tu préfères que je reste sur l’attente, dis-le-moi avec un horizon — même large — et je fermerai à nouveau la PR sereinement.
Merci pour ton temps, et désolé si la réponse est dense — j’ai préféré te donner du factuel plutôt que de l’affirmation.