etm-powersync-energy-plugin.../docs/DESIGN_DEROGATION.md
Patrick Schurig ee67b21c2d docs(dérogation): une cible qui cesse d'être mesurable termine la dérogation
LA QUESTION QUE NI L'UN NI L'AUTRE N'AVAIT POSÉE : que fait la dérogation ENTRE le
figement de sessionEnergy et l'échéance de durée ? Elle achète au maximum sur une
mesure morte, et sur un lien qui perd 40 % de ses trames « au bout » peut vouloir
dire des heures.

POSITION RETENUE : une dérogation dont la cible cesse d'être mesurable CESSE. Elle
n'attend pas sa durée. C'est R6 transposée — là-bas « servie oui, tenue jamais » ;
ici « active oui, vérifiable non, donc on s'arrête ». Une dérogation est une
commande qui ACHÈTE, et la maintenir sans savoir où elle en est, c'est acheter à
l'aveugle. L'instrument existe déjà : measurement.source et le régime de LM-1009,
qui le disent au cycle même.

QUATRE FINS, et le motif dit laquelle a mordu : targetReached, unplugged,
measurementLost, durationElapsed. Les quatre appellent des gestes différents — et
une dérogation qui se terminerait sans dire laquelle laisserait l'utilisateur
relancer un boost que le lien empêchera encore.

L'ASYMÉTRIE, reprise du plugin V2C. La confirmation de débranchement existe
(k_absencesPourClore polls) et porte une propriété qu'il faut garder : SEULS LES
POLLS RÉUSSIS font avancer le compteur d'absences. Une coupure réseau le gèle au
lieu de le faire progresser — un lien mort ne peut donc pas fabriquer un faux
débranchement.

Et les deux erreurs ne coûtent pas la même chose : terminer à tort rend la main,
continuer à tort achète au maximum sur une mesure morte. D'où la règle, gravée dans
AGENTS.md : un mécanisme qui achète s'arrête dans le doute, et ses seuils de
confirmation sont asymétriques par conception — prompts à conclure la fin, lents à
conclure la poursuite. C'est l'inverse du réglage qu'on choisirait pour un mode.

La durée devient le FILET DU FILET : la perte de mesure attrape le cas où
l'instrument se tait et se signale ; la durée attrape le cas où même le régime ne
se signale pas. Deux filets parce que le premier repose lui-même sur un instrument.
2026-09-08 06:15:34 +02:00

215 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Dérogations — le boost n'est pas un mode
> Ouvert le 2026-09-07. **Design avant code, rien d'implémenté.**
> Cadrage donné par Patrick, et il vaut au-delà du VE.
## 1. La distinction qui structure tout le lot
| | mode | **dérogation** |
|---|---|---|
| se choisit | **une fois** | pour un **besoin ponctuel** |
| vaut | jusqu'au prochain choix | **jusqu'à ce que le besoin cesse** |
| se termine | jamais tout seul | **toujours tout seul** |
> **Un boost encore actif après la fin est un mode qu'on a oublié de quitter. Un boost qui rend
> la main est une dérogation terminée.**
C'est le même geste que forcer un chauffe-eau parce qu'on veut plus d'eau chaude : on ne
reconfigure pas l'installation, on demande quelque chose **maintenant**.
**Conséquence de conception** : ce n'est ni un `ChargingMode` de plus, ni une demande dans
`needs`. Une dérogation se **superpose** au mode courant et s'efface. Elle n'a ni cible d'énergie,
ni échéance, ni cadence — les trois supposent un besoin qui dure.
## 2. Ce qui la borne : la sûreté, et rien d'autre
Les trois comportements se distinguent par **ce qui les borne** :
| | borné par |
|---|---|
| PV | le **surplus** |
| Min+PV | un **plancher** + le surplus |
| **Boost** | la **sûreté seule** — `DRAW_CAP` et le délestage |
Et c'est cette dernière ligne qui le rend sûr : les plafonds deviennent la seule limite, **donc
une limite obligatoire**. Un boost sur un branchement 3×30 A sans `phasePowerLimit` ferait
disjoncter — ce n'est plus une hypothèse depuis le 2026-09-07, où l'on a découvert des semaines
de fonctionnement à `0`.
**Le boost prend la tête de la passe éco ; il ne surplombe pas la sûreté.** Si `DRAW_CAP` mord, il
est servi le premier **dans ce qui reste**, pas au-delà. Sans cette précision on retomberait sur
le second chemin d'achat refusé pour le minimum (`DESIGN_MIN_RESEAU.md` §3).
### Le rang
Le boost passe **devant**, y compris devant une charge mieux rangée. `LM-1002` dit que le rang est
ce que l'**installateur** décide ; elle ne dit rien d'une **action ponctuelle de l'utilisateur**.
Prendre la tête ne contredit donc pas la règle : cela passe par une porte qu'elle ne ferme pas.
Un boost servi après une autre charge contredirait « tout de suite », qui est sa définition.
## 3. Précaution 1 — le mode précédent se MÉMORISE, il ne se déduit pas
À la fin de la dérogation, la charge revient **au mode qu'elle avait**, relu depuis ce qui a été
stocké au déclenchement. Min+PV redevient Min+PV, PV redevient PV.
**Revenir à un défaut serait un défaut.** Retomber sur `Normal` sortirait la borne de l'arbitrage
— l'inverse exact du but, et une dérogation qui laisse l'installation moins pilotée qu'avant.
C'est la même famille que le repli d'échéance qui réécrivait `EcoWithMinCurrent` en `Eco` :
un état détruit en silence par un mécanisme qui croyait bien faire.
## 4. Précaution 2 — « terminé » se définit PAR MÉCANISME
Le moteur ne peut pas deviner ce qui met fin à une dérogation : cela dépend de ce qu'on déroge.
| charge | fins possibles |
|---|---|
| borne VE | **débranchement**, **cible en ÉNERGIE** (`sessionEnergy`), durée |
| chauffe-eau | **température** atteinte (si sonde), durée |
| batterie | cible en énergie, durée |
### La cible se dit en ÉNERGIE, jamais en SOC — et ce n'est pas un pis-aller
**Il n'y a pas de communication véhicule**, et il n'y en aura pas : aucun plugin constructeur
n'est prévu, et une intégration cloud Skoda/VW serait contraire au **100 % local**, fragile, et
rafraîchie à la minute au mieux.
Mais surtout, l'énergie est la **bonne** grandeur, indépendamment de cette contrainte :
| | ce qu'il faut savoir pour l'affirmer |
|---|---|
| « 20 kWh puis tu t'arrêtes » | `sessionEnergy`, que le plugin V2C construit depuis le 2026-08-29 |
| « jusqu'à 80 % » | la **capacité**, l'**état de santé** et le **rendement** de charge |
C'est exactement ce que `LM-1009` a établi pour l'obligation éco : **on mesure de l'énergie parce
que c'est ce qu'on sait mesurer.** Un SoC demanderait trois inconnues pour dire la même chose en
moins sûr.
> **La fin par SOC ne figure donc pas dans ce design**, et son absence n'est pas un manque à
> combler plus tard : c'est une décision. L'écrire comme un manque aurait laissé quelqu'un
> l'ouvrir comme un lot.
### Et `sessionEnergy` a un silence connu, qui justifie le filet
`LM-1012-d`, mesuré sur la V2C le 2026-08-30 : à la **perte du lien**, `currentPower` tombe à 0
et les booléens à `false`, mais **`sessionEnergy` reste à sa dernière valeur**.
Une dérogation « 20 kWh puis stop » dont le compteur se fige à 12 kWh **n'atteindra jamais sa
cible**. Elle achèterait au maximum jusqu'à ce que quelqu'un s'en aperçoive — sauf que la durée
est là. Ce n'est pas un cas d'école : c'est le comportement documenté de la borne installée.
**Chaque mécanisme déclare ce qui termine sa dérogation.** C'est un ajout à `ILoadAdapter`, au
même titre que `commandSettleS()` : une propriété du mécanisme, pas une constante du moteur.
## 5. LA RÈGLE — une dérogation qui ne sait pas dire quand elle finit ne devrait pas exister
C'est la règle centrale du lot, et elle se teste.
Sur un chauffe-eau **sans sonde**, « jusqu'à ce que ce soit chaud » n'est **pas mesurable**. La
fin doit alors être une **durée**, explicitement — et **l'app doit le dire à la saisie**, au
moment où l'utilisateur croit demander une température.
C'est exactement le raisonnement de `progressMeasurable` : une capacité absente se **dit** à la
saisie, parce que l'écran de résultat parle trop tard. Une dérogation dont la fin n'est pas
observable est une dérogation qui ne finira pas.
### La question qui manquait : que fait-elle ENTRE le figement et l'échéance ?
La durée arrêtera la dérogation — **au bout**. Entre-temps, elle achète au maximum sur une mesure
morte. Sur un lien qui perd 40 % de ses trames, « au bout » peut vouloir dire des heures.
**Position retenue : une dérogation dont la cible cesse d'être MESURABLE cesse.** Elle n'attend
pas sa durée.
C'est `R6` transposée. Là-bas : *servie oui, tenue jamais* — on ne conclut pas « tenue » sur un
avancement qu'on ne mesure pas. Ici : **active oui, vérifiable non, donc on s'arrête.** Une
dérogation est une commande qui **achète** ; la maintenir sans pouvoir dire où elle en est, c'est
acheter à l'aveugle.
**Et l'instrument existe déjà** : `measurement.source` et le régime de `LM-1009`. Quand le régime
d'une charge en dérogation cesse d'être mesuré, la condition de fin est perdue — et cela se sait
au cycle même, pas au bout de la durée.
### QUATRE fins, et le motif dit laquelle a mordu
| fin | ce qu'elle dit à l'utilisateur | geste |
|---|---|---|
| `targetReached` | la cible en énergie est atteinte | aucun — c'est le succès |
| `unplugged` | le véhicule est parti | aucun |
| **`measurementLost`** | on ne sait plus où en est la charge | **regarder le lien / le compteur** |
| `durationElapsed` | le temps imparti est écoulé | relancer si besoin |
Les quatre appellent des gestes différents, et une dérogation qui se terminerait sans dire
laquelle a mordu laisserait l'utilisateur relancer un boost que le lien empêchera encore.
### L'asymétrie, et la règle qu'elle donne
Le débranchement peut **clignoter** sur cette borne. Le plugin V2C a déjà la parade —
`k_absencesPourClore` polls consécutifs avant de clore une session — et elle porte une propriété
qu'il faut reprendre : **seuls les polls RÉUSSIS font avancer le compteur.** Une coupure réseau
le **gèle**, elle ne le fait pas progresser. Un lien mort ne peut donc pas fabriquer un faux
débranchement.
Cette confirmation vaut pour la dérogation aussi. Mais les deux erreurs ne coûtent pas la même
chose :
> **Terminer à tort** rend la main : la charge repasse à son mode mémorisé, et l'utilisateur
> relance s'il veut.
> **Continuer à tort** achète au maximum sur une mesure morte.
D'où la règle : **dans le doute, une dérogation s'arrête.** Les seuils de confirmation sont donc
asymétriques par conception — prompts à conclure la fin, lents à conclure la poursuite. C'est
l'inverse du réglage qu'on choisirait pour un mode, et c'est parce qu'une dérogation achète.
### Corollaire — la DURÉE est un filet, pas une option
Toute dérogation porte une durée maximale, **même quand elle a une meilleure condition de fin**.
Raison : la condition de fin repose sur un **instrument**, et un instrument peut se taire. Une
cible de charge qui ne remonte plus, une sonde qui tombe, un compteur qui se fige — et la
dérogation ne se termine jamais, en tirant le maximum. C'est « l'instrument dont le silence
ressemble à un résultat », appliqué à une commande qui achète.
**Donc : jamais « jusqu'à annulation ».** Quatre fins, et la durée est toujours l'une d'elles.
La durée est désormais **le filet du filet** : la perte de mesure attrape le cas où l'instrument
se tait ET se signale ; la durée attrape le cas où **même le régime ne se signale pas**. Deux
filets parce que le premier repose lui-même sur un instrument.
Cela rejoint ce que la maquette batterie demande pour son propre boost.
## 6. Ce que ça publie
Une dérogation active **se voit**, et sa fin **se lit** :
- un motif dédié, qui dit que la charge est en dérogation et **ce qui y mettra fin** ;
- l'instant de fin prévu, pour que l'écran affiche un compte à rebours plutôt qu'un état sans
horizon ;
- l'instant est **absolu, ancré à la capture** (`toUTC()`), jamais une heure murale — cf. la
dérive mesurée le 2026-09-06.
**Et la fin est un ÉVÉNEMENT**, publié le cycle où elle survient. Sans lui, l'écran ne peut pas
distinguer « la dérogation s'est terminée normalement » de « quelqu'un l'a annulée ».
## 7. Étapes, à comportement constant d'abord
**A** — le type et sa publication, sans qu'aucune dérogation ne puisse être posée. Le champ
existe, il ne décide de rien.
**B** — la déclaration des fins par mécanisme (`ILoadAdapter`), avec la durée obligatoire.
**C** — la pose, la mémorisation du mode précédent, et le retour automatique.
**D** — le service en tête de passe éco, borné par `DRAW_CAP` et le délestage.
Le pas **D** est le seul qui change ce qui est commandé, et il arrive quand tout le reste est
déjà vérifiable.
## 8. Ce que le banc ne prouvera pas
**Les trois fins du VE sont exerçables sans véhicule communicant** : débranchement, cible en
énergie via `sessionEnergy`, et durée. C'est ce qui rend ce lot éprouvable sur le banc réel, là
où une fin par SoC ne l'aurait jamais été.
Reste hors de portée : la fin par **température**, qui demande la sonde du §12 — elle n'existe
qu'en mock, et c'est le sixième manque de `INVENTAIRE_MOCK.md` sous un autre angle.
**La durée, elle, s'éprouve partout** — une raison de plus d'en faire le filet plutôt qu'un
recours.