Patrick Schurig f20b08a3a2 test : reproductions du lot délestage triphasé (rouges sur 1.15.2+etm76)
- testOverloadLeavesTheChargerItsPhases (3 cas) et testAnUnassignedCarTakesItsPhasesAway :
  une borne triphasée sans voiture, ou dont la voiture monophasée est désassignée, comptée sur
  une phase — 3 680 W alloués et attendus pour 16 A × 3.
- testOverloadReactsToTheTotalWhenPhasesLag : des courants par phase en retard sur le total
  retardent la protection.
- testPhaseSafetyMargin : la marge de 10 % (décision du 2026-10-27).
- testTheDrawCapHoldsAgainstAnOptimizerAtMaximum (refoptimizer) : sous un optimiseur qui
  demande le maximum, ce que la protection a retiré revient au cycle suivant.
- Témoins verts sur etm76 : testSheddingOpensTheRelaysUnderMinOn (le délestage franchit minOn
  et ouvre les relais) et testCommandDivergenceSeesAPhaseCountError (×3 vu en régime établi).
- Optimiseur de référence : mode over-limit (toute consigne en watts à 50 kW).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 12:28:42 +01:00
..

tools/

Outils de développement et d'exploitation du plugin. Ce fichier décrit etm-powersync-trace-replay et les deux outils du contrat du protocole optimiseur ; l'optimiseur de référence a le sien (etm-powersync-ref-optimizer/README.md), les autres scripts portent leur mode d'emploi en en-tête.

Noms d'optimiseur : powersync-optimizer = exemple de référence GPL (ce dépôt) ; etm-optimizer = optimiseur ETM (propriétaire, hors de ce dépôt). L'optimiseur de référence s'annonce powersync-optimizer au handshake ; son fichier garde le préfixe des artefacts.

etm-powersync-trace-replay — rejouer des traces de décision

Python 3, bibliothèque standard seule, GPL-3.0 comme le dépôt.

Le moteur écrit, quand le réglage optimizerTrace est activé (NymeaEnergy.SetOptimizerTrace, défaut : désactivé), une ligne NDJSON par cycle dans <storagePath>/etm-powersync-optimizer-trace-AAAA-MM-JJ.ndjson (format : INTERFACE.md, « Traces de décision » ; protocole : docs/OPTIMIZER_PROTOCOL.md annexe D). L'outil renvoie chaque request d'un de ces fichiers à un optimiseur et compare ses actions au plan enregistré.

# optimiseur local (défaut : unix:///run/powersync/optimizer.sock)
./tools/etm-powersync-trace-replay etm-powersync-optimizer-trace-2026-10-17.ndjson

# optimiseur sur le LAN, avec token, échec si une action diffère
./tools/etm-powersync-trace-replay trace.ndjson --endpoint tcp://nas:7600 \
    --token-file ~/.config/etm-optimizer.token --fail-on-divergence
  • Token : seulement par --token-file (jamais en argument : il se verrait dans ps). Il n'est ni affiché ni écrit.
  • Sorties : <trace>.replay-<horodatage>.ndjson (une ligne par requête rejouée : réponse, latence, écarts) et ….summary.txt, à côté de la trace. Jamais d'écrasement (--out vers un fichier existant est refusé).
  • Comparaison : charge, kind, puissance d'une consigne (setpoint, à --tolerance-w près), état d'une action state — la puissance d'une action state n'est pas comparée (le §6 ne la définit pas, et le plan du rule-based tracé la porte).
  • Résumé : requêtes rejouées, taux d'abstention, computeMs médian et max (s'il est fourni dans diagnostics), cycles non comparables (réponse enregistrée en erreur), cycles dont les actions du créneau courant diffèrent du plan enregistré, avec la liste.
  • Code de sortie : 0 ; 1 avec --fail-on-divergence s'il y a divergence ; 2 sur erreur (fichier, connexion, handshake refusé).
  • Les lignes L2 (strategy: "none") n'ont pas de requête : elles ne sont pas rejouées.

Les traces contiennent des mesures du site : elles restent sur le site. L'export est manuel (copie du fichier par l'installateur) ; l'outil ne parle qu'à l'endpoint donné.

Test : python3 -I tests/tools/etm-powersync-trace-replay-test.py (serveur de test minimal qui renvoie les réponses enregistrées — ce n'est pas l'optimiseur de référence).

Le contrat du protocole optimiseur — etm-powersync-schema-check, etm-powersync-gen-protocol-doc

Python 3, bibliothèque standard seule, aucun accès réseau. Le contrat : docs/etm-powersync-optimizer-protocol.openrpc.json (OpenRPC 1.3), servi par le moteur (NymeaEnergy.GetOptimizerProtocol).

# un message, une session NDJSON (les deux sens), des traces de décision
./tools/etm-powersync-schema-check --method GetPlan --params requete.json
./tools/etm-powersync-schema-check --method GetPlan --result plan.json --request requete.json --semantic
./tools/etm-powersync-schema-check --ndjson session.ndjson --semantic
./tools/etm-powersync-schema-check --ndjson sonde.ndjson --responses-only   # requêtes fautives à dessein
./tools/etm-powersync-schema-check --method GetPlan --error erreur.json --request requete.json \
    --handshake handshake.json                     # error.data d'un -32602 (protocole 1.3)
./tools/etm-powersync-schema-check --trace etm-powersync-optimizer-trace-2026-10-17.ndjson --semantic
./tools/etm-powersync-schema-check --subset        # le sous-ensemble de JSON Schema géré

# la prose du protocole, régénérée depuis le contrat ; --check : code 1 si pas à jour (ci-quality 5/5)
./tools/etm-powersync-gen-protocol-doc [--check]
# les listes fermées du contrat, régénérées depuis le CODE
ETM_POWERSYNC_CODE_LISTS=/tmp/listes.json <build>/tests/auto/protocolschema/etm-powersync-protocolschema \
    testClosedListsAreGeneratedFromTheCode
./tools/etm-powersync-gen-protocol-doc --enums-from /tmp/listes.json
  • Sous-ensemble géré : $ref local, type, enum, const, properties, required, additionalProperties, propertyNames, items, minItems, maxItems, minimum, maximum, exclusiveMinimum, minLength, maxLength, pattern, oneOf, anyOf, allOf. Tout autre mot-clé est une erreur de schéma (code 2), jamais ignoré.
  • --semantic applique x-etm-semantic-rules : ce que le validateur du moteur refuse en plus de la forme, dans son ordre, sous le code qu'il publie (fallbackCode).
  • error.data (protocole 1.3) : la forme d'error.data d'une erreur, quand le contrat la norme (x-etm-data-schema, -32602 de GetPlan), est exigée d'un optimiseur qui s'annonce x-etm-data-required-from ou plus, et seulement AVERTIE en deçà (ligne AVERTISSEMENT, sans effet sur le code de sortie) ; une valeur reçue recopiée dans error.data est un écart dès 1.2.
  • Code de sortie : 0 conforme ; 1 écart de forme ou refus sémantique ; 2 erreur d'usage ou de schéma.
  • Conformité d'un optimiseur de bout en bout, avec le vrai moteur : suite etm-powersync-optimizer-conformance (tests/auto/conformance/README.md).

Tests : python3 -I tests/tools/etm-powersync-schema-check-test.py et python3 -I tests/tools/etm-powersync-gen-protocol-doc-test.py.