DÉFAUT MESURÉ, non corrigé. Bascule Europe/Paris → Europe/London à 13:37:26Z,
sans redémarrage de nymead :
periodStartedAt : 1788645600 → 1788649200 (+3600 s) CORRECT, la journée suit
measuredSince : 1788645607 → 1788649207 (+3600 s) FAUX, un instant absolu
ne bouge pas
Cause : `m_baselinePosee = now` conserve le QDateTime en spécification
Qt::LocalTime, qui garde des composantes d'horloge murale et résout son époque au
moment de la CONVERSION. Après bascule, la même lecture « 00:00:07 » rend une
époque différente d'une heure.
Le champ posé vendredi pour rendre une couture lisible est lui-même déplacé par la
couture. Correctif : `m_baselinePosee = now.toUTC()`, une ligne — un instant
absolu se stocke en UTC, là où periodStartedAt doit rester construit dans le
fuseau courant puisque c'est une frontière locale. Les deux champs n'ont pas la
même nature et le stockage doit le refléter. Non appliqué : le déploiement
redémarrerait nymead et reposerait la baseline pendant la fenêtre de capture de
l'app.
EV_MIN_GRID NE SERA PAS VÉRIFIABLE SUR MACHINE (DESIGN_MIN_RESEAU §8), et c'est
écrit avant plutôt que découvert en mesurant : la voiture est débranchée,
pluggedIn retombe, la borne sort de loads[] — vérifié, il ne reste que chauffe-eau
et pac-terrain. Le lot tiendra sur ses tests et sa contre-épreuve. Trois
préconditions cumulatives pour lever la réserve, aucune à notre main aujourd'hui.
L'INERTIE SAINE, distinguée (AGENTS.md). Tout ce qui précède traite une
contre-épreuve inerte comme un signal d'alarme ; il existe un cas où elle est
correcte — le mécanisme tire sa valeur du CONTEXTE, pas du champ perturbé. Le
plancher du minimum réseau vient de la borne, pas du champ déclaré.
Le test qui sépare : « qu'est-ce qui déciderait, si ce n'est pas ça ? » Inertie
malsaine, on ne sait pas nommer l'autre source. Inertie saine, on la nomme ET on
peut la perturber elle. D'où la conséquence pratique : une inertie saine n'est
acceptable que si la contre-épreuve est DÉPLACÉE sur la vraie source, jamais
abandonnée — sinon c'est refermer l'identité sur elle-même une fois de plus.
13 KiB
Minimum réseau — design
Ouvert le 2026-09-06. Option 2 retenue : livrer le minimum réseau maintenant, avec la machinerie du §10 ; le renommage l'accompagne. L'arbitrage au prix (option 3) reste l'horizon, il demande une source tarifaire et une prévision.
1. Le constat, et il est double
Le mode ChargingModeEcoWithMinCurrent n'a jamais eu de minimum réseau — non pas parce que
le code manquait, mais parce qu'il n'a jamais été atteint. Établi dans DESIGN_3g.md §5 : mort
par réécriture chez l'amont, puis mort par exclusion depuis 3g-2.
Et son nom promet l'inverse de ce qu'il fait. MinCurrent désigne l'exigence de la borne
— les 6 A en dessous desquels elle refuse de charger — pas une garantie du mode. Un lecteur y
voit « le mode maintient un courant minimum » ; le code, lui, ne fait que nommer un seuil
matériel.
Trois modes annoncés, deux implémentés — et celui qui manque est celui que l'utilisateur croyait utiliser. C'est la formulation à retenir : le défaut n'est pas qu'une fonction manque, c'est qu'elle est annoncée.
2. Pourquoi livrer le minimum maintenant, sans attendre le prix
Chiffrage retenu, tarif de rachat récent à 0,01 €/kWh :
| coût effectif | |
|---|---|
| charger en journée, 30 % au réseau | 0,073 €/kWh |
| charger la nuit, 100 % au réseau | 0,180 €/kWh |
Sur ces installations — la majorité du parc neuf — le minimum réseau en journée est simplement le bon choix, et aucun arbitrage n'est nécessaire pour le savoir. Le prix ne devient décisif que sur les anciens contrats à 0,1296 €, où l'écart tombe à deux centimes et s'inverse sous 62 % de couverture solaire.
Conclusion de conception : l'option 3 (comparer deux scénarios au prix) est le bon horizon, mais elle exige une source tarifaire et une prévision. L'option 2 livre l'utile aujourd'hui, et elle ne ferme pas la porte : un arbitrage au prix choisira plus tard s'il faut garantir, pas comment garantir.
3. Condition 1 — la machinerie du §10, pas un second chemin d'achat
Le minimum réseau passe par la passe éco, celle qui achète déjà :
- borné par l'autorisation de soutirage (
DrawCap/DrawMargin, trois sources) — comme tout achat ; funding: Gridquand le surplus ne couvre pas ;- le motif dit combien est acheté, comme
ECO_FLOOR_GRIDle fait déjà.
Ce que cela EXCLUT explicitement : porter le minimum par EV_GRID_START. Ses trois bornes
dérivent toutes du surplus (budgetW > 0, seuil proportionnel, consommation de tout le budget) ;
un minimum permanent en est indépendant par définition, donc les lever revient à créer le second
mécanisme. Et EV_GRID_START achète hors du registre d'autorisation — deux mécanismes dont
un seul est borné, et c'est le second qui ferait disjoncter. Raisonnement complet dans
DESIGN_3g.md §5.4.
4. Condition 2 — une puissance permanente, portée sans dupliquer needs
Le champ
Un champ de plus dans needs, frère de minEnergyWhPerDay — pas une structure parallèle :
needs : {
minEnergyWhPerDay : Uint, // énergie, avec échéance
dailyDeadline : String,
minPowerWAlways : Uint // NOUVEAU — puissance, tant que la charge est là
}
À ne pas confondre avec minPowerW (LM-1201) :
| ce que ça dit | |
|---|---|
minPowerW |
SI on sert cette charge, au moins ceci. Un plancher de modulation. |
needs.minPowerWAlways |
On sert cette charge au moins ceci, tant qu'elle est là. Une demande, pas un plancher. |
Le premier borne une consigne ; le second en crée une. Les deux coexistent sans se contredire.
POURQUOI une puissance ici, et une énergie ailleurs — et ce n'est PAS la modulation
L'ECS multi-paliers module aussi. Ce qui sépare vraiment les deux cas :
On ne sait pas combien de temps la voiture reste branchée.
Une obligation en énergie suppose une échéance — c'est ce qui donne un dénominateur à la cadence. Une borne n'en a aucune tant que personne n'en déclare. « 4 140 W tant qu'elle est là » est la seule formulation qui ne suppose rien.
Un chauffe-eau, lui, a une échéance naturelle et une capacité finie : l'énergie y est la bonne grandeur, et l'obligation éco la sert déjà. La différence est de nature du besoin, pas de capacité technique de la charge.
La composition dans la passe éco
computeEcoTarget() rend aujourd'hui une cadence dérivée de l'obligation en énergie. Elle rendra
le maximum des deux :
watts = max( cadenceObligation , minPowerWAlways )
Quatre invariants, et chacun ferme un silence :
- Le minimum ne se « tient » JAMAIS.
tenuene décrit que l'obligation en énergie ; une puissance demandée en permanence n'a pas de cible et ne se complète pas. Donc quand l'obligation est tenue,wattsretombe sur le minimum — pas sur zéro. C'est exactement la différence de nature, et c'est là qu'elle se voit dans le code. progressne décrit que l'obligation. Sur une charge à minimum seul,progressest absent — jamais à zéro : un avancement nul se lirait « rien livré », alors qu'il n'y a rien à avancer.- Le motif dit LEQUEL DES DEUX MORD. Sans ça, l'écran affiche un nombre qu'il ne peut pas
expliquer — et deux causes différentes produiraient le même chiffre. Le motif gagne
minAlwaysWet une marque de ce qui borne. - Un minimum non servi se DIT. Si l'autorisation de soutirage ne le permet pas, il n'est
pas servi — et c'est
DRAW_CAPqui le dit, en le nommant. Le silence ici ferait croire à un minimum honoré, ce qui est précisément la promesse qu'on refuse de faire.
4-bis. Le RANG et la PRÉCÉDENCE — ce que le minimum ne bouscule pas
Le minimum est servi à SON rang, il ne le contourne pas. Un minimum déclassé par une charge
mieux rangée est le comportement voulu, et il découle de LM-1002 : le rang est ce que
l'installateur décide. Le contredire par un mécanisme serait exactement le doublon qu'on refuse
partout — deux autorités sur le même ordre. Si le client veut sa voiture avant l'ECS, il la
range avant.
Nuance à connaître, parce qu'elle décide du mot au §5-bis : la hiérarchie éco/confort surplombe le rang — « un confort de rang 1 passe après un éco de rang 3 » (
rulebasedscheduler.cppl. 243-246). Un minimum passe donc avant tout le confort, mais le rang le départage face à un autre besoin éco.
DRAW_CAP et la réserve batterie PRIMENT. Un minimum qui passerait outre un plafond de
branchement ferait disjoncter l'installation. C'est un besoin, pas une exception aux plafonds
— et quand un plafond l'empêche, c'est DRAW_CAP qui le dit en le nommant (invariant 4 du §4).
5. Condition 3 — nommer les trois modes pour ce qu'ils font
Le fil ne peut pas être renommé : ChargingMode est une énumération publiée, et changer ses
identifiants casserait tout client déployé. Ce qui se corrige, c'est la description au
contrat — c'est là que vit le mensonge, et c'est de là que l'app tire ses libellés.
| valeur | ce que le contrat dit AUJOURD'HUI | ce que le code FAIT |
|---|---|---|
ChargingModeNormal |
« Recharge immédiate au maximum disponible » | MANUEL. La borne sort de l'arbitrage — « l'utilisateur pilote, l'arbitre s'abstient ». Le moteur ne commande rien. Seule la protection de surcharge la touche encore. |
ChargingModeEco |
« Recharge sur surplus solaire uniquement » | exact |
ChargingModeEcoWithMinCurrent |
« Eco + courant minimum réseau (6 A) si pas de surplus » | rien — le chemin n'est pas atteint |
Normal n'est pas un « boost ». Un boost commanderait le maximum ; celui-ci ne commande pas.
La seule chose que le moteur y fasse encore est de délester en surcharge — ce qui doit rester, et
doit être dit : un mode « manuel » qui coupe quand même mérite de l'annoncer.
5-bis. LE MOT — et ni « garanti » ni « prioritaire » ne tiennent
La question est la bonne, et elle est la même que celle qu'on est en train de corriger : un nom de mode qui promet autre chose que ce que le code fait. Les deux candidats échouent au même test, dans deux registres différents.
| mot | vrai quand | FAUX quand | verdict |
|---|---|---|---|
| garanti | jamais entièrement | plafond de soutirage atteint, réserve batterie, ou un autre éco mieux rangé épuise l'autorisation | promet une certitude que rien ne tient |
| prioritaire | face à tout le confort — la passe éco surplombe le rang | face à un autre besoin éco mieux rangé | vrai en régime normal, faux exactement dans le cas d'échec |
« Prioritaire » est le moindre mensonge, et c'est ce qui le rend pire ici : il est vrai 95 %
du temps, donc il ne se fait jamais démentir — sauf le jour où la voiture ne charge pas, où il
dit précisément l'inverse de ce qui s'est passé. Un mot qui ne se trompe que dans le cas
intéressant est un mot qui ne sera pas relu. Et il contredirait LM-1002 dans le libellé : il
laisse croire que le mode réordonne, alors que c'est l'installateur qui range.
Le mot retenu : on nomme le FINANCEMENT
C'est la seule propriété qui tienne toujours, et c'est aussi la vraie différence avec Eco :
Ecodépense le surplus. Ce mode-ci ACHÈTE le complément.
- Libellé du mode : « Surplus + minimum réseau ». Il dit ce que le moteur fera — servir sur surplus, et acheter le reste jusqu'au minimum — et ne promet rien sur le résultat, qui dépend du rang et des plafonds.
- Nom du champ :
minPowerWAlways. Grammaire de son frèreminEnergyWhPerDay: les deux déclarent une demande, aucun ne promet une livraison. Personne ne litminEnergyWhPerDaycomme une garantie ; ce champ-ci doit se lire pareil. - Mots bannis dans le code, le contrat et l'app : « garanti », « prioritaire ». Y compris dans les commentaires — c'est là que les promesses se réintroduisent.
Ce qui dit le résultat, c'est le motif, pas le nom. ECO_FLOOR_GRID chiffre ce qui est
acheté ; DRAW_CAP nomme le plafond qui a empêché. Le nom déclare l'intention, le motif rapporte
les faits — et c'est la séparation qu'on tient partout ailleurs.
6. Les étapes, à comportement constant d'abord
Étape A — corriger le contrat. Les trois descriptions ci-dessus, plus la note sur MinCurrent
qui nomme l'exigence de la borne. Aucun code. Comportement constant par construction, et c'est
le pas qui rend la suite lisible.
Étape B — porter minPowerWAlways dans LoadNeeds, le persister, le publier, sans que la
passe éco s'en serve. Aller-retour Get/SetLoadConfig, schéma déclaré une fois. Comportement
constant : le champ existe et ne décide de rien.
Étape C — la passe éco le prend en compte. Le seul pas qui change le comportement, et il
arrive quand tout le reste est déjà en place et vérifiable. Avec les quatre invariants du §4, le
motif enrichi, et le DRAW_CAP qui nomme le minimum.
Étape D — le mode s'y branche. ChargingModeEcoWithMinCurrent posé sur une borne écrit
minPowerWAlways = plancher de la borne. C'est le pas qui rend le mode opérant — et il
vient en dernier, parce qu'il n'a de sens qu'une fois C livrée.
7. Ce qu'on ne saura pas prouver au banc, et il faut le dire
L'installation de .75 produit 2 kWc contre un plancher de borne triphasée à 4 140 W. Un
minimum sur cette borne serait donc toujours financé au réseau, jamais au surplus — le banc
ne peut pas exercer le cas mixte, celui où le minimum complète un surplus partiel.
Il l'exercera en revanche très bien sur une charge en watts — le chauffe-eau — dès qu'elle aura un
compteur réel (TEST_TERRAIN.md §12). C'est la même précondition que pour l'étape 4 de la
commande perdue, et elle est maintenant nommée par un quatrième chemin.
8. Ce lot ne sera PAS vérifiable sur machine — écrit avant, pas découvert en mesurant
La voiture a été débranchée le 2026-09-06. pluggedIn retombe, la borne sort de loads[]
(vérifié : ['chauffe-eau', 'pac-terrain']), et rien ne la commande plus.
Donc EV_MIN_GRID tiendra sur ses tests unitaires et sa contre-épreuve, pas sur un relevé.
C'est acceptable à condition de le dire maintenant : le découvrir au moment de mesurer ferait
prendre une absence de mesure pour une absence de défaut.
Ce qu'il faudra pour lever la réserve, et c'est cumulatif :
- une voiture branchée — trivial, mais elle décide de tout le reste ;
- un lien V2C sain — trois semaines que c'est le blocage (
PORTING_STATUS.md) ; - et pour le cas mixte — minimum complété par un surplus partiel — une charge en watts avec un compteur réel, parce que 2 kWc contre un plancher de 4 140 W ne le produira jamais.
Les trois sont indépendantes, et aucune n'est à notre main aujourd'hui.