docs(spec): spec_ecs 0.5.1 — câblage réel 500/1000/1500, les trois consignations s'inversent

La 0.5.0 consignait 500/1000/2000 sur la foi de la consigne d'étape 1. Le
simulateur du banc contredit cette donnée ; arbitrage : le simulateur reflète
l'installation, la donnée était fausse.

Câblage : 500/1000/1500 W, additif, 7 valeurs distinctes de 0 à 3000 W, avec UNE
redondance — 1500 W admet {R1500} et {R500+R1000}.

Trois corrections :

1. ECS-302 est EXERCÉ. Il redevient préalable structurel de l'étape 3 et reprend
   son rang devant tout ce qui s'appuiera sur m_relayMapping.

2. ECS-303 SUPPRIMÉ. Le « point dur » 1500→2000 n'est pas une exigence
   d'hystérésis, c'est une conséquence directe d'ECS-302 non traité :
   relayrouter.cpp:58 retient la première combinaison rencontrée (masque le plus
   bas), donc {R500+R1000} pour 1500 W, et jette {R1500}. La transition vers
   2000 W = {R500+R1500} demande alors d'ouvrir R1000 et de fermer R1500, avec le
   creux qu'impose l'ordre coupure-avant-fermeture. Avec {R1500} pour 1500 W, la
   même transition ne coûterait QU'UNE commutation : fermer R500. Aucune entrée
   ECS-303 ni ECS-605 de ce côté.

3. 7 valeurs et plafond 3000 W, non 8 et 3500. ECS-305 reste valable, comptes
   corrigés : par balayage complet R500 commute 5 fois, R1000 3 fois, R1500 une
   seule (vérifié sur l'encodage effectivement retenu par le code actuel).

RÉSERVE INSCRITE : la source est le simulateur (sim/sim_ecs_router.py:38, sommé
dans _power_w(), confirmé en vol sur MQTT), PAS une mesure physique. Ce chiffre a
déjà changé trois fois et il commande désormais l'ordre de l'étape 3 : il doit
être confirmé sur plaque signalétique ou au pince ampèremétrique AVANT
l'ouverture de cette étape. Consigné en §13-3 et en garde-fou sous l'étape 3.

Aucun code touché.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Patrick Schurig 2026-08-08 12:15:40 +02:00
parent e7a5c7fbab
commit 985a7641bd

View File

@ -1,6 +1,6 @@
# SPEC — ECS multi-palier # SPEC — ECS multi-palier
Version : 0.5.0 Version : 0.5.1
Dépôt : `etm-powersync-energy-plugin-etm` (experience-plugin ems) Dépôt : `etm-powersync-energy-plugin-etm` (experience-plugin ems)
Branche : `feature/beta-rulebased` Branche : `feature/beta-rulebased`
Contrats faisant autorité : `docs/OPTIMIZER_PROTOCOL.md`, `docs/SAFETY.md`, `AGENTS.md` Contrats faisant autorité : `docs/OPTIMIZER_PROTOCOL.md`, `docs/SAFETY.md`, `AGENTS.md`
@ -33,8 +33,13 @@ remonte. Il ne tranche pas seul.
> >
> **0.5.0** — §13-1 et §13-2 **tranchés et consignés** ; l'étape 1 est débloquée. > **0.5.0** — §13-1 et §13-2 **tranchés et consignés** ; l'étape 1 est débloquée.
> ECS-412 réduit à `m_lastSwitch` (lecture (b)) et redirigé vers sa cause racine. > ECS-412 réduit à `m_lastSwitch` (lecture (b)) et redirigé vers sa cause racine.
> Câblage du banc confirmé **500/1000/2000** : ECS-302 n'est plus exercé par ce >
> matériel, ECS-305 monte, ECS-303 créé pour le point dur 1500→2000 W. > **0.5.1 — CORRECTION du câblage.** La 0.5.0 consignait 500/1000/**2000** ;
> c'était faux. Le câblage réel est **500/1000/1500**. Trois consignations
> s'inversent : ECS-302 **est** exercé et redevient préalable structurel de
> l'étape 3 ; **ECS-303 est supprimé** (le « point dur » était une conséquence
> d'ECS-302 non traité, pas une exigence d'hystérésis) ; 7 valeurs et plafond
> 3000 W, non 8 et 3500. Ne pas se fier à une copie 0.5.0.
--- ---
@ -159,8 +164,14 @@ unitaire et conditionne l'étape 3.
> parle à aucun matériel. L'étape 2 ne sort **aucune** classe de ce genre : elle > parle à aucun matériel. L'étape 2 ne sort **aucune** classe de ce genre : elle
> extrait le noyau de calcul **interne au mécanisme relais**. Rien à voir. > extrait le noyau de calcul **interne au mécanisme relais**. Rien à voir.
**Étape 3 — ECS-302 / ECS-305.** Changement de structure de `m_relayMapping`. **Étape 3 — ECS-302 / ECS-305.** Changement de structure de `m_relayMapping` :
Doit précéder tout ce qui s'appuiera dessus. un palier doit pouvoir porter PLUSIEURS combinaisons. **Préalable structurel** —
doit précéder tout ce qui s'appuiera dessus. Exercé par le câblage réel
(1500 W a deux encodages, §4).
> **Ne pas ouvrir l'étape 3 avant confirmation physique du câblage** (§13-3) :
> c'est lui qui justifie l'ordre de cette étape, et il provient aujourd'hui du
> simulateur, pas d'une mesure.
**Étape 4 — ECS-410** (échec d'écriture). ECS-411 a été **remonté en étape 1** **Étape 4 — ECS-410** (échec d'écriture). ECS-411 a été **remonté en étape 1**
(§13-2). ECS-410 reste ici : il touche le même chemin `applyRelayStage` / (§13-2). ECS-410 reste ici : il touche le même chemin `applyRelayStage` /
@ -250,44 +261,47 @@ fenêtre de verrou active ».
## §4 — Combinatoire (`RelayRouter`) ## §4 — Combinatoire (`RelayRouter`)
> **Câblage du banc CONFIRMÉ le 2026-08-08 : 500 / 1000 / 2000 W.** Pondération > **Câblage CONFIRMÉ le 2026-08-08 : 500 / 1000 / 1500 W.** Additif (les relais
> binaire — huit valeurs distinctes de 0 à 3500 W au pas de 500 W, **aucune > se somment), **7 valeurs distinctes** de 0 à **3000 W**, avec **une
> redondance**, un seul encodage par palier. > redondance** : 1500 W a DEUX encodages.
> >
> | Palier | Relais ON | W | > | W | Encodage(s) |
> |---|---|---| > |---|---|
> | 0 | — | 0 | > | 0 | — |
> | 1 | 500 | 500 | > | 500 | R500 |
> | 2 | 1000 | 1000 | > | 1000 | R1000 |
> | 3 | 500+1000 | 1500 | > | **1500** | **R1500** *ou* **R500+R1000** ← deux encodages |
> | 4 | 2000 | 2000 | > | 2000 | R500+R1500 |
> | 5 | 500+2000 | 2500 | > | 2500 | R1000+R1500 |
> | 6 | 1000+2000 | 3000 | > | 3000 | R500+R1000+R1500 |
> | 7 | 500+1000+2000 | 3500 |
> >
> Ceci clôt le point ouvert « 500/1000/1500 ou 500/1000/2000 » et déplace trois > **Source : le simulateur du banc** (`etm-powersync-hems-sim`,
> exigences — voir ECS-302, ECS-305 et ECS-303 ci-dessous. > `sim/sim_ecs_router.py:38` — `PALIERS = {1: 500.0, 2: 1000.0, 3: 1500.0}`,
> sommés dans `_power_w()`), confirmé en vol sur MQTT (relais 2+3 fermés →
> 2500 W publiés, ce qu'une cascade ne produirait pas). **Ce n'est PAS une
> mesure physique.** Cette valeur a déjà changé trois fois et elle commande
> désormais l'ordre de l'étape 3 : elle DOIT être confirmée sur plaque
> signalétique ou au pince ampèremétrique **avant l'ouverture de l'étape 3**.
**ECS-302 — Encodages équivalents. NON EXERCÉ PAR CE MATÉRIEL.** Avec une **ECS-302 — Encodages équivalents. EXERCÉ PAR CE MATÉRIEL.** 1500 W admet
pondération binaire il n'y a **rien à arbitrer** : un seul encodage par palier. `{R1500}` et `{R500+R1000}` : il y a bien un arbitrage à rendre. L'exigence
L'exigence reste valable pour un câblage 1-2-3 ailleurs (deux encodages pour **redevient un préalable structurel de l'étape 3** et reprend son rang **devant**
1500 W), mais **le changement de structure de `m_relayMapping` perd sa tout ce qui s'appuiera sur `m_relayMapping`.
justification immédiate** : il redescend dans l'ordre de l'étape 3 et **cesse
d'être un préalable structurel**. Ne plus le traiter comme tel.
**ECS-303 — Point dur 1500 → 2000 W.** Cette transition bascule **les trois Ce que fait le code aujourd'hui, et ce que ça coûte : le constructeur retient la
relais d'un coup** (R500 et R1000 s'ouvrent, R2000 se ferme). Avec l'ordre **première** combinaison rencontrée, c'est-à-dire le masque le plus bas
coupure-avant-fermeture d'ECS-402, elle produit un **trou de puissance à chaque (`relayrouter.cpp:58`, `if (!byPower.contains(sum))`). Pour 1500 W il retient
franchissement**, en plein milieu de la plage de surplus utile. donc `{R500+R1000}` et **jette `{R1500}`**. Conséquence directe sur la transition
1500 → 2000 W (`{R500+R1500}`) : il faut ouvrir R1000, garder R500, fermer
R1500 — et comme l'encodage retenu impose de passer par l'ordre
coupure-avant-fermeture d'ECS-402, la charge traverse un creux. Avec l'encodage
`{R1500}` pour 1500 W, la même transition ne demanderait qu'**une seule
commutation** : fermer R500.
La parade n'est PAS dans la combinatoire — aucun réordonnancement ne supprime le > **Il n'y a donc pas d'exigence d'hystérésis à ouvrir de ce côté.** Le « point
trou, puisque aucun sous-ensemble intermédiaire ne vaut ni 1500 ni 2000 W. Elle > dur 1500→2000 » qu'envisageait la 0.5.0 est une **conséquence d'ECS-302 non
est dans un **seuil d'hystérésis élargi à cette frontière précise**. Entrée > traité**, pas un phénomène propre au câblage. Aucune entrée pour ECS-303 (créé
commune avec ECS-605 (hystérésis paramétrable par charge). **À MESURER au banc > puis **supprimé** en 0.5.1) ni pour ECS-605. Un palier DOIT pouvoir porter **plusieurs**
avant de coder quoi que ce soit** : c'est le relevé de commutations qui dira
quelle largeur donner au seuil.
**ECS-302 (suite) — Encodages équivalents.** Un palier DOIT pouvoir porter **plusieurs**
combinaisons de relais, et la combinaison retenue DOIT être celle dont le coût de combinaisons de relais, et la combinaison retenue DOIT être celle dont le coût de
transition depuis l'état courant est le plus faible : d'abord le nombre de relais transition depuis l'état courant est le plus faible : d'abord le nombre de relais
à basculer, puis le relais dont le compteur de commutations est le moins entamé. à basculer, puis le relais dont le compteur de commutations est le moins entamé.
@ -301,15 +315,17 @@ rencontrée gagne, c'est-à-dire le masque le plus bas. Sur un câblage
porter deux combinaisons. ECS-302 est donc un changement de structure, pas porter deux combinaisons. ECS-302 est donc un changement de structure, pas
l'ajout d'un critère. l'ajout d'un critère.
**ECS-305 — Compteurs de commutations. IMPORTANCE RELEVÉE** par le câblage **ECS-305 — Compteurs de commutations.** Chaque relais DOIT porter un compteur
binaire confirmé. Chaque relais DOIT porter un compteur de commutations, exposé de commutations, exposé en télémétrie.
en télémétrie.
Sur un balayage complet des huit paliers, **R500 commute sept fois, R1000 trois Sur un balayage complet des sept paliers (0 → 3000 W), avec l'encodage retenu
fois, R2000 une seule** : le bit de poids faible s'use sept fois plus vite que le aujourd'hui : **R500 commute 5 fois, R1000 3 fois, R1500 une seule**. Le relais
bit de poids fort. Le comptage par relais devient **la seule façon de savoir de plus faible puissance s'use cinq fois plus vite que le plus gros. Le comptage
quand le contacteur 500 W arrivera en fin de vie** — la puissance commutée, elle, par relais est **la seule façon de savoir quand le contacteur 500 W arrivera en
ne le dit pas. fin de vie** — la puissance commutée, elle, ne le dit pas.
\note Ces comptes dépendent de l'encodage choisi : traiter ECS-302 les modifie.
Raison de plus pour instrumenter avant d'optimiser.
**ECS-307 — Grain des temporisations.** `minOn`/`minOff` sont aujourd'hui **par **ECS-307 — Grain des temporisations.** `minOn`/`minOff` sont aujourd'hui **par
charge** (un unique `m_lastSwitch`, `relayrouter.h:107`), ce qui satisfait charge** (un unique `m_lastSwitch`, `relayrouter.h:107`), ce qui satisfait
@ -446,9 +462,8 @@ mécanique reste **L0**. Perte de sonde → mode sans sonde, pas arrêt d'urgenc
|---|---|---| |---|---|---|
| ECS-306 | simulation | `testEcsBudgetUnderLock` | | ECS-306 | simulation | `testEcsBudgetUnderLock` |
| ECS-412 | simulation | `testEcsRebuildPreservesLock` | | ECS-412 | simulation | `testEcsRebuildPreservesLock` |
| ECS-303 | banc, puis simulation | relevé de commutations à la frontière 1500/2000 — **mesurer avant de coder** |
| ECS-305 | unitaire | `testEcsSwitchCount` — compteur par relais | | ECS-305 | unitaire | `testEcsSwitchCount` — compteur par relais |
| ECS-302 | unitaire | `testEcsSwitchCost` — **non exerçable sur le câblage 500/1000/2000** (encodage unique) | | ECS-302 | unitaire | `testEcsSwitchCost` — **exerçable** : 1500 W = `{R1500}` ou `{R500+R1000}` |
| ECS-304 | unitaire | `testEcsLevelsPure` | | ECS-304 | unitaire | `testEcsLevelsPure` |
| ECS-410 | simulation | `testEcsPartialFailure` | | ECS-410 | simulation | `testEcsPartialFailure` |
| ECS-411 | simulation | `testEcsRestartRecovery` | | ECS-411 | simulation | `testEcsRestartRecovery` |
@ -521,7 +536,10 @@ protocole dans le même lot.**
| 2026-08-08 | **§13-2 CLOS, lecture (b)** — ECS-412 réduit à `m_lastSwitch` + cause racine (rebuild incrémental) ; ECS-411 remonté en étape 1 | | 2026-08-08 | **§13-2 CLOS, lecture (b)** — ECS-412 réduit à `m_lastSwitch` + cause racine (rebuild incrémental) ; ECS-411 remonté en étape 1 |
| 2026-08-08 | **Étape 1 OUVERTE** — trois exigences : ECS-306, ECS-411, ECS-412 | | 2026-08-08 | **Étape 1 OUVERTE** — trois exigences : ECS-306, ECS-411, ECS-412 |
| 2026-08-08 | ECS-412 : au démarrage à froid, le verrou est **ARMÉ** (défaut sûr), jamais purgé | | 2026-08-08 | ECS-412 : au démarrage à froid, le verrou est **ARMÉ** (défaut sûr), jamais purgé |
| 2026-08-08 | Câblage banc confirmé **500/1000/2000** — ECS-302 non exercé, ECS-305 relevé, **ECS-303 créé** (point dur 1500→2000) | | 2026-08-08 | ~~Câblage 500/1000/2000~~ — **donnée fausse**, corrigée le jour même (cf. ligne suivante) |
| 2026-08-08 | **Câblage 500/1000/1500** — 7 valeurs, plafond 3000 W, 1500 W à deux encodages. **Source : le simulateur** (`sim/sim_ecs_router.py:38`), **PAS une mesure** — à confirmer sur plaque ou au pince avant l'ouverture de l'étape 3. Troisième volte-face sur ce chiffre, et il commande désormais l'ordre de l'étape 3 |
| 2026-08-08 | **ECS-302 exercé** — redevient préalable structurel de l'étape 3, devant tout ce qui s'appuie sur `m_relayMapping` |
| 2026-08-08 | **ECS-303 SUPPRIMÉ** — le « point dur » 1500→2000 est une conséquence d'ECS-302 non traité, pas une exigence d'hystérésis. Aucune entrée ECS-605 de ce côté |
--- ---
@ -529,11 +547,13 @@ protocole dans le même lot.**
1. **ECS-307** — grain des temporisations par relais : nécessaire, ou le compteur 1. **ECS-307** — grain des temporisations par relais : nécessaire, ou le compteur
de commutations suffit-il ? Le câblage binaire rend la question plus concrète de commutations suffit-il ? Le câblage binaire rend la question plus concrète
(R500 commute 7× plus que R2000), sans la trancher. (R500 commute 5× plus que R1500), sans la trancher.
2. **ECS-303 / ECS-605** — largeur de l'hystérésis à la frontière 1500/2000 W, 2. **ECS-605** — asymétrie de l'hystérésis montée/descente, à régler à l'usage.
et asymétrie montée/descente. **À mesurer au banc avant de coder** : c'est le Sans rapport avec la frontière 1500/2000 : celle-ci relève d'ECS-302.
relevé de commutations qui donne le chiffre. 3. **Câblage à confirmer physiquement** — 500/1000/1500 provient du simulateur,
3. **Conflit d'écriture** — `connectIO()` est le mécanisme natif si l'on veut un pas d'une mesure. À vérifier sur plaque ou au pince ampèremétrique **avant
l'ouverture de l'étape 3**, dont il commande l'ordre.
4. **Conflit d'écriture** — `connectIO()` est le mécanisme natif si l'on veut un
jour exposer un relais ECS au pilotage manuel de l'utilisateur. Ce serait jour exposer un relais ECS au pilotage manuel de l'utilisateur. Ce serait
alors un conflit d'écriture à arbitrer, pas une aide. alors un conflit d'écriture à arbitrer, pas une aide.
@ -548,8 +568,9 @@ protocole dans le même lot.**
ECS-411 remonte en étape 1. L'étape 1 porte donc trois exigences. ECS-411 remonte en étape 1. L'étape 1 porte donc trois exigences.
`testEcsRebuildPreservesLock` couvre obligatoirement « `SetLoadConfig` pendant `testEcsRebuildPreservesLock` couvre obligatoirement « `SetLoadConfig` pendant
une fenêtre de verrou active ». une fenêtre de verrou active ».
- **Câblage du banc** — confirmé **500/1000/2000 W** (cf. §4). ECS-302 n'est pas - **Câblage du banc** — **500/1000/1500 W**, source simulateur (cf. §4, et la
exercé par ce matériel ; ECS-305 monte en importance ; ECS-303 créé. réserve : à confirmer physiquement avant l'étape 3). ECS-302 **est** exercé et
redevient préalable structurel de l'étape 3.
**CLOS par l'audit** : le waterfall ne choisit pas entre encodages équivalents **CLOS par l'audit** : le waterfall ne choisit pas entre encodages équivalents
(il n'en voit qu'un) ; `ioConnections()`/`connectIO()` est un lien 1↔1 (il n'en voit qu'un) ; `ioConnections()`/`connectIO()` est un lien 1↔1