# `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 ` 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": ""} # 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 : /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 /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).