Le §7 disait « des compteurs figés fabriquent de faux cas ». La fenêtre du 2026-09-08 (292 points par charge) donne le chiffre : σ = 1879 W sur la V2C, 0,0 W sur les trois autres. Aucun compteur du banc ne suit la commande de la charge qu'il mesure, sauf un. Ce n'est pas « des instruments imprécis » mais UN SEUL INSTRUMENT VALIDE SUR QUATRE, et la conséquence change de nature : toute conclusion tirée de ce banc sur l'OBÉISSANCE des charges est une conclusion sur cette borne, jamais sur le moteur. Le critère §6 rend le manque visible, il ne le comble pas — il écarte correctement les trois compteurs constants, sans nommer personne, sur la seule propriété observable de la donnée. Mais un tri qui garde un instrument sur quatre a trié, il n'a pas mesuré. De vrais SDM120 sur la PAC et l'ECS sont prévus en fin de semaine. C'est la condition d'EXISTENCE de l'étape 4, pas un confort : décider de rejouer une commande demande de savoir si des commandes se perdent, et la question n'est pas posable sur un banc qui ne mesure qu'une charge. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015F7G5VeaPVSMeVNjiGj36p
219 lines
13 KiB
Markdown
219 lines
13 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.~~ — OUVERT le 2026-09-02.** La classe
|
||
`dryRelay` a été ajoutée : elle commute, elle ne mesure pas. Une charge **saine et non mesurée**
|
||
est désormais fabricable, et `testHealthyButUnmeasuredDiffersFromFaulty` vérifie ce que rien ne
|
||
pouvait vérifier avant — que le moteur **distingue** « rien ne mesure cette charge » de « cette
|
||
charge est en panne », deux affirmations que le seul chemin disponible (un Thing manquant)
|
||
mélangeait.
|
||
|
||
> **Ce que l'ajout a révélé au passage** : `executeAction()` déréférençait le contrôleur HTTP de
|
||
> **chaque** Thing sans vérifier — le mock supposait que toute classe en possède un. Un relais sec
|
||
> n'en a pas besoin, n'ayant rien à mesurer. Le plantage était immédiat et net ; l'hypothèse, elle,
|
||
> était invisible tant qu'aucune classe ne la contredisait. **Le mock avait sa propre prémisse
|
||
> fausse**, protégée par son homogénéité.
|
||
|
||
**2. ~~Un compteur par phase incohérent.~~ — OUVERT le 2026-09-03.** `voltagePhaseX` et
|
||
`currentPhaseX` sont désormais réglables **indépendamment** des puissances, par des paramètres
|
||
OPTIONNELS — sans eux, le comportement dérivé d'origine est conservé et aucun scénario existant
|
||
ne bouge.
|
||
|
||
> **Et l'ouverture a corrigé une erreur de RAISONNEMENT, pas seulement comblé un trou.** Le
|
||
> premier test de `LM-1210-a` attendait qu'une phase en export donne une marge *supérieure* au
|
||
> plafond — c'est ce que le design affirmait. Il a échoué : `calculateAllowanceAmpere()` part du
|
||
> plafond et ne fait que des `qMin`, donc la marge y est **écrêtée**. Physiquement juste — l'export
|
||
> ne permet pas de tirer plus que le disjoncteur — mais la phrase du design était fausse. **La
|
||
> conclusion tenait, la raison non**, et rien ne pouvait le révéler tant que le déséquilibre était
|
||
> infabricable.
|
||
|
||
**3. ~~`temperature`.~~ — OUVERT le 2026-09-05.** La classe `thermalProbe` porte l'état, et
|
||
`testThermalProbeFeedsTelemetry` exerce enfin la branche « sonde présente » de
|
||
`attachMeasurements()`, écrite depuis toujours et **jamais parcourue**. C'était la seule entrée du
|
||
volet thermique, et rien ne l'exerçait.
|
||
|
||
> **Ce qu'il reste à dire, et qui n'est pas corrigé ici** : une sonde DÉSIGNÉE mais incapable —
|
||
> un Thing sans état `temperature` — produit exactement la même charge utile qu'aucune sonde du
|
||
> tout (`temperatureC` absent). Le geste de réparation, lui, diffère : désigner une sonde, ou en
|
||
> changer. C'est un silence de la famille `progressUnmeasurableCause`, relevé par le test et
|
||
> laissé ouvert : le fermer demande un catalogue de causes thermiques qui n'existe pas encore.
|
||
|
||
**4. ~~Une triphasée déséquilibrée.~~ — OUVERT le 2026-09-03.**
|
||
`testWorstPhaseBindsWhenPhasesAreUnbalanced` exerce enfin `LM-1210-a` : phase B saturée à 30 A
|
||
sous un plafond de 25 pendant que A et C exportent, et la maison **exporte globalement**. La
|
||
marge retenue est celle de B — négative — et non celle du total, qui rassurerait à tort.
|
||
|
||
Ce manque était nommé **deux fois par deux chemins indépendants** — cet inventaire et le balayage
|
||
des contrôles — avant d'être comblé. C'est ce qui lui a donné sa priorité.
|
||
|
||
## 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é.
|
||
|
||
**5. ~~Un compteur d'énergie FIGÉ.~~ — OUVERT le 2026-09-04.** `totalEnergyConsumed` n'était
|
||
écrit par personne : il valait sa valeur par défaut, indéfiniment. **Un compteur figé est un
|
||
compteur qui n'existe pas pour tout ce qui l'intègre** — `deliveredWh` restait à 0, `remainingWh`
|
||
à 4 000, et l'étalement adaptatif n'était **jamais exercé**. Le comportement se confondait avec
|
||
`unmeasurable` pour une raison entièrement différente et bénigne.
|
||
|
||
Le mock intègre désormais `currentPower` dans le cumul, une fois par seconde. **En export il ne
|
||
recule pas — il ne bouge pas** : un compteur de consommation est monotone, et le faire reculer
|
||
produirait un cas que le terrain ne produit pas, que la garde de sémantique traiterait comme une
|
||
anomalie.
|
||
|
||
**Et `LM-1014-b` s'y applique, question posée et répondue** : `totalEnergyConsumed` est déclaré
|
||
`cached`, donc il **continue** au redémarrage du mock. Repartir de zéro ferait reculer un cumul
|
||
monotone — le mock fabriquerait l'anomalie que le moteur est censé détecter.
|
||
|
||
**Quatrième occurrence.** Après les six états cohérents du `rootmeter`, les classes portant toutes
|
||
une puissance, et la triphasée équilibrée.
|
||
|
||
**6. ~~Le compteur sans énergie et la sonde thermique.~~ — OUVERTS le 2026-09-05**, et les deux
|
||
derniers de la liste. `meterNoEnergy` remplace le `powerSwitch` détourné en instrument de mesure —
|
||
un montage que personne ne fait, et qui faisait tenir DEUX rôles à la même Thing dans le même
|
||
scénario. `thermalProbe` ouvre le volet thermique.
|
||
|
||
> **Et l'ouverture a révélé un défaut, comme les trois précédentes.** `setupThing()` du mock
|
||
> n'avait **aucune sortie par défaut** : une classe sans branche ne rencontrait jamais de
|
||
> `info->finish()`. Le setup ne échouait pas — **il ne se terminait jamais**. Le symptôme est un
|
||
> Thing éternellement « en cours de configuration » et un test qui expire ailleurs, très loin de
|
||
> la cause.
|
||
>
|
||
> C'est le même mécanisme que le contrôleur déréférencé pour toute classe : **une hypothèse du
|
||
> mock que son homogénéité protégeait.** Tant que chaque classe avait sa branche, l'absence de
|
||
> sortie par défaut ne pouvait pas se voir. Corrigé : une classe oubliée se dit maintenant en
|
||
> `qCWarning` et rend `ThingErrorSetupFailed`.
|
||
>
|
||
> **Et une garde a mordu au passage** : la première version du test thermique lisait `loads[]`
|
||
> sans compteur racine — donc une liste vide, et trois assertions qui ne se seraient jamais
|
||
> exécutées. `QCOMPARE(parId.count(), 3)` l'a attrapé. C'est la parade écrite plus haut dans ce
|
||
> document, appliquée le jour même à son auteur.
|
||
|
||
## 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.~~ — **FAIT le
|
||
2026-09-05** : `meterNoEnergy`. Elle ne déclare PAS l'interface `energymeter`, qui exige
|
||
`totalEnergyConsumed` — promettre une interface qu'on ne tient pas ferait mentir le mock là où
|
||
il doit être littéral.
|
||
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.
|
||
|
||
---
|
||
|
||
## 7. L'homogénéité qui FABRIQUE un cas — 2026-09-05
|
||
|
||
Les six premières entrées décrivent des cas que le banc ne savait pas **produire**. Celle-ci est
|
||
l'inverse : un cas que le banc produit **en trop**, et qui n'existe pas ailleurs.
|
||
|
||
`ECS-Meter` publie 1500 W en permanence, `PAC-Meter` 800 W. Ce sont des simulateurs à valeur
|
||
fixe : ils ne suivent aucune consigne. Le journal de divergence a donc compté **14 épisodes en
|
||
24 h** qui ne sont pas des désobéissances, mais la distance entre une consigne variable et un
|
||
compteur constant.
|
||
|
||
**Un cas masqué se signale par un test qui ne mord pas. Un cas fabriqué se signale par des données
|
||
qui rassurent** — et rien, dans un compte juste, n'annonce de quelle question il est la réponse.
|
||
|
||
Le critère de tri est écrit avant les données : `docs/DESIGN_COMMANDE_PERDUE.md` §6.
|
||
|
||
**Ce qu'il faudrait au mock, et qui manque** : un compteur dont la valeur SUIT la commande de la
|
||
charge qu'il mesure. Aucune classe ne le fait aujourd'hui — les compteurs sont des sources
|
||
indépendantes qu'on pilote à la main. Tant que c'est le cas, aucun test ne peut exercer une charge
|
||
qui obéit, donc aucun ne peut distinguer l'obéissance de la désobéissance.
|
||
|
||
> **Nommé DEUX FOIS par deux chemins indépendants, le 2026-09-05 et le 2026-09-06** — et c'est ce
|
||
> qui lui donne sa priorité, comme au manque n° 4 avant lui.
|
||
>
|
||
> - Par les **traces de divergence** : quatorze épisodes qui mesurent l'écart entre une consigne
|
||
> variable et un compteur constant, et non une désobéissance.
|
||
> - Par la **pose d'une obligation éco** : sur un banc dont le compteur ne suit pas la commande,
|
||
> on ne peut pas avoir à la fois une cible modeste et un chemin d'achat longuement exercé. Ce
|
||
> qui borne n'est pas la cible, c'est la constance du compteur.
|
||
>
|
||
> Deux lots différents, deux symptômes sans rapport apparent, une seule cause. Quand un manque se
|
||
> fait nommer deux fois sans qu'on l'ait cherché, il ne s'agit plus d'un confort de test : il
|
||
> décide de ce que plusieurs lots pourront prouver.
|
||
|
||
---
|
||
|
||
## 8. Le sixième manque sous sa forme réelle — mesuré le 2026-09-08
|
||
|
||
Le §7 disait « des compteurs figés fabriquent de faux cas ». La fenêtre d'observation du
|
||
2026-09-08 (12:10 → 18:00, 292 points par charge) donne le chiffre, et il est plus net que la
|
||
formulation :
|
||
|
||
| charge | σ sur la fenêtre | min → max |
|
||
|---|---|---|
|
||
| **V2C Trydan** | **1 879 W** | 0 → 5 871 |
|
||
| chauffe-eau | **0,0 W** | 0 → 0 |
|
||
| pac-terrain | **0,0 W** | 0 → 0 |
|
||
| Simulated wallbox | **0,0 W** | 0 → 0 |
|
||
|
||
> **Aucun compteur du banc ne suit la commande de la charge qu'il mesure — sauf un.**
|
||
|
||
Ce n'est pas « des instruments imprécis ». C'est **un seul instrument valide sur quatre**, et il
|
||
faut le dire ainsi parce que la conséquence change de nature : toute conclusion tirée de ce banc
|
||
sur l'**obéissance** des charges est une conclusion **sur cette borne**, jamais sur le moteur.
|
||
|
||
**Le critère de tri du §6 de `DESIGN_COMMANDE_PERDUE.md` rend le manque visible ; il ne le
|
||
comble pas.** Il écarte correctement les épisodes des trois compteurs constants — sans nommer
|
||
personne, sur la seule propriété observable de la donnée. Mais un tri qui garde un instrument sur
|
||
quatre a **trié**, il n'a pas **mesuré**.
|
||
|
||
### Ce qui débloque, et c'est daté
|
||
|
||
De **vrais SDM120** sur la PAC et l'ECS, prévus en fin de semaine. Deux instruments de plus, et
|
||
le critère aurait enfin quelque chose à trier.
|
||
|
||
**C'est la condition d'existence de l'étape 4**, pas un confort : décider de rejouer une commande
|
||
demande de savoir si des commandes se perdent, et cette question n'est pas posable sur un banc
|
||
qui ne mesure qu'une charge.
|