On attendait la déduplication ; on a trouvé une écrasure. 06:03 RÉVISION du run 00Z — 48 créneaux 07:02 RÉVISION du run 00Z — 48 créneaux <- MÊME RUN Deux écritures pour la même identité de run à une heure d'écart : les valeurs avaient changé sous un issuedAt inchangé, et INSERT OR REPLACE les a remplacées sans rien dire. LA PREUVE A ÉTÉ DÉTRUITE PAR LE DÉFAUT LUI-MÊME. À 07:14 la table et l'état publié coïncidaient parfaitement — l'écrasure avait déjà aligné les deux, et la question « quels créneaux ont changé, les passés ou les futurs ? » est devenue impossible à poser. CORRECTION : un run est immuable. Des valeurs neuves sous une identité connue n'écrasent plus rien — écriture refusée, premières valeurs conservées, anomalie journalisée AVEC ce qui manquait pour décider : combien de créneaux, et le partage passé/futur. Ce partage est LE discriminant et il commande deux conduites opposées. Passés seulement : le producteur fusionne l'observé, légitime chez lui, et c'est NOTRE clé qui est trop courte — il lui faudrait une composante de contenu. Futurs : c'est un run qui n'identifie pas son contenu, donc un défaut à lui remonter. Et un cas neuf pour la famille des modes d'érosion : un défaut qui écrase la donnée qui le prouverait ne laisse pas de preuve, il laisse une COHÉRENCE. À 07:14 tout concordait — exactement ce qu'on aurait vu si tout allait bien. Au passage, le geste du motif s'adapte au nombre de candidats : « désignez-le » devant un seul, « choisissez-en un parmi les N » au-delà. Envoyer chercher une liste qui n'existe pas fait perdre le temps que l'aide prétend gagner, et c'est le pire endroit pour se tromper puisque celui qui la lit ne sait pas déjà. loadmodel 33/33, charging 48/48, spotmarket 32/32, simulation 69/69, 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
107 lines
5.2 KiB
C++
107 lines
5.2 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();
|
|
//! Une clé de run inconnue se REFUSE — l'heure de réception ne s'y substitue jamais.
|
|
void testAnUnknownRunKeyIsRefusedNotGuessed();
|
|
//! Un RUN est immuable : des valeurs neuves sous une identité connue n'écrasent rien.
|
|
void testARunIsImmutable();
|
|
//! La charge utile de transport se REFUSE plutôt que de s'interpréter au mieux.
|
|
void testAnUnknownPayloadIsRefusedNotBestEffort();
|
|
};
|
|
|
|
#endif // TESTLOADMODEL_H
|