test : la marge de sécurité dans les tests existants
- Tests de l'algorithme amont (charging, simulation::run) et famille « heures creuses à pleine puissance » : marge nulle, posée explicitement au point d'appel. Ils figent une arithmétique contre la limite ; la marge déplace le seuil sans toucher à ce calcul. - testWorstPhaseBindsWhenPhasesAreUnbalanced : marge et plafond au seuil (22,5 A pour 25 A). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
5ab5213aad
commit
3dfb74dcb3
@ -332,7 +332,8 @@ void TestCharging::addChargingInfo()
|
||||
|
||||
// Set phase power limit
|
||||
uint phasePowerLimit = 25000;
|
||||
response = injectAndWait("NymeaEnergy.SetPhasePowerLimit", QVariantMap({{"phasePowerLimit", phasePowerLimit}}));
|
||||
// Lot délestage triphasé — algorithme AMONT, figé à marge de sécurité nulle (seuil = limite).
|
||||
response = injectAndWait("NymeaEnergy.SetPhasePowerLimit", QVariantMap({{"phasePowerLimit", phasePowerLimit}, {"phaseSafetyMarginPercent", 0}}));
|
||||
verifyEnergyError(response);
|
||||
|
||||
removeDevices();
|
||||
@ -691,7 +692,8 @@ void TestCharging::testEcoMode()
|
||||
|
||||
// Set phase power limit
|
||||
params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
|
||||
response = injectAndWait("NymeaEnergy.SetPhasePowerLimit", QVariantMap({{"phasePowerLimit", phasePowerLimit}}));
|
||||
// Lot délestage triphasé — algorithme AMONT, figé à marge de sécurité nulle (seuil = limite).
|
||||
response = injectAndWait("NymeaEnergy.SetPhasePowerLimit", QVariantMap({{"phasePowerLimit", phasePowerLimit}, {"phaseSafetyMarginPercent", 0}}));
|
||||
QVERIFY(response.toMap().value("params").toMap().value("energyError").toString() == "EnergyErrorNoError");
|
||||
|
||||
// Add mock meter
|
||||
@ -1069,7 +1071,8 @@ void TestCharging::testEcoModePhaseSwitching()
|
||||
|
||||
// Set phase power limit
|
||||
params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
|
||||
response = injectAndWait("NymeaEnergy.SetPhasePowerLimit", QVariantMap({{"phasePowerLimit", phasePowerLimit}}));
|
||||
// Lot délestage triphasé — algorithme AMONT, figé à marge de sécurité nulle (seuil = limite).
|
||||
response = injectAndWait("NymeaEnergy.SetPhasePowerLimit", QVariantMap({{"phasePowerLimit", phasePowerLimit}, {"phaseSafetyMarginPercent", 0}}));
|
||||
QVERIFY(response.toMap().value("params").toMap().value("energyError").toString() == "EnergyErrorNoError");
|
||||
|
||||
// Add mock meter
|
||||
@ -1381,7 +1384,8 @@ void TestCharging::testBatteryLevelConsideration()
|
||||
|
||||
// Set phase power limit
|
||||
params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
|
||||
response = injectAndWait("NymeaEnergy.SetPhasePowerLimit", QVariantMap({{"phasePowerLimit", phasePowerLimit}}));
|
||||
// Lot délestage triphasé — algorithme AMONT, figé à marge de sécurité nulle (seuil = limite).
|
||||
response = injectAndWait("NymeaEnergy.SetPhasePowerLimit", QVariantMap({{"phasePowerLimit", phasePowerLimit}, {"phaseSafetyMarginPercent", 0}}));
|
||||
QVERIFY(response.toMap().value("params").toMap().value("energyError").toString() == "EnergyErrorNoError");
|
||||
|
||||
// Add mock meter
|
||||
@ -1602,7 +1606,8 @@ void TestCharging::batteryLevelConsiderationStopCharging()
|
||||
|
||||
// Set phase power limit
|
||||
params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
|
||||
response = injectAndWait("NymeaEnergy.SetPhasePowerLimit", QVariantMap({{"phasePowerLimit", phasePowerLimit}}));
|
||||
// Lot délestage triphasé — algorithme AMONT, figé à marge de sécurité nulle (seuil = limite).
|
||||
response = injectAndWait("NymeaEnergy.SetPhasePowerLimit", QVariantMap({{"phasePowerLimit", phasePowerLimit}, {"phaseSafetyMarginPercent", 0}}));
|
||||
QVERIFY(response.toMap().value("params").toMap().value("energyError").toString() == "EnergyErrorNoError");
|
||||
|
||||
// Add mock meter
|
||||
@ -1840,7 +1845,8 @@ void TestCharging::testOverloadProtectionManualMode()
|
||||
|
||||
// Set phase power limit
|
||||
params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
|
||||
response = injectAndWait("NymeaEnergy.SetPhasePowerLimit", QVariantMap({{"phasePowerLimit", phasePowerLimit}}));
|
||||
// Lot délestage triphasé — algorithme AMONT, figé à marge de sécurité nulle (seuil = limite).
|
||||
response = injectAndWait("NymeaEnergy.SetPhasePowerLimit", QVariantMap({{"phasePowerLimit", phasePowerLimit}, {"phaseSafetyMarginPercent", 0}}));
|
||||
QVERIFY(response.toMap().value("params").toMap().value("energyError").toString() == "EnergyErrorNoError");
|
||||
|
||||
// Add mock meter
|
||||
@ -2011,7 +2017,8 @@ void TestCharging::testOverloadProtectionEcoMode()
|
||||
|
||||
// Set phase power limit
|
||||
params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
|
||||
response = injectAndWait("NymeaEnergy.SetPhasePowerLimit", QVariantMap({{"phasePowerLimit", phasePowerLimit}}));
|
||||
// Lot délestage triphasé — algorithme AMONT, figé à marge de sécurité nulle (seuil = limite).
|
||||
response = injectAndWait("NymeaEnergy.SetPhasePowerLimit", QVariantMap({{"phasePowerLimit", phasePowerLimit}, {"phaseSafetyMarginPercent", 0}}));
|
||||
QVERIFY(response.toMap().value("params").toMap().value("energyError").toString() == "EnergyErrorNoError");
|
||||
|
||||
// Add mock meter
|
||||
|
||||
@ -3495,7 +3495,9 @@ void Simulation::run()
|
||||
Electricity::Phases chargerPhasesConverted = Electricity::convertPhasesFromString(chargerPhases);
|
||||
|
||||
// Set phase power limit
|
||||
response = injectAndWait("NymeaEnergy.SetPhasePowerLimit", QVariantMap({{"phasePowerLimit", phasePowerLimit}}));
|
||||
// Lot délestage triphasé — scénarios ENREGISTRÉS sous la règle amont : marge de sécurité
|
||||
// nulle, le seuil est la limite. La marge est éprouvée par testPhaseSafetyMargin.
|
||||
response = injectAndWait("NymeaEnergy.SetPhasePowerLimit", QVariantMap({{"phasePowerLimit", phasePowerLimit}, {"phaseSafetyMarginPercent", 0}}));
|
||||
QVERIFY(response.toMap().value("params").toMap().value("energyError").toString() == "EnergyErrorNoError");
|
||||
|
||||
// Add mock spotmarket provider
|
||||
@ -10259,9 +10261,11 @@ void Simulation::testWorstPhaseBindsWhenPhasesAreUnbalanced()
|
||||
const DrawMargin marge = arb->houseDrawMargin();
|
||||
QVERIFY2(marge.borne, "un plafond est réglé : la marge doit exister");
|
||||
|
||||
// 1. LA PIRE PHASE L'EMPORTE — et c'est bien B, pas le total. 25 A − 30 A = −5 A, soit
|
||||
// −1150 W. Le total, lui, afficherait un confort trompeur : la maison exporte.
|
||||
QCOMPARE(qRound(marge.watts), -1150);
|
||||
// 1. LA PIRE PHASE L'EMPORTE — et c'est bien B, pas le total. 22,5 A − 30 A = −7,5 A, soit
|
||||
// −1725 W : le seuil est la limite de 25 A moins la marge de sécurité de 10 % (lot délestage
|
||||
// triphasé ; −1150 W avant lui). Le total, lui, afficherait un confort trompeur : la maison
|
||||
// exporte.
|
||||
QCOMPARE(qRound(marge.watts), -1725);
|
||||
QCOMPARE(marge.phase, 1); // phase B (0 = A, 1 = B, 2 = C)
|
||||
|
||||
// 1-bis. ET L'ÉCRAN DOIT POUVOIR NOMMER CETTE PHASE. `perPhaseBound` dit qu'une phase borne,
|
||||
@ -10296,7 +10300,9 @@ void Simulation::testWorstPhaseBindsWhenPhasesAreUnbalanced()
|
||||
// (« une phase qui exporte a une marge plus grande que le plafond »), et la phrase a été
|
||||
// corrigée. La conclusion, elle, tient : une phase qui exporte est au maximum de la marge,
|
||||
// donc elle ne borne jamais.
|
||||
QCOMPARE(qRound(equilibree.watts), qRound(25.0 * 230.0));
|
||||
// Le plafond est le SEUIL : 25 A moins la marge de sécurité de 10 %, 22,5 A (lot délestage
|
||||
// triphasé).
|
||||
QCOMPARE(qRound(equilibree.watts), qRound(22.5 * 230.0));
|
||||
QVERIFY2(equilibree.watts > 0, "rééquilibrée, la marge redevient positive");
|
||||
#endif
|
||||
}
|
||||
@ -12246,7 +12252,10 @@ void Simulation::testOffPeakFullPowerYieldsToThePhaseLimit()
|
||||
BorneHc::Triphasee);
|
||||
QVERIFY(s.arb && s.borne && s.batterie);
|
||||
const QString borne = s.borneId.toString();
|
||||
QCOMPARE(injectAndWait("NymeaEnergy.SetPhasePowerLimit", QVariantMap({{"phasePowerLimit", 25}}))
|
||||
// Lot délestage triphasé — ce test fige le plafond d'heures creuses contre la LIMITE ;
|
||||
// marge de sécurité nulle, pour que ses séries restent les siennes. La marge déplace le
|
||||
// seuil sans toucher à ce calcul (effectivePhaseLimit()), éprouvée par testPhaseSafetyMargin.
|
||||
QCOMPARE(injectAndWait("NymeaEnergy.SetPhasePowerLimit", QVariantMap({{"phasePowerLimit", 25}, {"phaseSafetyMarginPercent", 0}}))
|
||||
.toMap().value("params").toMap().value("energyError").toString(),
|
||||
QString("EnergyErrorNoError"));
|
||||
s.borne->setStateValue("pluggedIn", true);
|
||||
@ -12392,7 +12401,10 @@ void Simulation::testOffPeakFullPowerSettlesUnderThePhaseLimit()
|
||||
.arg(qRound(c.maisonA * 10)),
|
||||
BatterieHc::Aucune, hphcT4(), c.borne);
|
||||
QVERIFY2(s.arb && s.borne, c.quoi);
|
||||
QCOMPARE(injectAndWait("NymeaEnergy.SetPhasePowerLimit", QVariantMap({{"phasePowerLimit", c.limiteA}}))
|
||||
// Lot délestage triphasé — ce test fige le plafond d'heures creuses contre la LIMITE ;
|
||||
// marge de sécurité nulle, pour que ses séries restent les siennes. La marge déplace le
|
||||
// seuil sans toucher à ce calcul (effectivePhaseLimit()), éprouvée par testPhaseSafetyMargin.
|
||||
QCOMPARE(injectAndWait("NymeaEnergy.SetPhasePowerLimit", QVariantMap({{"phasePowerLimit", c.limiteA}, {"phaseSafetyMarginPercent", 0}}))
|
||||
.toMap().value("params").toMap().value("energyError").toString(),
|
||||
QString("EnergyErrorNoError"));
|
||||
s.borne->setStateValue("pluggedIn", true);
|
||||
@ -12454,7 +12466,10 @@ void Simulation::testOffPeakFullPowerStaysUnderTheLimitOnABlindMeter()
|
||||
ScenarioHc s = preparerScenarioHc(QStringLiteral("compteur-aveugle-%1").arg(totalVoitLaBorne),
|
||||
BatterieHc::Aucune, hphcT4(), BorneHc::Monophasee);
|
||||
QVERIFY2(s.arb && s.borne, quoi);
|
||||
QCOMPARE(injectAndWait("NymeaEnergy.SetPhasePowerLimit", QVariantMap({{"phasePowerLimit", 16}}))
|
||||
// Lot délestage triphasé — ce test fige le plafond d'heures creuses contre la LIMITE ;
|
||||
// marge de sécurité nulle, pour que ses séries restent les siennes. La marge déplace le
|
||||
// seuil sans toucher à ce calcul (effectivePhaseLimit()), éprouvée par testPhaseSafetyMargin.
|
||||
QCOMPARE(injectAndWait("NymeaEnergy.SetPhasePowerLimit", QVariantMap({{"phasePowerLimit", 16}, {"phaseSafetyMarginPercent", 0}}))
|
||||
.toMap().value("params").toMap().value("energyError").toString(),
|
||||
QString("EnergyErrorNoError"));
|
||||
s.borne->setStateValue("pluggedIn", true);
|
||||
@ -12497,7 +12512,10 @@ void Simulation::testOffPeakFullPowerAtDawnStaysInTheCeiling()
|
||||
ScenarioHc s = preparerScenarioHc(QStringLiteral("aube"), BatterieHc::Aucune, hphcT4(),
|
||||
BorneHc::Monophasee);
|
||||
QVERIFY(s.arb && s.borne);
|
||||
QCOMPARE(injectAndWait("NymeaEnergy.SetPhasePowerLimit", QVariantMap({{"phasePowerLimit", 16}}))
|
||||
// Lot délestage triphasé — ce test fige le plafond d'heures creuses contre la LIMITE ;
|
||||
// marge de sécurité nulle, pour que ses séries restent les siennes. La marge déplace le
|
||||
// seuil sans toucher à ce calcul (effectivePhaseLimit()), éprouvée par testPhaseSafetyMargin.
|
||||
QCOMPARE(injectAndWait("NymeaEnergy.SetPhasePowerLimit", QVariantMap({{"phasePowerLimit", 16}, {"phaseSafetyMarginPercent", 0}}))
|
||||
.toMap().value("params").toMap().value("energyError").toString(),
|
||||
QString("EnergyErrorNoError"));
|
||||
s.borne->setStateValue("pluggedIn", true);
|
||||
@ -12592,7 +12610,10 @@ void Simulation::testOffPeakFullPowerGivesNothingBackWhileSwitchingPhases()
|
||||
ScenarioHc s = preparerScenarioHc(QStringLiteral("garde-bascule"), BatterieHc::Aucune, hphcT4(),
|
||||
BorneHc::Bascule);
|
||||
QVERIFY(s.arb && s.borne);
|
||||
QCOMPARE(injectAndWait("NymeaEnergy.SetPhasePowerLimit", QVariantMap({{"phasePowerLimit", 25}}))
|
||||
// Lot délestage triphasé — ce test fige le plafond d'heures creuses contre la LIMITE ;
|
||||
// marge de sécurité nulle, pour que ses séries restent les siennes. La marge déplace le
|
||||
// seuil sans toucher à ce calcul (effectivePhaseLimit()), éprouvée par testPhaseSafetyMargin.
|
||||
QCOMPARE(injectAndWait("NymeaEnergy.SetPhasePowerLimit", QVariantMap({{"phasePowerLimit", 25}, {"phaseSafetyMarginPercent", 0}}))
|
||||
.toMap().value("params").toMap().value("energyError").toString(),
|
||||
QString("EnergyErrorNoError"));
|
||||
const QString borne = s.borneId.toString();
|
||||
@ -13382,9 +13403,9 @@ void Simulation::testOverloadReactsToTheTotalWhenPhasesLag()
|
||||
QSKIP("nécessite ETM_ARBITRATOR.");
|
||||
#else
|
||||
// DÉFAUT 2. Sur l'installation d'essai, la maison est montée à 22,3 kW importés — 32,3 A par
|
||||
// phase — à 11:08:10, et le délestage ne s'est ouvert qu'à 11:09:55. Les courants PAR PHASE
|
||||
// que le moteur recevait étaient en retard sur le total : 29,1 A à 11:09, 30,4 A à 11:10, pour
|
||||
// 32,3 A réels. La protection jugeait sur eux seuls et attendait qu'ils franchissent la limite.
|
||||
// phase — et le délestage ne s'est ouvert que 1 min 45 plus tard. Les courants PAR PHASE que
|
||||
// le moteur recevait étaient en retard sur le total : 29,1 A, puis 30,4 A au cycle suivant,
|
||||
// pour 32,3 A réels. La protection jugeait sur eux seuls et attendait qu'ils franchissent la limite.
|
||||
//
|
||||
// Or le total suffit à prouver la surcharge : la phase la plus chargée porte AU MOINS le tiers
|
||||
// du total importé. Ici, 26 A lus sur chaque phase pour 22 277 W au total : 32,3 A en moyenne,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user