docs(contrat): une charge absente de la télémétrie n'est pas « none »

Distinction remontée par l'agent app, qui a dû l'ajouter parce que le contrat ne
la portait pas — et confirmée sur le banc le jour même.

`none` est PUBLIÉ, donc il affirme « cette charge n'est pas mesurable ».
L'absence n'affirme rien : elle dit que la charge n'a pas participé à l'arbitrage
du cycle — mode manuel, aucun véhicule branché — ce qui est sans rapport avec sa
mesurabilité. Sans la distinction, une borne débranchée se verrait annoncer « le
compteur dédié est la seule voie » alors qu'elle publiera la sienne au prochain
cycle.

Même famille que LM-1009 D (un champ absent se voit, un champ qui s'efface se lit
comme un zéro), appliquée un cran plus haut : ici c'est l'ENTRÉE entière qui
s'efface, et la lire comme un `none` transformerait un silence en affirmation.

La garantie qui rend la règle utilisable est posée explicitement : `measurement`
est présent pour toute charge de loads[], et pour elle seule.

Constaté tel quel sur .75 en +etm26 : deux bornes pluggedIn:false absentes de
loads[], dont la V2C qui porte currentPower.

AGENTS.md consigne aussi l'état du déploiement et les trois vérifications de banc
masquées par la configuration de la box — chauffe-eau et pac-terrain portant un
meterThingId réel, elles publient `meter` et le chemin `device` n'est pas atteint.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015F7G5VeaPVSMeVNjiGj36p
This commit is contained in:
Patrick Schurig 2026-08-28 15:21:33 +02:00
parent b1394bb681
commit 3876eb6bdd
3 changed files with 50 additions and 0 deletions

View File

@ -204,6 +204,26 @@ vers un gestionnaire d'énergie complet (EV, ECS, PAC SG-Ready, batterie).
> (3 680 W au lieu de 6 000). Suites : simulation 29/29, charging 16/16, loadmodel 18/18,
> spotmarket 7/7 — amd64 0 erreur / 0 avertissement.
**DÉPLOYÉ SUR `.75` — `1.15.2+etm26`, 2026-08-28.** Cross-build arm64 dans `build-cross-arm64`
(bundle git → `/root/build-etm26`), posé par `deploy.sh`, version relue sur la box, `nymead`
actif. `measuredW` a bien disparu de la charge utile ; `measurement {source, powerW}` est publié.
> **Trois vérifications de banc restent MASQUÉES par la configuration de la box, et ce n'est pas
> un défaut** — c'est LM-1105 qui joue :
> - `chauffe-eau` et `pac-terrain` portent chacun un `meterThingId` vers un compteur RÉEL et
> distinct (`ECS-Meter` 1 500 W, `PAC-Meter` 800 W). Ils publient donc `source: "meter"`, et
> le chemin `device` n'est jamais atteint. **La `pac-terrain` ne publie donc PAS `none`** : le
> compteur explicite l'emporte, y compris sur un `sg-ready`. LM-1105-b décrit ce que la PAC
> publierait *sans* compteur dédié.
> - Les **deux bornes sont `pluggedIn: false`** (V2C Trydan en `ChargingModeEcoWithMinCurrent`,
> wallbox simulée en `ChargingModeEco`, aucune voiture assignée — donc plus exclues par ce
> motif depuis `+etm25`). Elles sont **absentes de `loads[]`**, pas en `none` : la V2C ne peut
> donc pas montrer son `source: "meter"` tant que rien n'y est branché.
>
> Pour observer `device` sur l'ECS et `none` sur la PAC, il faut **retirer temporairement leur
> `meterThingId`** — `SetLoadConfig`, champ SOUPLE, aucun rebuild d'adaptateur ni réarmement de
> verrou. Non fait : c'est la configuration du banc, elle appartient à l'installateur.
**À VÉRIFIER AU BANC, distinct et non tranché** : **le compteur racine lit ~1,5 kW de moins que
la somme des charges mesurées** (3 250 W contre 3 980 W pour la seule V2C, plus 800 W de PAC).
Même famille que l'aveuglement documenté aux résistances ECS. Ce n'est pas un problème

View File

@ -717,6 +717,24 @@ allocations irréconciliable avec `budget.allocatedW`.
> pouvoir détecter un mensonge.** C'est ce qui justifie de laisser `meterThingId` **disponible**
> sur une charge qui publie déjà sa puissance : masqué par défaut, jamais interdit.
> **Une charge ABSENTE de `loads[]` n'est pas `none`.** *(Distinction remontée par l'agent app,
> 2026-08-28, et confirmée sur le banc le jour même.)* `none` est **publié** : il AFFIRME « cette
> charge n'est pas mesurable ». L'absence n'affirme rien — elle dit seulement que la charge n'a
> **pas participé à l'arbitrage de ce cycle**, ce qui est une question sans rapport avec la
> mesure. Une borne en mode manuel ou sans véhicule branché sort de `loads[]` (voir
> `GetLoadTelemetry`, exclusions du waterfall) alors qu'elle publiera sa propre puissance dès le
> cycle où elle y rentrera.
>
> Sans cette distinction, un client afficherait « pas mesurable — le compteur dédié est la seule
> voie » sur une borne débranchée qui, une fois branchée, mesure très bien toute seule.
> **Constaté tel quel sur `.75` en `+etm26`** : deux bornes `pluggedIn: false`, absentes de
> `loads[]`, dont l'une porte `currentPower` et publiera `device`.
>
> La règle est simple parce que `measurement` n'est jamais omis d'une entrée présente : **la clé
> existe pour toute charge de `loads[]`, et pour elle seule.** Donc — charge dans `loads[]` :
> lire `measurement.source`. Charge absente : ne rien conclure sur sa mesurabilité, et ne rien
> afficher qui l'affirme.
> **Décidez sur `source`, jamais sur `adapter`.** « Masquer le compteur dédié sauf pour
> `sg-ready` » paraît équivalent et ne l'est pas : un `relay-router` à topologie **mixte**
> publie lui aussi `none`, et un client qui déciderait au mécanisme masquerait le champ sur un

View File

@ -997,6 +997,18 @@ o:measurement {
distinguer « pas mesurable » de « compteur en panne », et continue d'afficher un bloc de
mesure vide sur un `sg-ready` qui n'en aura jamais.
> **Et `none` ne se confond pas avec l'ABSENCE de la charge.** *(Distinction remontée par
> l'agent app, 2026-08-28.)* `none` est **publié**, donc il AFFIRME. L'absence n'affirme rien :
> elle dit que la charge n'a pas participé à l'arbitrage du cycle — mode manuel, aucun véhicule
> branché — ce qui est sans rapport avec sa mesurabilité. Une borne débranchée est absente de
> `loads[]` et publiera `device` dès qu'on y branchera une voiture ; lui annoncer « le compteur
> dédié est la seule voie » serait faux.
>
> C'est la même famille que LM-1009 D — *un champ absent se voit ; un champ qui s'efface se lit
> comme un zéro* — appliquée un cran plus haut : ici c'est l'ENTRÉE entière qui s'efface, et la
> lire comme un `none` transformerait un silence en affirmation. La garantie qui rend la règle
> utilisable : **`measurement` est présent pour toute charge de `loads[]`, et pour elle seule.**
> **Sous `none`, aucune valeur n'est publiée sous ce nom.** La puissance *nominale* existe et
> reste lisible — `mechanism.stageW`, `mechanism.state`, `mechanism.estimatedPowerW` la portent
> déjà, et disent d'où elle vient. La faire passer pour une mesure produirait un écart