diff --git a/docs/DESIGN_MIN_GARANTI.md b/docs/DESIGN_MIN_GARANTI.md new file mode 100644 index 0000000..6e9e476 --- /dev/null +++ b/docs/DESIGN_MIN_GARANTI.md @@ -0,0 +1,148 @@ +# Minimum garanti — design + +> Ouvert le 2026-09-06. **Option 2 retenue** : livrer le minimum garanti maintenant, avec la +> machinerie du §10 ; le renommage l'accompagne. L'arbitrage au prix (option 3) reste l'horizon, +> il demande une source tarifaire et une prévision. + +## 1. Le constat, et il est double + +**Le mode `ChargingModeEcoWithMinCurrent` n'a jamais eu de minimum garanti** — non pas parce que +le code manquait, mais parce qu'il n'a jamais été atteint. Établi dans `DESIGN_3g.md` §5 : mort +par réécriture chez l'amont, puis mort par exclusion depuis 3g-2. + +**Et son nom promet l'inverse de ce qu'il fait.** `MinCurrent` désigne **l'exigence de la borne** +— les 6 A en dessous desquels elle refuse de charger — **pas une garantie du mode**. Un lecteur y +voit « le mode maintient un courant minimum » ; le code, lui, ne fait que nommer un seuil +matériel. + +> **Trois modes annoncés, deux implémentés — et celui qui manque est celui que l'utilisateur +> croyait utiliser.** C'est la formulation à retenir : le défaut n'est pas qu'une fonction +> manque, c'est qu'elle est *annoncée*. + +## 2. Pourquoi livrer le minimum maintenant, sans attendre le prix + +Chiffrage retenu, tarif de rachat récent à **0,01 €/kWh** : + +| | coût effectif | +|---|---| +| charger en journée, 30 % au réseau | **0,073 €/kWh** | +| charger la nuit, 100 % au réseau | **0,180 €/kWh** | + +Sur ces installations — **la majorité du parc neuf** — le minimum garanti en journée est +simplement le bon choix, et **aucun arbitrage n'est nécessaire pour le savoir**. Le prix ne +devient décisif que sur les anciens contrats à **0,1296 €**, où l'écart tombe à deux centimes et +**s'inverse sous 62 % de couverture solaire**. + +**Conclusion de conception** : l'option 3 (comparer deux scénarios au prix) est le bon horizon, +mais elle exige une source tarifaire et une prévision. L'option 2 livre l'utile aujourd'hui, et +elle ne ferme pas la porte : un arbitrage au prix choisira plus tard *s'il faut* garantir, pas +*comment* garantir. + +## 3. Condition 1 — la machinerie du §10, pas un second chemin d'achat + +Le minimum garanti **passe par la passe éco**, celle qui achète déjà : + +- **borné par l'autorisation de soutirage** (`DrawCap` / `DrawMargin`, trois sources) — comme + tout achat ; +- **`funding: Grid`** quand le surplus ne couvre pas ; +- **le motif dit combien est acheté**, comme `ECO_FLOOR_GRID` le fait déjà. + +**Ce que cela EXCLUT explicitement** : porter le minimum par `EV_GRID_START`. Ses trois bornes +dérivent toutes du surplus (`budgetW > 0`, seuil proportionnel, consommation de tout le budget) ; +un minimum permanent en est indépendant par définition, donc les lever revient à créer le second +mécanisme. Et `EV_GRID_START` achète **hors** du registre d'autorisation — deux mécanismes dont +un seul est borné, et c'est le second qui ferait disjoncter. Raisonnement complet dans +`DESIGN_3g.md` §5.4. + +## 4. Condition 2 — une puissance permanente, portée sans dupliquer `needs` + +### Le champ + +Un champ de plus **dans `needs`**, frère de `minEnergyWhPerDay` — pas une structure parallèle : + +``` +needs : { + minEnergyWhPerDay : Uint, // énergie, avec échéance + dailyDeadline : String, + guaranteedPowerW : Uint // NOUVEAU — puissance, permanente +} +``` + +**À ne pas confondre avec `minPowerW`** (`LM-1201`), et la différence est de nature : + +| | ce que ça dit | +|---|---| +| `minPowerW` | **SI** on sert cette charge, au moins ceci. Un plancher de **modulation**. | +| `needs.guaranteedPowerW` | On sert cette charge au moins ceci, **TOUJOURS** — surplus ou non. Une obligation de **présence**. | + +Le premier borne une consigne ; le second en crée une. Les deux peuvent coexister sur la même +charge sans se contredire. + +### La composition dans la passe éco + +`computeEcoTarget()` rend aujourd'hui une cadence dérivée de l'obligation en énergie. Elle rendra +le **maximum des deux** : + +``` +watts = max( cadenceObligation , guaranteedPowerW ) +``` + +Quatre invariants, et chacun ferme un silence : + +1. **La garantie ne se « tient » JAMAIS.** `tenue` ne décrit que l'obligation en énergie ; une + puissance permanente n'a pas de cible et ne se complète pas. Donc quand l'obligation est + tenue, `watts` retombe sur la garantie — **pas sur zéro**. C'est exactement la différence de + nature, et c'est là qu'elle se voit dans le code. +2. **`progress` ne décrit que l'obligation.** Sur une charge à garantie seule, `progress` est + **absent** — jamais à zéro : un avancement nul se lirait « rien livré », alors qu'il n'y a rien + à avancer. +3. **Le motif dit LEQUEL DES DEUX MORD.** Sans ça, l'écran affiche un nombre qu'il ne peut pas + expliquer — et deux causes différentes produiraient le même chiffre. Le motif gagne + `guaranteedW` et une marque de ce qui borne. +4. **Une garantie non honorée se DIT.** Si l'autorisation de soutirage ne la permet pas, elle + n'est pas servie — et c'est `DRAW_CAP` qui le dit, en nommant la garantie. Le silence ici + ferait croire à une garantie tenue. + +## 5. Condition 3 — nommer les trois modes pour ce qu'ils font + +**Le fil ne peut pas être renommé** : `ChargingMode` est une énumération publiée, et changer ses +identifiants casserait tout client déployé. Ce qui se corrige, c'est **la description au +contrat** — c'est là que vit le mensonge, et c'est de là que l'app tire ses libellés. + +| valeur | ce que le contrat dit AUJOURD'HUI | ce que le code FAIT | +|---|---|---| +| `ChargingModeNormal` | « Recharge immédiate au maximum disponible » | **MANUEL.** La borne sort de l'arbitrage — *« l'utilisateur pilote, l'arbitre s'abstient »*. Le moteur ne commande **rien**. Seule la protection de surcharge la touche encore. | +| `ChargingModeEco` | « Recharge sur surplus solaire uniquement » | exact | +| `ChargingModeEcoWithMinCurrent` | « Eco + courant minimum garanti (6 A) si pas de surplus » | **rien** — le chemin n'est pas atteint | + +**`Normal` n'est pas un « boost ».** Un boost commanderait le maximum ; celui-ci ne commande pas. +La seule chose que le moteur y fasse encore est de délester en surcharge — ce qui doit rester, et +doit être **dit** : un mode « manuel » qui coupe quand même mérite de l'annoncer. + +## 6. Les étapes, à comportement constant d'abord + +**Étape A — corriger le contrat.** Les trois descriptions ci-dessus, plus la note sur `MinCurrent` +qui nomme l'exigence de la borne. **Aucun code.** Comportement constant par construction, et c'est +le pas qui rend la suite lisible. + +**Étape B — porter `guaranteedPowerW` dans `LoadNeeds`, le persister, le publier**, sans que la +passe éco s'en serve. Aller-retour `Get`/`SetLoadConfig`, schéma déclaré une fois. Comportement +constant : le champ existe et ne décide de rien. + +**Étape C — la passe éco le prend en compte.** Le seul pas qui change le comportement, et il +arrive quand tout le reste est déjà en place et vérifiable. Avec les quatre invariants du §4, le +motif enrichi, et le `DRAW_CAP` qui nomme la garantie. + +**Étape D — le mode s'y branche.** `ChargingModeEcoWithMinCurrent` posé sur une borne écrit +`guaranteedPowerW = plancher de la borne`. C'est le pas qui rend la promesse du nom vraie — et il +vient en dernier, parce qu'il n'a de sens qu'une fois C livrée. + +## 7. Ce qu'on ne saura pas prouver au banc, et il faut le dire + +L'installation de `.75` produit **2 kWc** contre un plancher de borne triphasée à **4 140 W**. Une +garantie sur cette borne serait donc **toujours** financée au réseau, jamais au surplus — le banc +ne peut pas exercer le cas mixte, celui où la garantie complète un surplus partiel. + +Il l'exercera en revanche très bien sur une charge en watts — le chauffe-eau — dès qu'elle aura un +compteur réel (`TEST_TERRAIN.md` §12). **C'est la même précondition que pour l'étape 4 de la +commande perdue**, et elle est maintenant nommée par un quatrième chemin.