diff --git a/docs/DESIGN_10.md b/docs/DESIGN_10.md index 554b798..b9cf9d0 100644 --- a/docs/DESIGN_10.md +++ b/docs/DESIGN_10.md @@ -229,7 +229,58 @@ une entrée de clôture (`testDecisionReasonRendering`). | Code | params | Quand | |---|---|---| -| `DRAW_CAP` | `capW`, `source`, `requiredW`, `o:phase` | Le plafond de soutirage refuse, et **`source` dit lequel** : raccordement, gestionnaire de réseau, ou auto-imposé. `phase` présent si c'est une phase qui borne. | +| `DRAW_CAP` | `capW`, `source`, `requiredW`, `o:phase` | Le plafond de soutirage refuse, et **`source` dit lequel** : raccordement, gestionnaire de réseau, ou auto-imposé. `phase` présent si c'est une phase qui borne. **Réservé à l'autorisation résiduelle NULLE** — voir la précédence ci-dessous. | + +**PRÉCÉDENCE `DRAW_CAP` ↔ `BELOW_MIN_POWER` — tranchée le 2026-08-29 (Patrick), sur une question +de l'agent app.** + +Autorisation résiduelle **non nulle mais inférieure au plus petit palier** du mécanisme : +**`BELOW_MIN_POWER`**. Les deux motifs sont vrais, mais pas au même niveau. + +`DRAW_CAP` dit « **il n'y a plus d'autorisation** ». Or il en reste — simplement **pas au bon +format**. C'est exactement la distinction posée en `+etm19` entre `SURPLUS_INSUFFICIENT` (« il +n'y a rien ») et `BELOW_MIN_POWER` (« il y en a, mais pas au bon format »), **transposée telle +quelle au soutirage** : ce n'est pas une règle nouvelle, c'est la même appliquée à une seconde +ressource. + +**Ce qui la décide est le GESTE qu'elle appelle** — la règle 7-b : un motif faux envoie +diagnostiquer le mauvais problème, avec l'autorité d'une explication. + +| Motif | Ce qu'il dit | Le geste utile | +|---|---|---| +| `DRAW_CAP` | l'autorisation est **épuisée** | revoir le plafond, ou attendre | +| `BELOW_MIN_POWER` | il reste de l'autorisation, **la charge ne sait pas la prendre** | lire la fiche technique du mécanisme | + +> **Conséquence sur le « point de rupture » de l'écran 2** : il est bien un `DRAW_CAP`. Une +> charge refusée **après** lui pour cause de palier porte `BELOW_MIN_POWER` et **ne déplace pas** +> le point de rupture — elle est en aval de la coupure, pas la coupure elle-même. +> +> **La règle est écrite ICI, une fois, par le moteur** — pas établie de chaque côté de la +> frontière. Deux précédences dérivées séparément divergeraient au premier motif ajouté. + +**RÉPONSE À R4 — ce que les compteurs de budget comptent : le DÉCIDÉ.** *(Question de l'agent +app, 2026-08-29.)* + +`budget.allocatedW` et ses voisins comptent **ce que la cascade a décidé**, jamais ce que le +matériel a reçu. Ce n'est pas un choix de commodité : le budget est le **registre de la +cascade**, et ce qui détermine ce que reçoit la charge suivante est la valeur décidée. L'arrondi, +lui, vit **après** — dans l'adaptateur, au dispatch, et il est propre à chaque mécanisme. Compter +le commandé obligerait à **rouvrir le budget après le dispatch**, c'est-à-dire à décider deux +fois. + +> ⚠️ **Et cela corrige une prémisse de R4, qu'il vaut mieux corriger maintenant.** La maquette +> écrit « `allocatedW` publie ce qui a été **commandé** ». **C'est faux aujourd'hui** : +> `buildTelemetry()` publie `action.estimatedPowerW` du plan, et `applyActionsToAdapters()` +> reçoit le slot **`const`** — rien n'est réécrit après le dispatch. Mesurable : +> `testUnpluggedChargerTakesNoBudget` attend `allocatedW == 3000` sur une borne dont le courant +> commandé donne 2 990 W. +> +> Donc **`Σ targetW == allocatedW` tiendra exactement**, par construction — les deux sont du +> décidé. L'écart que l'écran veut nommer (« 1 400 W décidés → palier 1 500 W ») n'est **pas** +> entre `targetW` et `allocatedW` : il est entre `allocatedW` (**décidé**) et la charge utile de +> mécanisme (**appliqué**) — `mechanism.stageW`, `mechanism.setpointW`, `mechanism.currentA` — +> **qui est déjà publiée aujourd'hui**. L'écran a déjà les deux nombres ; ce sont leurs noms qui +> étaient inversés. | `ECO_FLOOR_GRID` | `deliveredWh`, `targetWh`, `gridW` | Une obligation éco servie **au réseau**. `gridW` chiffre l'achat, comme `EV_GRID_START` le fait déjà. | | `ECO_FLOOR_MET` | `deliveredWh`, `targetWh` | L'obligation est **atteinte** — et elle ne peut se dire atteinte que sur le régime ÉNERGIE (LM-1009). | | `COMFORT_SKIPPED` | `o:sensorValue`, `o:setpoint` | La charge n'est plus demandeuse de son bonus (comparateur de LM-1008). Distinct de « pas de budget ». | diff --git a/specs/spec_loadmodel.md b/specs/spec_loadmodel.md index 225b8e2..b0eb1e8 100644 --- a/specs/spec_loadmodel.md +++ b/specs/spec_loadmodel.md @@ -805,6 +805,51 @@ publiée, au même titre que la source d'un plafond en LM-1006-1 — **deux caus --- +**LM-1010 — Le §10 est DÉBLOQUÉ : la maquette des deux passes fait contrat.** *(Réponse de +l'agent app au §9 du brief, 2026-08-29. `etm-powersync-app/docs/mockups/` +`deux_passes_eco_confort_mockup.html` **fait foi** ; huit exigences argumentées et la charge +utile complète.)* + +Les trois questions de 8.5 sont tranchées, et **l'argument décisif n'est ni le nôtre ni celui du +design** : + +1. **La liste reste ordonnée par RANG** — non pas d'abord parce que le rang doit rester lisible, + mais parce que **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 sous le + doigt qui vient d'y glisser une charge. Trier par ordre de service ne rendrait pas le rang + illisible — cela rendrait la **liste instable**, ce qui est pire. +2. **Une entrée par charge, deux lignes dans la carte.** La commande est le **parent**, les + niveaux sa **décomposition**. Règle générale : *on dédouble là où l'objet affiché est la + SÉQUENCE, jamais là où l'objet est la CHARGE.* D'où un second écran, en lecture seule, + « ordre de service du cycle » — c'est lui qui montre le rang 4 éco devant le rang 1 confort, + rangs visibles, et qui désamorce « l'ordre n'est pas respecté ». +3. **Éco servi / confort non = deux ÉTATS NOMMÉS**, jamais une fraction. Affiché comme une + fraction, le régime **nominal** se lirait comme une dégradation permanente. + +**Ce que le moteur doit publier** : `levels[]`, un élément par passe **réellement parcourue** — +rien de retiré. Les quatre exigences qui coûteraient cher à rattraper : + +| | | | +|---|---|---| +| **R2** | `funding` descend au **niveau** | **la plus urgente** : seule des huit à être un *changement de forme* d'un champ existant. 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** — un `funding` par charge devient indécidable, et c'est lui qui porte l'identité de réconciliation | +| **R3** | chaque watt tombe dans **exactement un** compteur, et le mapping est **publié** | trois destinations coexisteront : `budget.allocatedW`, `budget.evReservedW`, `draw.committedW`. Leçon d'`EV_GRID_START`, qui partage une allocation entre deux compteurs | +| **R4** | **cible ≠ commande**, et l'arrondi **ne se répartit pas** | toute clé de répartition serait une fiction dont l'app hériterait comme d'un fait. *Réponse du moteur sur ce que les compteurs comptent : `docs/DESIGN_10.md`* | +| **R6** | `ECO_FLOOR_MET` **interdit** sous régime `unmeasurable` | servie oui, tenue jamais (LM-1009 C1). Cas réel **dès aujourd'hui** : la V2C ne publie pas `sessionEnergy` | + +**Et ce qu'il ne faut PAS fabriquer** — trois refus, tous du même genre : ne pas publier ce qui +se déduit. + +- **Aucun index d'ordre de service.** La séquence 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. +- **Aucun second rang**, sous quelque nom que ce soit. La maquette n'a de sens que si LM-1002 + tient. +- **Aucun pourcentage d'avancement**, même indicatif. + +> La maquette **vaut comme contrat, pas comme feuille de route** : elle fixe la forme de ce qui +> sera publié, pas une date. Et côté app, rien ne commence avant que `levels[]` soit en ligne. + **LM-1007 — Compatibilité descendante, et elle est gratuite.** Une charge qui ne déclare **aucun** besoin éco ne participe qu'à la passe 2 — soit exactement le comportement d'aujourd'hui. Le modèle à deux niveaux **dégrade proprement** vers le modèle actuel, ce qui