docs(§10): réponse au §9 — deux passes, une commande, et la forme qui la publie
La maquette `docs/mockups/deux_passes_eco_confort_mockup.html` répond aux trois questions du brief plugin §9 (= DESIGN_10 8.5), et sert de contrat au moteur. - la liste reste ordonnée par RANG : l'ordre de service n'est pas une propriété de la configuration — il change d'un cycle à l'autre selon les planchers déjà atteints, donc une liste triée par lui bougerait sous le doigt ; - une entrée par charge, deux lignes dans la carte : on dédouble là où l'objet affiché est la SÉQUENCE, jamais là où c'est la CHARGE — d'où un second écran, « ordre de service du cycle », en lecture seule ; - « éco servi / confort non » s'affiche en deux états NOMMÉS : en fraction, le régime nominal se lirait comme une dégradation permanente. Huit exigences pour `GetLoadTelemetry`, dont R2 (`funding` descend au niveau) et R4, dont la prémisse a été corrigée par le moteur : `allocatedW` publie le DÉCIDÉ, donc `Σ targetW == allocatedW` est une identité, et l'écart à afficher vit entre le décidé et `mechanism.*` (appliqué). D'où le bloc « trois nombres ». Contrat, pas feuille de route : aucun écran ne se construit avant `levels[]`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GKM8FDNHZWogAsjjfB8pSw
This commit is contained in:
parent
438f261de7
commit
6f19863620
@ -1,5 +1,157 @@
|
||||
# Brief plugin — ce que les maquettes « Configurer » demandent au moteur
|
||||
|
||||
---
|
||||
|
||||
## 2026-08-29 — RÉPONSE au §9 : les deux passes du §10, vues de l'écran
|
||||
|
||||
**Maquette : `docs/mockups/deux_passes_eco_confort_mockup.html`** — trois écrans, un catalogue
|
||||
d'états et la charge utile demandée. Ce qui suit en est le résumé ; la maquette fait foi.
|
||||
Lisible sans cloner le dépôt : <https://claude.ai/code/artifact/95faf613-f79a-4906-b82d-e264b4a40b11>
|
||||
(même contenu, republiée à chaque révision du fichier).
|
||||
|
||||
### Les trois réponses
|
||||
|
||||
**1. La liste reste ordonnée par RANG.** Deux raisons, et la seconde est la vraie :
|
||||
|
||||
- c'est la **surface d'édition du rang** — le glisser-déposer y écrit `priority`. Une liste qui
|
||||
se réordonne sous le doigt après le geste est inutilisable ;
|
||||
- surtout, **l'ordre de service n'est pas une propriété de la configuration** : il dépend des
|
||||
planchers déjà atteints, donc il change d'un cycle à l'autre sans que personne ne touche à
|
||||
rien. Une liste de configuration ne peut pas avoir une colonne vertébrale qui bouge toute
|
||||
seule. Trier par ordre de service ne rendrait pas le rang illisible — ça rendrait la **liste**
|
||||
instable, ce qui est pire.
|
||||
|
||||
**Et il faut le dire, donc on le dit deux fois** : un bandeau permanent au-dessus de la liste
|
||||
(« tous les planchers éco d'abord, le confort ensuite — votre ordre est respecté à l'intérieur
|
||||
de chaque passe »), et un **second écran en lecture seule**, l'*ordre de service du cycle*, qui
|
||||
montre la séquence réelle. Le bandeau n'apparaît que si au moins une charge porte une obligation
|
||||
éco : sans obligation, il n'y a qu'une passe utile et la phrase serait du bruit (LM-1007).
|
||||
|
||||
**2. Une seule entrée par charge dans la liste ; deux lignes DANS la carte.** La règle qu'on
|
||||
s'est donnée : **on dédouble là où l'objet affiché est la SÉQUENCE, jamais là où l'objet est la
|
||||
CHARGE.** Donc une charge apparaît deux fois dans l'écran 2 — c'est son sujet — et une seule fois
|
||||
dans la liste. Dans la carte, la **commande est le parent** (« Commandé 1 500 W · une seule
|
||||
consigne ») et les deux niveaux sont sa **décomposition**, décalés et rattachés par un filet.
|
||||
C'est ce qui rend LM-1003 lisible sans avoir à l'écrire.
|
||||
|
||||
**3. Éco servi / confort non** — le cas fréquent — s'affiche comme **deux états nommés**, jamais
|
||||
comme une fraction :
|
||||
|
||||
```
|
||||
Décidé 1 400 W · une seule décision
|
||||
↳ appliqué au mécanisme : palier 1 000 W (relais 1+2)
|
||||
ÉCO Plancher servi — 1 400 W achetés au réseau
|
||||
2,1 kWh livrés sur les 3,0 dus avant 07:00 [MESURÉ]
|
||||
CONFORT En attente
|
||||
Aucun surplus ce cycle — le bonus n'est pas refusé, il n'a rien à prendre.
|
||||
```
|
||||
|
||||
S'il s'affichait comme une fraction de quelque chose, le régime **nominal** se lirait comme une
|
||||
dégradation permanente. Et « en attente » (demandeur, pas de budget) et « pas demandeur »
|
||||
(comparateur LM-1008 satisfait) sont **deux états distincts** : l'un se règle en changeant la
|
||||
consigne, l'autre en attendant le soleil.
|
||||
|
||||
### Ce que ça demande à `GetLoadTelemetry` — un tableau, rien de retiré
|
||||
|
||||
```jsonc
|
||||
"allocatedW": 1400, // INCHANGÉ — le DÉCIDÉ. Σ targetW == allocatedW, exactement.
|
||||
"levels": [ // AJOUT — un élément par passe RÉELLEMENT parcourue
|
||||
{ "level": "eco", "targetW": 1400, "funding": "grid",
|
||||
"decision": {"code": "ECO_FLOOR_GRID", "params": {…}},
|
||||
"progress": {"regime": "measured", "deliveredWh": 2100, "targetWh": 3000} },
|
||||
{ "level": "comfort", "targetW": 0,
|
||||
"decision": {"code": "SURPLUS_INSUFFICIENT", "params": {…}} }
|
||||
],
|
||||
"decision": {"level": "eco", "code": "ECO_FLOOR_GRID", "params": {…}} // CONSERVÉ + niveau reflété
|
||||
// une fois par cycle, à côté de "budget" :
|
||||
"draw": {"authorisedW": 4000, "committedW": 4000, "remainingW": 0,
|
||||
"binding": "gridOperator", "perPhaseBound": false}
|
||||
```
|
||||
|
||||
Huit exigences, détaillées dans la maquette. Les quatre qui coûteraient cher à rattraper :
|
||||
|
||||
- **R2 — `funding` descend au NIVEAU.** Il est aujourd'hui par charge ; au §10 la même charge est
|
||||
financée **au réseau pour son éco et au surplus pour son confort dans le même cycle**. Laissé à
|
||||
la charge, il devient indécidable — et c'est lui qui porte notre identité de réconciliation.
|
||||
- **R3 — chaque watt publié tombe dans exactement un compteur, et le mapping est publié.** Trois
|
||||
destinations coexisteront (`budget.allocatedW`, `budget.evReservedW`, `draw.committedW`) ;
|
||||
l'app ne doit pas deviner laquelle, sinon deux clients afficheront deux bilans du même cycle.
|
||||
C'est la leçon d'`EV_GRID_START`.
|
||||
- **R4 — `Σ targetW == allocatedW`, une identité et non un écart.** *(Notre prémisse était
|
||||
fausse, corrigée le 2026-08-29 : `allocatedW` publie le **décidé**, pas le commandé —
|
||||
`buildTelemetry()` publie le plan, `applyActionsToAdapters()` reçoit le slot en `const`.)* Les
|
||||
niveaux et `allocatedW` sont la même monnaie, donc leur somme est **exacte** — c'est ce qui rend
|
||||
`levels[]` vérifiable contre un champ déjà en ligne, et toute violation sera traitée comme une
|
||||
incohérence de la box. **Ce qui reste demandé** : ne pas attribuer l'**appliqué** à un niveau —
|
||||
il vit dans `mechanism` (`stageW`, `setpointW`, `currentA`), et l'adaptateur arrondit une
|
||||
consigne unique sans savoir de quel besoin elle venait.
|
||||
- **R6 — `ECO_FLOOR_MET` interdit sous le régime `unmeasurable`.** Servie oui, tenue jamais
|
||||
(LM-1009 C1). Côté app la combinaison sera affichée comme une **incohérence de la box**, pas
|
||||
rendue. Cas réel dès aujourd'hui : la V2C Trydan ne publie pas `sessionEnergy`.
|
||||
|
||||
Les quatre autres : **R1** `levels[]` ne contient que les passes parcourues (une charge sans
|
||||
obligation n'a pas d'entrée `eco` à zéro — l'absence est l'information, LM-1007) et son absence
|
||||
totale vaut « box antérieure » ; **R5** le régime de LM-1009 est porté par le niveau éco, avec
|
||||
`deliveredWh` **absent** sous `unmeasurable` ; **R7** `decision.level` sur le motif legacy, pour
|
||||
que la règle de fusion ne soit pas devinée par chaque client ; **R8** `DRAW_CAP` publie sa
|
||||
source — trois sources, trois phrases, trois gestes.
|
||||
|
||||
### Ce qu'il ne faut PAS fabriquer
|
||||
|
||||
- Un **index d'ordre de service**. La séquence de l'écran 2 se dérive d'un tri lexicographique
|
||||
sur `(niveau, priority)` : un index publié serait une seconde source de vérité pour une donnée
|
||||
déductible, et divergerait un jour.
|
||||
- Un **second rang**, sous quelque nom que ce soit. La maquette est construite pour rendre le
|
||||
rang unique lisible *malgré* les deux passes ; elle n'a de sens que si LM-1002 tient.
|
||||
- Un **pourcentage** d'avancement, même à titre indicatif. D1 s'applique sans exception.
|
||||
|
||||
### La précédence des motifs — TRANCHÉE (Patrick, 2026-08-29)
|
||||
|
||||
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 ici il en reste, simplement **pas au bon format**. C'est 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.
|
||||
Le geste utile est de lire la fiche technique du mécanisme, pas de revoir le plafond.
|
||||
|
||||
`DRAW_CAP` reste réservé à **l'autorisation résiduelle nulle**. Le point de rupture de l'écran 2
|
||||
est donc 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. **La règle est écrite par le moteur**,
|
||||
pas établie de chaque côté.
|
||||
|
||||
### L'écart à afficher n'est pas là où nous l'avions mis
|
||||
|
||||
Un écran construit sur notre prémisse inverse aurait comparé `Σ targetW` à `allocatedW` — **deux
|
||||
valeurs égales par construction**. Il n'aurait jamais rien montré, **et sans erreur**. Corrigé
|
||||
dans la maquette : les cartes affichent désormais *décidé → appliqué*, et l'écran 3 porte un bloc
|
||||
« trois nombres » qui fixe le vocabulaire :
|
||||
|
||||
| Nombre | Champ | Ce qu'il dit |
|
||||
|---|---|---|
|
||||
| **Décidé** | `allocatedW` | ce que l'arbitre a planifié — et ce que les compteurs de budget comptent |
|
||||
| **Appliqué** | `mechanism.*` | ce que l'adaptateur a su produire (`stageW`, `setpointW`, `currentA`) |
|
||||
| **Mesuré** | `measurement.powerW` | ce que la charge tire, quand c'est mesurable |
|
||||
|
||||
**décidé ↔ appliqué** = l'arrondi du mécanisme, normal et permanent. **appliqué ↔ mesuré** = la
|
||||
charge n'obéit pas, et ce que l'écart prouve dépend de `measurement.source`. **décidé ↔ mesuré** =
|
||||
la somme des deux, à ne jamais lire seule : elle mélange une cause bénigne et une qui ne l'est pas.
|
||||
|
||||
Conséquence assumée du choix « les compteurs comptent le décidé » : le budget d'un cycle peut
|
||||
annoncer 1 400 W pendant que l'installation en tire 1 000. C'est le bloc « trois nombres » qui
|
||||
empêche de lire cet écart comme une erreur de comptage.
|
||||
|
||||
### Priorité, et statut de cette maquette
|
||||
|
||||
**R2 est la plus urgente des huit** : c'est la seule qui soit un **changement de forme d'un champ
|
||||
existant** et non un ajout. Chaque écran qui s'appuiera d'ici là sur un `funding` par charge en
|
||||
augmentera le coût de déplacement ; les sept autres attendent sans se renchérir.
|
||||
✅ **Partie le 2026-08-29** — première des huit à être écrite côté moteur.
|
||||
|
||||
**La maquette vaut comme contrat, pas comme feuille de route.** Le §10 n'est pas implémenté, le
|
||||
transport du §14a n'existe pas, `draw` non plus. **Côté app, rien ne commence avant que
|
||||
`levels[]` soit en ligne** — ce document fixe la forme de ce qui sera publié, pas une date.
|
||||
|
||||
|
||||
---
|
||||
|
||||
## 2026-08-29 (suite) — l'insertion en queue casse la contiguïté des domaines
|
||||
|
||||
760
docs/mockups/deux_passes_eco_confort_mockup.html
Normal file
760
docs/mockups/deux_passes_eco_confort_mockup.html
Normal file
@ -0,0 +1,760 @@
|
||||
<!doctype html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>ETM PowerSync — §10 : deux passes, une commande</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@500;600;700&family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
:root{
|
||||
--bg:#081f2b; --panel:#0d2b3b; --card:#12384a; --card2:#0f3242;
|
||||
--line:rgba(255,255,255,.08); --line2:rgba(255,255,255,.14);
|
||||
--blue:#31a3dd; --yellow:#fec113; --green:#35c98a; --red:#e07a7a; --violet:#9d8ce0;
|
||||
--txt:#e8f1f6; --muted:#90a8b6; --muted2:#6f8b9a; --r:16px;
|
||||
}
|
||||
*{box-sizing:border-box}
|
||||
body{margin:0;background:
|
||||
radial-gradient(900px 500px at 15% -10%, #0e3145 0%, transparent 60%),
|
||||
radial-gradient(900px 600px at 110% 10%, #0b2535 0%, transparent 55%),
|
||||
var(--bg);
|
||||
font-family:'Inter',system-ui,sans-serif;color:var(--txt);min-height:100vh;padding:34px 20px 60px}
|
||||
.head{max-width:1300px;margin:0 auto 26px}
|
||||
.head h1{font-family:'Poppins';font-weight:700;font-size:20px;margin:0 0 6px}
|
||||
.head p{margin:0 0 8px;color:var(--muted);font-size:13.5px;line-height:1.55;max-width:940px}
|
||||
.head code{color:#9fd4ee} .head b{color:var(--yellow)}
|
||||
.head .rep{border-left:2px solid var(--blue);padding-left:12px;margin:14px 0 0;max-width:940px}
|
||||
.head .rep p{margin:0 0 5px;font-size:13px}
|
||||
.head .rep b{color:var(--blue)}
|
||||
.stage{display:flex;gap:34px;justify-content:center;flex-wrap:wrap;align-items:flex-start}
|
||||
.col{display:flex;flex-direction:column;align-items:center;gap:12px;max-width:392px;width:100%}
|
||||
.cap{color:var(--muted2);font-size:12px;font-family:'Poppins';font-weight:500;letter-spacing:.4px;text-transform:uppercase}
|
||||
.cap b{color:var(--blue)}
|
||||
|
||||
.phone{width:min(392px,100%);background:var(--panel);border:1px solid var(--line2);border-radius:30px;
|
||||
overflow:hidden;box-shadow:0 30px 80px -30px rgba(0,0,0,.7);display:flex;flex-direction:column;height:900px}
|
||||
.appbar{display:flex;align-items:center;gap:12px;padding:18px 16px 14px;border-bottom:1px solid var(--line);
|
||||
background:linear-gradient(#102f40,#0d2b3b)}
|
||||
.appbar .ic{width:26px;height:26px;display:grid;place-items:center;color:var(--muted);font-size:18px}
|
||||
.appbar h2{font-family:'Poppins';font-weight:600;font-size:15px;margin:0;flex:1;text-align:center}
|
||||
.scroll{overflow:auto;padding:16px 15px 26px}
|
||||
.scroll::-webkit-scrollbar{width:0}
|
||||
|
||||
.sect{font-family:'Poppins';font-weight:600;font-size:12px;color:var(--muted);letter-spacing:.3px;
|
||||
margin:20px 0 9px;display:flex;align-items:baseline;gap:8px}
|
||||
.sect:first-child{margin-top:2px}
|
||||
.sect small{font-weight:500;color:var(--muted2);letter-spacing:0;font-size:11px}
|
||||
|
||||
/* ─ bandeau arbitrage : DEUX comptabilités ─ */
|
||||
.arb{background:var(--card2);border:1px solid var(--line);border-radius:13px;padding:12px 13px}
|
||||
.arb .top{display:flex;align-items:center;gap:8px;margin-bottom:8px}
|
||||
.arb .dot{width:8px;height:8px;border-radius:50%;background:var(--green);box-shadow:0 0 0 4px rgba(53,201,138,.15)}
|
||||
.arb .top b{font-family:'Poppins';font-size:12.5px;font-weight:600}
|
||||
.arb .top i{margin-left:auto;font-style:normal;color:var(--muted2);font-size:11px}
|
||||
.arb .lab{font-size:10px;color:var(--muted2);font-family:'Poppins';font-weight:600;letter-spacing:.3px;
|
||||
text-transform:uppercase;margin:9px 0 5px;display:flex;align-items:center;gap:6px}
|
||||
.arb .lab u{text-decoration:none;color:var(--muted2)}
|
||||
.arb .lab .p1{color:var(--yellow)} .arb .lab .p2{color:var(--green)}
|
||||
.arb .grid{display:grid;grid-template-columns:repeat(5,1fr);gap:6px}
|
||||
.arb .grid.g4{grid-template-columns:repeat(4,1fr)}
|
||||
.arb .cell{background:var(--panel);border:1px solid var(--line);border-radius:9px;padding:7px 4px;text-align:center}
|
||||
.arb .cell u{display:block;text-decoration:none;font-size:9.5px;color:var(--muted2);margin-bottom:2px}
|
||||
.arb .cell b{font-family:'Poppins';font-weight:600;font-size:11.5px}
|
||||
.arb .cell.zero b{color:var(--muted2)}
|
||||
.arb .cell.bind{border-color:rgba(224,122,122,.4);background:rgba(224,122,122,.07)}
|
||||
.arb .cell.bind b{color:var(--red);font-size:10px}
|
||||
|
||||
/* ─ la phrase qui DIT que l'ordre affiché n'est pas l'ordre de service ─ */
|
||||
.twopass{margin-top:9px;border:1px solid rgba(157,140,224,.34);background:rgba(157,140,224,.08);
|
||||
border-radius:11px;padding:10px 11px;font-size:11.5px;line-height:1.5;color:var(--muted)}
|
||||
.twopass b{color:var(--violet);font-family:'Poppins';font-size:11.5px;display:block;margin-bottom:3px}
|
||||
.twopass a{color:var(--violet);text-decoration:none;font-weight:600;display:block;margin-top:6px}
|
||||
|
||||
/* ─ carte de charge ─ */
|
||||
.load{background:var(--card);border:1px solid var(--line);border-radius:14px;padding:12px;margin-bottom:9px}
|
||||
.load.spot{border-color:rgba(157,140,224,.42);box-shadow:0 0 0 3px rgba(157,140,224,.07)}
|
||||
.load .r1{display:flex;align-items:center;gap:10px}
|
||||
.load .grip{color:var(--muted2);font-size:15px;cursor:grab}
|
||||
.rank{width:23px;height:23px;border-radius:7px;background:rgba(49,163,221,.16);color:var(--blue);
|
||||
font-family:'Poppins';font-weight:700;font-size:11.5px;display:grid;place-items:center;flex:none}
|
||||
.load .nm{flex:1;min-width:0}
|
||||
.load .nm b{font-family:'Poppins';font-weight:600;font-size:13.5px;display:block}
|
||||
.load .nm span{font-size:11px;color:var(--muted2)}
|
||||
.sw{width:38px;height:22px;border-radius:20px;background:rgba(53,201,138,.85);position:relative;cursor:pointer;flex:none}
|
||||
.sw::after{content:"";position:absolute;top:2px;left:18px;width:18px;height:18px;border-radius:50%;background:#fff}
|
||||
.sw.off{background:rgba(255,255,255,.16)} .sw.off::after{left:2px}
|
||||
|
||||
/* ─ LA COMMANDE : le parent. Les niveaux sont sa décomposition. ─ */
|
||||
.cmd{margin-top:10px;padding:9px 10px;border:1px solid var(--line2);border-radius:11px;background:var(--card2)}
|
||||
.cmd .l{display:flex;align-items:baseline;gap:8px}
|
||||
.cmd .l b{font-family:'Poppins';font-weight:700;font-size:14px}
|
||||
.cmd .l i{font-style:normal;font-size:10.5px;color:var(--muted2);margin-left:auto}
|
||||
.cmd .l small{font-size:11px;color:var(--muted)}
|
||||
.cmd.idle .l b{color:var(--muted2)}
|
||||
.cmd .round{font-size:10.5px;color:var(--yellow);margin-top:4px}
|
||||
|
||||
.lvls{margin-top:8px;border-left:2px solid var(--line2);padding-left:10px;margin-left:4px}
|
||||
.lvl{display:flex;gap:8px;padding:6px 0;font-size:11.5px;line-height:1.45}
|
||||
.lvl + .lvl{border-top:1px dashed var(--line)}
|
||||
.lvl .tag{flex:none;width:60px;font-family:'Poppins';font-weight:700;font-size:9.5px;letter-spacing:.4px;
|
||||
padding:3px 0;text-align:center;border-radius:6px;height:19px}
|
||||
.lvl .tag.eco{background:rgba(254,193,19,.14);color:var(--yellow)}
|
||||
.lvl .tag.cft{background:rgba(53,201,138,.13);color:var(--green)}
|
||||
.lvl .bd{flex:1;min-width:0}
|
||||
.lvl .bd b{font-family:'Poppins';font-weight:600;font-size:11.5px;display:block}
|
||||
.lvl .bd span{color:var(--muted2);display:block;margin-top:1px}
|
||||
.lvl .bd em{font-style:normal;color:var(--muted)}
|
||||
.lvl.ok .bd b{color:var(--green)}
|
||||
.lvl.grid .bd b{color:var(--yellow)}
|
||||
.lvl.wait .bd b{color:var(--muted)}
|
||||
.lvl.no .bd b{color:var(--red)}
|
||||
.lvl.mute .bd b{color:var(--muted2)}
|
||||
.pill{display:inline-block;font-size:9.5px;font-family:'Poppins';font-weight:700;padding:1px 6px;border-radius:20px;
|
||||
border:1px solid var(--line2);color:var(--muted2);margin-left:6px;vertical-align:1px}
|
||||
.pill.meas{border-color:rgba(53,201,138,.4);color:var(--green)}
|
||||
.pill.est{border-color:rgba(254,193,19,.4);color:var(--yellow)}
|
||||
.pill.nm{border-color:rgba(224,122,122,.45);color:var(--red)}
|
||||
|
||||
.bar{height:5px;border-radius:4px;background:rgba(255,255,255,.08);margin-top:5px;overflow:hidden;display:flex}
|
||||
.bar i{display:block;height:100%;background:var(--yellow)}
|
||||
.bar.dash{background:repeating-linear-gradient(90deg,rgba(255,255,255,.14) 0 5px,transparent 5px 10px)}
|
||||
|
||||
.cfg{margin-top:10px;width:100%;background:transparent;border:1px solid rgba(49,163,221,.42);color:var(--blue);
|
||||
font-family:'Poppins';font-weight:600;font-size:12px;padding:9px;border-radius:10px;cursor:pointer;
|
||||
display:flex;align-items:center;justify-content:center;gap:7px}
|
||||
|
||||
/* ─ écran 2 : ordre de service ─ */
|
||||
.hint{border:1px solid rgba(49,163,221,.28);background:rgba(49,163,221,.07);border-radius:12px;
|
||||
padding:11px 12px;font-size:11.5px;color:var(--muted);line-height:1.5}
|
||||
.hint b{color:var(--blue);font-family:'Poppins';font-size:12px;display:block;margin-bottom:3px}
|
||||
.pass{margin-top:18px}
|
||||
.pass .ph{display:flex;align-items:center;gap:9px;margin-bottom:9px}
|
||||
.pass .ph .n{width:22px;height:22px;border-radius:7px;font-family:'Poppins';font-weight:700;font-size:11px;
|
||||
display:grid;place-items:center;flex:none}
|
||||
.pass.p1 .ph .n{background:rgba(254,193,19,.16);color:var(--yellow)}
|
||||
.pass.p2 .ph .n{background:rgba(53,201,138,.16);color:var(--green)}
|
||||
.pass .ph b{font-family:'Poppins';font-weight:600;font-size:12.5px}
|
||||
.pass .ph i{margin-left:auto;font-style:normal;font-size:10.5px;color:var(--muted2)}
|
||||
.pass.p1 .ph b{color:var(--yellow)} .pass.p2 .ph b{color:var(--green)}
|
||||
.srv{display:flex;align-items:flex-start;gap:9px;padding:9px 10px;border-radius:11px;background:var(--card);
|
||||
border:1px solid var(--line);margin-bottom:6px}
|
||||
.srv .rk{width:21px;height:21px;border-radius:6px;background:rgba(49,163,221,.16);color:var(--blue);
|
||||
font-family:'Poppins';font-weight:700;font-size:11px;display:grid;place-items:center;flex:none;margin-top:1px}
|
||||
.srv .bd{flex:1;min-width:0;font-size:11.5px;line-height:1.45}
|
||||
.srv .bd b{font-family:'Poppins';font-weight:600;font-size:12.5px;display:block}
|
||||
.srv .bd span{color:var(--muted2);display:block;font-size:11px;margin-top:1px}
|
||||
.srv .w{flex:none;text-align:right;font-family:'Poppins';font-weight:700;font-size:12.5px}
|
||||
.srv .w u{display:block;text-decoration:none;font-size:9.5px;color:var(--muted2);font-weight:600;margin-top:2px}
|
||||
.srv.g .w{color:var(--yellow)} .srv.s .w{color:var(--green)}
|
||||
.srv.nil{opacity:.72} .srv.nil .w{color:var(--muted2)}
|
||||
.srv.cut{border-color:rgba(224,122,122,.35);background:rgba(224,122,122,.06)} .srv.cut .w{color:var(--red)}
|
||||
.rule{display:flex;align-items:center;gap:8px;margin:9px 0;color:var(--muted2);font-size:10.5px}
|
||||
.rule:before,.rule:after{content:"";flex:1;height:1px;background:var(--line2)}
|
||||
.exh{border:1px dashed rgba(224,122,122,.4);border-radius:10px;padding:8px 10px;font-size:11px;color:var(--muted);
|
||||
line-height:1.45;margin-bottom:6px}
|
||||
.exh b{color:var(--red);font-family:'Poppins';font-size:11px}
|
||||
|
||||
/* ─ écran 3 : catalogue ─ */
|
||||
.cat{background:var(--card);border:1px solid var(--line);border-radius:12px;padding:10px 11px;margin-bottom:8px}
|
||||
.cat .h{display:flex;align-items:center;gap:8px;margin-bottom:6px}
|
||||
.cat .h .tag{flex:none;font-family:'Poppins';font-weight:700;font-size:9.5px;letter-spacing:.4px;
|
||||
padding:3px 7px;border-radius:6px}
|
||||
.cat .h .tag.eco{background:rgba(254,193,19,.14);color:var(--yellow)}
|
||||
.cat .h .tag.cft{background:rgba(53,201,138,.13);color:var(--green)}
|
||||
.cat .h b{font-family:'Poppins';font-weight:600;font-size:12px;flex:1}
|
||||
.cat .h code{font-size:10px;color:#9fd4ee}
|
||||
.cat .txt{font-size:11.5px;color:var(--txt);line-height:1.45}
|
||||
.cat .why{font-size:10.8px;color:var(--muted2);line-height:1.5;margin-top:4px}
|
||||
.never{margin-top:16px;border:1px solid rgba(224,122,122,.32);background:rgba(224,122,122,.06);
|
||||
border-radius:12px;padding:11px 12px}
|
||||
.never b{color:var(--red);font-family:'Poppins';font-size:11.5px;display:block;margin-bottom:5px}
|
||||
.never ul{margin:0;padding-left:16px;font-size:11px;color:var(--muted);line-height:1.6}
|
||||
.never li b{display:inline;color:var(--red);font-size:11px}
|
||||
|
||||
.note{width:min(392px,100%);font-size:11.5px;color:var(--muted2);line-height:1.55;text-align:left}
|
||||
.note b{color:var(--yellow)} .note code{color:#9fd4ee}
|
||||
.note ul{margin:7px 0 0;padding-left:16px} .note li{margin-bottom:5px}
|
||||
|
||||
/* ─ le brief moteur ─ */
|
||||
.brief{max-width:1300px;margin:44px auto 0;border-top:1px solid var(--line2);padding-top:28px}
|
||||
.brief h2{font-family:'Poppins';font-weight:700;font-size:17px;margin:0 0 4px}
|
||||
.brief h3{font-family:'Poppins';font-weight:600;font-size:13.5px;margin:22px 0 6px;color:var(--blue)}
|
||||
.brief p{color:var(--muted);font-size:13px;line-height:1.6;max-width:940px;margin:0 0 9px}
|
||||
.brief b{color:var(--yellow)} .brief code{color:#9fd4ee;font-size:12px}
|
||||
.brief pre{background:#06171f;border:1px solid var(--line);border-radius:12px;padding:14px 16px;overflow:auto;
|
||||
font-size:11.8px;line-height:1.55;color:#cfe6f2;max-width:940px}
|
||||
.brief pre .c{color:#5d8698}
|
||||
.brief pre .k{color:#9fd4ee}
|
||||
.brief pre .n{color:var(--yellow)}
|
||||
.brief pre .s{color:#9ee0b8}
|
||||
.req{border:1px solid var(--line);background:var(--card2);border-radius:12px;padding:12px 14px;margin-bottom:9px;
|
||||
max-width:940px}
|
||||
.req b{font-family:'Poppins';font-weight:600;font-size:12.5px;color:var(--txt);display:block;margin-bottom:3px}
|
||||
.req b u{text-decoration:none;color:var(--blue);margin-right:7px}
|
||||
.req p{margin:0;font-size:12px;color:var(--muted);line-height:1.55}
|
||||
.req.hard{border-color:rgba(224,122,122,.35);background:rgba(224,122,122,.05)}
|
||||
.req.hard b u{color:var(--red)}
|
||||
.brief .no{border-left:2px solid var(--line2);padding-left:12px;max-width:940px}
|
||||
.brief .no li{color:var(--muted);font-size:12.5px;line-height:1.6;margin-bottom:4px}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="head">
|
||||
<h1>§10 — Éco et confort : deux passes, deux lectures, <b>une seule commande</b></h1>
|
||||
<p>Réponse de l'app aux trois questions du brief plugin <code>docs/BRIEF_depuis_plugin.md</code> §9
|
||||
(= <code>DESIGN_10.md</code> 8.5, LM-1002 / LM-1003 / LM-1006 couplage 2 / LM-1009). Rien n'est câblé côté app :
|
||||
cette maquette est le <b>brief</b>, et la charge utile demandée est en bas de page.</p>
|
||||
<p><b>Elle vaut comme contrat, pas comme feuille de route.</b> Le §10 n'est pas implémenté, le transport du
|
||||
§14a n'existe pas, <code>draw</code> non plus : aucun de ces trois écrans ne se construit avant que
|
||||
<code>levels[]</code> soit en ligne. Ce qu'elle fixe, c'est la <em>forme</em> de ce qui sera publié — pour
|
||||
que le moteur n'ait pas à inventer une charge utile que l'écran devra ensuite arranger.</p>
|
||||
|
||||
<div class="rep">
|
||||
<p><b>1 · La liste reste ordonnée par RANG</b> — parce que c'est la surface d'édition du rang, et parce que
|
||||
l'ordre de service <em>n'est pas une propriété de la configuration</em> : il change à chaque cycle selon les
|
||||
planchers déjà atteints. Une liste qui se réordonne toute seule sous le doigt est inutilisable.</p>
|
||||
<p><b>2 · Une seule entrée par charge dans la liste, deux lignes DANS la carte</b> — la commande est le parent,
|
||||
les deux niveaux sont sa décomposition. On ne dédouble que là où l'objet affiché est la <em>séquence</em>,
|
||||
jamais là où l'objet est la <em>charge</em>.</p>
|
||||
<p><b>3 · L'ordre de service devient un second écran, en lecture seule</b> — c'est lui qui « le dit ». Il montre
|
||||
le rang 4 éco avant le rang 1 confort, avec les rangs visibles, donc il montre en même temps que
|
||||
<em>votre ordre est respecté dans chaque passe</em>. C'est l'écran qui désamorce « l'ordre n'est pas respecté ».</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stage">
|
||||
|
||||
<!-- ══════════ ÉCRAN 1 — la liste, au rang ══════════ -->
|
||||
<div class="col">
|
||||
<div class="phone">
|
||||
<div class="appbar"><div class="ic">‹</div><h2>Rôles & appareils</h2><div class="ic">⟳</div></div>
|
||||
<div class="scroll">
|
||||
|
||||
<div class="sect">Arbitrage en direct <small>· cycle de 06:41, il y a 22 s</small></div>
|
||||
<div class="arb">
|
||||
<div class="top"><span class="dot"></span><b>Matin sans soleil</b><i>2 comptabilités</i></div>
|
||||
|
||||
<div class="lab"><span class="p2">■</span><u>Passe 2 — surplus disponible</u></div>
|
||||
<div class="grid">
|
||||
<div class="cell zero"><u>Surplus</u><b>0</b></div>
|
||||
<div class="cell zero"><u>Réservé VE</u><b>0</b></div>
|
||||
<div class="cell zero"><u>Recrédité</u><b>0</b></div>
|
||||
<div class="cell zero"><u>Alloué</u><b>0</b></div>
|
||||
<div class="cell zero"><u>Reliquat</u><b>0</b></div>
|
||||
</div>
|
||||
|
||||
<div class="lab"><span class="p1">■</span><u>Passe 1 — autorisation de soutirage</u></div>
|
||||
<div class="grid g4">
|
||||
<div class="cell"><u>Autorisé</u><b>4 000</b></div>
|
||||
<div class="cell"><u>Engagé</u><b>4 000</b></div>
|
||||
<div class="cell zero"><u>Restant</u><b>0</b></div>
|
||||
<div class="cell bind"><u>Borné par</u><b>GRD</b></div>
|
||||
</div>
|
||||
|
||||
<div class="twopass">
|
||||
<b>◆ Deux passes ce cycle</b>
|
||||
Tous les <em>planchers éco</em> sont servis d'abord, par rang croissant ; le <em>confort</em> ensuite,
|
||||
dans le même ordre. Un plancher de rang 4 passe donc avant un confort de rang 1 —
|
||||
<em>c'est voulu, et votre ordre est respecté à l'intérieur de chaque passe.</em>
|
||||
<a>Voir l'ordre de service de ce cycle ›</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sect">Charges pilotables <small>· ordonnées par rang — glissez pour classer</small></div>
|
||||
|
||||
<!-- LE CAS FRÉQUENT : éco servi, confort non -->
|
||||
<div class="load spot">
|
||||
<div class="r1">
|
||||
<span class="grip">⠿</span><span class="rank">1</span>
|
||||
<div class="nm"><b>Chauffe-eau</b><span>Routeur de relais · 3 relais · ECS</span></div>
|
||||
<span class="sw"></span>
|
||||
</div>
|
||||
<div class="cmd">
|
||||
<div class="l"><b>Décidé 1 400 W</b><i>une seule décision</i></div>
|
||||
<div class="round">↳ appliqué au mécanisme : <b>palier 1 000 W</b> (relais 1+2) — l'adaptateur
|
||||
n'a pas ce point exact</div>
|
||||
</div>
|
||||
<div class="lvls">
|
||||
<div class="lvl grid">
|
||||
<span class="tag eco">ÉCO</span>
|
||||
<div class="bd">
|
||||
<b>Plancher servi — 1 400 W achetés au réseau</b>
|
||||
<span>2,1 kWh livrés sur les 3,0 dus avant 07:00 <span class="pill meas">MESURÉ</span></span>
|
||||
<div class="bar"><i style="width:70%"></i></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="lvl wait">
|
||||
<span class="tag cft">CONFORT</span>
|
||||
<div class="bd">
|
||||
<b>En attente</b>
|
||||
<span>Aucun surplus ce cycle — le bonus n'est pas refusé, il n'a rien à prendre.</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button class="cfg">⚙ Configurer</button>
|
||||
</div>
|
||||
|
||||
<div class="load">
|
||||
<div class="r1">
|
||||
<span class="grip">⠿</span><span class="rank">2</span>
|
||||
<div class="nm"><b>PAC banc</b><span>SG-Ready · chauffage</span></div>
|
||||
<span class="sw"></span>
|
||||
</div>
|
||||
<div class="cmd idle">
|
||||
<div class="l"><b>Décidé 0 W</b><small>état SG-Ready 2 appliqué</small><i>une seule décision</i></div>
|
||||
</div>
|
||||
<div class="lvls">
|
||||
<div class="lvl ok">
|
||||
<span class="tag eco">ÉCO</span>
|
||||
<div class="bd">
|
||||
<b>Plancher atteint</b>
|
||||
<span>3,0 kWh livrés sur 3,0 <span class="pill meas">MESURÉ</span> — plus rien à servir en passe 1.</span>
|
||||
<div class="bar"><i style="width:100%;background:var(--green)"></i></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="lvl wait">
|
||||
<span class="tag cft">CONFORT</span>
|
||||
<div class="bd"><b>En attente</b><span>Aucun surplus ce cycle.</span></div>
|
||||
</div>
|
||||
</div>
|
||||
<button class="cfg">⚙ Configurer</button>
|
||||
</div>
|
||||
|
||||
<div class="load">
|
||||
<div class="r1">
|
||||
<span class="grip">⠿</span><span class="rank">3</span>
|
||||
<div class="nm"><b>V2C Trydan</b><span>Borne de recharge · VE</span></div>
|
||||
<span class="sw"></span>
|
||||
</div>
|
||||
<div class="cmd">
|
||||
<div class="l"><b>Décidé 2 600 W</b><i>une seule décision</i></div>
|
||||
<div class="round">↳ appliqué au mécanisme : <b>11 A · 1 phase ≈ 2 530 W</b> — le courant est
|
||||
entier, la décision ne l'est pas</div>
|
||||
</div>
|
||||
<div class="lvls">
|
||||
<div class="lvl grid">
|
||||
<span class="tag eco">ÉCO</span>
|
||||
<div class="bd">
|
||||
<b>Plancher servi — 2 600 W achetés au réseau</b>
|
||||
<span>Avancement <em>non mesurable</em> : cette borne ne publie pas d'énergie de session.
|
||||
<span class="pill nm">NON MESURABLE</span></span>
|
||||
<div class="bar dash"></div>
|
||||
<span>L'obligation est servie ; elle ne pourra jamais être annoncée <em>tenue</em>.</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="lvl wait">
|
||||
<span class="tag cft">CONFORT</span>
|
||||
<div class="bd"><b>En attente</b><span>Aucun surplus ce cycle.</span></div>
|
||||
</div>
|
||||
</div>
|
||||
<button class="cfg">⚙ Configurer</button>
|
||||
</div>
|
||||
|
||||
<div class="load">
|
||||
<div class="r1">
|
||||
<span class="grip">⠿</span><span class="rank">4</span>
|
||||
<div class="nm"><b>Plancher chauffant</b><span>Charge modulable · chauffage</span></div>
|
||||
<span class="sw"></span>
|
||||
</div>
|
||||
<div class="cmd idle">
|
||||
<div class="l"><b>Décidé 0 W</b><i>une seule décision</i></div>
|
||||
</div>
|
||||
<div class="lvls">
|
||||
<div class="lvl no">
|
||||
<span class="tag eco">ÉCO</span>
|
||||
<div class="bd">
|
||||
<b>Plancher NON servi — plafond de soutirage atteint</b>
|
||||
<span>Plafond imposé par le <em>gestionnaire de réseau</em> (4 000 W), épuisé au rang 3.
|
||||
Ce n'est pas la protection de surcharge de l'installation.</span>
|
||||
<span>0,4 kWh livrés sur 2,0 <span class="pill meas">MESURÉ</span> — obligation reportée.</span>
|
||||
<div class="bar"><i style="width:20%;background:var(--red)"></i></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="lvl wait">
|
||||
<span class="tag cft">CONFORT</span>
|
||||
<div class="bd"><b>En attente</b><span>Aucun surplus ce cycle.</span></div>
|
||||
</div>
|
||||
</div>
|
||||
<button class="cfg">⚙ Configurer</button>
|
||||
</div>
|
||||
|
||||
<div class="load">
|
||||
<div class="r1">
|
||||
<span class="grip">⠿</span><span class="rank">5</span>
|
||||
<div class="nm"><b>Sèche-serviettes</b><span>Routeur de relais · 1 relais</span></div>
|
||||
<span class="sw"></span>
|
||||
</div>
|
||||
<div class="cmd idle">
|
||||
<div class="l"><b>Décidé 0 W</b><i>une seule décision</i></div>
|
||||
</div>
|
||||
<div class="lvls">
|
||||
<div class="lvl mute">
|
||||
<span class="tag cft">CONFORT</span>
|
||||
<div class="bd">
|
||||
<b>Pas demandeur</b>
|
||||
<span>22,4 °C mesurés ≥ 22 °C de consigne — le bonus n'est plus utile.</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="lvl mute" style="padding-left:14px;border-top:1px dashed var(--line)">
|
||||
<div class="bd"><span>Aucune obligation éco déclarée : cette charge ne participe qu'à la passe 2 —
|
||||
c'est exactement le comportement d'aujourd'hui.</span></div>
|
||||
</div>
|
||||
<button class="cfg">⚙ Configurer</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="cap">Écran 1 — <b>liste au rang</b>, une entrée par charge</div>
|
||||
<div class="note">
|
||||
<b>Ce qui ne change pas :</b> le glisser-déposer produit toujours une suite d'entiers <code>priority</code> à plat,
|
||||
et il n'existe qu'un rang. La maquette est un <b>rejet appliqué</b> de <code>prioritéEco</code>/<code>prioritéConfort</code>.
|
||||
<ul>
|
||||
<li>Les deux lignes de niveau vivent <b>sous</b> la ligne de commande, décalées et rattachées par un filet :
|
||||
la commande est le fait, les niveaux en sont la décomposition. C'est ce qui rend LM-1003 lisible sans
|
||||
l'écrire.</li>
|
||||
<li><b>L'écart affiché est décidé ↔ appliqué</b>, jamais <code>Σ targetW</code> ↔ <code>allocatedW</code> :
|
||||
ces deux-là sont <b>égaux par construction</b>, tous deux du décidé. Un écran bâti sur la prémisse
|
||||
inverse comparerait deux valeurs toujours égales — il ne montrerait jamais rien, <b>et sans
|
||||
erreur</b>. L'appliqué se lit dans la charge utile de mécanisme (<code>stageW</code>,
|
||||
<code>setpointW</code>, <code>currentA</code>), qui existe déjà aujourd'hui.</li>
|
||||
<li><b>Et il ne se répartit pas non plus.</b> L'écart appartient à la charge, pas à un niveau :
|
||||
l'adaptateur arrondit une consigne unique, il ne sait pas de quel besoin elle venait.</li>
|
||||
<li>Le bandeau ◆ n'apparaît <b>que si au moins une charge porte une obligation éco</b>. Sans obligation,
|
||||
il n'y a qu'une passe utile et la phrase serait du bruit (LM-1007).</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ══════════ ÉCRAN 2 — l'ordre de service ══════════ -->
|
||||
<div class="col">
|
||||
<div class="phone">
|
||||
<div class="appbar"><div class="ic">‹</div><h2>Ordre de service</h2><div class="ic">⟳</div></div>
|
||||
<div class="scroll">
|
||||
|
||||
<div class="hint">
|
||||
<b>Ce que le moteur a fait, dans l'ordre</b>
|
||||
Cycle de 06:41 · lecture seule. Une charge apparaît <em>deux fois</em> — une par passe : ici l'objet
|
||||
affiché est la <b>séquence</b>, pas la charge. Votre rang décide de l'ordre <em>à l'intérieur</em> de
|
||||
chaque passe, jamais entre les deux.
|
||||
</div>
|
||||
|
||||
<div class="pass p1">
|
||||
<div class="ph"><span class="n">1</span><b>PLANCHERS ÉCO</b><i>4 000 W autorisés au réseau</i></div>
|
||||
|
||||
<div class="srv g">
|
||||
<span class="rk">1</span>
|
||||
<div class="bd"><b>Chauffe-eau</b><span>2,1 / 3,0 kWh · au réseau</span></div>
|
||||
<div class="w">1 400 W<u>reste 2 600</u></div>
|
||||
</div>
|
||||
|
||||
<div class="srv nil">
|
||||
<span class="rk">2</span>
|
||||
<div class="bd"><b>PAC banc</b><span>Plancher déjà atteint — rien à servir</span></div>
|
||||
<div class="w">0 W<u>reste 2 600</u></div>
|
||||
</div>
|
||||
|
||||
<div class="srv g">
|
||||
<span class="rk">3</span>
|
||||
<div class="bd"><b>V2C Trydan</b><span>Avancement non mesurable · au réseau</span></div>
|
||||
<div class="w">2 600 W<u>reste 0</u></div>
|
||||
</div>
|
||||
|
||||
<div class="exh"><b>⚠ Autorisation de soutirage épuisée ici</b> — plafond du gestionnaire de réseau
|
||||
(4 000 W). Les charges suivantes ne verront pas leur plancher ce cycle.</div>
|
||||
|
||||
<div class="srv cut">
|
||||
<span class="rk">4</span>
|
||||
<div class="bd"><b>Plancher chauffant</b><span>0,4 / 2,0 kWh · obligation reportée</span></div>
|
||||
<div class="w">refusé<u>plafond GRD</u></div>
|
||||
</div>
|
||||
|
||||
<div class="srv nil">
|
||||
<span class="rk">5</span>
|
||||
<div class="bd"><b>Sèche-serviettes</b><span>Aucune obligation déclarée</span></div>
|
||||
<div class="w">—<u>hors passe 1</u></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rule">puis, sur le reliquat, et seulement là</div>
|
||||
|
||||
<div class="pass p2">
|
||||
<div class="ph"><span class="n">2</span><b>CONFORT</b><i>0 W de surplus</i></div>
|
||||
|
||||
<div class="exh" style="border-color:rgba(255,255,255,.18)">
|
||||
<b style="color:var(--muted)">Aucun surplus ce cycle</b> — la passe 2 a été parcourue en entier et n'a
|
||||
rien pu servir. Ce n'est pas un refus par charge : c'est une passe sans budget.
|
||||
</div>
|
||||
|
||||
<div class="srv nil"><span class="rk">1</span>
|
||||
<div class="bd"><b>Chauffe-eau</b><span>Demandeur · en attente</span></div><div class="w">0 W<u>reste 0</u></div></div>
|
||||
<div class="srv nil"><span class="rk">2</span>
|
||||
<div class="bd"><b>PAC banc</b><span>Demandeur · en attente</span></div><div class="w">0 W<u>reste 0</u></div></div>
|
||||
<div class="srv nil"><span class="rk">3</span>
|
||||
<div class="bd"><b>V2C Trydan</b><span>Demandeur · en attente</span></div><div class="w">0 W<u>reste 0</u></div></div>
|
||||
<div class="srv nil"><span class="rk">4</span>
|
||||
<div class="bd"><b>Plancher chauffant</b><span>Demandeur · en attente</span></div><div class="w">0 W<u>reste 0</u></div></div>
|
||||
<div class="srv nil"><span class="rk">5</span>
|
||||
<div class="bd"><b>Sèche-serviettes</b><span>Pas demandeur — 22,4 °C ≥ 22 °C</span></div>
|
||||
<div class="w">—<u>sauté</u></div></div>
|
||||
</div>
|
||||
|
||||
<div class="hint" style="margin-top:20px;border-color:rgba(157,140,224,.3);background:rgba(157,140,224,.07)">
|
||||
<b style="color:var(--violet)">Pourquoi le rang 4 passe avant le rang 1</b>
|
||||
Le rang classe les <em>charges</em>. La hiérarchie éco/confort classe les <em>besoins</em>, et elle
|
||||
surplombe le rang : un plancher est une obligation, un confort est un bonus. Aucune charge ne double
|
||||
une autre <em>à niveau égal</em> — et c'est exactement ce que cet écran donne à vérifier, ligne à ligne.
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="cap">Écran 2 — <b>séquence</b>, une charge deux fois</div>
|
||||
<div class="note">
|
||||
<b>Écran dérivé, sans champ nouveau.</b> La séquence se reconstruit entièrement côté app par un tri
|
||||
lexicographique sur <code>(niveau, priority)</code> : <b>ne publiez pas d'index d'ordre</b>, il serait une
|
||||
seconde source de vérité pour une donnée déjà déductible.
|
||||
<ul>
|
||||
<li>Les compteurs « reste N » de chaque ligne sont un <b>cumul local</b> — ils exigent seulement que
|
||||
chaque niveau publie ce qu'il a pris <em>et</em> son financement.</li>
|
||||
<li>Le point de rupture (« autorisation épuisée ici ») est <b>lu</b>, jamais déduit d'un solde à zéro :
|
||||
il vient du motif <code>DRAW_CAP</code> de la première charge refusée, avec sa <b>source</b>.</li>
|
||||
<li>Cet écran s'ouvre depuis le bandeau ◆ et depuis chaque carte ; il n'a aucun réglage, donc aucun
|
||||
risque de devenir un second endroit où l'on classe.</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ══════════ ÉCRAN 3 — catalogue d'états ══════════ -->
|
||||
<div class="col">
|
||||
<div class="phone">
|
||||
<div class="appbar"><div class="ic">‹</div><h2>États d'un niveau</h2><div class="ic">?</div></div>
|
||||
<div class="scroll">
|
||||
|
||||
<div class="hint">
|
||||
<b>Le vocabulaire, et il est fermé</b>
|
||||
Chaque niveau d'une charge est <em>toujours</em> dans un de ces états, et chacun est un état
|
||||
<b>positif</b> — jamais « à moitié servie », jamais un pourcentage d'avancement.
|
||||
</div>
|
||||
|
||||
<div class="sect">Niveau ÉCO <small>· présent seulement si la charge porte une obligation</small></div>
|
||||
|
||||
<div class="cat"><div class="h"><span class="tag eco">ÉCO</span><b>Plancher servi</b><code>ECO_FLOOR_*</code></div>
|
||||
<div class="txt">« Plancher servi — 1 400 W pris sur le surplus »<br>
|
||||
« Plancher servi — 1 400 W achetés au réseau »</div>
|
||||
<div class="why">Deux phrases, parce que ce sont deux faits différents pour le client : l'un est gratuit,
|
||||
l'autre est une facture. Le financement se lit sur le <b>niveau</b>, pas sur la charge.</div></div>
|
||||
|
||||
<div class="cat"><div class="h"><span class="tag eco">ÉCO</span><b>Plancher atteint</b><code>ECO_FLOOR_MET</code></div>
|
||||
<div class="txt">« Plancher atteint — 3,0 / 3,0 kWh livrés »</div>
|
||||
<div class="why">Le seul état qui <b>ferme</b> l'obligation. Interdit sous le régime « non mesurable » :
|
||||
on ne peut pas annoncer tenu ce qu'on ne mesure pas (LM-1009 C1).</div></div>
|
||||
|
||||
<div class="cat"><div class="h"><span class="tag eco">ÉCO</span><b>Non servi — plafond</b><code>DRAW_CAP</code></div>
|
||||
<div class="txt">« Plafond de soutirage atteint — <em>gestionnaire de réseau</em> »<br>
|
||||
« … — <em>protection du branchement</em> » · « … — <em>limite que vous vous imposez</em> »</div>
|
||||
<div class="why">Trois sources, trois phrases, trois gestes : subir et se renseigner, délester ou revoir
|
||||
l'abonnement, ou changer un réglage. Un motif qui les confond envoie diagnostiquer le mauvais
|
||||
problème.</div>
|
||||
<div class="why" style="border-top:1px dashed rgba(255,255,255,.1);padding-top:5px;margin-top:6px">
|
||||
<b style="color:var(--yellow)">Sa frontière</b> : <code>DRAW_CAP</code> ne sort que si
|
||||
l'autorisation résiduelle est <b>nulle</b>. S'il en reste mais sous le plus petit palier, le motif
|
||||
est <code>BELOW_MIN_POWER</code> — il y en a, pas au bon format. Deux gestes : revoir le plafond,
|
||||
ou lire la fiche technique.</div></div>
|
||||
|
||||
<div class="cat"><div class="h"><span class="tag eco">ÉCO</span><b>Avancement non mesurable</b><code>regime</code></div>
|
||||
<div class="txt">« Servi · avancement non mesurable » — barre <em>hachurée</em>, aucun chiffre livré.</div>
|
||||
<div class="why">Un régime, pas un motif : les trois régimes coexistent dans le même cycle sur des
|
||||
charges différentes (LM-1009 C2). <b>Zéro serait un mensonge</b> ; l'absence est la vérité.</div></div>
|
||||
|
||||
<div class="sect">Niveau CONFORT <small>· toujours présent</small></div>
|
||||
|
||||
<div class="cat"><div class="h"><span class="tag cft">CONFORT</span><b>Servi</b><code>SURPLUS_SETPOINT</code></div>
|
||||
<div class="txt">« Bonus servi — 2 100 W de surplus »</div>
|
||||
<div class="why">L'état d'aujourd'hui, inchangé. Une charge sans obligation éco n'a jamais que cette
|
||||
ligne : le modèle à deux niveaux dégrade proprement vers l'actuel.</div></div>
|
||||
|
||||
<div class="cat"><div class="h"><span class="tag cft">CONFORT</span><b>En attente</b><code>SURPLUS_INSUFFICIENT</code></div>
|
||||
<div class="txt">« En attente — aucun surplus ce cycle »<br>« En attente — reliquat épuisé au rang 2 »</div>
|
||||
<div class="why">La charge <em>veut</em> son bonus et ne l'a pas. Un état de file d'attente, pas un
|
||||
échec — et surtout pas « 0 W » tout seul, qui ne dit pas s'il y avait une demande.</div></div>
|
||||
|
||||
<div class="cat"><div class="h"><span class="tag cft">CONFORT</span><b>Pas demandeur</b><code>COMFORT_SKIPPED</code></div>
|
||||
<div class="txt">« Pas demandeur — 22,4 °C ≥ 22 °C de consigne »</div>
|
||||
<div class="why">Rien ne manque, rien n'attend : le comparateur de LM-1008 est satisfait. <b>À ne jamais
|
||||
confondre avec « en attente »</b> — l'un se règle en changeant la consigne, l'autre en attendant le
|
||||
soleil.</div></div>
|
||||
|
||||
<div class="cat" style="border-color:rgba(49,163,221,.3);background:rgba(49,163,221,.05)">
|
||||
<div class="h"><span class="tag" style="background:rgba(49,163,221,.16);color:var(--blue)">×3</span>
|
||||
<b>Trois nombres, et jamais le même sens</b></div>
|
||||
<div class="txt">
|
||||
<b>Décidé</b> <code>allocatedW</code> — ce que l'arbitre a planifié, et ce que les compteurs de
|
||||
budget comptent.<br>
|
||||
<b>Appliqué</b> <code>mechanism.*</code> — ce que l'adaptateur a su produire.<br>
|
||||
<b>Mesuré</b> <code>measurement.powerW</code> — ce que la charge tire, quand c'est mesurable.
|
||||
</div>
|
||||
<div class="why">Chaque écart dit autre chose : <b>décidé ↔ appliqué</b> = l'arrondi du mécanisme,
|
||||
normal et permanent ; <b>appliqué ↔ mesuré</b> = la charge n'obéit pas (et ce que ça prouve dépend
|
||||
de <code>measurement.source</code>) ; <b>décidé ↔ mesuré</b> = la somme des deux, à ne jamais lire
|
||||
seule — elle mélange une cause bénigne et une cause qui ne l'est pas.</div>
|
||||
</div>
|
||||
|
||||
<div class="never">
|
||||
<b>✖ Ce que l'écran n'affichera jamais</b>
|
||||
<ul>
|
||||
<li>Un <b>pourcentage</b> d'avancement d'obligation — l'énergie livrée, ou rien (LM-1009 D1).</li>
|
||||
<li>Une charge <b>« servie à moitié »</b> : deux niveaux, deux états nommés.</li>
|
||||
<li><b>Deux consignes</b> pour une charge : les deux lignes décomposent une commande unique.</li>
|
||||
<li>Un <b>zéro</b> à la place d'une mesure absente.</li>
|
||||
<li>Un <b>second classement</b> à régler.</li>
|
||||
<li>Un écart entre <b><code>Σ targetW</code> et <code>allocatedW</code></b> : ils sont égaux par
|
||||
construction. Cherché là, l'arrondi est invisible et l'écran se tait sans se tromper.</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="cap">Écran 3 — <b>catalogue</b> des états de niveau</div>
|
||||
<div class="note">
|
||||
<b>Pourquoi un catalogue plutôt qu'une échelle :</b> « éco servi / confort non » est le cas le plus fréquent
|
||||
en régime normal. S'il s'affiche comme une <em>fraction</em> de quelque chose, il se lira comme une
|
||||
dégradation permanente. Nommé, il se lit comme le fonctionnement nominal — <b>le plancher est tenu, le bonus
|
||||
attend le soleil</b>.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- ══════════════════ BRIEF MOTEUR ══════════════════ -->
|
||||
<div class="brief">
|
||||
<h2>Ce que ces écrans demandent à <code>GetLoadTelemetry</code></h2>
|
||||
<p>Aucun de ces trois écrans n'est constructible sur la charge utile d'aujourd'hui : un <code>allocatedW</code> et
|
||||
un <code>decision</code> par charge, un seul de chaque. Voici la forme minimale qui les rend possibles — elle
|
||||
ajoute <b>un tableau</b> et ne retire rien.</p>
|
||||
|
||||
<pre>{
|
||||
<span class="k">"loadId"</span>: "…", <span class="k">"available"</span>: true, <span class="k">"measurement"</span>: { … }, <span class="c">// inchangés</span>
|
||||
|
||||
<span class="k">"allocatedW"</span>: <span class="n">1400</span>, <span class="c">// INCHANGÉ — le DÉCIDÉ du cycle. Σ targetW == allocatedW, exactement.</span>
|
||||
<span class="c">// L'appliqué vit dans "mechanism" ; le mesuré dans "measurement".</span>
|
||||
|
||||
<span class="k">"levels"</span>: [ <span class="c">// AJOUT — un élément par passe RÉELLEMENT parcourue</span>
|
||||
{ <span class="k">"level"</span>: <span class="s">"eco"</span>,
|
||||
<span class="k">"targetW"</span>: <span class="n">1400</span>, <span class="c">// la part de ce décidé qui revient à ce niveau</span>
|
||||
<span class="k">"funding"</span>: <span class="s">"grid"</span>, <span class="c">// le financement descend au NIVEAU</span>
|
||||
<span class="k">"decision"</span>: { <span class="k">"code"</span>: <span class="s">"ECO_FLOOR_GRID"</span>,
|
||||
<span class="k">"params"</span>: { <span class="k">"deliveredWh"</span>: <span class="n">2100</span>, <span class="k">"targetWh"</span>: <span class="n">3000</span>, <span class="k">"gridW"</span>: <span class="n">1400</span> } },
|
||||
<span class="k">"progress"</span>: { <span class="k">"regime"</span>: <span class="s">"measured"</span>, <span class="c">// measured | estimated | unmeasurable</span>
|
||||
<span class="k">"deliveredWh"</span>: <span class="n">2100</span>, <span class="k">"targetWh"</span>: <span class="n">3000</span> } }, <span class="c">// deliveredWh ABSENT si unmeasurable</span>
|
||||
{ <span class="k">"level"</span>: <span class="s">"comfort"</span>,
|
||||
<span class="k">"targetW"</span>: <span class="n">0</span>,
|
||||
<span class="k">"decision"</span>: { <span class="k">"code"</span>: <span class="s">"SURPLUS_INSUFFICIENT"</span>, <span class="k">"params"</span>: { <span class="k">"requiredW"</span>: <span class="n">900</span>, <span class="k">"availableW"</span>: <span class="n">0</span> } } }
|
||||
],
|
||||
|
||||
<span class="k">"decision"</span>: { <span class="k">"level"</span>: <span class="s">"eco"</span>, <span class="k">"code"</span>: <span class="s">"ECO_FLOOR_GRID"</span>, <span class="k">"params"</span>: { … } } <span class="c">// CONSERVÉ + le niveau reflété</span>
|
||||
}
|
||||
|
||||
<span class="c">// et, une fois par cycle, à côté de "budget" :</span>
|
||||
<span class="k">"draw"</span>: { <span class="k">"authorisedW"</span>: <span class="n">4000</span>, <span class="k">"committedW"</span>: <span class="n">4000</span>, <span class="k">"remainingW"</span>: <span class="n">0</span>,
|
||||
<span class="k">"binding"</span>: <span class="s">"gridOperator"</span>, <span class="k">"perPhaseBound"</span>: false }</pre>
|
||||
|
||||
<h3>Huit exigences, et chacune tient un endroit précis de la maquette</h3>
|
||||
|
||||
<div class="req"><b><u>R1</u>`levels[]` ne contient que les passes réellement parcourues</b>
|
||||
<p>Une charge sans obligation éco porte <b>une seule entrée</b>, <code>comfort</code> — pas une entrée
|
||||
<code>eco</code> à zéro. L'absence est alors l'information (« ne participe qu'à la passe 2 », LM-1007), et un
|
||||
zéro forgé la détruirait. <code>levels[]</code> entièrement absent = box antérieure : l'app retombe sur
|
||||
<code>allocatedW</code>/<code>decision</code> et n'affiche ni bandeau ◆ ni écran 2.</p></div>
|
||||
|
||||
<div class="req hard"><b><u>R2</u>`funding` descend au niveau — <span style="color:var(--green)">✔ EN COURS côté moteur</span></b>
|
||||
<p>Aujourd'hui il est <em>par charge</em>. Au §10, la même charge peut être financée <b>au réseau pour son éco
|
||||
et au surplus pour son confort dans le même cycle</b> : un <code>funding</code> par charge devient
|
||||
indécidable. Or c'est lui qui porte notre identité de réconciliation (« somme des allocations financées au
|
||||
surplus == <code>budget.allocatedW</code> »). S'il reste à la charge, cette identité cesse d'être vérifiable
|
||||
sans que rien ne le signale.</p>
|
||||
<p><b>Si vous hiérarchisez, prenez celle-ci en premier</b> : c'est la seule des huit qui soit un
|
||||
<b>changement de forme d'un champ existant</b> et non un ajout. Chaque écran qui s'appuiera d'ici là sur
|
||||
un <code>funding</code> par charge en augmentera le coût de déplacement — les sept autres attendent sans
|
||||
se renchérir.</p>
|
||||
<p><span style="color:var(--green)"><b>Parti le 2026-08-29</b></span> — l'argument de coût a emporté la
|
||||
priorisation. C'est la première des huit à être écrite.</p></div>
|
||||
|
||||
<div class="req hard"><b><u>R3</u>Chaque watt publié tombe dans exactement un compteur, et le mapping est publié</b>
|
||||
<p>Trois destinations coexisteront : <code>budget.allocatedW</code> (surplus), <code>budget.evReservedW</code>
|
||||
(réseau, proxy VE) et <code>draw.committedW</code> (réseau, plancher éco). <b>Dites où va chaque
|
||||
(niveau, financement)</b> — l'app ne doit pas le deviner, sinon deux clients afficheront deux bilans
|
||||
différents du même cycle. C'est la leçon d'<code>EV_GRID_START</code>, qui partage une allocation entre deux
|
||||
compteurs et qui aurait cassé notre réconciliation en silence. <b>Tranché le 2026-08-29</b> : les compteurs
|
||||
comptent le <b>décidé</b> — reste à publier la destination de chaque couple (niveau, financement).</p></div>
|
||||
|
||||
<div class="req"><b><u>R4</u>`Σ targetW == allocatedW` — une identité, pas un écart <span style="color:var(--green)">✔ tranché</span></b>
|
||||
<p><b>Corrigé le 2026-08-29 : notre prémisse était fausse.</b> <code>allocatedW</code> publie le
|
||||
<b>décidé</b>, pas le commandé — <code>buildTelemetry()</code> publie le plan, <code>applyActionsToAdapters()</code>
|
||||
reçoit le slot en <code>const</code>, et un test attend <code>allocatedW == 3000</code> sur une borne dont
|
||||
le courant commandé donne 2 990 W. Les niveaux et <code>allocatedW</code> sont donc <b>la même monnaie</b>,
|
||||
et leur somme est une <b>identité exacte</b> — c'est ce qui rend <code>levels[]</code> vérifiable contre un
|
||||
champ qui existe déjà. L'app traitera toute violation comme une incohérence de la box, pas comme un
|
||||
arrondi.</p>
|
||||
<p><b>Ce qui reste demandé</b> : ne pas attribuer l'appliqué à un niveau. L'écart vit entre
|
||||
<code>allocatedW</code> et la charge utile de mécanisme (<code>stageW</code>, <code>setpointW</code>,
|
||||
<code>currentA</code>) ; l'adaptateur arrondit une consigne unique et ne sait pas de quel besoin elle
|
||||
venait. Toute répartition serait une fiction — le principe n'a pas bougé, seulement l'endroit où l'écart
|
||||
se trouve.</p>
|
||||
<p><b>Et les compteurs de budget comptent le décidé</b>, parce que c'est la valeur qui détermine ce que
|
||||
reçoit la charge suivante. Conséquence d'écran, assumée : le budget d'un cycle peut annoncer 1 400 W
|
||||
pendant que l'installation en tire 1 000 — d'où le bloc « trois nombres » de l'écran 3, qui empêche de
|
||||
lire cet écart comme une erreur de comptage.</p></div>
|
||||
|
||||
<div class="req"><b><u>R5</u>Le régime de LM-1009 est porté par le NIVEAU éco, pas par la charge</b>
|
||||
<p><code>progress.regime ∈ measured | estimated | unmeasurable</code>, et <code>deliveredWh</code>
|
||||
<b>absent</b> sous <code>unmeasurable</code> — jamais 0. C'est la même règle que
|
||||
<code>measurement.source = "none"</code>, qui nous a déjà servis : un régime publié sépare « pas mesurable »
|
||||
de « rien reçu », et c'est lui qui autorise la barre hachurée plutôt qu'une barre vide.</p></div>
|
||||
|
||||
<div class="req hard"><b><u>R6</u>`ECO_FLOOR_MET` interdit sous le régime `unmeasurable`</b>
|
||||
<p>Une obligation non mesurable peut être <b>servie</b>, jamais annoncée <b>tenue</b> (LM-1009 C1). C'est une
|
||||
garde testable côté moteur — et côté app, la combinaison sera traitée comme une <b>incohérence de la box</b>,
|
||||
affichée comme telle, pas rendue. Le cas est réel dès aujourd'hui : la V2C Trydan ne publie pas
|
||||
<code>sessionEnergy</code>.</p></div>
|
||||
|
||||
<div class="req"><b><u>R7</u>`decision` legacy conservé, avec le niveau qu'il reflète</b>
|
||||
<p>Ajoutez <code>decision.level</code>. Sans lui, chaque client réinvente sa propre règle de fusion (« le
|
||||
motif du dernier niveau servi ? du premier refusé ? ») et deux écrans affichent deux motifs différents pour le
|
||||
même cycle. Une clé de plus, et la règle cesse d'être devinée.</p></div>
|
||||
|
||||
<div class="req"><b><u>R8</u>`DRAW_CAP` publie sa source, et l'app en fait trois phrases</b>
|
||||
<p>Déjà prévu au <code>DESIGN_10.md</code> §6, on le confirme comme <b>condition d'écran</b> : c'est la source
|
||||
qui décide du texte, et les trois textes appellent trois gestes différents. Le point de rupture de l'écran 2
|
||||
est lu sur ce motif, jamais déduit d'un solde tombé à zéro.</p></div>
|
||||
|
||||
<h3>Ce dont nous n'avons PAS besoin — et qu'il ne faut pas fabriquer</h3>
|
||||
<ul class="no">
|
||||
<li>Un <b>index d'ordre de service</b> (<code>servedIndex</code>, <code>order</code>…) : la séquence de
|
||||
l'écran 2 se dérive entièrement d'un tri lexicographique sur <code>(niveau, priority)</code>. Un index
|
||||
publié serait une seconde source de vérité pour une donnée déductible — et diverger un jour.</li>
|
||||
<li>Un <b>second rang</b>, sous quelque nom que ce soit. La maquette est construite pour rendre le rang unique
|
||||
lisible <em>malgré</em> les deux passes ; elle n'a de sens que si LM-1002 tient.</li>
|
||||
<li>Une <b>phrase</b> composée par la box : les motifs restent des codes à catalogue fermé avec paramètres
|
||||
numériques, le rendu reste dans <code>services/telemetry_text.dart</code>.</li>
|
||||
<li>Un <b>pourcentage</b> d'avancement, même « à titre indicatif ». D1 est appliqué sans exception.</li>
|
||||
</ul>
|
||||
|
||||
<h3>La précédence des motifs — <b>tranchée</b> (Patrick, 2026-08-29)</h3>
|
||||
<p>Autorisation résiduelle <b>non nulle mais inférieure au plus petit palier</b> du mécanisme :
|
||||
<code>BELOW_MIN_POWER</code>. Les deux motifs sont vrais, mais pas au même niveau —
|
||||
<code>DRAW_CAP</code> dit <em>« il n'y a plus d'autorisation »</em>, or ici il en reste, simplement
|
||||
<b>pas au bon format</b>. C'est exactement la distinction posée en <code>+etm19</code> entre
|
||||
<code>SURPLUS_INSUFFICIENT</code> (« il n'y a rien ») et <code>BELOW_MIN_POWER</code> (« il y en a,
|
||||
mais pas au bon format »), transposée telle quelle au soutirage. Le geste utile est de lire la fiche
|
||||
technique du mécanisme, pas de revoir le plafond.</p>
|
||||
<p><code>DRAW_CAP</code> reste réservé à <b>l'autorisation résiduelle nulle</b>. Le point de rupture de
|
||||
l'écran 2 (« autorisation épuisée ici ») est donc bien un <code>DRAW_CAP</code> ; une charge refusée
|
||||
<em>après</em> lui pour cause de palier porterait <code>BELOW_MIN_POWER</code> et ne déplacerait pas le
|
||||
point de rupture. La règle est écrite par le moteur, pas établie de chaque côté.</p>
|
||||
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
Loading…
x
Reference in New Issue
Block a user