Deux gardes sur lastConfirmedAt, demandées et tranchées. JAMAIS UNE CLÉ DE RÉVISION. firstIssuedAt identifie la prévision ; la fraîcheur dit seulement jusqu'à quand elle a tenu. Dater avec elle ferait paraître une prévision née au moment où elle a été reconduite — et deux prévisions différentes reconduites par le même run porteraient la même date. Écrit dans l'en-tête, au contrat ET dans un test : une interdiction qui ne vit qu'à un endroit n'est pas une interdiction. SUR UNE LIGNE JAMAIS RECONDUITE, ELLE VAUT firstIssuedAt — jamais absente. Et la tranche vient de ce que le champ RÉPOND : il ne dit pas « quand quelqu'un a confirmé » mais « quel est le dernier run qui a AFFIRMÉ ces valeurs ». Le run émetteur les a affirmées. La réponse est vraie, et uniformément — il n'y a pas un cas confirmation et un cas copie, il y a une seule question avec une seule sorte de réponse. L'ambiguïté redoutée ne se lève pas en choisissant une convention : elle se lève en DÉFINISSANT correctement le champ. Une fois la définition posée, les deux cas n'en font plus qu'un. Et ici l'absence serait le mensonge, à rebours de la règle habituelle du dépôt : elle se lirait « on ne sait pas jusqu'à quand elle a tenu » alors qu'on le sait, et forcerait chaque client à écrire `confirmedAt ?? issuedAt` — ce calcul-ci, ailleurs, une fois par client, avec une chance de le faire de travers. Invariant publié : lastConfirmedAt >= firstIssuedAt, toujours. Une prévision ne peut pas avoir tenu avant d'avoir été émise. Sur le nom : lastConfirmedAt et non confirmedAt — le préfixe dit qu'il s'agit du DERNIER d'une suite, là où confirmedAt se lirait comme l'instant d'un événement unique, et une ligne reconduite quatre fois n'a pas « un » instant de confirmation. loadmodel 31/31, charging 48/48, spotmarket 32/32, ci-quality 4/4, build 0 erreur 0 warning. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015F7G5VeaPVSMeVNjiGj36p
103 lines
4.9 KiB
C++
103 lines
4.9 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();
|
|
|
|
//! Une révision n'existe que si la PRÉVISION a changé — un run qui reconduit met à jour la
|
|
//! fraîcheur, il n'ouvre pas de ligne. Et les deux instants ne se confondent pas.
|
|
void testARevisionExistsOnlyIfTheForecastChanged();
|
|
//! La courbe se relit telle qu'elle était connue à un instant, 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 fraîcheur ne date JAMAIS la prévision, et elle n'est jamais absente.
|
|
void testFreshnessNeverDatesTheForecast();
|
|
//! La charge utile de transport se REFUSE plutôt que de s'interpréter au mieux.
|
|
void testAnUnknownPayloadIsRefusedNotBestEffort();
|
|
};
|
|
|
|
#endif // TESTLOADMODEL_H
|