diff --git a/tests/auto/charging/testcharging.cpp b/tests/auto/charging/testcharging.cpp index ca1706a..f1eb36b 100644 --- a/tests/auto/charging/testcharging.cpp +++ b/tests/auto/charging/testcharging.cpp @@ -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 diff --git a/tests/auto/simulation/simulation.cpp b/tests/auto/simulation/simulation.cpp index 7d236cf..2094418 100644 --- a/tests/auto/simulation/simulation.cpp +++ b/tests/auto/simulation/simulation.cpp @@ -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,