3 Commits

Author SHA1 Message Date
Patrick Schurig
6481515b64 fix(moteur): « acheté » compté sur l'énergie mesurée, pas sur la consigne (correctif c)
`noterAchatEco()` intégrait la CIBLE éco avant palier et verrou : sur un relay-router, une
cible de 167 ou 495 W laisse le relais ouvert et ces watts étaient comptés achetés ; sous un
optimiseur, l'ombre rule-based notait sa propre cible, jamais exécutée.

Règle (choix par défaut du brief) : achatWh += ΔE × partÉco, ΔE = énergie MESURÉE de la charge
depuis le cycle précédent (sessionWh), partÉco = min(1, Σ targetW « eco » / estimatedPowerW)
de l'action APPLIQUÉE au cycle précédent (m_actionsAppliquees, lue avant le dispatch). Niveau
unique `optimizer`, charge délestée ou rien d'appliqué : part 0. Un recul ne contribue jamais.

- Sans mesure d'énergie : purchasedWh ABSENT (EcoProgress::purchased), jamais 0 ni la commande.
  Non borné par l'import du site : attribution ambiguë avec plusieurs charges.
- Comptage à chaque cycle, AVANT ecoProgress() : la valeur publiée inclut l'intervalle écoulé,
  et l'intervalle qui enjambe l'échéance va à la période close (base gardée à la bascule).
- Aucune décision ne lit purchasedWh : consignes inchangées. update() inchangé.
- INTERFACE.md : contrat réécrit (« publié dans TOUS les régimes » retiré).
- Recorder `decisions` : purchasedWh absent sans mesure, comme la RPC.
- Tests : testPurchasedIsCountedOnTheMeasuredEnergy (simulation, cas i-vi) et
  testThePlanOfAnOptimizerBuysNothingEco (socketscheduler, cas vii).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 01:51:21 +02:00
Patrick Schurig
e5c649e69a test(décisions): horloge murale figée au démarrage de chaque processus (brief 4 T0.5)
Constaté le 2026-10-16 : sur un code inchangé, la vérification échouait une heure après la
génération de la référence. testTheLearningLogIsFedByTheCycle date ses entrées sur
QDateTime::currentDateTimeUtc() (l'heure courante plus trois jours) : la référence ne valait
que dans l'heure UTC où elle avait été produite.

L'enregistreur définit clock_gettime : avec ETM_DECISIONS_EPOCH, CLOCK_REALTIME part de cet
instant au premier appel puis avance au rythme réel ; les horloges monotones (QTimer,
QElapsedTimer) ne sont pas touchées. Le lanceur pose 2026-10-16T05:00:00Z, l'heure où la
référence a été générée : la référence commitée n'est pas régénérée, elle est reproduite à
l'octet (md5 ea2c1db7…, 23 739 lignes).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 14:18:09 +02:00
Patrick Schurig
d4b7d4dced test(décisions): référence au bit près des scénarios de simulation (brief 4 T0.5)
Preuve « sans endpoint = identique au bit près », posée AVANT tout refactoring.

`nymea-energy-decisions` est la suite `simulation` compilée à l'identique, plus un
enregistreur branché à l'édition de liens (`--wrap`) : aucune ligne de la suite ni du
moteur n'est modifiée. Chaque cycle (`simulationCallUpdate`) écrit une ligne NDJSON :
télémétrie de l'arbitre (actions du créneau dans l'ordre, motifs, niveaux, budget, soutirage,
consigne posée), actions laissées au proxy, état des Things. Les scénarios qui interrogent
un `RuleBasedScheduler` local voient leur `Plan` sérialisé champ par champ.

Exclus : `command.since` et les compteurs `totalEnergy*` du mock (horloge murale), les
ThingId (alias par rang dans le créneau, à chaque ligne : deux bornes identiques
reçoivent leur rang provisionné dans l'ordre d'un QHash). `planId` n'est pas lu.

Référence générée sur 83483cc (1.15.2+etm69) : 66 fonctions, 23 673 cycles + 36 plans
directs, deux exécutions identiques à l'octet. Chaque processus a un /tmp privé : libnymea
range l'état de test sous /tmp/nymea-test, partagé et cumulatif (348 bornes orphelines
trouvées dans energy-load-configuration.json).

  tests/auto/decisions/run-decisions.sh <build> check

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 07:24:00 +02:00