diff --git a/docs/DESIGN_DEROGATION.md b/docs/DESIGN_DEROGATION.md new file mode 100644 index 0000000..281d315 --- /dev/null +++ b/docs/DESIGN_DEROGATION.md @@ -0,0 +1,128 @@ +# 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 de charge atteinte, durée | +| chauffe-eau | **température** atteinte (si sonde), durée | +| batterie | SoC cible atteint, duré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. + +### 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 ».** Trois fins, et la durée est toujours l'une d'elles. +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 + +La fin par **débranchement** est exerçable dès qu'une voiture est là. La fin par **cible** ne +l'est pas : aucun Thing `car` n'est appairé, donc aucun SoC n'est lisible — le journal le répète +vingt fois par heure (`"V2C Trydan" has no car assigned. Ignoring...`). Et la fin par +**température** demande la sonde du §12, qui n'existe qu'en mock. + +**La durée, elle, s'éprouve partout** — ce qui est une raison de plus d'en faire le filet.