From 6f198636207674de8a6d53dd0774b5deb946d552 Mon Sep 17 00:00:00 2001 From: Patrick Schurig Date: Sun, 30 Aug 2026 08:10:31 +0200 Subject: [PATCH] =?UTF-8?q?docs(=C2=A710):=20r=C3=A9ponse=20au=20=C2=A79?= =?UTF-8?q?=20=E2=80=94=20deux=20passes,=20une=20commande,=20et=20la=20for?= =?UTF-8?q?me=20qui=20la=20publie?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) Claude-Session: https://claude.ai/code/session_01GKM8FDNHZWogAsjjfB8pSw --- docs/BRIEF_plugin_depuis_maquettes.md | 152 ++++ .../deux_passes_eco_confort_mockup.html | 760 ++++++++++++++++++ 2 files changed, 912 insertions(+) create mode 100644 docs/mockups/deux_passes_eco_confort_mockup.html diff --git a/docs/BRIEF_plugin_depuis_maquettes.md b/docs/BRIEF_plugin_depuis_maquettes.md index 137acfa..1414cc5 100644 --- a/docs/BRIEF_plugin_depuis_maquettes.md +++ b/docs/BRIEF_plugin_depuis_maquettes.md @@ -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 : +(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 diff --git a/docs/mockups/deux_passes_eco_confort_mockup.html b/docs/mockups/deux_passes_eco_confort_mockup.html new file mode 100644 index 0000000..c934f97 --- /dev/null +++ b/docs/mockups/deux_passes_eco_confort_mockup.html @@ -0,0 +1,760 @@ + + + + + +ETM PowerSync — §10 : deux passes, une commande + + + + + + + +
+

§10 — Éco et confort : deux passes, deux lectures, une seule commande

+

Réponse de l'app aux trois questions du brief plugin docs/BRIEF_depuis_plugin.md §9 + (= DESIGN_10.md 8.5, LM-1002 / LM-1003 / LM-1006 couplage 2 / LM-1009). Rien n'est câblé côté app : + cette maquette est le brief, et la charge utile demandée est en bas de page.

+

Elle 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 : aucun de ces trois écrans ne se construit avant que + levels[] soit en ligne. Ce qu'elle fixe, c'est la forme de ce qui sera publié — pour + que le moteur n'ait pas à inventer une charge utile que l'écran devra ensuite arranger.

+ +
+

1 · La liste reste ordonnée par RANG — parce que c'est la surface d'édition du rang, et parce que + l'ordre de service n'est pas une propriété de la configuration : il change à chaque cycle selon les + planchers déjà atteints. Une liste qui se réordonne toute seule sous le doigt est inutilisable.

+

2 · Une seule entrée par charge dans la liste, deux lignes DANS la carte — la commande est le parent, + les deux niveaux sont sa décomposition. On ne dédouble que là où l'objet affiché est la séquence, + jamais là où l'objet est la charge.

+

3 · L'ordre de service devient un second écran, en lecture seule — 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 + votre ordre est respecté dans chaque passe. C'est l'écran qui désamorce « l'ordre n'est pas respecté ».

+
+
+ +
+ + +
+
+
‹

Rôles & appareils

⟳
+
+ +
Arbitrage en direct · cycle de 06:41, il y a 22 s
+
+
Matin sans soleil2 comptabilités
+ +
■Passe 2 — surplus disponible
+
+
Surplus0
+
Réservé VE0
+
Recrédité0
+
Alloué0
+
Reliquat0
+
+ +
■Passe 1 — autorisation de soutirage
+
+
Autorisé4 000
+
Engagé4 000
+
Restant0
+
Borné parGRD
+
+ +
+ ◆ Deux passes ce cycle + Tous les planchers éco sont servis d'abord, par rang croissant ; le confort ensuite, + dans le même ordre. Un plancher de rang 4 passe donc avant un confort de rang 1 — + c'est voulu, et votre ordre est respecté à l'intérieur de chaque passe. + Voir l'ordre de service de ce cycle › +
+
+ +
Charges pilotables · ordonnées par rang — glissez pour classer
+ + +
+
+ ⠿1 +
Chauffe-eauRouteur de relais · 3 relais · ECS
+ +
+
+
Décidé 1 400 Wune seule décision
+
↳ appliqué au mécanisme : palier 1 000 W (relais 1+2) — l'adaptateur + n'a pas ce point exact
+
+
+
+ É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. +
+
+
+ +
+ +
+
+ ⠿2 +
PAC bancSG-Ready · chauffage
+ +
+
+
Décidé 0 Wétat SG-Ready 2 appliquéune seule décision
+
+
+
+ ÉCO +
+ Plancher atteint + 3,0 kWh livrés sur 3,0 MESURÉ — plus rien à servir en passe 1. +
+
+
+
+ CONFORT +
En attenteAucun surplus ce cycle.
+
+
+ +
+ +
+
+ ⠿3 +
V2C TrydanBorne de recharge · VE
+ +
+
+
Décidé 2 600 Wune seule décision
+
↳ appliqué au mécanisme : 11 A · 1 phase ≈ 2 530 W — le courant est + entier, la décision ne l'est pas
+
+
+
+ ÉCO +
+ Plancher servi — 2 600 W achetés au réseau + Avancement non mesurable : cette borne ne publie pas d'énergie de session. + NON MESURABLE +
+ L'obligation est servie ; elle ne pourra jamais être annoncée tenue. +
+
+
+ CONFORT +
En attenteAucun surplus ce cycle.
+
+
+ +
+ +
+
+ ⠿4 +
Plancher chauffantCharge modulable · chauffage
+ +
+
+
Décidé 0 Wune seule décision
+
+
+
+ ÉCO +
+ Plancher NON servi — plafond de soutirage atteint + Plafond imposé par le gestionnaire de réseau (4 000 W), épuisé au rang 3. + Ce n'est pas la protection de surcharge de l'installation. + 0,4 kWh livrés sur 2,0 MESURÉ — obligation reportée. +
+
+
+
+ CONFORT +
En attenteAucun surplus ce cycle.
+
+
+ +
+ +
+
+ ⠿5 +
Sèche-serviettesRouteur de relais · 1 relais
+ +
+
+
Décidé 0 Wune seule décision
+
+
+
+ CONFORT +
+ Pas demandeur + 22,4 °C mesurés ≥ 22 °C de consigne — le bonus n'est plus utile. +
+
+
+
+
Aucune obligation éco déclarée : cette charge ne participe qu'à la passe 2 — + c'est exactement le comportement d'aujourd'hui.
+
+ +
+ +
+
+
Écran 1 — liste au rang, une entrée par charge
+
+ Ce qui ne change pas : le glisser-déposer produit toujours une suite d'entiers priority à plat, + et il n'existe qu'un rang. La maquette est un rejet appliqué de prioritéEco/prioritéConfort. +
    +
  • Les deux lignes de niveau vivent sous 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.
  • +
  • L'écart affiché est décidé ↔ appliqué, jamais Σ targetW ↔ allocatedW : + ces deux-là sont égaux par construction, tous deux du décidé. Un écran bâti sur la prémisse + inverse comparerait deux valeurs toujours égales — il ne montrerait jamais rien, et sans + erreur. L'appliqué se lit dans la charge utile de mécanisme (stageW, + setpointW, currentA), qui existe déjà aujourd'hui.
  • +
  • Et il ne se répartit pas non plus. L'écart appartient à la charge, pas à un niveau : + l'adaptateur arrondit une consigne unique, il ne sait pas de quel besoin elle venait.
  • +
  • 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).
  • +
+
+
+ + +
+
+
‹

Ordre de service

⟳
+
+ +
+ Ce que le moteur a fait, dans l'ordre + Cycle de 06:41 · lecture seule. Une charge apparaît deux fois — une par passe : ici l'objet + affiché est la séquence, pas la charge. Votre rang décide de l'ordre à l'intérieur de + chaque passe, jamais entre les deux. +
+ +
+
1PLANCHERS ÉCO4 000 W autorisés au réseau
+ +
+ 1 +
Chauffe-eau2,1 / 3,0 kWh · au réseau
+
1 400 Wreste 2 600
+
+ +
+ 2 +
PAC bancPlancher déjà atteint — rien à servir
+
0 Wreste 2 600
+
+ +
+ 3 +
V2C TrydanAvancement non mesurable · au réseau
+
2 600 Wreste 0
+
+ +
⚠ Autorisation de soutirage épuisée ici — plafond du gestionnaire de réseau + (4 000 W). Les charges suivantes ne verront pas leur plancher ce cycle.
+ +
+ 4 +
Plancher chauffant0,4 / 2,0 kWh · obligation reportée
+
refuséplafond GRD
+
+ +
+ 5 +
Sèche-serviettesAucune obligation déclarée
+
—hors passe 1
+
+
+ +
puis, sur le reliquat, et seulement là
+ +
+
2CONFORT0 W de surplus
+ +
+ Aucun surplus ce cycle — 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. +
+ +
1 +
Chauffe-eauDemandeur · en attente
0 Wreste 0
+
2 +
PAC bancDemandeur · en attente
0 Wreste 0
+
3 +
V2C TrydanDemandeur · en attente
0 Wreste 0
+
4 +
Plancher chauffantDemandeur · en attente
0 Wreste 0
+
5 +
Sèche-serviettesPas demandeur — 22,4 °C ≥ 22 °C
+
—sauté
+
+ +
+ Pourquoi le rang 4 passe avant le rang 1 + Le rang classe les charges. La hiérarchie éco/confort classe les besoins, et elle + surplombe le rang : un plancher est une obligation, un confort est un bonus. Aucune charge ne double + une autre à niveau égal — et c'est exactement ce que cet écran donne à vérifier, ligne à ligne. +
+ +
+
+
Écran 2 — séquence, une charge deux fois
+
+ Écran dérivé, sans champ nouveau. La séquence se reconstruit entièrement côté app par un tri + lexicographique sur (niveau, priority) : ne publiez pas d'index d'ordre, il serait une + seconde source de vérité pour une donnée déjà déductible. +
    +
  • Les compteurs « reste N » de chaque ligne sont un cumul local — ils exigent seulement que + chaque niveau publie ce qu'il a pris et son financement.
  • +
  • Le point de rupture (« autorisation épuisée ici ») est lu, jamais déduit d'un solde à zéro : + il vient du motif DRAW_CAP de la première charge refusée, avec sa source.
  • +
  • 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.
  • +
+
+
+ + +
+
+
‹

États d'un niveau

?
+
+ +
+ Le vocabulaire, et il est fermé + Chaque niveau d'une charge est toujours dans un de ces états, et chacun est un état + positif — jamais « à moitié servie », jamais un pourcentage d'avancement. +
+ +
Niveau ÉCO · présent seulement si la charge porte une obligation
+ +
ÉCOPlancher serviECO_FLOOR_*
+
« Plancher servi — 1 400 W pris sur le surplus »
+ « Plancher servi — 1 400 W achetés au réseau »
+
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 niveau, pas sur la charge.
+ +
ÉCOPlancher atteintECO_FLOOR_MET
+
« Plancher atteint — 3,0 / 3,0 kWh livrés »
+
Le seul état qui ferme l'obligation. Interdit sous le régime « non mesurable » : + on ne peut pas annoncer tenu ce qu'on ne mesure pas (LM-1009 C1).
+ +
ÉCONon servi — plafondDRAW_CAP
+
« Plafond de soutirage atteint — gestionnaire de réseau »
+ « … — protection du branchement » · « … — limite que vous vous imposez »
+
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.
+
+ Sa frontière : DRAW_CAP ne sort que si + l'autorisation résiduelle est nulle. S'il en reste mais sous le plus petit palier, le motif + est BELOW_MIN_POWER — il y en a, pas au bon format. Deux gestes : revoir le plafond, + ou lire la fiche technique.
+ +
ÉCOAvancement non mesurableregime
+
« Servi · avancement non mesurable » — barre hachurée, aucun chiffre livré.
+
Un régime, pas un motif : les trois régimes coexistent dans le même cycle sur des + charges différentes (LM-1009 C2). Zéro serait un mensonge ; l'absence est la vérité.
+ +
Niveau CONFORT · toujours présent
+ +
CONFORTServiSURPLUS_SETPOINT
+
« Bonus servi — 2 100 W de surplus »
+
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.
+ +
CONFORTEn attenteSURPLUS_INSUFFICIENT
+
« En attente — aucun surplus ce cycle »
« En attente — reliquat épuisé au rang 2 »
+
La charge veut 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.
+ +
CONFORTPas demandeurCOMFORT_SKIPPED
+
« Pas demandeur — 22,4 °C ≥ 22 °C de consigne »
+
Rien ne manque, rien n'attend : le comparateur de LM-1008 est satisfait. À ne jamais + confondre avec « en attente » — l'un se règle en changeant la consigne, l'autre en attendant le + soleil.
+ +
+
×3 + Trois nombres, et jamais le même sens
+
+ 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.
+ Mesuré measurement.powerW — ce que la charge tire, quand c'est mesurable. +
+
Chaque écart dit autre chose : décidé ↔ appliqué = l'arrondi du mécanisme, + normal et permanent ; appliqué ↔ mesuré = la charge n'obéit pas (et ce que ça 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 cause qui ne l'est pas.
+
+ +
+ ✖ Ce que l'écran n'affichera jamais +
    +
  • Un pourcentage d'avancement d'obligation — l'énergie livrée, ou rien (LM-1009 D1).
  • +
  • Une charge « servie à moitié » : deux niveaux, deux états nommés.
  • +
  • Deux consignes pour une charge : les deux lignes décomposent une commande unique.
  • +
  • Un zéro à la place d'une mesure absente.
  • +
  • Un second classement à régler.
  • +
  • Un écart entre Σ targetW et allocatedW : ils sont égaux par + construction. Cherché là, l'arrondi est invisible et l'écran se tait sans se tromper.
  • +
+
+ +
+
+
Écran 3 — catalogue des états de niveau
+
+ Pourquoi un catalogue plutôt qu'une échelle : « éco servi / confort non » est le cas le plus fréquent + en régime normal. S'il s'affiche comme une fraction de quelque chose, il se lira comme une + dégradation permanente. Nommé, il se lit comme le fonctionnement nominal — le plancher est tenu, le bonus + attend le soleil. +
+
+ +
+ + +
+

Ce que ces écrans demandent à GetLoadTelemetry

+

Aucun de ces trois écrans n'est constructible sur la charge utile d'aujourd'hui : un allocatedW et + un decision par charge, un seul de chaque. Voici la forme minimale qui les rend possibles — elle + ajoute un tableau et ne retire rien.

+ +
{
+  "loadId": "…", "available": true, "measurement": { … },   // inchangés
+
+  "allocatedW": 1400,        // INCHANGÉ — le DÉCIDÉ du cycle. Σ targetW == allocatedW, exactement.
+                              // L'appliqué vit dans "mechanism" ; le mesuré dans "measurement".
+
+  "levels": [                    // AJOUT — un élément par passe RÉELLEMENT parcourue
+    { "level":   "eco",
+      "targetW": 1400,        // la part de ce décidé qui revient à ce niveau
+      "funding": "grid",      // le financement descend au NIVEAU
+      "decision": { "code": "ECO_FLOOR_GRID",
+                    "params": { "deliveredWh": 2100, "targetWh": 3000, "gridW": 1400 } },
+      "progress": { "regime": "measured",   // measured | estimated | unmeasurable
+                    "deliveredWh": 2100, "targetWh": 3000 } },   // deliveredWh ABSENT si unmeasurable
+    { "level":   "comfort",
+      "targetW": 0,
+      "decision": { "code": "SURPLUS_INSUFFICIENT", "params": { "requiredW": 900, "availableW": 0 } } }
+  ],
+
+  "decision": { "level": "eco", "code": "ECO_FLOOR_GRID", "params": { … } }  // CONSERVÉ + le niveau reflété
+}
+
+// et, une fois par cycle, à côté de "budget" :
+"draw": { "authorisedW": 4000, "committedW": 4000, "remainingW": 0,
+          "binding": "gridOperator", "perPhaseBound": false }
+ +

Huit exigences, et chacune tient un endroit précis de la maquette

+ +
R1`levels[]` ne contient que les passes réellement parcourues +

Une charge sans obligation éco porte une seule entrée, comfort — pas une entrée + eco à zéro. L'absence est alors l'information (« ne participe qu'à la passe 2 », LM-1007), et un + zéro forgé la détruirait. levels[] entièrement absent = box antérieure : l'app retombe sur + allocatedW/decision et n'affiche ni bandeau ◆ ni écran 2.

+ +
R2`funding` descend au niveau — ✔ EN COURS côté moteur +

Aujourd'hui il est par charge. Au §10, la même charge peut être financée au réseau pour son éco + et au surplus pour son confort dans le même cycle : un funding par charge devient + indécidable. Or c'est lui qui porte notre identité de réconciliation (« somme des allocations financées au + surplus == budget.allocatedW »). S'il reste à la charge, cette identité cesse d'être vérifiable + sans que rien ne le signale.

+

Si vous hiérarchisez, prenez celle-ci en premier : c'est la seule des huit 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.

+

Parti le 2026-08-29 — l'argument de coût a emporté la + priorisation. C'est la première des huit à être écrite.

+ +
R3Chaque watt publié tombe dans exactement un compteur, et le mapping est publié +

Trois destinations coexisteront : budget.allocatedW (surplus), budget.evReservedW + (réseau, proxy VE) et draw.committedW (réseau, plancher éco). Dites où va chaque + (niveau, financement) — 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'EV_GRID_START, qui partage une allocation entre deux + compteurs et qui aurait cassé notre réconciliation en silence. Tranché le 2026-08-29 : les compteurs + comptent le décidé — reste à publier la destination de chaque couple (niveau, financement).

+ +
R4`Σ targetW == allocatedW` — une identité, pas un écart ✔ tranché +

Corrigé le 2026-08-29 : notre prémisse était fausse. allocatedW publie le + décidé, pas le commandé — buildTelemetry() publie le plan, applyActionsToAdapters() + reçoit le slot en const, et un test attend allocatedW == 3000 sur une borne dont + le courant commandé donne 2 990 W. Les niveaux et allocatedW sont donc la même monnaie, + et leur somme est une identité exacte — c'est ce qui rend levels[] 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.

+

Ce qui reste demandé : ne pas attribuer l'appliqué à un niveau. L'écart vit entre + allocatedW et la charge utile de mécanisme (stageW, setpointW, + currentA) ; 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.

+

Et les compteurs de budget comptent le décidé, 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.

+ +
R5Le régime de LM-1009 est porté par le NIVEAU éco, pas par la charge +

progress.regime ∈ measured | estimated | unmeasurable, et deliveredWh + absent sous unmeasurable — jamais 0. C'est la même règle que + measurement.source = "none", 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.

+ +
R6`ECO_FLOOR_MET` interdit sous le régime `unmeasurable` +

Une obligation non mesurable peut être servie, jamais annoncée tenue (LM-1009 C1). C'est une + garde testable côté moteur — et côté app, la combinaison sera traitée comme une incohérence de la box, + affichée comme telle, pas rendue. Le cas est réel dès aujourd'hui : la V2C Trydan ne publie pas + sessionEnergy.

+ +
R7`decision` legacy conservé, avec le niveau qu'il reflète +

Ajoutez decision.level. 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.

+ +
R8`DRAW_CAP` publie sa source, et l'app en fait trois phrases +

Déjà prévu au DESIGN_10.md §6, on le confirme comme condition d'écran : 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.

+ +

Ce dont nous n'avons PAS besoin — et qu'il ne faut pas fabriquer

+
    +
  • Un index d'ordre de service (servedIndex, order…) : la séquence de + l'écran 2 se dérive entièrement d'un tri lexicographique sur (niveau, priority). Un index + publié serait une seconde source de vérité pour une donnée déductible — et diverger 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.
  • +
  • Une phrase composée par la box : les motifs restent des codes à catalogue fermé avec paramètres + numériques, le rendu reste dans services/telemetry_text.dart.
  • +
  • Un pourcentage d'avancement, même « à titre indicatif ». D1 est appliqué 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 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. 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 (« autorisation épuisée ici ») est donc bien un DRAW_CAP ; une charge refusée + après lui pour cause de palier porterait BELOW_MIN_POWER et ne déplacerait pas le + point de rupture. La règle est écrite par le moteur, pas établie de chaque côté.

+ +
+ + +