fix(scheduler): ECS-309-b — le motif ne nomme un verrou que s'il a déplacé la consigne
Le critère était « consigne > budget ». Il est vrai dès que le budget passe sous zéro à palier nul — 0 > -389 — et le motif annonçait alors « Verrou minOn — chauffe-eau maintenue à 0 W (puissance engagée, budget -389 W) » : un verrou qui ne mordait pas, sur une puissance engagée nulle, quand le soutirage était la seule cause. Constaté au banc le 2026-08-13 sur +etm10, sept fois en quatre heures, toujours au cycle suivant un palier 0. Le défaut est antérieur à ECS-309 : celui-ci a ajouté la branche minOff après la branche minOn sans voir que la première capturait déjà un cas étranger. Corriger une branche ne dit rien de ses voisines. Le critère compare désormais la consigne appliquée à la consigne VOULUE avant écrêtage — la mémoire qu'ECS-309 avait justement introduite. Relevée par le verrou → minOn ; rabaissée → minOff ; inchangée → le budget. Les deux branches se lisent sur le même axe, et le motif nomme un mécanisme parce qu'il a agi, non parce qu'il aurait pu. Ce n'était pas qu'un défaut de lisibilité. La branche minOn étant évaluée en premier, elle masquait le motif d'armement à froid d'ECS-412 chaque fois que le budget était négatif au redémarrage — c'est-à-dire au fond du creux, précisément l'instant où il faut redémarrer pour observer minOff. La campagne du volet 2 aurait mesuré le mauvais verrou. Le test reproduit le cas du banc : palier 0, budget négatif, aucun verrou actif. Sans le correctif il produit le texte exact relevé sur la machine. Suite complète : 112 tests (34 simulation + 46 charging + 32 spotmarket), 0 échec. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
2305a6b825
commit
1ccea65ecd
@ -1,3 +1,24 @@
|
||||
powersync-energy-plugin-nymea (1.15.2+etm11) trixie; urgency=medium
|
||||
|
||||
* ECS-309-b — le motif ne nomme un verrou que si ce verrou a déplacé la consigne. Le
|
||||
critère était « consigne > budget », vrai dès que le budget passe sous zéro à palier
|
||||
nul : 0 > -389. Le motif annonçait alors « Verrou minOn — chauffe-eau maintenue à 0 W
|
||||
(puissance engagée, budget -389 W) » — un verrou qui ne mordait pas, sur une puissance
|
||||
engagée nulle, quand le soutirage était la seule cause. Le critère compare désormais la
|
||||
consigne appliquée à la consigne VOULUE avant écrêtage : relevée → minOn, rabaissée →
|
||||
minOff, inchangée → le budget. Les deux branches se lisent sur le même axe.
|
||||
* Constaté au banc le 2026-08-13 sur +etm10, sept fois en quatre heures, toujours au
|
||||
cycle suivant un palier 0. Le défaut est antérieur à ECS-309, qui a ajouté la branche
|
||||
minOff après lui sans voir que la première capturait déjà un cas étranger.
|
||||
* Il ne s'agissait pas que de lisibilité. La branche minOn étant évaluée en premier, ce
|
||||
défaut masquait le motif d'armement à froid d'ECS-412 chaque fois que le budget était
|
||||
négatif au redémarrage — soit au fond du creux, précisément l'instant où il faut
|
||||
redémarrer pour observer minOff. La campagne aurait mesuré le mauvais verrou.
|
||||
* Suite complète : 112 tests, 0 échec. Le cas est vérifié échouant sans le correctif,
|
||||
où il reproduit le texte exact relevé sur la machine.
|
||||
|
||||
-- Patrick Schurig <etm.schurig@gmail.com> Thu, 13 Aug 2026 08:00:00 +0200
|
||||
|
||||
powersync-energy-plugin-nymea (1.15.2+etm10) trixie; urgency=medium
|
||||
|
||||
* ECS-309 — motif de décision fidèle au mécanisme. Lorsqu'un verrou, et non le budget,
|
||||
|
||||
@ -254,7 +254,14 @@ LoadAction RuleBasedScheduler::buildSetpointAction(const LoadContext &lc,
|
||||
// vrai. Un motif faux envoie diagnostiquer le mauvais problème, avec l'autorité d'une
|
||||
// explication — constaté au banc le 2026-08-09 : palier 0 avec 7706 W disponibles, et
|
||||
// « Surplus insuffisant (7706 W) » publié.
|
||||
if (setpointW > budgetW)
|
||||
// ECS-309-b — le critère est ce que le verrou a FAIT à la consigne voulue, pas une
|
||||
// comparaison au budget. « setpointW > budgetW » nommait minOn dès que le budget était
|
||||
// négatif à palier nul : 0 > −389 est vrai, et le motif annonçait « maintenue à 0 W
|
||||
// (puissance engagée) » alors que rien n'était engagé et que le soutirage était la seule
|
||||
// cause. Constaté au banc le 2026-08-13, sept fois en quatre heures, toujours au cycle
|
||||
// suivant un palier 0. Les deux branches se lisent maintenant sur le même axe :
|
||||
// le verrou a relevé la consigne (minOn), ou il l'a rabaissée (minOff).
|
||||
if (setpointW > vouluW)
|
||||
la.reason = QStringLiteral("Verrou minOn — %1 maintenue à %2 W (puissance engagée, "
|
||||
"budget %3 W) ; le résidu en tient compte")
|
||||
.arg(lc.label).arg(qRound(setpointW)).arg(qRound(budgetW));
|
||||
|
||||
@ -248,6 +248,30 @@ Trois cas DOIVENT être distinguables à la lecture du texte :
|
||||
| verrou `minOn` maintenant un palier **supérieur** au budget | le verrou `minOn`, et le palier qu'il retient |
|
||||
| verrou `minOff` empêchant l'enclenchement | le verrou `minOff`, et l'interdiction de monter |
|
||||
|
||||
**ECS-309-b — Le critère est l'effet du verrou sur la consigne, pas une comparaison
|
||||
au budget.** Le motif NE DOIT nommer un verrou que lorsque ce verrou a effectivement
|
||||
déplacé la consigne. Il DOIT donc se déduire de la comparaison entre la consigne
|
||||
appliquée et la consigne **voulue** avant écrêtage : relevée → `minOn`, rabaissée →
|
||||
`minOff`, inchangée → le budget. Comparer la consigne au **budget** ne distingue pas
|
||||
ces cas.
|
||||
|
||||
> Défaut constaté au banc le 2026-08-13, sur `1.15.2+etm10`, **sept fois en quatre
|
||||
> heures**, systématiquement au cycle suivant un palier 0 : `« Verrou minOn —
|
||||
> chauffe-eau maintenue à 0 W (puissance engagée, budget -389 W) »`. Le test
|
||||
> `setpointW > budgetW` est vrai dès que le budget est négatif à palier nul — `0 >
|
||||
> -389` — et le motif annonçait alors un verrou qui ne mordait pas, sur une
|
||||
> « puissance engagée » de 0 W, quand le soutirage était la seule cause.
|
||||
>
|
||||
> Ce critère est **antérieur à ECS-309**, qui ne l'a pas introduit mais ne l'a pas
|
||||
> audité non plus : ECS-309 a ajouté la branche `minOff` *après* lui, sans voir que
|
||||
> la première branche capturait déjà un cas qui ne lui appartenait pas.
|
||||
>
|
||||
> **Conséquence sur le protocole de mesure.** La branche `minOn` étant évaluée avant
|
||||
> celle de `minOff`, ce défaut masquait le motif d'armement à froid d'ECS-412 chaque
|
||||
> fois que le budget était négatif au moment du redémarrage — c'est-à-dire au fond du
|
||||
> creux, précisément là où il faut redémarrer pour observer `minOff`. Il fallait donc
|
||||
> le corriger **avant** la campagne, non après.
|
||||
|
||||
**Cas observé au banc (2026-08-09)**, pendant la fenêtre d'armement à froid
|
||||
d'ECS-412 : palier plafonné à 0 avec **7706 W de surplus disponible**, et motif
|
||||
publié « Surplus insuffisant (7706 W) ». La décision était juste, le motif disait
|
||||
@ -695,6 +719,7 @@ mécanique reste **L0**. Perte de sonde → mode sans sonde, pas arrêt d'urgenc
|
||||
|---|---|---|
|
||||
| ECS-306 | simulation | `testEcsBudgetUnderLock` |
|
||||
| ECS-309 | simulation | `testEcsColdStartLockExpires` — les trois causes distinguées par le TEXTE du motif (minOff / budget / minOn), vérifié échouant sans le correctif |
|
||||
| ECS-309-b | simulation | `testEcsColdStartLockExpires` §4 — palier 0 + budget négatif SANS verrou actif : le motif nomme le budget, jamais `minOn`. Vérifié échouant sans le correctif, où il reproduit le texte exact du banc |
|
||||
| ECS-412 | simulation | `testEcsRebuildPreservesLock` + **`testEcsColdStartLockExpires`** (armement à froid transitoire) |
|
||||
| ECS-303 | banc | relevé de commutations à la frontière de recombinaison (§4.0) — **mesurer avant de coder** |
|
||||
| ECS-305 | unitaire | `testEcsSwitchCount` — compteur par relais |
|
||||
@ -777,6 +802,9 @@ protocole dans le même lot.**
|
||||
| 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-09 | ECS-412 précisé : cet armement est **TRANSITOIRE** et posé paresseusement au premier `now`. Défaut de blocage circulaire constaté **au banc**, corrigé, couvert par `testEcsColdStartLockExpires` |
|
||||
| 2026-08-13 | **ECS-309-b créé et FAIT** — le critère du motif est l'effet du verrou sur la consigne voulue, pas une comparaison au budget. `setpointW > budgetW` nommait `minOn` à palier nul dès que le budget était négatif ; constaté au banc sept fois en quatre heures sur `+etm10`. Antérieur à ECS-309, non audité par lui. Masquait le motif d'armement à froid d'ECS-412 au fond du creux — donc corrigé avant la campagne du volet 2, pas après |
|
||||
| 2026-08-13 | **Journal du banc `.75` rendu persistant** — Raspberry Pi OS force `Storage=volatile` (`40-rpi-volatile-storage.conf`) : le journal vivait en RAM, plafonné à 8,3 Mo, et la fenêtre d'armement à froid du 12 août avait été rotée avant d'être lue. Drop-in `99-etm-retention.conf` (200 Mo, 1 mois) + `journalctl --flush`, et `ThingManager.debug` coupé — 137 → 25 lignes/min, soit ~6,5 Mo/jour sur la carte SD |
|
||||
| 2026-08-13 | **ECS-411 vérifié sur le chemin réel** — au redémarrage de 06:15, `« [RelayRouter] chauffe-eau — palier repris au démarrage: 2500 W »`. La relecture des relais au démarrage fonctionne sur la machine, pas seulement en simulation |
|
||||
| 2026-08-11 | **ECS-309 créé et FAIT** — motif fidèle au mécanisme. Le chemin minOff attribuait l'issue au budget ; le motif nomme désormais le verrou et cite le budget DISPONIBLE pour couper court à l'explication budgétaire. Les cas minOn et `minStateHold` étaient déjà fidèles |
|
||||
| 2026-08-10 | **Va-et-vient RPC cassé par LM-300** — `GetLoadConfig` sérialise via le méta-objet, donc un `sgReady` VIDE sur toute charge non SG-Ready ; le schéma SET exigeait `states` dès la clé présente, et nymea rejetait avant le handler. Constaté au banc à la première tentative réelle de `SetLoadConfig` : aucun test ne rejouait le geste de l'app (lire, modifier, réécrire). Corrigé en `o:states` — c'est le mur documenté en LM-302, la validation réelle restant dans `isValid()` |
|
||||
| 2026-08-09 | **ECS-110-b créé et FAIT** — un Thing n'appartient qu'à une charge active. Trou trouvé en répondant à la question « le cas général est-il couvert ? » : il ne l'était pas du tout. `LoadConfigStore::validateSet()` ; doublons intra-charge également refusés |
|
||||
|
||||
@ -1189,6 +1189,20 @@ void Simulation::testEcsColdStartLockExpires()
|
||||
const QString mOn = motifPour(r4, -400, t0.addSecs(10)); // import 400 W → budget 600 < 1000
|
||||
QVERIFY2(mOn.contains("minOn"), qUtf8Printable("motif: " + mOn));
|
||||
|
||||
// 4. [ECS-309-b] Palier 0 et budget NÉGATIF, sans aucun verrou actif. Le soutirage est
|
||||
// la seule cause ; nommer minOn ici est un mensonge à double titre — aucun verrou ne
|
||||
// mord, et « puissance engagée » désigne 0 W. Le critère « setpointW > budgetW »
|
||||
// l'affirmait pourtant, 0 > −389 étant vrai. Constaté au banc le 2026-08-13, sept
|
||||
// fois en quatre heures, toujours au cycle suivant un palier 0.
|
||||
// Sans le correctif ce cas produit le texte exact du banc :
|
||||
// « Verrou minOn — ... maintenue à 0 W (puissance engagée, budget -389 W) ».
|
||||
const QString mSoutirage = motifPour(r3, -389, t0);
|
||||
QVERIFY2(mSoutirage.contains("insuffisant", Qt::CaseInsensitive),
|
||||
qUtf8Printable("motif: " + mSoutirage));
|
||||
QVERIFY2(!mSoutirage.contains("Verrou"), qUtf8Printable("motif: " + mSoutirage));
|
||||
QVERIFY2(!mSoutirage.contains("puissance engagée"), qUtf8Printable("motif: " + mSoutirage));
|
||||
QVERIFY2(mSoutirage.contains("-389"), qUtf8Printable("motif: " + mSoutirage));
|
||||
|
||||
// Les trois motifs sont distincts : c'est la lisibilité qu'ECS-309 exige.
|
||||
QVERIFY(mOff != mBudget && mBudget != mOn && mOff != mOn);
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user