LM-1013-b-vi. Le mode ChargingModeEcoWithMinCurrent promettait un courant minimum depuis toujours sans qu'aucun chemin ne le serve : mort par réécriture chez l'amont, puis mort par exclusion quand le waterfall a pris la borne. LA LOI. computeEcoTarget() rend max(cadence de l'obligation, minimum). Le corps historique est isolé dans calculerObligation() et une sortie anticipée rend EXACTEMENT l'ancien résultat quand aucun minimum n'est demandé — ce n'est pas une optimisation, c'est la garantie que l'ajout ne déplace rien pour les charges qui n'en veulent pas. Les quatre suites le confirment. LE MINIMUM NE SE TIENT JAMAIS. `tenue` ne décrit que l'obligation en énergie ; quand elle est tenue, la cadence vaut 0 et la consigne retombe SUR LE MINIMUM, pas sur zéro. C'est là, et seulement là, que la différence de nature entre une énergie avec échéance et une puissance permanente se voit dans le code. L'ordonnanceur ne publie donc ECO_FLOOR_MET que si watts vaut aussi 0 : sinon il couperait ce qu'il doit continuer de servir. DÉRIVÉ, JAMAIS STOCKÉ. EvAdapter calcule le minimum depuis evFloorW() à chaque cycle quand le mode le demande. L'écrire en configuration à l'appairage créerait une copie qui se périme au premier recâblage — phaseCount change, la copie reste fausse en silence. Correction apportée par le brief app f2bd07d, et elle est juste. LE MOTIF DIT LEQUEL DES DEUX MORD. ECO_FLOOR_GRID gagne minAlwaysW et boundBy, et OMET remainingWh/targetWh/remainingS/regime sur une charge à minimum seul : un remainingS à 0 inventerait une échéance imminente et un remainingWh à 0 une obligation tenue. progress est absent pour la même raison — il ne décrit que l'obligation. CONTRAT. Les trois descriptions de ChargingMode sont corrigées. Normal n'est pas « recharge immédiate au maximum » : c'est MANUEL, le moteur ne commande rien, et seule la protection de surcharge la touche encore — ce qui doit être dit, un mode qui promet l'abstention et coupe quand même étant une surprise. Et les mots « garanti » et « prioritaire » sont bannis à propos de ce mode : le premier promet une certitude que rien ne tient, le second est vrai face au confort et faux face à un autre besoin éco mieux rangé, donc faux exactement dans le cas d'échec. TEST vu mordre : désarmé, minSeul.watts rend 0 au lieu de 4 140. Cinq points, dont la contre-épreuve qui distingue « 4 140 vient du minimum » de « 4 140 vient d'un reliquat d'obligation », et le cas 1 qui vérifie que rien ne bouge sans minimum. Suites : loadmodel 26/26 (+1), simulation 67/67, charging 48/48, spotmarket 32/32. NON DÉPLOYÉ — +etm57 attend le feu vert.
91 lines
4.1 KiB
C++
91 lines
4.1 KiB
C++
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
#ifndef TESTLOADMODEL_H
|
|
#define TESTLOADMODEL_H
|
|
|
|
#include <QObject>
|
|
|
|
/*!
|
|
* \brief Logique pure du modèle de charge : table de paliers et identité matérielle.
|
|
*
|
|
* Aucun serveur nymea ici. Ce qui est éprouvé ne dépend ni d'un ThingManager ni d'un
|
|
* cycle d'arbitrage : la dérivation des paliers est une combinatoire, et
|
|
* \c EnergyArbitrator::sameHardware() une comparaison.
|
|
*/
|
|
class TestLoadModel : public QObject
|
|
{
|
|
Q_OBJECT
|
|
|
|
private slots:
|
|
//! La table dérive de TOUTES les sommes de sous-ensembles, triées, 0 compris.
|
|
void testStageTableIsDerivedFromSubsetSums();
|
|
|
|
//! Puissances distinctes : l'ordre de déclaration n'a aucun effet observable.
|
|
void testRelayOrderIsInertWhenPowersAreDistinct();
|
|
|
|
//! Puissances égales : l'ordre décide QUEL contact sert le palier — et ça se voit.
|
|
void testRelayOrderDecidesWhichContactServesATiedStage();
|
|
|
|
//! Un réordonnancement sans effet ne doit PAS coûter une reconstruction.
|
|
void testReorderingDistinctRelaysKeepsTheAdapter();
|
|
|
|
//! Mais un échange de contacts à puissance égale, si.
|
|
void testSwappingTiedRelaysRebuilds();
|
|
|
|
//! Libellé, rang et besoins se mettent à jour en place : jamais de reconstruction.
|
|
void testSoftFieldsNeverRebuild();
|
|
|
|
// ── §11 — compteur et sonde par charge ─────────────────────────────────
|
|
//! Les deux champs traversent l'aller-retour, et sont OMIS quand ils sont vides.
|
|
void testMeasurementThingsRoundTrip();
|
|
|
|
//! Ils ne sont pas des canaux de commande : ils n'entrent pas dans claimedThingIds().
|
|
void testMeasurementThingsAreNotClaimed();
|
|
|
|
//! Donc deux charges peuvent partager le même compteur et la même sonde.
|
|
void testTwoLoadsMayShareAMeter();
|
|
|
|
//! Et les changer ne reconstruit rien : ils n'ont aucun effet sur le matériel.
|
|
void testMeasurementThingsAreSoftFields();
|
|
|
|
// ── §12 — le plancher de modulation ────────────────────────────────────
|
|
//! Déclaré en dynamic, et il traverse l'aller-retour.
|
|
void testFloorIsDeclaredForContinuousLoads();
|
|
|
|
//! Interdit là où il se DÉRIVE : deux sources d'une même vérité divergent.
|
|
void testFloorIsRefusedWhereItWouldBeDerived();
|
|
|
|
//! Un plancher au-dessus du plafond rendrait la charge inatteignable.
|
|
void testFloorAboveCeilingIsRefused();
|
|
|
|
// ── 3g-2 — le rang de la borne vit dans LoadConfig (LM-1206/LM-1207) ───
|
|
//! Une entrée evcharger est valide SANS charge utile, et traverse l'aller-retour.
|
|
void testEvChargerConfigIsRankOnly();
|
|
|
|
//! Toute charge utile d'un autre mécanisme y est refusée (LM-300, union discriminée).
|
|
void testEvChargerConfigRefusesPayloads();
|
|
|
|
//! Elle revendique son Thing : une borne ne peut pas être aussi une charge pilotée.
|
|
void testEvChargerClaimsItsThing();
|
|
|
|
// ── §12 / LM-1209 — l'auto-provisionnement remplit un trou, il ne décide pas ───
|
|
//! L'unicité de rang REFUSE une écriture, mais n'écarte JAMAIS au chargement : les deux
|
|
//! vérifications sont séparées parce que leurs gestes le sont (LM-1209-a).
|
|
void testRankUniquenessRefusesWriteButNeverDiscards();
|
|
|
|
//! La marque de rang s'efface sur un RANG changé, pas sur une écriture reçue — et un
|
|
//! client ne peut pas fabriquer un "auto" (LM-1209-c).
|
|
void testRankOriginFollowsTheRankNotTheWrite();
|
|
|
|
// ── LM-1006-1 — le plafond de soutirage, étape 1 ───────────────────────
|
|
//! On compare des MARGES, jamais des plafonds : le plus contraignant mord, et on sait
|
|
//! lequel. Aucun plafond déclaré ≠ marge nulle. Départage stable à marge égale.
|
|
void testEcoSpreadHardensAndSaysSo();
|
|
void testEcoUnmeasurableBuysAtAConstantRate();
|
|
void testMinimumIsServedEvenWhenTheObligationIsMet();
|
|
void testStoredInstantsSurviveATimeZoneChange();
|
|
void testReportingDayBoundaryIsPublishedNotDeducible();
|
|
void testDrawMarginComparesHeadroomsNotCaps();
|
|
};
|
|
|
|
#endif // TESTLOADMODEL_H
|