Patrick Schurig ccf05cea87 feat(R6): la mesurabilité se déclare à la saisie, et l'inventaire du mock
Le signal vit dans GetLoadConfig, en r:, et non dans la télémétrie.
`measurement` n'existe que pour les charges arbitrées ; or une charge qu'on
DÉCLARE est précisément celle qui n'y est pas encore. L'écran serait muet au
moment exact où il doit parler — celui de la saisie, avant qu'une obligation
n'achète à l'aveugle tous les jours.

Trois causes, trois gestes : noMeter se répare en une manipulation,
meterWithoutEnergy demande de remplacer le compteur, noSessionEnergy ne se
répare pas. Sans la cause, l'avertissement ne peut dire que « ça ne marchera
pas ».

Et meterThingId ne permet pas de le déduire : un compteur peut publier
currentPower sans totalEnergyConsumed — mesuré sur l'ECS du banc, reproduit
au test avec un powerSwitch désigné comme compteur. La puissance ne suffit
pas, il faut une énergie. Contre-épreuve : déduction par meterThingId seul,
le cas du banc passe pour mesurable.

INVENTAIRE À FROID DU MOCK. Trois fois en trois jours qu'un cas réel s'est
révélé inatteignable ; ce n'est plus un incident, c'est une propriété du banc
— il est plus homogène que la réalité, ayant été construit pour faire marcher
des scénarios et non pour représenter la diversité du parc.

Quatre cas restent structurellement introuvables, dont `source: none` pour
une charge SAINE — toutes les classes pilotables portent une puissance, si
bien qu'une charge non mesurée mais en bon état est impossible à fabriquer,
alors qu'un relais sec est banal sur le terrain. Et la branche `temperature`,
écrite et jamais exécutée faute d'une classe qui porte l'état.

La règle : avant d'écrire un test qui exerce une ABSENCE, vérifier que le
mock peut la produire. Une assertion sur un cas inatteignable ne passe pas,
elle NE S'EXÉCUTE PAS — ce qui est pire, car elle compte comme verte. Le
symptôme se reconnaît : la contre-épreuve ne mord pas.

Deux ajouts dans AGENTS.md. L'asymétrie des latences est une PROPRIÉTÉ, pas
une chance : le refus qui coûte de l'argent est celui de la descente, et
c'est le plus vite constaté — ce qui n'est vrai que parce que le seuil vient
du mécanisme. Et la question opératoire appliquée aux trois autres identités,
avec un résultat inégal qui est le but : celle de `draw` repose sur le fait
que le compteur voie toute la maison, ce que rien ne vérifie ;
Σ levels[].targetW == estimatedPowerW est interne au plan et ne dit rien du
matériel — il a fallu un champ hors de la formule pour relier les deux.

Suite : simulation 61/61, loadmodel 22/22, charging 48/48 identique à la
référence, spotmarket 32/32, doxygen 0 avertissement.

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

80 lines
4.4 KiB
Markdown

# INVENTAIRE À FROID DU MOCK — ce qu'il rend structurellement introuvable
*(Fait le 2026-09-01, après trois occurrences en trois jours d'un cas réel impossible à
reproduire. Ce n'est plus un incident, c'est une propriété du banc d'essai.)*
## Pourquoi cet inventaire
Trois fois cette semaine, un défaut réel s'est révélé **inatteignable par la suite** :
| Occurrence | Ce qui était introuvable | Comment on l'a su |
|---|---|---|
| Correctif amont `calculateAllowanceAmpere` | un compteur publiant les courants **sans** les puissances par phase | le mock pose `currentPhaseB = currentPowerPhaseB / 230`, cohérent **par construction** |
| Test `command` sous `source: none` | une charge dont rien ne mesure la puissance | **toutes** les classes pilotables portent `currentPower` |
| Test `ECO_FLOOR_*` en régime `unmeasurable` | une borne sans `sessionEnergy` | il a fallu **ajouter** l'état à une seule classe pour avoir les deux cas |
Le point commun : **le mock est plus homogène que la réalité.** Il a été construit pour faire
marcher des scénarios, pas pour représenter la diversité du parc — et une suite verte ne dit alors
rien sur les cas qu'il ne sait pas produire.
## Les classes, et ce qu'elles portent
| Classe | `currentPower` | `totalEnergyConsumed` | `sessionEnergy` |
|---|---|---|---|
| `meter` | ✅ | ✅ | — |
| `charger` | ✅ | ✅ | ❌ |
| `chargerPhaseSwitching` | ✅ | ✅ | ✅ *(ajouté le 2026-08-31)* |
| `simpleCharger` | ❌ | ❌ | ❌ |
| `powerSwitch` | ✅ | ❌ | — |
| `etmVariableLoad` | ✅ *(`currentPowerW`)* | ❌ | — |
| `energyStorage` | ✅ | ❌ | — |
| `car`, `notification` | ❌ | ❌ | — |
**Ce que la table donne, et c'est la bonne nouvelle** : `sessionEnergy` et `totalEnergyConsumed`
sont désormais **discriminants** — il existe des classes avec et sans. Les régimes `measured` /
`unmeasurable` et les causes `noSessionEnergy` / `meterWithoutEnergy` sont donc exerçables.
## Ce qui reste STRUCTURELLEMENT introuvable
**1. `measurement.source: none` pour une charge saine.** Toutes les classes pilotables portent une
puissance. Le seul chemin est une charge dont le **Thing n'existe pas** (câblage manquant) — cas
réel, mais qui traîne avec lui `available: false`. **Une charge saine et non mesurée est
impossible à fabriquer ici**, alors qu'elle est banale sur le terrain (un relais sec).
**2. Un compteur par phase incohérent.** `currentPhaseX` et `currentPowerPhaseX` sont posés
ensemble par le mock, dans un rapport fixe de 230. Aucun scénario ne peut produire une tension
réelle ≠ 230 V, ni un compteur qui ne publierait qu'une des deux familles.
**3. `temperature`.** Le code teste `hasState("temperature")` et **aucune classe du mock ne le
porte** : la branche « sonde présente » n'a jamais été exécutée. Elle est écrite, elle n'est pas
testée.
**4. Une borne triphasée à phases déséquilibrées.** Les états existent, mais aucun helper de test
ne les pose indépendamment — d'où le pessimisme de `LM-1210-a` qui n'a jamais été exercé sur un
vrai déséquilibre.
## La règle qui en sort
> **Avant d'écrire un test qui exerce une ABSENCE, vérifier que le mock peut la produire.**
> Une assertion sur un cas inatteignable ne passe pas : elle **ne s'exécute pas**, ce qui est
> pire — elle compte comme verte.
>
> Le symptôme se reconnaît : **la contre-épreuve ne mord pas**. C'est le corollaire fin
> d'`AGENTS.md` appliqué au banc d'essai plutôt qu'au code.
**Et la parade tient en une ligne de test** : quand on itère pour trouver des cas, **compter
combien on en a examinés**, et échouer si le compte est nul. C'est ce qui a rattrapé le test de
`command`, et ce qui garde celui de la mesurabilité.
## Ce qu'il faudrait ajouter, par ordre d'utilité
1. **Une classe pilotable SANS aucune puissance** — un relais sec. Débloque `source: none` sur
une charge saine, et sépare enfin « non mesurée » de « en panne ».
2. **Un compteur sans `totalEnergyConsumed`** en tant que classe déclarée, plutôt qu'un
`powerSwitch` détourné comme aujourd'hui.
3. **Une tension par phase réglable** indépendamment des puissances — pour exercer le correctif
amont ailleurs qu'en inversant la fonction.
> Aucune de ces trois n'est urgente. Ce qui l'est, c'est de **savoir qu'elles manquent** : sans
> cette liste, chaque lot repaiera le même temps à découvrir qu'un cas est introuvable.