// SPDX-License-Identifier: GPL-3.0-or-later #ifndef TESTLOADMODEL_H #define TESTLOADMODEL_H #include /*! * \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(); //! Une révision n'existe que si la PRÉVISION a changé — et le contenu seul en décide. void testTheContentIsTheRevisionIdentity(); //! La courbe se relit telle qu'elle était connue, et ses trous restent des trous. void testTheCurveIsReproducibleAndKeepsItsHoles(); //! La rétention garde les DEUX bouts : n'en garder qu'un détruirait la révision. void testRetentionKeepsBothEndsOfEachRevision(); //! La courbe COURANTE est la dernière VUE, pas la dernière apparue. void testTheCurrentCurveIsTheLastSeenNotTheLastNew(); //! La charge utile se REFUSE plutôt que de s'interpréter au mieux — mais pas pour un attribut. void testAPayloadIsRefusedOnFormNeverOnAMissingAttribute(); void testTransportV2IsReadAndAnUnknownCorrectionRefused(); void testTheSiteCurveIsClippedAfterTheSum(); //! Brief 3 T2 — une ligne par pas ÉCOULÉ et par plan ; v1 laisse gti/tempC/paramètres v2 NULL. void testTheLearningLogWritesOneRowPerElapsedStepAndPlan(); //! Mesure, écrêtage, neige : LUS ou NULL, jamais devinés. void testTheLearningLogReadsMeasureCurtailmentAndSnow(); //! « J−1 18:00 locale » et H−1 : la révision du site en vigueur, par sa clé de contenu. void testTheLearningLogPointsAtTheRevisionsInForce(); //! Base abîmée : table refaite, journalisé, le service continue ; rétention 400 jours. void testTheLearningLogSurvivesADamagedTable(); //! L'écriture ne bloque jamais l'appelant, même base verrouillée. void testTheLearningLogNeverBlocksTheCycle(); //! etm68 — une table etm67 RÉELLE est migrée en place : lignes conservées, colonnes NULL. void testTheLearningLogMigratesAnEtm67TableInPlace(); //! etm68 — le GTI observé va au pas qu'il moyenne, complète seulement ses colonnes, si NULL. void testTheLearningLogAttachesTheObservedGtiToItsStep(); // ── Brief 3 T4 — tarif saisi ─────────────────────────────────────────── //! Validation STRICTE : prix ≥ 0, plages lisibles, 24 h sans trou ni chevauchement — refus codés. void testTheTariffIsValidatedStrictly(); //! Les plages sont des heures MURALES : jours de 23 h et de 25 h, oracle hors Qt. void testTheTariffFollowsLocalTimeAcrossDstDays(); //! Un créneau à cheval sur deux zones reçoit la moyenne pondérée par la durée. void testAStraddlingSlotGetsTheTimeWeightedMean(); // ── Brief 3 T4 — consommation de base ────────────────────────────────── //! La minute retranche chaque charge selon sa source ; commande inconnue → minute écartée. void testTheBaseloadMinuteSubtractsEachLoadBySource(); //! Profil = médiane par créneau local et type de jour sur 28 jours ; qualité dite ; 23 h / 25 h. void testTheBaseloadProfileIsTheMedianPerSlotAndDayType(); //! Sous 7 jours couverts (à 90 %) : `none`, et rien d'inventé. void testTheBaseloadIsNoneUnderSevenDays(); //! Tables dédiées, écriture asynchrone, recalcul, rétention — sans toucher forecastLearning. void testTheBaseloadLogWritesWithoutTouchingTheOtherTables(); // ── Brief 4 T1 — sérialiseur GetPlan (OPTIMIZER_PROTOCOL 1.1 §5) ─────── // Corps dans testoptimizerprotocol.cpp ; références dans data/optimizer/. //! Golden : site sans PV — grille, base sans profil et tarif saisi, aucune courbe. void testGetPlanParamsGoldenSiteSansPv(); //! Golden : PV + ECS relay-router + borne + SG-Ready, batterie, dérogation à cible. void testGetPlanParamsGoldenPvEcsBorneSgReady(); //! Golden : prévision absente (motif) → `pv: null` ; sans instantané → `forecast: null`. void testGetPlanParamsGoldenPrevisionAbsente(); //! Golden : charge `available=false` figée, et borne configurée sans appareil. void testGetPlanParamsGoldenChargeIndisponibleFigee(); //! Inconnu = null ou absent, jamais 0 ; ni `group`, ni champs réservés, ni `force`. void testGetPlanParamsNeverInventsAValue(); //! `meter.exportW` est la mesure brute (règle 8). void testGetPlanParamsMeterIsRaw(); //! Wh par pas → puissance moyenne ; une case sans point reste null. void testForecastWhPerStepBecomesMeanPower(); // ── Brief 4 T5 — catalogue des motifs (corps dans testdecisioncatalogue.cpp) ── //! Anti-dérive : knownCodes() == les constantes de l'en-tête, chacune avec un rendu connu. void testDecisionCodesListIsComplete(); //! Liste blanche de l'optimiseur (E14) : ni sécurité, ni verrou, ni état du moteur. void testOptimizerMayEmitOnlyWhitelistedCodes(); // Brief 4 T3 — plan d'un optimiseur (OptimizerPlan, fonctions pures) void testOptimizerParamKeysMatchInterface(); void testOptimizerPlanIsConverted(); void testOptimizerPlanVerdicts_data(); void testOptimizerPlanVerdicts(); void testOptimizerReasonIsFiltered(); void testOptimizerForceAndReservedAreIgnored(); void testOptimizerUnavailableLoadKeepsTheShadow(); void testOptimizerFinaliseUsesTheAppliedAction(); }; #endif // TESTLOADMODEL_H