diff --git a/docs/BRIEF_plugin_depuis_maquettes.md b/docs/BRIEF_plugin_depuis_maquettes.md index 1414cc5..451754b 100644 --- a/docs/BRIEF_plugin_depuis_maquettes.md +++ b/docs/BRIEF_plugin_depuis_maquettes.md @@ -2,6 +2,98 @@ --- +## 2026-08-30 — R3 : `counts` est retenu, et `funding` sort de la réconciliation + +**Réponse à la question du moteur sur le niveau mixte.** La forme proposée est prise telle +quelle : `funding` reste, `counts` s'y ajoute, `Σ counts == targetW` toujours, et `counts` +est publié même à une seule destination. + +### Le code unique l'emporte — et l'argument contre l'omission est vérifiable, pas seulement juste + +L'omission n'est pas neutre **dans le code qui tourne aujourd'hui** : `resolvedFunding` rend +`null` quand rien n'est publié, et `isSurplusFunded` rend alors `true` +(`lib/models/load_telemetry.dart`). Un niveau mixte sans `funding` serait donc rangé **avec +les charges du soleil**, en silence. L'omission porterait déjà une affirmation — et la pire +des deux. + +### Mais « approximation » est le mauvais mot, et le garder coûterait le champ + +Tant que `funding` est décrit comme approximatif, quelqu'un finira par vouloir le rendre +exact : part dominante, seuil de majorité. Ce serait un libellé qui **bascule d'un cycle à +l'autre** sur 900/480, sans qu'aucune décision n'ait changé. + +**La règle que nous demandons d'écrire à sa place : `grid` l'emporte dès qu'un watt est +acheté.** Déterministe, indépendante des proportions, et alignée sur ce qu'un humain doit +savoir — l'achat est le fait actionnable, le surplus est l'ordinaire. Elle vaudra telle +quelle pour le plancher éco acheté au réseau (§10) sans nouvelle décision à prendre. + +### Ce qui rend l'ensemble soutenable : `funding` sort de la comptabilité + +Avec `counts` **toujours** publié, `funding` ne participe plus à aucun calcul de +réconciliation. Il ne dit plus que : *faut-il en parler ?* — et `counts` dit *combien, et +où*. Aucun des deux ne se dérive de l'autre, et l'approximation qui reste n'entre plus dans +un total. Côté écran, la carte d'un niveau mixte dira **« en partie achetée — 480 W sur +1 380 »** : `funding` déclenche la phrase, `counts` la chiffre. + +### Et `"mixed"` publié ? Non — et c'est notre propre code qui le dit + +C'est bien la troisième voie, elle mérite d'être posée, et nous la refusons. Trois raisons, +dans l'ordre de force : + +**1. Il reproduirait EXACTEMENT la panne de l'omission, sur le même chemin de code.** Tout +client qui teste `funding == 'grid'` a une branche par défaut, et cette branche est +« surplus » — la nôtre l'était encore ce matin (`isSurplusFunded => funding != 'grid'`). +Une troisième valeur y tombe **sans erreur**, et range au soleil un niveau dont une part est +achetée. C'est le défaut que l'omission produisait, avec un champ présent en plus pour faire +croire le contraire. + +**2. Il serait dérivable — donc il violerait votre propre règle.** `"mixed"` ⟺ `counts` a +plus d'une destination non nulle. Un champ publié qui se recalcule depuis un autre est +précisément celui que quelqu'un supprimera un jour comme redondant, et il aura raison. + +**3. Il ne répond pas à la question que `funding` pose.** `funding` dit *d'où viennent ces +watts* ; `"mixed"` dit *qu'il y a deux réponses* — une réponse sur la question, pas sur les +watts. Et l'information actionnable qu'elle promet (**combien** est acheté) est déjà dans +`counts`, en clair. + +**La ligne qui sépare nos deux `mixed`** : le nôtre est **dérivé, local, jamais transporté** +— `resolvedFunding` le fabrique pour une CHARGE dont les niveaux divergent, uniquement pour +empêcher une carte d'affirmer une origine unique là où il y en a deux. Il est légitime parce +qu'il est dérivable ; le vôtre serait illégitime pour la même raison. **On publie ce qui ne +se dérive pas, on dérive ce qui se dérive** — et cette règle range `funding`, `counts` et +`mixed` chacun à sa place sans qu'aucun ne soit redondant. + +### Trois choses en échange, et elles sont de notre champ + +**a. Une clé inconnue ne doit jamais disparaître.** Nous traiterons les clés de `counts` +comme des **identifiants opaques**, jamais comme des chemins à parser. Si `draw.committedW` +arrive sur une box plus récente que l'app, elle vérifiera quand même `Σ counts == targetW` +— la règle ne dépend pas du sens des clés — mais dira **« destination inconnue de cette +version »**, jamais zéro. Corollaire pour vous : **ne réutilisez jamais une clé existante +pour un sens nouveau**. C'est le seul cas qu'aucun client ne peut détecter. + +**b. Publiez `counts` même à zéro, avec sa destination.** Mesuré sur `.75` ce matin (cycle +06:14) : un niveau `targetW: 0` **est** publié, avec le motif qui dit pourquoi. Un +`counts: {}` y satisferait l'identité (0 == 0) tout en perdant la destination — un trou +exactement sur le cas le plus fréquent, et de la même famille que celui que `counts` vient +fermer. Une clé, valeur 0. + +**c. Dites qui fait foi entre `counts` et les params d'`EV_GRID_START`.** Les deux porteront +le même partage (`budgetW` / `gridW`). Notre lecture : **`counts` fait foi pour la +comptabilité**, les params restent pour la **phrase** du motif — catalogue fermé, testé sur +le texte. Si vous les gardez, dites-le ; s'ils divergent un jour, c'est `counts` que nous +croirons. + +### Ce que ça change pour nous, et quand + +R3 devient vérifiable **par niveau**, sans connaître le sens des compteurs — c'est plus fort +que ce que nous demandions. L'exception `EV_GRID_START` disparaîtra du contrat **le jour où +la sonde la verra disparaître de la box** : d'ici là elle reste écrite dans `CLAUDE.md` et +traitée dans `surplusShareW`, et nous ne touchons ni l'un ni l'autre. Contrat, pas feuille +de route — comme pour `levels[]`. + +--- + ## 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 diff --git a/docs/mockups/deux_passes_eco_confort_mockup.html b/docs/mockups/deux_passes_eco_confort_mockup.html index c934f97..f92a057 100644 --- a/docs/mockups/deux_passes_eco_confort_mockup.html +++ b/docs/mockups/deux_passes_eco_confort_mockup.html @@ -611,6 +611,8 @@
  • 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.
  • +
  • Une exception de réconciliation : avec counts, il n'y en a plus une seule. + Un écart qui subsiste est un défaut, et il se dit.
  • @@ -643,7 +645,10 @@ "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 + "funding": "grid", // L'ORIGINE — « ces watts sont achetés ». Livré en +etm30. + "counts": { "draw.committedW": 1400 }, // LE REGISTRE — « ils sont comptés ici ». + // Σ counts == targetW, toujours. Publié même à une seule + // destination, et même à zéro, avec sa destination. "decision": { "code": "ECO_FLOOR_GRID", "params": { "deliveredWh": 2100, "targetWh": 3000, "gridW": 1400 } }, "progress": { "regime": "measured", // measured | estimated | unmeasurable @@ -681,13 +686,29 @@

    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).

    +
    R3Chaque watt tombe dans un compteur nommé — ✔ résolu par `counts` +

    La forme du moteur est prise telle quelle (2026-08-30) : à côté de funding, un + counts qui nomme les registres, avec Σ counts == targetW toujours, publié + même à une seule destination. L'identité devient calculable sans exception et par niveau : + l'exception EV_GRID_START, qui partageait une allocation entre deux compteurs, disparaît — + remplacée par une règle vérifiable qui vaudra telle quelle quand draw.committedW arrivera.

    +

    Les deux champs ne se dérivent pas l'un de l'autre, et c'est ce qui empêchera qu'on en supprime un : + funding dit l'origine (« ces watts sont achetés » — voisin du motif), counts + dit le registre (« ils sont comptés ici » — une comptabilité).

    +

    Sur un niveau mixte, un code unique : "grid" — et la règle demandée n'est pas « une + approximation » mais « grid l'emporte dès qu'un watt est acheté ». Déterministe, indépendante des + proportions (un libellé qui bascule sur 900/480 serait pire que faux), et alignée sur ce qu'un humain doit + savoir : l'achat est le fait actionnable. Elle vaudra sans nouvelle décision pour le plancher éco du §10. + Ni omission (notre isSurplusFunded range alors le niveau au soleil, en silence) + ni "mixed" publié : une troisième valeur tomberait dans la branche par défaut de tout + client — « surplus » — donc reproduirait la panne de l'omission, et elle serait dérivable de + counts, donc supprimée un jour à juste titre.

    +

    Ce que l'app promet en échange : les clés de counts sont traitées comme des + identifiants opaques, jamais comme des chemins. Une clé inconnue d'une version ancienne n'est pas + perdue : l'identité Σ counts == targetW reste vérifiable sans connaître le sens des clés, et + l'écran dit « destination inconnue de cette version », jamais zéro. Corollaire pour le moteur : ne + jamais réutiliser une clé existante pour un sens nouveau — c'est le seul cas qu'aucun client ne peut + détecter.

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

    Corrigé le 2026-08-29 : notre prémisse était fausse. allocatedW publie le