Patrick Schurig c1ce67942c feat(optimiseur): machine de repli §7, état et plan publiés vers l'app (brief 4 T4)
- OptimizerStatus::Vivacite : optimizerAlive, échecs comptés PAR REQUÊTE (E17), deux
  consécutifs = mort, socket fermé = mort, retour au premier plan valide ; une abstention
  rompt la série sans ressusciter. strategieActive() : none sous L2, prioritaire sur tout ;
  rules dès qu'un optimiseur n'est plus vivant, sans attendre le cycle suivant.
- SocketScheduler : branchement de la vivacité, cache abandonné à la mort, UpdateLearned
  journalisé une fois et ignoré.
- NymeaEnergy.GetOptimizerStatus + OptimizerStatusChanged (une notification par
  transition, charge utile déclarée une fois) ; champs installateur absents s'ils sont
  inconnus ; jamais le token, tokenConfigured seulement. Pas de tier (Q5).
- SetOptimizerEndpoint (persisté, appliqué à chaud) et SetOptimizerToken (écriture seule,
  fichier 0600, Q2), tous deux PermissionScopeAdmin ; token déclaré Variant pour qu'un
  mauvais type ne soit pas recopié au journal par le validateur nymea.
- NymeaEnergy.GetPlan : plan en vigueur (appliqué au créneau courant, demandé ensuite),
  codes et paramètres filtrés seulement, inForce toujours présent, fallbackCode quand un
  optimiseur configuré n'a pas commandé.
- Tests : tableau §7 ligne par ligne, transitions notifiées une fois, RPC, token absent de
  tout journal du moteur et de toute réponse.

Sans endpoint : decisions IDENTIQUE (23 792 lignes), suites inchangées, journal >= info
inchangé (8 739 lignes).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 09:34:39 +02:00

67 lines
3.2 KiB
C++

// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (C) 2025 - 2026, Patrick Schurig / ETM PowerSync
#pragma once
#include <QDateTime>
#include <QHash>
#include <QString>
#include <QVariantMap>
#include "optimizerplan.h"
/*!
* \file publishedplan.h
* \brief Le plan EN VIGUEUR tel que l'app le lit (\c NymeaEnergy.GetPlan, brief 4 T4, exigence
* app 2).
*
* Fonctions PURES. L'arbitre appelle \c serialiser() en fin de cycle et garde le résultat : le
* \c Get rend ce qui a été publié, il ne recalcule rien (même règle que \c GetLoadTelemetry).
*
* \par Ce qui franchit la frontière — invariant 12
* Des codes, des nombres, des instants. Le \c reason libre d'un optimiseur n'y est jamais ;
* \c expected et \c diagnostics sont réduits à leurs clés documentées (§6), le reste est retiré ;
* ni \c planId, ni \c strategy, ni \c solver de l'optimiseur — des chaînes qu'il écrit librement.
*/
namespace PublishedPlan {
//! Valeur de \c strategy d'un plan du rule-based publié (celle de \c Plan::strategy).
constexpr const char *StrategieRegles = "rule-based";
/*!
* \brief Le plan du cycle, pour l'app.
*
* - **Sous un optimiseur** (\p plan.strategy == \c "optimizer", \p recu non nul) : le créneau
* courant, actions À LA PUISSANCE APPLIQUÉE (celles que l'adaptateur a rendues, après bornes,
* verrous, paliers et délestage L4), puis les créneaux suivants du plan reçu, actions à la
* puissance DEMANDÉE. \c computedAt et \c validUntil sont ceux de l'optimiseur, absents s'il ne
* les a pas donnés.
* - **Sous le rule-based** : un seul créneau, le courant, actions à la puissance appliquée ;
* \c computedAt = \p now, \c validUntil = fin du créneau. \c fallbackCode porte \p codeRepli
* s'il n'est pas vide (un optimiseur est configuré mais n'a pas commandé ce cycle).
*
* \param plan Plan du cycle (après \c finaliser()). \param slot Créneau dispatché, délestage L4
* fusionné. \param appliquees Retours d'\c applyAction() du cycle.
* \param recu Plan d'optimiseur appliqué ce cycle ; ignoré sous le rule-based.
* \param codeRepli Motif du repli rule-based d'une stratégie optimiseur ; vide sans optimiseur.
* \param now Instant du cycle.
* \return La charge utile de \c GetPlan, \c inForce à vrai.
*/
QVariantMap serialiser(const Plan &plan, const Slot &slot, const QHash<QString, LoadAction> &appliquees,
const OptimizerPlan::PlanRecu *recu, const QString &codeRepli, const QDateTime &now);
/*!
* \brief \c expected d'un créneau réduit aux clés documentées (§6).
* \param expected Tel que reçu. \return \c gridImportW, \c gridExportW s'ils sont des nombres finis.
*/
QVariantMap attenduPublie(const QVariantMap &expected);
/*!
* \brief \c diagnostics réduits à ce que l'app lit (§6, exigence app 4).
* \param diagnostics Tels que reçus.
* \return \c status s'il est l'une des quatre valeurs du §6, et \c inputsUsed.pv s'il vaut
* \c physics, \c hybrid (« plan calculé sur une prévision corrigée ») ou \c none ; rien
* d'autre — une valeur hors de cette liste fermée est un texte libre, elle est retirée.
*/
QVariantMap diagnosticsPublies(const QVariantMap &diagnostics);
}