L'agent app a répondu au §9. Décisions inscrites en LM-1010, précédence des motifs et réponse à R4 en DESIGN_10.md, réponse à l'app en brief §10. SON ARGUMENT SUR LA LISTE EST MEILLEUR QUE LE NÔTRE, et il est repris tel quel : nous disions « le rang doit rester lisible », il dit que l'ORDRE DE SERVICE N'EST PAS UNE PROPRIÉTÉ DE LA CONFIGURATION — il change d'un cycle à l'autre selon les planchers atteints. Trier par lui ne rendrait pas le rang illisible, ça rendrait la LISTE instable, ce qui est pire. Une liste de configuration ne peut pas avoir une colonne vertébrale qui bouge sous le doigt qui vient d'y glisser une charge. Les deux autres réponses : une entrée par charge et deux lignes dans la carte — on dédouble là où l'objet affiché est la SÉQUENCE, jamais là où il est la CHARGE ; et éco servi / confort non en deux ÉTATS NOMMÉS, jamais une fraction, sans quoi le régime nominal se lirait comme une dégradation permanente. PRÉCÉDENCE DRAW_CAP ↔ BELOW_MIN_POWER, tranchée et écrite ICI plutôt que de chaque côté de la frontière. Autorisation résiduelle non nulle mais sous le plus petit palier : BELOW_MIN_POWER. DRAW_CAP dit « il n'y a plus d'autorisation », or il en reste — pas au bon format. C'est la distinction de +etm19 entre SURPLUS_INSUFFICIENT et BELOW_MIN_POWER, transposée telle quelle au soutirage : pas une règle nouvelle, la même appliquée à une seconde ressource. Ce qui la décide est le GESTE qu'elle appelle — revoir le plafond, ou lire la fiche technique. RÉPONSE À R4 : les compteurs de budget comptent le DÉCIDÉ. Le budget est le registre de la cascade, et ce qui détermine ce que reçoit la charge suivante est la valeur décidée. L'arrondi vit après, dans l'adaptateur, propre à chaque mécanisme — compter le commandé obligerait à rouvrir le budget après le dispatch, c'est-à-dire à décider deux fois. ET UNE PRÉMISSE DE R4 EST FAUSSE, mieux vaut le dire maintenant : la maquette pose que allocatedW publie « ce qui a été commandé ». buildTelemetry() publie le PLAN, et applyActionsToAdapters() reçoit le slot const — rien n'est réécrit après le dispatch. Mesurable : testUnpluggedChargerTakesNoBudget attend allocatedW == 3000 sur une borne dont le courant commandé donne 2 990 W. Donc Σ targetW == allocatedW tiendra exactement, et l'écart que l'écran veut nommer est entre allocatedW (décidé) et la charge utile de MÉCANISME (appliqué) — déjà publiée. L'app a les deux nombres ; leurs noms étaient inversés. PROCHAINE ACTION DU §10 : R2, funding descend au niveau. Seule des huit à être un changement de forme d'un champ existant ; les sept autres attendent sans se renchérir. Aucun code. ci-quality 3/3. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015F7G5VeaPVSMeVNjiGj36p
402 lines
23 KiB
Markdown
402 lines
23 KiB
Markdown
# DESIGN — §10 : éco / confort, et le délestage dans le même lot
|
||
|
||
**Statut : conception, à valider avant tout code** (`AGENTS.md`, workflow obligatoire).
|
||
**Entrée** : `specs/spec_loadmodel.md` §10 (LM-1000 → LM-1009) et §12.
|
||
**Écrit après relecture de** `rulebasedscheduler.cpp` (`getPlan`, `buildSetpointAction`,
|
||
`buildSgReadyStateAction`), `smartchargingmanager.cpp` (`verifyOverloadProtection`), et des deux
|
||
notes qui contraignent la forme : **LM-1006-1** (le plafond de soutirage) et **LM-1009** (les
|
||
deux régimes d'échéance).
|
||
|
||
> **Ce document ne demande pas si le §10 doit se faire. Il dit sous quelle forme, et nomme les
|
||
> points qui demandent une décision** — §8. **Quatre sont tranchés.** 2026-08-27 : la réserve
|
||
> batterie n'annule pas l'autorisation de soutirage (8.1), et le repli dégradé ne concerne pas ce
|
||
> lot (8.3). 2026-08-29 : le plafond §14a porte **les deux** formes — par phase et au total —
|
||
> sans jamais convertir (8.2), et la cible de confort reste un **comparateur**, le dosage
|
||
> attendant le modèle thermique du §11 (8.4).
|
||
>
|
||
> **Un seul reste ouvert, et il est chez l'app** : 8.5, l'ordre de service apparent. Question
|
||
> posée le 2026-08-29 dans `docs/BRIEF_depuis_plugin.md` §9.
|
||
|
||
---
|
||
|
||
## 1. Pourquoi le délestage part AVEC, et pas après
|
||
|
||
C'est la précondition écrite en §13 de la spec, et elle tient pour une raison arithmétique
|
||
simple : **la passe éco n'est pas bornée par le surplus** (LM-1005). Elle est bornée par une
|
||
autorisation de soutirage. Or aujourd'hui, la seule borne de soutirage qui existe dans le moteur
|
||
— `phasePowerLimit` — ne déleste **que les bornes de recharge**
|
||
(`verifyOverloadProtection`, boucle sur `m_evChargers`).
|
||
|
||
Livrer les planchers éco sans le délestage général donnerait donc : un matin sans soleil, chaque
|
||
charge se sert au réseau pour tenir son obligation, personne ne les additionne, et
|
||
l'installation disjoncte. Ce n'est pas une dégradation de confort — c'est une coupure, chez un
|
||
client.
|
||
|
||
**Conséquence de conception, pas de calendrier** : la passe éco a besoin d'un objet « plafond »
|
||
qui n'existe pas encore. Le §10 doit donc le créer. C'est le §2.
|
||
|
||
---
|
||
|
||
## 2. Le plafond de soutirage devient un OBJET (LM-1006-1)
|
||
|
||
### 2.1 Ce qu'il remplace
|
||
|
||
Aujourd'hui, la limite de raccordement est consultée à deux endroits, sous deux formes, sans
|
||
jamais être nommée :
|
||
|
||
- `RootMeter::calculateAllowanceAmpere(phases, phasePowerLimit)` — la place libre **par phase**,
|
||
interrogée par le proxy puis, depuis 3g-2, par `EnergyArbitrator::evPhaseAllowanceW()` ;
|
||
- `verifyOverloadProtection()` — la réaction temps réel, qui déleste les bornes.
|
||
|
||
Aucun des deux ne se demande *d'où vient* le plafond, parce qu'il n'y en a qu'un. Le §14a en
|
||
ajoutera un second, le §10 lui-même un troisième.
|
||
|
||
### 2.2 La forme proposée
|
||
|
||
```cpp
|
||
/*! Un plafond de soutirage, et ce qui l'a produit. */
|
||
struct DrawCap {
|
||
enum Source { Connection, GridOperator, EcoSelfImposed };
|
||
|
||
Source source = Connection;
|
||
double totalW = -1; //!< Plafond au POINT DE LIVRAISON. < 0 = pas de plafond total.
|
||
QList<double> perPhaseA; //!< Plafond PAR PHASE. Vide = pas de plafond par phase.
|
||
QDateTime until; //!< Fin de validité ; invalide = permanent.
|
||
};
|
||
|
||
/*! Le plafond EFFECTIF du cycle, et celui qui a mordu. */
|
||
struct DrawBudget {
|
||
QList<DrawCap> declared; //!< Tous les plafonds actifs, quelle que soit leur source.
|
||
double effectiveW = -1; //!< Le plus contraignant, ramené en watts.
|
||
DrawCap::Source binding = DrawCap::Connection; //!< QUI a mordu — publié, pas déduit.
|
||
bool perPhaseBound = false; //!< Vrai si c'est une phase qui borne, pas le total.
|
||
};
|
||
```
|
||
|
||
**`binding` est le champ qui justifie tout l'objet.** Sans lui, un client bridé lit « protection
|
||
de surcharge » là où la cause est le gestionnaire de réseau — deux causes, deux gestes
|
||
(LM-1006-1, propriété 1).
|
||
|
||
### 2.3 Le piège : par phase, ou au total ?
|
||
|
||
Les trois sources ne parlent pas la même langue, et c'est le seul vrai problème technique du §2.
|
||
|
||
| Source | Forme naturelle | Peut-on la convertir ? |
|
||
|---|---|---|
|
||
| Disjoncteur de branchement | **par phase** (A) | pas en un total : 3 × 32 A ne vaut pas 22 kW répartissables |
|
||
| §14a EnWG | **total** (W au point de livraison) | pas en par-phase : le gestionnaire ne dit rien de la répartition |
|
||
| Plancher éco (§10) | **total** (W qu'on s'autorise) | idem |
|
||
|
||
**Un plafond total ne protège pas d'une surcharge monophasée** : 4 kW tirés entièrement sur L1
|
||
passent un plafond total de 6 kW et font disjoncter un 16 A. **Et un plafond par phase ne sait
|
||
pas exprimer le §14a.**
|
||
|
||
**Décision de conception proposée** : `DrawBudget` porte **les deux**, les évalue **toutes les
|
||
deux**, et `binding` dit laquelle a mordu la première. Aucune conversion, jamais — c'est la
|
||
conversion qui perdrait l'information. `effectiveW` n'est qu'une commodité d'affichage, dérivée,
|
||
et `perPhaseBound` dit s'il faut la lire avec précaution.
|
||
|
||
### 2.4 Où il se calcule
|
||
|
||
Une fois par cycle, dans `buildContext()`, à côté de `ctx.meter` et `ctx.battery` — c'est déjà
|
||
l'endroit qui lit le compteur et connaît `phasePowerLimit`. Il devient un champ de
|
||
`SurplusContext` :
|
||
|
||
```cpp
|
||
struct SurplusContext {
|
||
// … existant …
|
||
DrawBudget drawCap; //!< [§10] Plafond de soutirage du cycle, toutes sources confondues.
|
||
};
|
||
```
|
||
|
||
Publié en télémétrie au même titre que `budget` — c'est la contrepartie de `binding` : un client
|
||
qui ne voit pas le plafond ne peut pas expliquer ce qu'il affiche.
|
||
|
||
---
|
||
|
||
## 3. Les deux passes, et ce qui borne chacune
|
||
|
||
`LM-1002` fixe la structure : deux passes sur la **même** liste, triée une seule fois par rang.
|
||
Ce qui change entre elles n'est pas l'ordre, c'est **la borne**.
|
||
|
||
```
|
||
┌────────────── passe 1 : ÉCO ──────────────┐
|
||
liste triée par │ borne : DrawBudget (autorisation de │
|
||
rang (une seule) │ SOUTIRAGE — pas le surplus) │
|
||
rang 1 ─────────▶│ sert : le plancher `needs` non atteint │
|
||
rang 2 ─────────▶│ produit : une CIBLE, pas une commande │
|
||
rang 3 ─────────▶└───────────────────────────────────────────┘
|
||
│
|
||
│ ┌──────────── passe 2 : CONFORT ────────────┐
|
||
└──────────▶│ borne : le RELIQUAT de surplus │
|
||
(même ordre) │ sert : la cible de confort si demandeuse │
|
||
│ produit : une CIBLE, pas une commande │
|
||
└───────────────────────────────────────────┘
|
||
│
|
||
somme par charge, arrondie UNE fois
|
||
│
|
||
UNE commande
|
||
```
|
||
|
||
**Ce que la passe 1 dépense n'est pas ce que la passe 2 dépense.** C'est le point que le code
|
||
actuel ne sait pas exprimer : `getPlan()` n'a qu'un compteur, `remainingSurplusW`, et
|
||
`buildSetpointAction()` le décrémente **et** émet l'action dans le même geste
|
||
(`rulebasedscheduler.cpp:207-212`). Le §10 doit séparer les deux — voir §4.
|
||
|
||
**Deux comptabilités, donc, et elles ne se mélangent pas** :
|
||
|
||
```cpp
|
||
struct PlanBudget { // existant, inchangé dans sa sémantique
|
||
double surplusW, evReservedW, allocatedW, recreditedW, remainingW;
|
||
};
|
||
struct PlanDraw { // [§10] ce que la passe 1 a décidé d'ACHETER
|
||
double authorisedW = 0; //!< Ce que le DrawBudget laissait prendre.
|
||
double committedW = 0; //!< Ce que la passe 1 a réellement engagé au réseau.
|
||
double remainingW = 0; //!< Reliquat d'autorisation, non dépensé.
|
||
};
|
||
```
|
||
|
||
> **Pourquoi deux structures et pas un champ de plus** : `PlanBudget` répond « d'où vient
|
||
> l'énergie servie » ; `PlanDraw` répond « combien a-t-on décidé d'acheter ». Les fondre
|
||
> rendrait l'invariant publié de `PlanBudget` (`surplus + recrédit − alloué == restant`) faux
|
||
> ou incompréhensible, et c'est lui qui rend la charge utile vérifiable par un client.
|
||
|
||
---
|
||
|
||
## 4. Une seule commande par charge et par cycle (LM-1003)
|
||
|
||
C'est la contrainte qui impose la refonte structurelle, et elle est petite à énoncer :
|
||
**les passes calculent, elles ne commandent pas.**
|
||
|
||
Découpage proposé de `getPlan()` :
|
||
|
||
```cpp
|
||
// 1. contexte, budget de surplus, plafond de soutirage (inchangé + DrawBudget)
|
||
// 2. tri UNE fois (inchangé)
|
||
QHash<QString, LoadTarget> cibles; // loadId → { ecoW, confortW, motifs… }
|
||
|
||
// 3. passe 1 — éco, bornée par ctx.drawCap
|
||
for (const LoadContext &lc : chargesTriees)
|
||
cibles[lc.id].eco = planifierEco(lc, drawRestant, planDraw);
|
||
|
||
// 4. passe 2 — confort, bornée par le reliquat de surplus
|
||
for (const LoadContext &lc : chargesTriees)
|
||
cibles[lc.id].confort = planifierConfort(lc, surplusRestant, planBudget);
|
||
|
||
// 5. UNE émission par charge : somme, arrondi au mécanisme, motif
|
||
for (const LoadContext &lc : chargesTriees)
|
||
slot.actions.append(emettre(lc, cibles.value(lc.id)));
|
||
```
|
||
|
||
**L'arrondi au mécanisme se fait à l'étape 5, une seule fois.** C'est ce qui empêche le défaut
|
||
que LM-1003 nomme : un relais commuté en passe 1 puis recommuté en passe 2, soit du
|
||
court-cycling fabriqué par l'arbitre lui-même.
|
||
|
||
**Le recrédit s'attribue à la première passe où la charge apparaît** (LM-1004) — donc dans
|
||
`planifierEco()` si elle a un plancher, dans `planifierConfort()` sinon. Un champ
|
||
`recreditAttribue` dans `LoadTarget` le rend vérifiable plutôt que discipliné.
|
||
|
||
> **Ce que cette refonte casse, et qu'il faut prévoir** : `buildSetpointAction()` et
|
||
> `buildSgReadyStateAction()` mélangent aujourd'hui décision, écrêtage de verrou, comptabilité
|
||
> et motif. Ils doivent se scinder en « décider une cible » et « émettre ». C'est le gros du
|
||
> travail du lot, et c'est du remaniement, pas de l'ajout. Les tests de motif existants
|
||
> (`testDecisionReasonRendering`, `testEcsBudgetUnderLock`, `testEcsSurplusPV`) sont le filet :
|
||
> ils portent sur le TEXTE publié, donc ils survivront au remaniement ou le condamneront.
|
||
|
||
---
|
||
|
||
## 5. Le délestage : ce qu'il coupe, et dans quel ordre
|
||
|
||
**Le plafond borne le PLAN, il ne le corrige pas après coup.** C'est la leçon de 3g-2 : un
|
||
plafond appliqué après l'allocation laisse le waterfall croire qu'il a servi.
|
||
|
||
- **En passe 1**, chaque charge servie décrémente `planDraw.remainingW`. Quand l'autorisation est
|
||
épuisée, les charges suivantes reçoivent 0 **et le motif nomme la source** (§6).
|
||
- **L'ordre de délestage est l'inverse de l'ordre de service** : le rang décide qui est servi en
|
||
premier, donc qui est coupé en dernier. Aucun second classement — c'est LM-1002 appliqué à
|
||
l'envers, et c'est la seule lecture qui ne surprendra pas l'installateur.
|
||
- **L4 ne bouge pas.** `verifyOverloadProtection()` garde le dernier mot en temps réel, sur la
|
||
mesure, après le dispatch. Le §10 lui ajoute des charges à délester ; il ne lui retire pas son
|
||
rôle et ne s'y substitue pas. **Sa position dans `update()` reste INTOUCHABLE** (AGENTS,
|
||
modèle de sécurité).
|
||
|
||
---
|
||
|
||
## 6. Les motifs — ce que le §10 ajoute au catalogue
|
||
|
||
Le catalogue est fermé et testé sur le texte ; toute clé ajoutée demande une ligne de rendu et
|
||
une entrée de clôture (`testDecisionReasonRendering`).
|
||
|
||
| Code | params | Quand |
|
||
|---|---|---|
|
||
| `DRAW_CAP` | `capW`, `source`, `requiredW`, `o:phase` | Le plafond de soutirage refuse, et **`source` dit lequel** : raccordement, gestionnaire de réseau, ou auto-imposé. `phase` présent si c'est une phase qui borne. **Réservé à l'autorisation résiduelle NULLE** — voir la précédence ci-dessous. |
|
||
|
||
**PRÉCÉDENCE `DRAW_CAP` ↔ `BELOW_MIN_POWER` — tranchée le 2026-08-29 (Patrick), sur une question
|
||
de l'agent app.**
|
||
|
||
Autorisation résiduelle **non nulle mais inférieure au plus petit palier** du mécanisme :
|
||
**`BELOW_MIN_POWER`**. Les deux motifs sont vrais, mais pas au même niveau.
|
||
|
||
`DRAW_CAP` dit « **il n'y a plus d'autorisation** ». Or il en reste — simplement **pas au bon
|
||
format**. C'est exactement la distinction posée en `+etm19` entre `SURPLUS_INSUFFICIENT` (« il
|
||
n'y a rien ») et `BELOW_MIN_POWER` (« il y en a, mais pas au bon format »), **transposée telle
|
||
quelle au soutirage** : ce n'est pas une règle nouvelle, c'est la même appliquée à une seconde
|
||
ressource.
|
||
|
||
**Ce qui la décide est le GESTE qu'elle appelle** — la règle 7-b : un motif faux envoie
|
||
diagnostiquer le mauvais problème, avec l'autorité d'une explication.
|
||
|
||
| Motif | Ce qu'il dit | Le geste utile |
|
||
|---|---|---|
|
||
| `DRAW_CAP` | l'autorisation est **épuisée** | revoir le plafond, ou attendre |
|
||
| `BELOW_MIN_POWER` | il reste de l'autorisation, **la charge ne sait pas la prendre** | lire la fiche technique du mécanisme |
|
||
|
||
> **Conséquence sur le « point de rupture » de l'écran 2** : il est bien un `DRAW_CAP`. Une
|
||
> charge refusée **après** lui pour cause de palier porte `BELOW_MIN_POWER` et **ne déplace pas**
|
||
> le point de rupture — elle est en aval de la coupure, pas la coupure elle-même.
|
||
>
|
||
> **La règle est écrite ICI, une fois, par le moteur** — pas établie de chaque côté de la
|
||
> frontière. Deux précédences dérivées séparément divergeraient au premier motif ajouté.
|
||
|
||
**RÉPONSE À R4 — ce que les compteurs de budget comptent : le DÉCIDÉ.** *(Question de l'agent
|
||
app, 2026-08-29.)*
|
||
|
||
`budget.allocatedW` et ses voisins comptent **ce que la cascade a décidé**, jamais ce que le
|
||
matériel a reçu. Ce n'est pas un choix de commodité : le budget est le **registre de la
|
||
cascade**, et ce qui détermine ce que reçoit la charge suivante est la valeur décidée. L'arrondi,
|
||
lui, vit **après** — dans l'adaptateur, au dispatch, et il est propre à chaque mécanisme. Compter
|
||
le commandé obligerait à **rouvrir le budget après le dispatch**, c'est-à-dire à décider deux
|
||
fois.
|
||
|
||
> ⚠️ **Et cela corrige une prémisse de R4, qu'il vaut mieux corriger maintenant.** La maquette
|
||
> écrit « `allocatedW` publie ce qui a été **commandé** ». **C'est faux aujourd'hui** :
|
||
> `buildTelemetry()` publie `action.estimatedPowerW` du plan, et `applyActionsToAdapters()`
|
||
> reçoit le slot **`const`** — rien n'est réécrit après le dispatch. Mesurable :
|
||
> `testUnpluggedChargerTakesNoBudget` attend `allocatedW == 3000` sur une borne dont le courant
|
||
> commandé donne 2 990 W.
|
||
>
|
||
> Donc **`Σ targetW == allocatedW` tiendra exactement**, par construction — les deux sont du
|
||
> décidé. L'écart que l'écran veut nommer (« 1 400 W décidés → palier 1 500 W ») n'est **pas**
|
||
> entre `targetW` et `allocatedW` : il est entre `allocatedW` (**décidé**) et la charge utile de
|
||
> mécanisme (**appliqué**) — `mechanism.stageW`, `mechanism.setpointW`, `mechanism.currentA` —
|
||
> **qui est déjà publiée aujourd'hui**. L'écran a déjà les deux nombres ; ce sont leurs noms qui
|
||
> étaient inversés.
|
||
| `ECO_FLOOR_GRID` | `deliveredWh`, `targetWh`, `gridW` | Une obligation éco servie **au réseau**. `gridW` chiffre l'achat, comme `EV_GRID_START` le fait déjà. |
|
||
| `ECO_FLOOR_MET` | `deliveredWh`, `targetWh` | L'obligation est **atteinte** — et elle ne peut se dire atteinte que sur le régime ÉNERGIE (LM-1009). |
|
||
| `COMFORT_SKIPPED` | `o:sensorValue`, `o:setpoint` | La charge n'est plus demandeuse de son bonus (comparateur de LM-1008). Distinct de « pas de budget ». |
|
||
|
||
**Et un champ, pas un code** : le **régime** de l'avancement (mesuré / estimé / non mesurable),
|
||
publié sur chaque charge portant une obligation. C'est la condition C2 de LM-1009, et elle ne
|
||
peut pas vivre dans un code : les trois régimes coexistent dans le même cycle, sur des charges
|
||
différentes.
|
||
|
||
---
|
||
|
||
## 7. Ce que LM-1009 impose à l'obligation éco
|
||
|
||
Trois décisions déjà prises, rappelées ici parce qu'elles ferment des choix de conception :
|
||
|
||
- **D1** — l'avancement se mesure et s'affiche en **énergie livrée**. La passe 1 compare donc des
|
||
Wh à des Wh, jamais un pourcentage.
|
||
- **D2** — **obligation par SESSION** d'abord. Pour une borne, la session est le branchement, et
|
||
la source est `sessionEnergy`. Pour une charge thermique, la « session » est le cycle
|
||
quotidien — et c'est là que l'analogie s'arrête : `minEnergyWhPerDay` **reste hors de ce lot**,
|
||
faute d'accumulation inter-sessions (§11).
|
||
- **C1** — une charge dont l'énergie livrée **n'est pas mesurable** dégrade **visiblement**. Le
|
||
cas est réel dès demain : la V2C Trydan ne publie pas `sessionEnergy`
|
||
(`docs/PREPA_BANC_20260828.md` §2.2). Le §10 doit donc prévoir ce régime **dès l'écriture**,
|
||
pas comme un cas dégradé ajouté après.
|
||
|
||
> **Conséquence directe** : une obligation éco sur une charge non mesurable est **déclarative,
|
||
> pas vérifiable**. Elle peut être servie ; elle ne peut jamais être annoncée **tenue**. C'est
|
||
> la même frontière que LM-1009 trace pour l'échéance VE.
|
||
|
||
---
|
||
|
||
## 8. LES POINTS À TRANCHER — deux le sont déjà
|
||
|
||
**8.1 — La réserve batterie annule-t-elle aussi la passe ÉCO ?** *(le plus important)*
|
||
|
||
LM-1203-b dit : sous la réserve, le budget est **annulé**. Mais ce budget est un budget de
|
||
**surplus**, et la passe 1 ne dépense pas de surplus — elle dépense une autorisation de
|
||
soutirage.
|
||
|
||
- **Si la réserve annule aussi la passe 1** : une installation dont la batterie est basse
|
||
n'atteindra **jamais** son plancher éco. Un ballon qui n'a pas vu 50 °C ne les verra pas, parce
|
||
que la batterie de la maison est à 15 %.
|
||
- **Si elle ne l'annule pas** : la maison achète au réseau pour un chauffe-eau pendant que sa
|
||
batterie est basse — ce qui peut être exactement ce qu'on veut (l'obligation prime) ou une
|
||
absurdité (on achète pendant qu'on manque).
|
||
|
||
**TRANCHÉ (Patrick, 2026-08-27) : la réserve N'annule PAS la passe 1.** La réserve protège le
|
||
**surplus** ; une obligation d'énergie sur échéance est d'une autre nature, et si elle cède à la
|
||
réserve, elle cesse d'être une obligation. Et la réserve garde son sens là où elle en a un —
|
||
empêcher de vider la batterie dans un chauffe-eau — ce qu'un soutirage réseau ne fait pas.
|
||
Inscrit en LM-1203-b.
|
||
|
||
**8.2 — Le plafond §14a est-il par phase, au total, ou les deux ?** Le §2.3 propose de porter les
|
||
deux sans jamais convertir. Reste à confirmer que c'est bien ainsi que le gestionnaire de réseau
|
||
l'exprimera — et le transport du §14a n'existe pas encore, donc la réponse peut attendre à
|
||
condition que la **forme** ne se ferme pas maintenant.
|
||
|
||
**8.3 — Le repli L2 doit-il TENIR le plafond ? TRANCHÉ, et la réponse retire le point du lot.**
|
||
*(Patrick, 2026-08-27.)* Les deux cas ne se confondent pas : le plafond du §10 est
|
||
**auto-imposé**, donc un repli qui **ne dépense pas** le respecte par construction — rien à
|
||
écrire. L'exigence dure — tenir une borne imposée par un TIERS alors que le moteur ne décide
|
||
plus — appartient au **§14a**, dont le transport n'existe pas, et elle vivra **hors du moteur**.
|
||
Les confondre ferait hériter au §10 d'une exigence qui ne le concerne pas encore. LM-1006-1 est
|
||
mis à jour en conséquence : **le §10 n'a rien à faire de ce côté.**
|
||
|
||
**8.4 — La cible de confort : comparateur seul, ou dosage ?** LM-1008 tranche déjà : comparateur
|
||
(`sonde < consigne` → demandeuse), le dosage attend le modèle thermique du §11. **À confirmer
|
||
que ça reste vrai pour ce lot**, parce que c'est ce qui le rend faisable sans historique.
|
||
|
||
**8.5 — Que fait l'app des deux passes ? POSÉE À L'AGENT APP le 2026-08-29**, et c'est le
|
||
dernier point ouvert du lot. LM-1006, couplage 2 : un plancher éco de rang 4 passe devant un
|
||
confort de rang 1 — voulu, et **contre-intuitif** pour l'installateur qui vient de poser son
|
||
ordre. Sans écran qui montre les deux passes, le premier retour terrain sera « l'ordre n'est pas
|
||
respecté ». Ce n'est pas du travail moteur, mais c'est une **condition de livraison**.
|
||
|
||
La question est formulée en `docs/BRIEF_depuis_plugin.md` §9, avec les trois choses à trancher —
|
||
sur quoi la liste est ordonnée, une entrée ou deux par charge, et ce qu'on voit d'une charge dont
|
||
l'éco est servi et le confort non. **Le moteur ne répond pas à sa place** : ce qui lui est demandé
|
||
est de dire quelle forme de publication rendrait l'écran possible, comme la maquette des
|
||
mécanismes l'a fait pour les six écrans. La charge utile suivra.
|
||
|
||
> ⚠️ **Tant qu'elle n'est pas tranchée, le §10 ne se code pas.** Ce n'est pas une dépendance
|
||
> molle : la troisième question — une charge dont l'éco est servi et le confort non — n'a
|
||
> **aucun équivalent** dans la télémétrie d'aujourd'hui, qui publie un motif et une allocation,
|
||
> un seul de chaque. Commencer par le moteur reviendrait à inventer une charge utile puis à
|
||
> demander à l'écran de s'en arranger, c'est-à-dire l'ordre inverse de celui qui a marché.
|
||
|
||
---
|
||
|
||
## 9. Ce qui n'est PAS dans ce lot
|
||
|
||
- **`minEnergyWhPerDay`** — obligation quotidienne, exige l'accumulation inter-sessions (§11).
|
||
Le lot livre l'obligation **par session**.
|
||
- **Le transport du §14a** — aucun canal n'existe. Le lot livre la **forme** du plafond, pas sa
|
||
source externe.
|
||
- **Le prix, l'heure, le signal tarifaire** (LM-1005) — le rule-based franchit la frontière de
|
||
l'achat **grossièrement** : « plancher non atteint à 22 h → chauffer ». Une règle d'horloge.
|
||
L'optimisation appartient au MILP, donc à l'optimiseur socket, donc à 3d.
|
||
- **Le dosage en Wh du confort** — voir 8.4.
|
||
- **La conversion degrés → Wh** — couche domaine, jamais l'arbitre (LM-1008).
|
||
|
||
---
|
||
|
||
## 10. Ordre de travail proposé
|
||
|
||
| Étape | Contenu | Éprouvable |
|
||
|---|---|---|
|
||
| **10-a** | `DrawCap` / `DrawBudget`, calculés dans `buildContext()`, publiés, **sans effet sur les décisions** | oui — télémétrie, aucun changement de comportement |
|
||
| **10-b** | Délestage : le plafond borne le plan ; motif `DRAW_CAP` avec sa source ; l'ordre de coupe est l'inverse du rang | oui — simulation + banc |
|
||
| **10-c** | Refonte décider/émettre (§4) : deux passes, une commande, recrédit unique — **à comportement CONSTANT**, passe 1 vide | oui — les 16 scénarios `run` doivent rester identiques au bit près |
|
||
| **10-d** | Passe 1 réellement servie : obligation par session, régimes de LM-1009, motifs éco | oui — banc |
|
||
| **10-e** | Passe 2 confort : comparateur de sonde | oui — banc, avec la sonde du §11 |
|
||
|
||
**10-a et 10-c sont les deux étapes qui ne changent rien**, et ce sont les plus importantes :
|
||
elles rendent le reste petit. 10-c en particulier doit être prouvée par l'**invariance** des
|
||
scénarios enregistrés — si la refonte décider/émettre change un seul point de contrôle, c'est
|
||
qu'elle a changé une décision, et il faut savoir laquelle avant d'aller plus loin.
|