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

68 lines
3.5 KiB
Markdown

# `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
```sh
# 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
```sh
# 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
```sh
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).