From 38a831407a81cd02358a07474ab5bd3d51e9ce65 Mon Sep 17 00:00:00 2001 From: Patrick Schurig Date: Thu, 27 Aug 2026 10:31:46 +0200 Subject: [PATCH] =?UTF-8?q?fix(3g-1):=20la=20conversion=20watts=E2=86=92am?= =?UTF-8?q?p=C3=A8res=20arrondit,=20elle=20ne=20tronque=20pas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Quatrième défaut latent de 3g-1. Il vaut son propre message parce que c'est une décision, et qu'elle s'est prise contre le test. 3g-1 convertit l'enveloppe du waterfall en ampères puis la passe à `static_cast(qBound(...))` — donc une TRONCATURE. Cela paraît prudent (« ne jamais dépasser l'enveloppe ») et coûte systématiquement un ampère : 230 W par phase, abandonnés à chaque cycle, toute la journée. Les scénarios de simulation le chiffrent — huit d'entre eux sortaient un ampère plus bas, et la voiture finissait la journée 1 % moins chargée. L'arbitrage : l'enveloppe est une ALLOCATION, pas une limite. Elle sort d'une mesure de compteur dont le bruit dépasse largement 115 W. Ce qui est une limite — le courant maximal de la borne et la place laissée par la protection de surcharge — n'est pas franchi pour autant : le plafond est déclaré à l'enveloppe (commit précédent), donc respecté AVANT l'allocation, et le qBound écrête au matériel. C'est pourquoi le CODE a été réaligné sur l'amont plutôt que le test : testOverloadProtectionEcoMode encode le comportement de l'arrondi, et la troncature faisait basculer la protection de surcharge de « réduire au minimum » à « couper ». Un ampère de conversion ne doit pas décider ça. Et c'est aussi pourquoi la limite de phase n'est PAS réappliquée en ampères entiers dans l'adaptateur : ce second écrêtage serait plus strict que celui de l'amont (plancher au lieu d'arrondi) et coûterait de nouveau un ampère, pour 69 W de marge théorique que la couche L4 mesure réellement en temps réel. Suite simulation `run` 16/16, testOverloadProtectionEcoMode vert. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_017TVywtayEeoRJ2yWUW4iZj --- energyplugin/etm/adapters/evadapter.cpp | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/energyplugin/etm/adapters/evadapter.cpp b/energyplugin/etm/adapters/evadapter.cpp index 6469ad4..458eef0 100644 --- a/energyplugin/etm/adapters/evadapter.cpp +++ b/energyplugin/etm/adapters/evadapter.cpp @@ -157,8 +157,27 @@ LoadAction EvAdapter::applyAction(const LoadAction &action, const QDateTime &now double demandeA = action.currentA; if (demandeA <= 0 && action.powerW > 0) - demandeA = action.powerW / (230.0 * phases); + // 3g-2 — arrondi au PLUS PROCHE, comme le faisait le proxy. Tronquer paraissait plus + // prudent (« ne jamais dépasser l'enveloppe ») et coûtait systématiquement un ampère : + // 230 W par phase abandonnés à chaque cycle, sur toute la journée. Les 18 scénarios de + // simulation le chiffrent — voiture 1 % moins chargée le soir, huit fois sur huit. + // + // L'enveloppe est une ALLOCATION, pas une limite : elle sort d'une mesure de compteur + // dont le bruit dépasse largement 115 W. Ce qui est une limite, c'est le plafond + // matériel — et celui-là n'est jamais franchi : il est déclaré à l'enveloppe par + // toLoadContext(), et le qBound ci-dessous écrête au courant maximal de la borne. + demandeA = qRound(action.powerW / (230.0 * phases)); + // La limite de phase n'est PAS réappliquée ici, et c'est délibéré : elle est déjà dans + // l'enveloppe, déclarée par toLoadContext() comme plafond de la charge, donc respectée par + // le waterfall AVANT d'allouer. La réappliquer en ampères entiers ferait un second + // écrêtage, plus strict que celui de l'amont (plancher au lieu d'arrondi), et coûterait à + // nouveau un ampère dans les scénarios de simulation — pour 69 W de marge théorique que la + // couche L4, elle, mesure réellement en temps réel. + // + // La répartition reste celle du modèle de sécurité : la stratégie respecte le plafond + // déclaré, l'adaptateur écrête au matériel, et verifyOverloadProtection() garde le dernier + // mot sur la mesure. const uint clampedA = static_cast( qBound(static_cast(minA), demandeA, static_cast(maxA)));