3 Commits

Author SHA1 Message Date
Patrick Schurig
5ab5213aad moteur : la marge de soutirage borne aussi les consignes d'un optimiseur (défauts 4 et 5)
La marge que le compteur laisse sous le seuil ne bornait que la cascade du rule-based. Sous un
optimiseur qui demande le maximum, ce que la protection venait de retirer revenait au cycle
suivant, dès que le compteur repassait sous la limite : 32,2 A pendant une minute sur
l'installation d'essai, après un délestage du chauffe-eau.

bornerOptimiseurAuSoutirage(), avant le dispatch : une consigne d'optimiseur ne fait monter une
charge que de la marge libre (pire phase, consommée dans l'ordre du créneau). Elle ne réduit
rien — c'est la protection qui réduit. L'engagé d'une borne est sa dernière commande tenue, si
bien que le dispatch ne défait plus, dans le même cycle, ce que la protection vient de faire.
Une consigne écrêtée publie DRAW_CAP et clamped, plus le motif de l'optimiseur (défaut 5).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 12:48:30 +01:00
Patrick Schurig
b45d0496c6 feat(moteur): listes fermées du protocole exposées par le code
OptimizerPlan::kinds() (désormais appliquée par convertirAction) et
verdictCodes(), dans l'ordre des contrôles ; SocketScheduler::knownTriggers()
(appliquée par rangDeclencheur), knownFallbackCodes(), knownLastErrorCodes() ;
OptimizerClient::knownErrorCodes(). Le contrat OpenRPC en régénère ses
énumérations. Aucun comportement changé : mêmes listes, mêmes ordres.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 21:30:44 +02:00
Patrick Schurig
a8c58d02f0 feat(optimiseur): validateur pur du plan d'un optimiseur — OptimizerPlan (brief 4 T3)
Fonctions pures (ni E/S ni horloge, « maintenant » = ctx.timestamp) qui
portent toute la sémantique du plan reçu ; le SocketScheduler les enchaînera.

- valider() : JSON → plan interne ou verdict à code stable. Créneaux à
  décalage horaire explicite, ordonnés, sans chevauchement ; loadId connu et
  unique ; kind cohérent avec le mécanisme et annoncé dans capabilities.kinds ;
  reason non vide ; valeurs dans leur domaine ; validUntil ; créneau courant ;
  COUVERTURE Q1 (toute charge disponible nommée). Abstention / expiré /
  invalide distingués ; un plan invalide n'est jamais converti en partie.
  `force` et champs réservés (annexe F) ignorés et listés.
- motifFiltre() : code hors liste blanche → OPTIMIZER_DECISION sans
  paramètres ; sinon seules les clés documentées (optimizerParamKeys, testé
  contre le tableau d'INTERFACE.md), valeurs nombre/booléen/code [A-Za-z]+.
  Le texte libre reste dans ActionRecue::texte, assaini, jamais dans le motif.
- substituer() : le créneau courant remplace les actions de l'ombre à leur
  place ; charges indisponibles → action de l'ombre ; bornes commandées
  sorties du proxy (waterfallEvIds) ; budget/draw de l'ombre jamais gardés.
- finaliser() : après dispatch, puissance APPLIQUÉE, niveau unique
  « optimizer », financement dérivé de la mesure (surplus puis réseau),
  counts cohérents, budget et draw recalculés (Q3, Q4), `clamped`.

LoadAction::clamped ajouté (interne, non publié dans ce lot).
Aucun appelant dans ce commit : le moteur est inchangé.

Tests (loadmodel) : 7 fonctions, dont 31 verdicts data-driven.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 22:06:33 +02:00