refactor(arbitre): l'action appliquée au cycle est conservée par charge (brief 4 T0.5)

Le retour d'applyAction() au dispatch (applyActionsToAdapters) était jeté : c'est pourtant ce
que l'adaptateur a réellement posé, après écrêtage et verrous. Il est désormais rangé dans
m_actionsAppliquees (loadId → LoadAction), daté par m_actionsAppliqueesAt, vidé à chaque
dispatch. Aucune lecture : rien n'en dépend pour décider. Il servira au retour d'exécution
vers l'optimiseur et à l'écart plan/appliqué (analyse des correctifs moteur, point c).

Seuls les deux appels du dispatch sont concernés. Les délestages L4 (dont le retour était
déjà lu) et le repli L2 ne sont pas touchés ; aucun appel ajouté ni retiré.

Neutre : référence de décisions IDENTIQUE (23 739 lignes) ; suites inchangées (simulation
211, loadmodel 135, charging 80, spotmarket 36 + 11 ignorés, 0 échec) ; journal >= info
identique (8 588 lignes, 553 gabarits, aucun nouveau).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Patrick Schurig 2026-10-16 15:08:39 +02:00
parent e0b83f55a7
commit 7622e11ea9
2 changed files with 14 additions and 2 deletions

View File

@ -1669,6 +1669,9 @@ void EnergyArbitrator::syncAdapters()
void EnergyArbitrator::applyActionsToAdapters(const Slot &slot, const QDateTime &now)
{
// Ce que chaque adaptateur a rendu est CONSERVÉ, jamais relu ici : voir m_actionsAppliquees.
m_actionsAppliquees.clear();
m_actionsAppliqueesAt = now;
for (const LoadAction &action : slot.actions) {
// L'adaptateur applique, écrête et verrouille — il ne décide pas (règle 2).
// Dispatch AGNOSTIQUE au mécanisme : routage par loadId, le polymorphisme ILoadAdapter
@ -1691,10 +1694,10 @@ void EnergyArbitrator::applyActionsToAdapters(const Slot &slot, const QDateTime
if (action.kind == LoadAction::State || action.kind == LoadAction::Setpoint) {
if (ILoadAdapter *adapter = m_loadAdapters.value(action.loadId, nullptr)) {
adapter->applyAction(action, now);
m_actionsAppliquees.insert(action.loadId, adapter->applyAction(action, now));
} else if (m_waterfallEvIds.contains(action.loadId)) {
if (EvAdapter *ev = m_adapters.value(action.loadId, nullptr)) {
ev->applyAction(action, now);
m_actionsAppliquees.insert(action.loadId, ev->applyAction(action, now));
// L'ÉTAT de charge publié dans ChargingInfo était posé par
// adjustEvChargers(), que ces bornes ne traversent plus. Sans cette
// reprise, chargingState resterait figé sur sa dernière valeur : l'app

View File

@ -1105,6 +1105,15 @@ private:
//! loadId → action de DÉLESTAGE L4 du cycle. Revendication : le dispatch saute ces charges,
//! un seul commandeur par organe (règle absolue 1). Vidée à chaque évaluation de L4.
QHash<QString, LoadAction> m_shedActions;
//! loadId → action APPLIQUÉE AU CYCLE : ce que l'adaptateur a rendu au dispatch
//! (\c applyActionsToAdapters()), après écrêtage et verrous — pas ce que le plan demandait.
//! Vidée à chaque dispatch ; les délestages L4 et le repli L2 n'y entrent pas (ils ont leur
//! propre trace). CONSERVÉE SANS ÊTRE LUE : aucune décision n'en dépend aujourd'hui. Elle
//! servira au retour d'exécution vers l'optimiseur (brief 4) et à l'écart plan/appliqué.
QHash<QString, LoadAction> m_actionsAppliquees;
//! Temps de cycle du dernier dispatch : date \c m_actionsAppliquees. Un cycle qui rend la
//! main avant le dispatch (repli L2) laisse la table et sa date telles quelles.
QDateTime m_actionsAppliqueesAt;
//! loadId → énergie de session (Wh) au moment où l'obligation s'est OUVERTE. Base des
//! différences d'avancement. Recapturée quand l'accumulateur de la borne recule (nouvelle
//! session), jamais au démarrage de nymead — voir \c ecoProgress().