diff --git a/specs/spec_loadmodel.md b/specs/spec_loadmodel.md index 3e9a54c..0c05016 100644 --- a/specs/spec_loadmodel.md +++ b/specs/spec_loadmodel.md @@ -671,6 +671,106 @@ Le **dosage en Wh** viendra avec le modèle thermique, que l'historique du §11 précisément d'apprendre. **Une raison de plus de faire le §11 en premier** : il ne prépare pas seulement les seuils, il prépare la seule chose qui rendra le dosage possible. +**LM-1009 — Une échéance ne vaut que ce que son avancement se mesure.** *(Question remontée par +l'agent app, 2026-08-27. Note de conception : ne déclenche aucun travail, mais elle CONDITIONNE +la forme du §10.)* + +**Le constat de l'app, et il est exact.** Sur `.75`, aucune classe `evcharger` ne déclare d'état +de charge et aucune ne porte d'interface véhicule — vérifié sur les cinq classes présentes +(`terraAcTcp`, `terraAcRtu`, `wallbox` ×2, `wallboxNoMeter`) : `batteryLevel` absent partout. +Les bornes publient `pluggedIn`, `charging`, `maxChargingCurrent`, `phaseCount` et, pour quatre +d'entre elles, `sessionEnergy`. Jamais un pourcentage. + +**Ce que le code fait aujourd'hui, et c'est plus troublant que « il manque une mesure ».** +`targetPercentage` est comparé à `carBatteryLevel`, lu sur le Thing **voiture** +(`smartchargingmanager.cpp:628`). Et ce `batteryLevel`, quand la voiture est une voiture +*manuelle* — le seul cas où le mécanisme s'active, il exige un `batteryLevel` **inscriptible** — +est **écrit par le moteur lui-même** : `updateManualSoCsWithMeter()` intègre la puissance de la +BORNE, applique un facteur de pertes, divise par une `capacity` déclarée, et **réinjecte le +résultat dans l'état du Thing** par une Action. + +Autrement dit : le moteur estime un pourcentage à partir de trois grandeurs dont **aucune n'est +mesurée** — le SoC de départ saisi par l'utilisateur, la capacité déclarée, un facteur de pertes +constant — puis compare la cible à sa propre estimation. **La boucle se referme sur elle-même**, +et rien, à aucun moment, ne lit l'état réel d'un véhicule. + +> Le piège est là, et il touche l'app en premier : l'estimation est écrite dans `batteryLevel`, +> donc elle sort par la frontière RPC **indistinguable d'une mesure**. Un écran qui affiche +> « 62 % » affiche une intégration, pas une lecture. Si le §10 conserve ce chemin, ce champ doit +> être marqué comme ESTIMÉ là où il est publié — sans quoi l'app ment de bonne foi. + +**Et pendant ce temps, la grandeur qui EST mesurée n'est lue par personne.** `sessionEnergy` — +l'énergie livrée depuis le branchement, comptée par la borne — est publiée par quatre des cinq +classes du banc. Le contrat porte déjà son emplacement : `LoadContextTelemetry::sessionWh` +(`surpluscontext.h:77`). **Il n'est rempli nulle part**, et `EvCharger` n'expose même pas +d'accesseur. Champ déclaré, jamais alimenté. + +### Réponse à la première question : OUI, et le modèle a déjà la bonne forme + +Un avancement utilisable se reconstruit sans communication véhicule, et les trois pièces +existent déjà : + +| Ce qu'il faut | Ce qui existe | État | +|---|---|---| +| une grandeur mesurée | `sessionEnergy` de la borne | publiée, **jamais lue** | +| un emplacement au contrat | `LoadContextTelemetry::sessionWh` | déclaré, **jamais rempli** | +| une cible dans la même unité | `needs { minEnergyWhPerDay }` (LM-1001) | déclaré, **jamais lu par le scheduler** | + +**C'est la forme que le §10 doit retenir** : une obligation exprimée en **énergie**, mesurée par +l'énergie livrée. Elle est vérifiable de bout en bout, et elle est déjà commune à toutes les +charges — un ballon et une borne portent le même `needs`, ce qui est exactement ce que LM-1001 +annonçait. + +**Quatre réserves, et chacune devient un défaut si on l'oublie :** + +1. **Livré ≠ stocké.** `sessionEnergy` est comptée à la borne ; les pertes de charge font que le + véhicule en reçoit moins. Ce n'est pas un défaut de la mesure — c'est le bon côté du compteur + pour une **obligation d'énergie** : celui qu'on paie. Mais la promesse doit se formuler + « livrer X kWh », jamais « mettre X kWh dans la batterie ». +2. **`sessionEnergy` se remet à zéro au branchement.** Parfait pour une obligation *par session*, + **inutilisable tel quel** pour un `minEnergyWhPerDay` : débrancher et rebrancher relance le + compteur, et l'obligation du jour se croirait à zéro. L'accumulation entre sessions est un + mécanisme distinct, et sa place naturelle est l'historique du §11. +3. **Toutes les bornes ne la publient pas** — mesuré : `wallboxNoMeter` et l'une des deux classes + `wallbox` ne l'ont pas. La dégradation doit être **VISIBLE** : une échéance qui cesse + silencieusement d'être mesurable est pire qu'une échéance absente, parce qu'elle continue de + s'afficher. Même exigence que `hasPowerMeter()`, qui est déjà testé avant d'estimer un SoC. +4. **Elle ne répondra JAMAIS à « la voiture sera-t-elle pleine ? »** Aucune énergie livrée ne dit + où en est une batterie dont on ne connaît ni l'état de départ ni la capacité réelle. Une cible + en énergie est vérifiable ; une cible en pourcentage ne l'est pas. Ce sont **deux objets + différents**, et le §10 ne doit pas les confondre sous le mot « échéance ». + +### Réponse à la seconde question : OUI, et elle tient MÊME avec la première + +Répondre « oui » à la première ne dispense pas d'écrire la seconde, parce que `targetPercentage` +**ne disparaît pas** : c'est un champ amont, c'est ce que l'app affiche, et c'est ce que +l'utilisateur saisit. Deux objets, donc deux régimes de vérité, et le motif publié doit les +distinguer : + +| Objet | Vérifiable ? | Ce que le motif a le droit de dire | +|---|---|---| +| obligation en **énergie** (`needs`) | **oui**, par `sessionEnergy` | « X kWh livrés sur les Y demandés » — et l'atteinte peut être annoncée | +| cible en **pourcentage** (`targetPercentage`) | **non**, sans communication véhicule | l'intention déclarée, et **l'avancement comme inconnu ou estimé** — jamais comme tenu | + +**Règle à tenir dès l'écriture** : *un motif n'affirme pas ce que le moteur ne mesure pas.* Une +échéance qu'on ne peut pas vérifier ne doit jamais se présenter comme tenue, ni comme en voie de +l'être. C'est la même exigence que celle qui sépare `BELOW_MIN_POWER` de `SURPLUS_INSUFFICIENT` +(§12) et que LM-1006-1 pose pour la source d'un plafond : **deux causes, deux phrases**, parce +qu'elles n'appellent pas le même geste. Ici le geste diffère radicalement — « attends, ça +charge » contre « je ne peux pas savoir où tu en es ». + +> **Ce que cette note NE tranche pas**, et qui demande une décision : +> — l'app garde-t-elle un écran en pourcentage, alimenté par l'estimation clairement étiquetée, +> ou bascule-t-elle en énergie livrée, qui est la seule chose que la box sait prouver ? +> — `minEnergyWhPerDay` exige-t-il l'accumulation inter-sessions du §11, ou le §10 se limite-t-il +> d'abord à une obligation **par session**, qui ne demande rien de plus que de lire un champ +> déjà publié ? +> +> La seconde est le vrai arbitrage de lot : l'obligation par session est faisable avec +> `sessionEnergy` seul, l'obligation quotidienne demande un historique. + +--- + **LM-1007 — Compatibilité descendante, et elle est gratuite.** Une charge qui ne déclare **aucun** besoin éco ne participe qu'à la passe 2 — soit exactement le comportement d'aujourd'hui. Le modèle à deux niveaux **dégrade proprement** vers le modèle actuel, ce qui @@ -866,6 +966,11 @@ l'émettre : négociation de courant réelle, le délai d'établissement de la charge, et le comportement d'un véhicule qui refuse. +> ⚠️ **À lire avec LM-1009 (§10)** : la machinerie existe, mais son AVANCEMENT ne se mesure pas. +> `targetPercentage` est comparé à un pourcentage que le moteur a lui-même estimé, et la seule +> grandeur réellement mesurée — `sessionEnergy` — n'est lue nulle part. Une échéance en énergie +> est vérifiable ; une échéance en pourcentage ne l'est pas. + **LM-1205 — La machinerie d'échéance existe déjà.** `needs { dailyDeadline, minEnergyWhPerDay }` est ce qui avait motivé le proxy — la crainte que le waterfall ne sache pas exprimer « cette voiture doit être chargée à 7 h ». Il sait : c'est le même champ que le plancher éco du §10