etm-powersync-app/Prompt 3g2 arbitre commande seul.md
Patrick Schurig a44d64d6c2 feat(3g-2): les bornes sont des charges configurées — une seule liste, un seul rang
+etm23 rend à chaque borne détectée une entrée GetLoadConfig, et l'invariant
loads[] ⊆ GetLoadConfig est rétabli. Sur .75 : 2 entrées → 4. L'app s'appuyait déjà sur cet
invariant pour résoudre libellé, domaine et rang de toute charge publiée.

Vérifié sur machine avant d'écrire quoi que ce soit :
- l'aller-retour verbatim reste NEUTRE avec les quatre entrées (loadconfig_roundtrip.dart,
  verdict IDENTIQUE) ;
- une entrée evcharger porte id/label/domain/priority/enabled, mode dynamic, et AUCUNE
  charge utile de mécanisme.

Trois défauts que ce contrat révèle côté app :

1. isVariableLoad était défini par défaut (« ni relais ni sg-ready »), donc une borne y
   tombait. L'écran de mécanisme lui proposait une puissance nominale, des paliers et des
   temporisations — des réglages qui n'existent pas pour elle et dont l'écriture partirait
   quand même. Pire, le sélecteur offrait de la basculer en routeur de relais : une bascule
   que basculerMecanisme() sait construire et qu'aucun écran ne sait défaire. Une borne a
   maintenant sa section, en lecture seule, qui dit pourquoi il n'y a rien à régler.

2. BorneSection existait pour rattraper des bornes qui n'apparaissaient nulle part. Elles
   apparaissent maintenant dans leur domaine : la section les afficherait DEUX fois, en
   affirmant qu'elles n'ont « ni rang ni domaine » et qu'elles ne passent pas par
   SetLoadConfig. Trois affirmations devenues fausses. Retirée ; son contenu de runtime
   vit désormais dans l'écran de mécanisme, où il a un sens.

3. Le rang par défaut d'une borne est un ARTEFACT, et l'écran le disait comme un réglage.
   À la création le plugin donne « le plus petit rang existant moins un, borné à 1 » ;
   le chauffe-eau occupant déjà le rang 1, l'intention « en tête » dégénère en égalité —
   trois charges à 1 sur le banc. Le tri reste total (l'id départage), donc l'ordre est
   reproductible, et c'est précisément ce qui le rend crédible : l'installateur lit un
   classement plausible et n'y touche pas, validant un ordre que personne n'a posé. Une
   note le nomme, et disparaît dès le premier vrai réglage.

Le glissement, lui, n'a demandé aucun cas particulier — c'était tout l'intérêt d'avoir une
seule liste. Vérifié par test, y compris l'inversion borne / charge pilotée.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016vrTVifar2GN5rtUh89Wkq
2026-08-27 14:32:56 +02:00

4.0 KiB

Prompt agent plugin — 3g-2 : l'arbitre commande seul

À coller tel quel dans l'agent powersync-plugin. Contexte : .75 en 1.15.2+etm22, seuil de réserve batterie à 0,4.


3g-2 — l'arbitre commande seul.

Aujourd'hui adjustEvChargers() re-décide après le waterfall : l'arbitre refuse, la ligne suivante commande 6 A. La transplantation ne commande donc rien, et si la Terra AC n'avait pas été hors ligne, l'ordre serait parti sur du matériel réel.

La règle à établir : une charge, un commandeur. Pour une borne arbitrée par le waterfall, adjustEvChargers() ne doit plus émettre d'action de surplus. Ce qui lui reste, et qui doit être délimité explicitement : les actions financées au réseau — marché de gros, échéance de départ — puisque l'échéance est restée au proxy par décision assumée. Le champ funding que tu viens d'ajouter est le discriminant naturel.

Deux pièges à ne pas laisser ouverts.

Le trou de commande : si le waterfall alloue et que le proxy ne commande plus, vérifie que quelqu'un commande. Le pire résultat serait une borne qui n'obéit à personne — l'arbitre croit avoir servi, rien ne se passe, et le budget est perdu comme dans le cas evFloorW.

La transition : une borne qui charge sous l'ancien régime au moment du déploiement ne doit pas rester bloquée dans un état commandé par un chemin qui n'existe plus. Même précaution qu'à la suppression du bloc SG-Ready codé en dur — passer par l'état sûr.

Traite evFloorW() dans le même lot. acquisitionTolerance est un seuil de déclenchement, pas un plancher : 2 070 W publiés là où 4 140 W sont exigés, donc 2 828 W retirés du budget et donnés à personne. Un plancher faux est pire qu'un plancher absent — le waterfall croit avoir servi. Et le plancher publié doit correspondre au nombre de phases visé, pas au courant du moment.

Et le rang d'une borne doit devenir configurable — priority = 100 en dur, std::sort non stable, donc le test d'inversion est impossible tel quel. Sans lui, LM-1204-b reste hors d'atteinte même avec deux bornes en ligne.

Vérification : .75 est à +etm22, seuil 0,4, la wallbox simulée répond, la Terra AC est hors ligne (192.168.1.132:502, ThingErrorTimeout) — à regarder avant, c'est le seul obstacle matériel restant à LM-1204-b.

Ton brief est commité en local dans le dépôt app (e84f59d) mais non poussé, faute d'identifiants HTTPS. L'agent app pousse en SSH ; signale-le-lui plutôt que de le laisser dormir.


Rappels d'état, au cas où la session redémarre à froid

Banc .75 1.15.2+etm22, seuil réserve batterie 0,4 (délibéré — 0,9 gèle tout à 50 % de SoC)
Plugin SunSpec 1.15.2+etm11 — la batterie déclare enfin energystorage, signe vérifié dans les 3 états
Banc .131 six conteneurs sur la même source, deploy.sh --check-only sort 0
Terra AC hors ligne, 192.168.1.132:502, ThingErrorTimeout — seul obstacle matériel à LM-1204-b
Déploiement toujours etm-powersync-deploy/deploy.sh, jamais dpkg -i
Accès banc ssh etm@192.168.1.75 (ni root ni nymea) · journal journalctl -u nymead

Non franchi : LM-1204-b — jamais exercé, c'est le critère qui justifie tout le lot.

Pour l'agent app, en attente dans docs/BRIEF_depuis_plugin.md : SetChargingInfo substitue silencieusement targetPercentage = 80 à un champ absent · loads[].funding (surplus/grid) et l'entrée des bornes dans la télémétrie · kCodesReserveBatterie vide alors que BATTERY_RESERVE publie socPercent/reservePercent/withheldW · et la mise en garde : pour une borne, allocatedW et son motif sont la décision de l'arbitre, pas l'état de la borne, tant que 3g-2 n'est pas livré.

Côté app, origin est en HTTPS sans credential helper — d'où des origin/… périmés qui font croire à des commits manquants. À basculer en SSH : git remote set-url origin gitea-lan:pakutz79/etm-powersync-app.git