# DESIGN — la commande perdue, et l'écart que personne ne mesure *(Ouvert le 2026-09-01, sur le premier constat de `RELEVE_APP_20260831.md`. Le design avant le code, comme pour le délestage et le §10.)* ## 1. Le fait, tel qu'il a été mesuré Coupure Modbus. La box journalise `Restoring manual values of false 6 A` trois secondes après. La borne, elle, reste à `power: true`, **7 A** — la consigne que l'arbitre lui avait donnée au cycle d'avant. Le lien revient : **rien ne réapplique**. En mode manuel personne ne commande, donc l'écart est **permanent**. **4,4 kW réellement tirés, `draw.committedW` à 0, un budget de surplus de 2 349 W qui ignore ces watts.** ## 2. Ce que le fait dit vraiment — et il est plus large que son titre « Réappliquer la commande de l'arbitre » ne décrit pas le cas : **en mode manuel, l'arbitre ne commande pas**. Ce qui s'est passé est autre chose, et c'est pire : 1. Le proxy amont a voulu restaurer des valeurs manuelles (`false 6 A`) ; 2. l'écriture a échoué pendant l'indisponibilité ; 3. **personne ne l'a rejouée** ; 4. l'organe est resté sur une consigne **dont plus aucun commandeur ne se réclame** — ni l'arbitre, qui ne commande plus, ni le mode manuel, dont la consigne n'a pas atterri. > **La troisième forme de la même famille.** Après la télémétrie qui ne distinguait pas une > issue de son contraire (`ECS-111`) et la valeur de retour qui faisait passer un refus pour un > succès (`ECS-415`), voici **l'exécution** : une écriture perdue, un état sain en apparence, > `available` à vrai, et personne pour savoir que la commande n'a jamais atterri. ## 3. Ce que le moteur a déjà — et ce qui manque | Pièce | État | |---|---| | Dernière consigne écrite, charges en watts | **existe** — `m_currentSetpointW`, `m_currentStage` | | Dernière consigne écrite, **bornes VE** | **MANQUE** — `EvAdapter` ne garde que `m_lastActionAt` | | Ce que l'organe FAIT | **existe** — `EvCharger::maxChargingCurrent()`, états du Thing | | Ce que l'organe MESURE, et ce que ça vaut | **existe** — `deviceMeasurement()`, `measurement.source` (`LM-1105`/`LM-1106`) | | Détection de la FIN d'une indisponibilité | **MANQUE** — le seul chemin est `clearFault()`, actionné par l'opérateur | | Comparaison commandé / constaté | **MANQUE** — nulle part | **Le constat porte sur les bornes, et c'est précisément là que la mémoire de la commande manque.** Ce n'est pas un hasard : les adaptateurs de charges en watts gardent leur consigne parce qu'ils en ont besoin pour l'écrêtage de verrou ; la borne, elle, était pilotée par le proxy amont, qui n'a jamais eu à se relire. ## 4. LES DÉCISIONS **4.1 — Réappliquer, ou signaler ?** Réappliquer automatiquement une consigne vieille de plusieurs minutes est un geste **dangereux** : le monde a changé pendant l'indisponibilité — le surplus, l'heure, l'échéance. Rejouer une décision périmée, c'est décider avec des données mortes, exactement ce que le mode dégradé L2 refuse de faire. **Recommandation : SIGNALER toujours, RÉAPPLIQUER jamais telle quelle.** Au retour de disponibilité, le cycle normal reprend la main et décide sur des données fraîches — c'est déjà le bon comportement. Ce qui manque n'est pas le rejeu, c'est que **l'écart soit dit tant qu'il dure**. Et il dure indéfiniment quand personne ne reprend la main, ce qui est le cas du relevé. **4.2 — Que publie-t-on, et sous quelle forme ?** Un écart n'a de sens que rapporté à ce qu'on sait de l'organe. Proposition : un objet `command` au niveau de la charge, publié **seulement quand la comparaison est possible** — ```jsonc "command": { "expectedW": 1380, "observedW": 4400, "divergent": true, "since": "…" } ``` **`measurement.source` conditionne ce que vaut cette comparaison**, et doit donc la gouverner : contre `none`, il n'y a rien à comparer et l'objet est **absent** — pas `divergent: false`, qui affirmerait la concordance qu'on ne peut pas constater. Même règle que partout ailleurs ici. **4.3 — La COMPTABILITÉ : le diagnostic était à corriger, et le vrai défaut est plus grave.** > **MESURÉ le 2026-09-01, avant d'implanter.** Le constat disait « un budget de surplus qui > ignore ces watts ». **Ce n'est pas ce qui se passe.** Quand une charge consomme et **obéit**, > la comptabilité est juste : sa conso est recréditée puis réallouée à elle-même, et les > suivantes ne reçoivent que le surplus réel (mesuré : export 500 W, A à 4 400 W obéissante → B > reçoit 500 W). **Corriger cela aurait été corriger un non-défaut.** **Le vrai défaut : le recrédit est un PRÊT consenti contre l'obéissance.** Son hypothèse est écrite dans le code — *« le budget est une mesure dont la conso de la charge a déjà été retranchée »* — c'est-à-dire **« je peux rendre ces watts parce que je peux les reprendre »**. Rien ne vérifie que l'emprunteur rembourse. Mesuré, le cas où la promesse est engagée : export 500 W, A consomme 4 400 W, plancher de A à 5 000 W. A est commandée à **0**, et ses 4 400 W sont **redistribués à B**. | | si A obéit | si A n'obéit pas | |---|---|---| | A | tombe à 0 | reste à 4 400 W | | B | 4 000 W réels | 4 000 W **fictifs** | | maison | 4 000 W pour 4 900 de production | **8 400 W pour 4 900** → 3 500 W importés | **Et l'identité de réconciliation TIENT dans les deux cas** — `surplus + recrédit − alloué == restant`. C'est ce qui distingue un prêt d'une erreur : la comptabilité est juste, c'est l'**hypothèse sur laquelle elle repose** qui n'est pas vérifiée. Aucun contrôle de cohérence ne pouvait donc l'attraper. **Ce qui change maintenant : l'obéissance est CONSTATABLE.** `command.divergent` (étape 2) dit si la charge a suivi au cycle précédent. Le recrédit peut donc être **conditionné** — c'est la proposition, et elle demande une décision parce qu'elle a son propre risque : l'anti-clignotement existe pour une raison, et le suspendre trop vite ferait osciller une charge saine sur un écart transitoire. `since` est précisément ce qui permet de distinguer les deux. **Ce que le §4.3 d'origine proposait — retrancher la conso subie du budget — est SANS OBJET** : elle en est déjà retranchée, par le compteur. La note est conservée ici pour que personne ne la réimplante en la croyant oubliée. **4.3-bis — l'ancienne formulation, conservée pour mémoire.** `draw.committedW` à 0 pendant que 4,4 kW sont tirés n'est pas un défaut d'affichage : le budget de surplus est calculé sans ces watts, donc **le moteur alloue de l'énergie qui n'existe pas**. Une charge que l'arbitre ne commande plus mais qui consomme est de la **consommation fixe**, au même titre que la base de la maison — c'est exactement le raisonnement d'`ECS-410` pour une charge gelée, appliqué à une cause nouvelle. **Recommandation : la retrancher du budget, sans l'allouer.** Elle n'est pas servie, elle est subie. La compter comme allouée serait prétendre l'avoir décidée. > **Sur 18 kVA c'est invisible ; sur 6 kVA c'est le disjoncteur.** Et le délestage ne rattrape > pas ce cas : il agit sur la marge MESURÉE, qui inclut bien ces 4,4 kW — donc il protège. Ce > qui reste faux est le **budget**, qui promet un surplus déjà consommé. **4.4 — Faut-il un état « commande en vol perdue » distinct d'un défaut ?** `ECS-410`/`ECS-414` couvrent l'écriture qui échoue trois fois → défaut. Ici l'écriture a échoué **pendant une indisponibilité**, et la charge est ensuite redevenue disponible : le défaut se lève, la divergence reste. À trancher : est-ce un `faultCode` de plus, ou un état orthogonal ? ## 5. ORDRE DE TRAVAIL — deux étapes à comportement constant d'abord **Étape 1 — la MÉMOIRE de la commande, sans rien en faire.** `EvAdapter` retient sa dernière consigne appliquée, comme les autres adaptateurs le font déjà. Publication seule, comportement constant. Sans cette pièce, aucune des suivantes n'est calculable. **Étape 2 — l'ÉCART publié, sans décider dessus.** L'objet `command`, gouverné par `measurement.source`. C'est la moitié qui rend le défaut **visible**, et elle ne change aucune décision — même stratégie qu'à l'étape C du §10, où publier le régime a précédé de décider avec. **Étape 3 — la COMPTABILITÉ corrigée.** La conso subie sort du budget. C'est ici que le comportement change, et c'est la seule étape qui le fasse. **Étape 4 — la reprise en main**, si elle est jugée nécessaire après avoir vu les traces des étapes 2 et 3. **Délibérément en dernier** : c'est la seule qui commande du matériel, et on saura alors combien de fois le cas se produit réellement. --- ## 6. CRITÈRE DE TRI DES TRACES — écrit le 2026-09-05, AVANT d'avoir les données > **Écrit avant exprès.** Un critère rédigé après coup se plie aux résultats : on écarte ce qui > gêne et on garde ce qui confirme, sans jamais mentir, et sans jamais pouvoir le prouver. Celui-ci > est daté et poussé avant l'ouverture de la fenêtre d'observation. S'il doit changer ensuite, ce > sera un commit visible, avec sa raison. ### Pourquoi il faut un tri Le journal de divergence (`+etm56`) a produit **14 épisodes en 24 h** sur le banc, avant même le retour de la V2C. Aucun n'est exploitable : ``` 08:05 DÉBUT "chauffe-eau" — attendu 3500 W, mesuré 1500 W 08:13 DÉBUT "chauffe-eau" — attendu 500 W, mesuré 1500 W 10:01 DÉBUT "chauffe-eau" — attendu 3000 W, mesuré 1500 W ``` `ECS-Meter` publie **1500 W en permanence**, `PAC-Meter` **800 W** — vérifié sur trois lectures espacées. Ce sont des simulateurs à valeur fixe. L'écart mesuré n'est donc pas une désobéissance : c'est la distance entre une consigne qui varie et un compteur qui ne varie pas. La divergence « finit » quand la consigne passe par 1500 W — une coïncidence arithmétique, pas une obéissance. **Dimensionner l'étape 4 là-dessus aurait été la dimensionner sur des divergences qui n'existent pas.** ### Le critère, en deux conditions Une divergence n'est exploitable que sur une charge dont le compteur **suit réellement la consigne**. Deux conditions, et les deux sont nécessaires : 1. **`measurement.source` ≠ `none`.** Sans mesure propre, l'écart est calculé contre une estimation, et il ne dit rien de la charge. 2. **Le compteur doit avoir VARIÉ sur la fenêtre**, indépendamment de la consigne. Un compteur dont l'écart-type est nul sur la période n'est pas un compteur : c'est une constante déguisée, et tout écart contre lui est un artefact. **Ne PAS trier par nom de charge.** Écarter `chauffe-eau` et `pac-terrain` nommément serait fragile de deux façons : ça se périme au premier renommage, et ça n'attrape pas la troisième charge à compteur fixe qu'on ajoutera sans y penser. Le critère porte sur une **propriété observable de la donnée**, pas sur une liste. ### Les faux positifs de la V2C, et ils sont d'une autre nature La V2C a un vrai compteur, donc elle passe le critère ci-dessus. Mais **chaque perte de lien produira une divergence qui n'est pas une commande perdue** : c'est une **mesure absente**. Il faut donc distinguer deux affirmations que l'écart seul confond : | | ce que ça veut dire | geste | |---|---|---| | **elle n'obéit pas** | la commande est partie, la mesure la contredit | c'est le cas de l'étape 4 | | **on ne sait pas si elle obéit** | la mesure manque ou est périmée | rien à reprendre en main | **C'est exactement la distinction `none` contre l'absence d'un champ, appliquée à un écart.** Une mesure absente qui se lit comme une mesure à zéro produit une divergence maximale, et c'est le pire faux positif possible : il ressemble trait pour trait au cas qu'on cherche. Concrètement, une divergence dont la mesure vaut 0 pendant que le lien est tombé doit être **comptée à part**, jamais avec les autres. Le relevé du 2026-08-30 le documente : à la perte du lien, la V2C publie `currentPower` **0** et ses booléens à `false`, tandis que `sessionEnergy` reste à sa dernière valeur (`LM-1012-d`). Le zéro est donc **publié**, pas absent — et rien dans l'objet `command` ne le distingue d'un zéro réel. > **Conséquence à porter au lot**, et à trancher avant de compter : `command` devrait pouvoir dire > que son `observedW` n'est pas fiable, plutôt que de laisser déduire. C'est la même parade que > `progress.regime` pour l'avancement. Tant qu'elle n'existe pas, le tri se fait à la main sur les > traces, en croisant avec les pertes de lien du journal. ### Ce qui sera compté, et ce qui ne le sera pas - **Compté** : les épisodes sur une charge à compteur variable, hors fenêtres de perte de lien. - **Compté à part** : les épisodes concomitants d'une perte de lien — ils mesurent la disponibilité, pas l'obéissance. - **Écarté** : les épisodes sur une charge dont le compteur est constant sur la fenêtre. - **Le compte des écartés est publié aussi.** Un tri dont on ne dit pas ce qu'il a jeté n'est pas un tri, c'est une sélection.