# `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. **Nom annoncé au handshake : `powersync-optimizer`** (c'est le `name` que `GetOptimizerStatus` publie). `powersync-optimizer` = exemple de référence GPL (ce dépôt) ; `etm-optimizer` = optimiseur ETM (propriétaire, hors de ce dépôt). Le fichier et son répertoire gardent le préfixe des artefacts du dépôt, `etm-powersync-ref-optimizer`. 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) · `wrong-type` (`planId` numérique : le moteur l'accepte, le contrat OpenRPC le refuse — contre-épreuve de la suite de conformité). `--mode-file FICHIER` : le mode est relu dans ce fichier à chaque message — un test change de mode sans reconnexion. `--capture FICHIER` : chaque message reçu et envoyé, une ligne JSON-RPC par message — la session que `tools/etm-powersync-schema-check --ndjson` juge contre le contrat ; le token y devient autant d'astérisques (forme gardée, valeur jamais écrite). `--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).