Audit manuel (doxygen non installé, pas de Doxyfile). 5 findings corrigés : - EvAdapter::applyAction : \param now manquant (param partiellement documenté → warning) ; toLoadContext : \param now ajouté. - EnergyArbitrator::buildContext : mention SG-Ready + \param now (source unique verrous). - applyActionsToAdapters : dispatch State→SG-Ready documenté (était ECS/Stage seul). - onMeterWatchdogTick : doc alignée sur le refactor 7c (délègue à evaluateMeterFreshness, QTimer sous #ifndef ENERGY_SIMULATION). - RuleBasedScheduler (classe + getPlan) : décrivait seulement le proxy EV → ajout du waterfall non-EV (budget net signé, priorité ASC, recrédit, clamp lock-aware) et correction "seul ctx.timestamp utilisé" (faux : meter + loads aussi). Concepts 3c/3e vérifiés documentés : seam de temps/lockWindow, minStage/maxStage, atomicité 2 bits (transientHarm), mode dégradé L2, waterfall unifié + ordre EV→ECS/SG-Ready, hystérésis SG-Ready. Build 0/0. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
118 lines
5.9 KiB
C++
118 lines
5.9 KiB
C++
// SPDX-License-Identifier: GPL-3.0-or-later
|
||
// Copyright (C) 2025 - 2026, Patrick Schurig / ETM PowerSync
|
||
#pragma once
|
||
|
||
#include <QObject>
|
||
#include "ischeduler.h"
|
||
|
||
class EvCharger;
|
||
class ChargingAction;
|
||
class EnergyArbitrator;
|
||
|
||
/*!
|
||
* \brief Planificateur règles GPL : EV (proxy amont) + waterfall surplus ECS / SG-Ready.
|
||
*
|
||
* \c getPlan() produit un plan à 1 créneau en DEUX temps :
|
||
*
|
||
* 1. **EV — proxy amont (beta, jusqu'à 3g)** : délègue la planification EV à
|
||
* \c planSurplusCharging() / \c planSpotMarketCharging() héritées de SmartChargingManager,
|
||
* relit \c m_chargingActions et les reformate en LoadAction(Setpoint) annotées d'un
|
||
* \c reason français. Le dispatch EV réel reste dans \c adjustEvChargers() amont.
|
||
*
|
||
* 2. **Waterfall non-EV (3c/3e)** : un budget de surplus UNIQUE — net SIGNÉ
|
||
* \c (exportW − importW) − evReservedW — cascade par **priorité ASC** (rang, 1 = premier
|
||
* servi) à travers les charges \c relay-stages (ECS, \c buildEcsStageAction) ET
|
||
* \c sg-ready (PAC, \c buildSgReadyStateAction). Anti-clignotement par **recrédit** de la
|
||
* conso allouée ; **clamp lock-aware** \c minStage/maxStage et \c minState/maxState (verrous
|
||
* minOn/minOff/minStateHold, protection compresseur) — la fenêtre est calculée au MÊME
|
||
* \c ctx.timestamp que l'exécution (cf. \c ILoadAdapter). Ces LoadAction sont RÉELLEMENT
|
||
* dispatchées par \c EnergyArbitrator::applyActionsToAdapters().
|
||
*
|
||
* À partir de **3g** : l'EV rejoindra le waterfall unifié (toutes charges classables ensemble).
|
||
*
|
||
* \invariant getPlan() retourne IMMÉDIATEMENT (AGENTS invariant 5) et toujours un Plan valide.
|
||
* \invariant Toute LoadAction a un \c reason non vide, en français.
|
||
* \invariant EV (étape 1) : priorité Deadline VE > Surplus PV > aWATTar > Min courant > Idle
|
||
* (iso-fonctionnel amont 3b). Non-EV (étape 2) : ordre = \c priority croissant.
|
||
*/
|
||
class RuleBasedScheduler : public QObject, public IScheduler
|
||
{
|
||
Q_OBJECT
|
||
public:
|
||
/*!
|
||
* \brief Constructeur.
|
||
* \param arbitrator Arbitre propriétaire — fournit l'accès à la planification et à l'état.
|
||
* \param parent Propriétaire Qt.
|
||
*/
|
||
explicit RuleBasedScheduler(EnergyArbitrator *arbitrator, QObject *parent = nullptr);
|
||
|
||
/*!
|
||
* \brief Retourne le plan pour le slot courant (EV proxy + waterfall non-EV).
|
||
*
|
||
* Étape 1 (EV) : \c runSpotMarketPlanning() + \c runSurplusPlanning() puis reformatage
|
||
* de \c scheduledActions() en LoadAction(Setpoint) — log [Arbitre], dispatch amont.
|
||
* Étape 2 (non-EV) : waterfall surplus sur les charges \c relay-stages / \c sg-ready
|
||
* triées par priorité — LoadAction(Stage/State) réellement dispatchées.
|
||
*
|
||
* \param ctx SurplusContext courant. Utilisé : \c ctx.timestamp (temps de cycle / verrous),
|
||
* \c ctx.meter (surplus net signé), \c ctx.loads (déclarés, télémétrie, fenêtres de verrou).
|
||
* \return Plan à 1 créneau couvrant \c ctx.timestamp + 60 s.
|
||
*/
|
||
Plan getPlan(const SurplusContext &ctx) override;
|
||
|
||
private:
|
||
/*!
|
||
* \brief Construit un LoadAction pour le cas "délai requis" (TimeRequirement).
|
||
* \param ev EvCharger concerné.
|
||
* \param ca ChargingAction planifiée (courant et phases déjà calculés par planSurplusCharging).
|
||
* \return LoadAction avec funding=Grid et reason "Deadline VE".
|
||
*/
|
||
LoadAction buildTimeRequirementAction(EvCharger *ev, const ChargingAction &ca) const;
|
||
|
||
/*!
|
||
* \brief Construit un LoadAction "courant minimum" pour les modes EcoMin.
|
||
* \param ev EvCharger concerné.
|
||
* \return LoadAction avec funding=Surplus, chargingEnabled=true, currentA=min.
|
||
*/
|
||
LoadAction buildMinCurrentAction(EvCharger *ev) const;
|
||
|
||
/*!
|
||
* \brief Construit un LoadAction "idle" (recharge désactivée, aucun surplus).
|
||
* \param ev EvCharger concerné.
|
||
* \return LoadAction avec chargingEnabled=false et reason appropriée.
|
||
*/
|
||
LoadAction buildIdleAction(EvCharger *ev) const;
|
||
|
||
/*!
|
||
* \brief Construit un LoadAction "stage" ECS par cascade de surplus (waterfall §6).
|
||
*
|
||
* Retient le palier déclaré le plus haut dont la puissance tient dans le budget courant.
|
||
* Correction B (anti-clignotement) : recrédite d'abord \c lc.telemetry.currentPowerW au
|
||
* budget (la conso actuelle de l'ECS est déjà soustraite de l'export mesuré), puis
|
||
* décrémente \c remainingSurplusW de la puissance du palier retenu.
|
||
*
|
||
* \param lc Charge ECS (adapter == "relay-stages") du SurplusContext.
|
||
* \param[in,out] remainingSurplusW Budget de surplus restant (W) ; mis à jour pour la
|
||
* charge ECS suivante (priorité inférieure / rang supérieur).
|
||
* \return LoadAction kind=Stage, funding=Surplus, \c reason français non vide.
|
||
*/
|
||
LoadAction buildEcsStageAction(const LoadContext &lc, double &remainingSurplusW) const;
|
||
|
||
/*!
|
||
* \brief Construit un LoadAction "state" SG-Ready (PAC) par mapping SÉMANTIQUE du surplus.
|
||
*
|
||
* 4 états normés (qualitatifs, pas des paliers) : surplus abondant stable → 4 (forcé,
|
||
* hystérésis P4×1,2 entrée / P4×1,0 sortie) ; surplus durable → 3 (recommandation, ≥P3) ;
|
||
* sinon → 2 (normal, mains off). L'état 1 (effacement) n'est PAS déclenché par le surplus
|
||
* seul (déféré : signal tarif/réseau). Recrédit (correction B) sur la puissance allouée
|
||
* (déclaré, 0 pour 1/2). Clamp lock-aware via \c minState/maxState (court-cycling PAC).
|
||
*
|
||
* \param lc Charge SG-Ready (adapter == "sg-ready") du SurplusContext.
|
||
* \param[in,out] remainingSurplusW Budget de surplus restant (W), mis à jour pour la suite.
|
||
* \return LoadAction kind=State, funding=Surplus, \c reason français non vide.
|
||
*/
|
||
LoadAction buildSgReadyStateAction(const LoadContext &lc, double &remainingSurplusW) const;
|
||
|
||
EnergyArbitrator *m_arbitrator;
|
||
};
|