`+etm32` publie `counts` par niveau : où chaque watt est compté, et combien.
Vérifié à la sonde sur `.75` avant de toucher au code — `counts` sur tous les
niveaux, destination publiée même à zéro, et les DEUX identités qui tiennent.
- `LoadLevel.counts`, `countsTotalW`, `countsCoherent`, `partSurplusW` ;
- `surplusShareW` LIT `counts["budget.allocatedW"]` au lieu d'interpréter ;
- `soutirageShareW` + `LoadTelemetry.soutirageIdentityHolds` : la seconde
identité, qui n'existait pas ce matin faute de registre pour les watts achetés ;
- `LoadTelemetry.drawCommittedW` — absent = box antérieure à `+etm31`, jamais
« rien acheté » ;
- `destinationsInconnues` : une clé née après cette app est NOMMÉE, jamais
comptée zéro, et l'identité par niveau tient sans connaître le sens des clés.
L'exception au motif survit exactement là où la box ne sait pas dire le partage
autrement — formes sans `counts`. Ce n'est pas une version qui l'a retirée, c'est
une charge utile qui l'a rendue inutile ; sur une installation déjà déployée, la
retirer casserait la réconciliation.
Le rapport de bogue porte désormais les deux identités, les destinations
inconnues et les niveaux incohérents (Σ counts != targetW) — une incohérence de
la box, pas un arrondi.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GKM8FDNHZWogAsjjfB8pSw
R2 est livrée côté moteur (1.15.2+etm30 sur .75) : `funding` ne figure plus à la
racine d'une entrée de `loads[]`, il vit dans `levels[]`, un par passe. La rupture
est effective, pas seulement contractuelle — `entry.funding` est désormais `null`
partout, donc la mention « servie au réseau » avait disparu de la carte et une
charge achetée au réseau aurait été comptée comme surplus dans l'identité. Une
panne muette : rien n'échoue, tout se lit de travers.
- `LoadLevel` — une passe : `level`, `targetW` (du DÉCIDÉ, comme `allocatedW`),
`funding` ;
- `resolvedFunding` / `isGridFunded` résolvent les DEUX formes ; `levels[]` vide
= box antérieure, pas « aucune passe » ;
- `'mixed'` est NOMMÉ plutôt que replié : impossible aujourd'hui (une seule passe
publiée), courant au §10, et l'affirmer d'un côté serait une demi-vérité ;
- `surplusShareW` somme les `targetW` financés au surplus, l'exception écrite
`EV_GRID_START` (part de surplus = `params.budgetW`) passant avant.
Vérifié à la sonde contre `.75`, pas en test : 3 500 + 3 000 == 6 500 ==
`budget.allocatedW` au cycle 06:10, puis 1 500 == 1 500 à 06:14. Le niveau publié
est « comfort », et c'est déjà le bon nom.
Aucun écran à deux niveaux n'est commencé : le reste de la maquette attend R1 et
R3 à R8, et le §10 n'est pas implémenté.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GKM8FDNHZWogAsjjfB8pSw
Écrit AVANT le déploiement du moteur, pour qu'il n'y ait pas de trou.
`measuredW` portait trois régimes sous un seul nom et jetait la distinction : une borne
rendait une mesure, un routeur de relais une mesure OU son palier nominal selon que ses
relais mesurent, un SG-Ready une puissance toujours déclarée. C'est la condition C2 de
LM-1009. Le régime se publie désormais :
measurement: { source: "meter" | "device" | "none", powerW }
Les trois pièges d'affichage du §7, figés par test :
· `none` est un état POSITIF — « cette charge n'est pas mesurable ». Il autorise à MASQUER
le bloc au lieu d'afficher un vide, et sépare enfin « pas mesurable » de « compteur en
panne ». Aucune puissance nominale n'est allée la chercher dans mechanism.* pour combler :
elle produirait un écart identiquement nul à tous les cycles, soit un « tout concorde »
permanent qui ne signale plus rien.
· {source: "device", powerW: 0} est une MESURE. Une borne branchée qui ne charge pas mesure
zéro watt. Si la source basculait à l'extinction, le bloc apparaîtrait et disparaîtrait au
rythme du thermostat.
· L'écart n'a pas la même valeur selon la source, et l'écran le DIT : sous `device`, il
montre la désobéissance mais une concordance ne prouve rien — plusieurs wallbox renvoient
leur consigne ; sous `meter`, la mesure est indépendante de l'organe commandé et la
concordance devient une preuve.
Le compteur dédié se décide sur `measurement.source`, JAMAIS sur `adapter` :
source == device → réglage avancé, replié (l'appareil publie déjà)
sinon → proposé, visible
La règle couvre SG-Ready sans avoir à le connaître — `none` par construction, LM-1105-b : un
contact de signal ne voit pas le courant de la machine, et pour une PAC le compteur dédié est
la SEULE voie vers une mesure. Elle couvre aussi la topologie de relais mixte, `none` elle
aussi, qu'un « masqué dès que la charge mesure, sauf SG-Ready » aurait manquée. Redéduire le
régime du mécanisme remettrait ici la déduction que le moteur vient d'en retirer.
⚠️ Une distinction que le contrat n'a pas à porter mais que l'écran doit faire : une charge
ABSENTE de la télémétrie n'est pas `none`. `none` est publié ; l'absence ne dit rien. Les
confondre annoncerait « seule voie vers une mesure » sur une borne qui publiera très bien la
sienne au prochain cycle.
⏳ Alias temporaire `measuredW` en repli, avec sa date de retrait : dès que +etm26 est sur
.75 (entrée TODO, sonde nommée). `measurement` gagne quand les deux sont là.
204 tests (+16), flutter analyze inchangé à 27 remarques. Prêt pour le déploiement.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SQbZKrWqsMFP1Lh2jjjd9f
/bug-report était un stub. Un champ libre seul produit « ça ne marche pas », et l'aller-retour
pour obtenir versions, charges et état d'arbitrage coûte des jours. Tout ce que l'app SAIT
est donc joint d'office : version du paquet, hôte et état de connexion, ligne de versions du
schéma, les charges déclarées avec adaptateur / rang / domaine, et le dernier cycle publié.
Deux choix qui ne sont pas cosmétiques :
- LES DEUX IDENTITÉS DU BUDGET sont dans le rapport, avec leur verdict. Elles valent souvent
plus que la description du symptôme : portées fausses, elles désignent le moteur ; portées
vraies, elles désignent l'écran. Sans elles, le premier échange consiste à les demander.
- Aucun secret n'y entre : ni PIN installateur même haché, ni jeton, ni mot de passe. Un
rapport voyage — courriel, capture, fil de discussion — et ce qui y entre en sort. Les
UUID de Things restent : ce sont eux qui permettent de recouper avec le journal de la box,
et ils n'ont aucune valeur hors de l'installation.
Les absences gardent leur sens, comme partout ailleurs : « aucune télémétrie reçue » n'est
pas un arbitrage à zéro, « budget ABSENT » n'est pas un budget de zéro watt, et un
financement omis se dit omis.
Pas d'envoi automatique : il n'existe aucun service de collecte, et un bouton « Envoyer »
sans destinataire donnerait le sentiment que quelqu'un l'a reçu. Le rapport se copie, ce qui
est vérifiable.
package_info_plus est ajouté plutôt qu'une constante de version recopiée à la main : une
constante dérive de pubspec.yaml en silence, et un rapport qui annonce la mauvaise version
envoie chercher un défaut dans un code qui n'est pas celui-là.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016vrTVifar2GN5rtUh89Wkq