docs(design): l'extension du délestage au waterfall — la précondition du §10
Écrit avant tout code de liaison, comme le workflow l'exige pour une couche de sécurité. Il dit sous quelle forme, dans quel ordre, et nomme trois points à trancher. L'écart mesuré : verifyOverloadProtection() parcourt m_evChargers et rien d'autre ; le waterfall ne connaît le plafond que par evPhaseAllowanceW(), et uniquement pour les bornes. Les charges pilotées et la PAC n'ont aucun plafond — invisible tant que la cascade ne dépense que du surplus, puisqu'on ne peut pas dépasser ce qu'on n'achète pas. Le §10 change cela. LE POINT TECHNIQUE DU LOT, et il n'est pas évident : on ne compare pas les plafonds — ils n'ont pas la même forme — on compare LES MARGES qu'ils laissent. Un plafond par phase donne sa marge sur la phase la plus chargée, un plafond total la sienne sur le soutirage mesuré ; les deux sont des watts, donc comparables. Le plus petit mord, et on retient sa source. Aucune conversion, donc aucune hypothèse d'équilibre entre phases — et la marge est exactement ce que la cascade doit connaître pour ne pas dépasser : le même nombre borne et explique. Deux endroits, deux rôles, et ce n'est pas une redondance : la cascade empêche de CRÉER le dépassement, la protection L4 rattrape ce qu'on n'a pas décidé — une plaque de cuisson, un sèche-linge. L4 garde le dernier mot. Quatre étapes dont DEUX À COMPORTEMENT CONSTANT (le type, puis la source Breaker branchée à résultat identique au watt près). Dans un lot de sécurité, ce qui coûte n'est pas d'écrire le comportement, c'est de prouver qu'on n'a rien cassé. Trois décisions demandées, avec recommandation : la marge tient-elle compte du commandé non encore mesuré (recommandé NON — une hypothèse fausse y coupe du courant) ; que fait le délestage d'une charge sous minOn (recommandé : bypass pour Breaker et GridOperator, PAS pour EcoSelfImposed — un plafond qu'on s'impose ne justifie pas d'abîmer un compresseur, et c'est la première conséquence concrète du fait que la source soit un paramètre) ; et ce que porte le motif DRAW_CAP. Et ce que le lot ne fait pas : aucun transport §14a, aucun changement du repli L2 — le plafond auto-imposé est respecté par construction dès lors que le repli ne dépense pas, et l'exigence dure appartient au §14a, hors du moteur. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015F7G5VeaPVSMeVNjiGj36p
This commit is contained in:
parent
bccce4a53a
commit
4950c37916
159
docs/DESIGN_DELESTAGE.md
Normal file
159
docs/DESIGN_DELESTAGE.md
Normal file
@ -0,0 +1,159 @@
|
||||
# DESIGN — l'extension du délestage au waterfall
|
||||
|
||||
**Statut : conception, à valider avant tout code de liaison** (`AGENTS.md`, workflow obligatoire).
|
||||
**Précondition de** : `§10` (LM-1006, couplage 1). Le §10 ne part pas sans elle.
|
||||
**Contrainte de forme** : `specs/spec_loadmodel.md` **LM-1006-1**, écrite le 2026-08-27
|
||||
*« pour que la porte reste ouverte »*. Elle se referme ici.
|
||||
|
||||
> **Ce document ne demande pas si le délestage doit s'étendre.** Il dit sous quelle forme, dans
|
||||
> quel ordre, et nomme **ce qui demande une décision** — §6.
|
||||
|
||||
---
|
||||
|
||||
## 1. L'écart, mesuré
|
||||
|
||||
| | Aujourd'hui |
|
||||
|---|---|
|
||||
| **Qui est délesté** | `verifyOverloadProtection()` parcourt `m_evChargers` et **rien d'autre** |
|
||||
| **Ce que le waterfall sait du plafond** | `evPhaseAllowanceW()` — et **uniquement pour les bornes**, comme plafond déclaré d'une borne |
|
||||
| **Charges pilotées, PAC** | aucun plafond : leur `maxPowerW` vient de la configuration, la cascade n'est bornée que par le **surplus** |
|
||||
|
||||
Tant que la cascade ne dépense que du surplus, l'absence de plafond ne se voit pas : on ne peut
|
||||
pas dépasser ce qu'on n'achète pas. **Le §10 change cela** — la passe éco décide d'acheter, et
|
||||
plusieurs planchers qui s'ouvrent le même matin sans soleil s'additionnent sur le disjoncteur de
|
||||
branchement.
|
||||
|
||||
---
|
||||
|
||||
## 2. Le mécanisme : UN plafond, TROIS sources
|
||||
|
||||
LM-1006-1 l'impose et c'est le cœur du lot : **la source est un paramètre, jamais une hypothèse
|
||||
enfouie dans le nom de la fonction.**
|
||||
|
||||
```
|
||||
struct DrawCap {
|
||||
Source source; // Breaker | GridOperator | EcoSelfImposed
|
||||
double perPhaseW; // < 0 = ce plafond ne borne pas par phase
|
||||
double totalW; // < 0 = ce plafond ne borne pas au total
|
||||
};
|
||||
```
|
||||
|
||||
**Les deux formes coexistent et ne se convertissent JAMAIS** (LM-1006-1). Un plafond par phase
|
||||
n'est pas un tiers d'un plafond total : une maison peut être à 20 A sur une phase et 2 A sur les
|
||||
deux autres.
|
||||
|
||||
| Source | Forme | Durée | Origine |
|
||||
|---|---|---|---|
|
||||
| `Breaker` | **par phase** | permanent | `phasePowerLimit`, réglage d'installation |
|
||||
| `GridOperator` | **total** | temporaire, sur ordre | §14a — *le transport n'existe pas* |
|
||||
| `EcoSelfImposed` | **total** | le temps de la passe 1 | §10 — *la passe n'existe pas* |
|
||||
|
||||
> **Deux des trois sources n'ont pas encore d'émetteur, et c'est voulu.** Le mécanisme se conçoit
|
||||
> pour trois parce que l'écrire pour une le rendrait irréversible : *« cela tient dans une
|
||||
> signature aujourd'hui ; ce serait une refonte dans deux ans »* (LM-1006-1).
|
||||
|
||||
---
|
||||
|
||||
## 3. Comment on compare sans convertir
|
||||
|
||||
C'est le point technique du lot, et il n'est pas évident. On ne compare pas les **plafonds** —
|
||||
ils n'ont pas la même forme. **On compare les MARGES qu'ils laissent.**
|
||||
|
||||
```
|
||||
marge(cap) = combien de watts SUPPLÉMENTAIRES ce plafond autorise, ici et maintenant
|
||||
```
|
||||
|
||||
- pour un plafond **par phase** : la marge est celle de la **phase la plus chargée**, ramenée en
|
||||
watts — c'est déjà ce que fait `RootMeter::calculateAllowanceAmpere()` ;
|
||||
- pour un plafond **total** : la marge est `totalW − soutirage mesuré`.
|
||||
|
||||
La marge est un **nombre de watts** dans les deux cas : elle se compare. **Le plafond qui laisse
|
||||
la plus petite marge MORD**, et c'est sa `source` qu'on retient.
|
||||
|
||||
> **Ce que cela préserve** : aucune conversion, donc aucune hypothèse d'équilibre entre phases.
|
||||
> Un plafond par phase reste évalué phase par phase jusqu'au bout ; il ne devient jamais un
|
||||
> « total équivalent ».
|
||||
>
|
||||
> **Et ce que ça donne gratuitement** : la marge est exactement ce que la cascade doit connaître
|
||||
> pour ne pas dépasser. Le même nombre sert à borner et à expliquer.
|
||||
|
||||
---
|
||||
|
||||
## 4. Où le plafond mord — deux endroits, deux rôles
|
||||
|
||||
Il faut les distinguer, sinon on écrit deux fois la même chose ou on en oublie une.
|
||||
|
||||
| | Quand | Ce qu'il fait |
|
||||
|---|---|---|
|
||||
| **Dans la cascade** (L3, décision) | à chaque cycle, **avant** de commander | borne l'allocation : on ne décide pas d'acheter au-delà de la marge |
|
||||
| **Dans la protection** (L4, réaction) | sur `powerBalanceChanged`, **après** la mesure | déleste ce qui dépasse déjà, dans l'**ordre inverse du rang** |
|
||||
|
||||
**Les deux sont nécessaires, et ce n'est pas une redondance** : la première empêche de créer le
|
||||
dépassement, la seconde rattrape ce qu'on n'a pas décidé — une plaque de cuisson, un
|
||||
sèche-linge, tout ce que le HEMS ne pilote pas. La couche L4 garde le dernier mot
|
||||
(`AGENTS.md`, MODÈLE DE SÉCURITÉ), et ce lot ne change pas cela.
|
||||
|
||||
> **L'ordre de coupe est l'inverse du rang** : la charge servie en dernier est délestée en
|
||||
> premier. C'est le seul ordre qui respecte le classement de l'installateur — l'inverse
|
||||
> reviendrait à le contredire au moment où il compte le plus.
|
||||
|
||||
---
|
||||
|
||||
## 5. Ordre de travail — quatre étapes, dont deux à comportement constant
|
||||
|
||||
**Étape 1 — le TYPE et sa résolution.** `DrawCap`, la marge, « le plus contraignant l'emporte et
|
||||
on sait lequel ». Testable **isolément** (`tests/auto/loadmodel`, sans serveur nymea).
|
||||
**Comportement inchangé** : rien ne le consomme encore.
|
||||
|
||||
**Étape 2 — la SOURCE `Breaker` branchée, à comportement CONSTANT.** `phasePowerLimit` devient un
|
||||
`DrawCap{Breaker, perPhaseW}` et `evPhaseAllowanceW()` passe par la résolution. Le résultat doit
|
||||
être **identique au watt près** — c'est ce qui rend l'étape vérifiable : les 46 tests de
|
||||
`charging` ne doivent pas bouger d'une ligne.
|
||||
|
||||
**Étape 3 — la cascade est bornée par la marge.** Le waterfall cesse d'allouer au-delà. C'est ici
|
||||
que le comportement change pour les charges non-EV, et c'est ici que le motif `DRAW_CAP` naît
|
||||
(**R8** : il publie sa source).
|
||||
|
||||
**Étape 4 — la protection L4 déleste le waterfall.** `verifyOverloadProtection()` cesse d'être
|
||||
réservée aux bornes ; ordre inverse du rang, `force = true`, verrous bypassés comme le repli L2.
|
||||
|
||||
> **Les deux premières ne changent rien et rendent les deux suivantes petites.** C'est la même
|
||||
> stratégie que le §10 : ce qui coûte, dans un lot de sécurité, n'est pas d'écrire le
|
||||
> comportement — c'est de prouver qu'on n'a rien cassé en chemin.
|
||||
|
||||
---
|
||||
|
||||
## 6. CE QUI DEMANDE UNE DÉCISION
|
||||
|
||||
**6.1 — La marge doit-elle tenir compte de ce que le HEMS a lui-même commandé et qui n'est pas
|
||||
encore mesuré ?** C'est la correction A, transposée au soutirage. Sans elle, deux cycles
|
||||
consécutifs peuvent décider d'acheter la même marge deux fois. Avec elle, on réintroduit un
|
||||
terme de latence dans une couche de **sécurité**, où une hypothèse fausse coupe du courant.
|
||||
**Recommandation : NON dans la cascade, OUI nulle part ailleurs** — la cascade borne sur la marge
|
||||
MESURÉE, et l'écart d'un cycle est absorbé par la marge que le disjoncteur garde par
|
||||
construction (`allowancePerPhase -= 1` existe déjà côté proxy). À trancher explicitement.
|
||||
|
||||
**6.2 — Que fait le délestage L4 d'une charge sous verrou `minOn` ?** Le repli L2 bypasse les
|
||||
verrous (`force = true`) parce qu'un compteur muet est une panne. Un dépassement de plafond
|
||||
est-il de la même gravité ? **Recommandation : OUI pour `Breaker` et `GridOperator`** — le
|
||||
disjoncteur ne connaît pas le court-cycling, et un ordre du gestionnaire ne se négocie pas.
|
||||
**NON pour `EcoSelfImposed`** : un plafond qu'on s'impose ne justifie pas d'abîmer un
|
||||
compresseur. C'est la première conséquence concrète du fait que la source soit un paramètre.
|
||||
|
||||
**6.3 — Le motif `DRAW_CAP` naît à l'étape 3 : que porte-t-il exactement ?** R8 demande la
|
||||
source. Faut-il aussi `capW`, `marginW`, et la **phase** qui borne quand la source est
|
||||
`Breaker` ? **Recommandation : oui aux trois** — sans la phase, un installateur devant une maison
|
||||
triphasée déséquilibrée ne sait pas où aller mesurer.
|
||||
|
||||
---
|
||||
|
||||
## 7. Ce que ce lot ne fait PAS
|
||||
|
||||
- **Il ne crée aucun transport §14a.** `GridOperator` sera une source qu'aucun émetteur ne
|
||||
remplit ; c'est le but — la porte reste ouverte, elle ne s'ouvre pas.
|
||||
- **Il ne touche pas au repli L2.** LM-1006-1, propriété 2 : le plafond **auto-imposé** est
|
||||
respecté par construction dès lors que le repli **ne dépense pas**. L'exigence dure — tenir
|
||||
une borne imposée par un tiers alors que le moteur ne décide plus — appartient au **§14a** et
|
||||
vivra **hors du moteur**. Les confondre ferait hériter à ce lot d'une contrainte qui ne le
|
||||
concerne pas.
|
||||
- **Il ne déplace pas la frontière de sécurité.** L4 garde le dernier mot, L0 reste physique.
|
||||
Loading…
x
Reference in New Issue
Block a user