Patrick Schurig 6fcce1a470 feat(tools): etm-powersync-ref-optimizer, optimiseur de référence 1.1 (brief 4 T7, annexe E)
Python 3, bibliothèque standard seule, GPL-3.0. Socket unix par défaut
(unix:///run/powersync/optimizer.sock, 0660), tcp:// accepté. Handshake (majeure, capacités,
token lu dans un fichier et relu à chaque handshake, -32001 puis fermeture s'il diffère),
GetPlan, abstention, plusieurs clients à la fois (un fil par connexion, plan fonction pure de
la requête).

Plan à un créneau qui reproduit la passe confort du waterfall rule-based depuis le seul
contexte GetPlan : tri (rang, id), surplus net signé, recrédit, réserve de stockage, plancher,
plafond, verrous, mapping SG-Ready avec hystérésis. Codes du catalogue émissible seulement,
clés documentées seulement (--list-codes, confronté au moteur par le test).

Modes d'injection : normal, abstain, slow, invalid, expired, wrong-major, drop, unknown-load,
forbidden-kind, missing-load, echo-token, protocol-1.0, deviate ; --mode-file pour changer de
mode sans reconnexion. README.

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

3.5 KiB

etm-powersync-ref-optimizer — optimiseur de référence

Python 3, bibliothèque standard seule, GPL-3.0 comme le dépôt. Protocole : docs/OPTIMIZER_PROTOCOL.md 1.1 (annexe E). Il répond un plan à un créneau qui reproduit le waterfall du rule-based (passe confort : même ordre, même budget, mêmes écrêtages) en ne lisant que le contexte GetPlan — aucun accès réseau sortant, aucun fichier hors le token. Usages : test de bout en bout du moteur, point de comparaison d'un banc, exemple pour qui écrit son propre optimiseur.

Ce qu'il ne reproduit pas (le contexte 1.1 ne le porte pas) : passe éco, dérogations, plafond de soutirage, paliers d'un relay-router (il envoie une consigne continue que le routeur arrondit), refus de commande, proxy VE. L'en-tête du script le détaille.

Lancer

# socket local (défaut : unix:///run/powersync/optimizer.sock, mode 0660)
./tools/etm-powersync-ref-optimizer/etm-powersync-ref-optimizer

# sur le LAN, avec un token (lu dans un FICHIER, jamais en argument)
./tools/etm-powersync-ref-optimizer/etm-powersync-ref-optimizer --listen tcp://0.0.0.0:7600 \
    --token-file /etc/powersync/optimizer.token

Il écrit ÉCOUTE <endpoint> sur la sortie standard quand il écoute (le port réel de tcp://…:0), et son journal sur la sortie d'erreur. Plusieurs clients à la fois (un fil par connexion, aucun état partagé) : le moteur et un rejeu peuvent lui parler ensemble. SIGTERM l'arrête proprement.

Pointer le moteur dessus

# méthodes NymeaEnergy (permission Admin) — INTERFACE.md, « Optimiseur »
NymeaEnergy.SetOptimizerEndpoint  {"endpoint": "unix:///run/powersync/optimizer.sock"}
NymeaEnergy.SetOptimizerToken     {"token": "<le même secret que dans --token-file>"}   # clé seule
NymeaEnergy.GetOptimizerStatus    # optimizerAlive, activeStrategy, name, lastErrorCode…

Sur tcp:// sans TLS, le token circule en clair sur le LAN (protocole §3).

Traces, puis rejeu

NymeaEnergy.SetOptimizerTrace {"enabled": true}      # défaut : désactivé
NymeaEnergy.GetOptimizerTrace                         # → directory
# une ligne par cycle : <directory>/etm-powersync-optimizer-trace-AAAA-MM-JJ.ndjson

# rejouer une journée sur un optimiseur (celui-ci, ou un candidat)
./tools/etm-powersync-trace-replay <directory>/etm-powersync-optimizer-trace-2026-10-17.ndjson \
    --endpoint unix:///run/powersync/optimizer.sock --fail-on-divergence

Le rejeu compare, cycle par cycle, les actions du créneau courant au plan enregistré (tools/README.md).

Modes d'injection de défauts (--mode, tests)

normal · abstain · slow (répond après --slow-s, défaut 12 s, au-delà des 10 s du moteur) · invalid (slots n'est pas une liste) · expired (validUntil passé) · wrong-major (handshake 2.0) · drop (ferme la connexion à chaque GetPlan) · unknown-load · forbidden-kind (kind constraint) · missing-load (une charge disponible absente du créneau courant) · echo-token (recopie le token dans reason et planId) · protocol-1.0 (optimiseur 1.0 : sans champs 1.1) · deviate (cascade en ordre inverse : doit diverger).

--mode-file FICHIER : le mode est relu dans ce fichier à chaque message — un test change de mode sans reconnexion. --list-codes : le catalogue des codes émissibles qu'il embarque (le test d'intégration le confronte au moteur).

Tests : tests/auto/refoptimizer (suite etm-powersync-refoptimizer, moteur réel + ce script en sous-processus).