diff --git a/src/main/java/org/gridsuite/securityanalysis/server/dto/LimitViolationDTO.java b/src/main/java/org/gridsuite/securityanalysis/server/dto/LimitViolationDTO.java index bcd34264..211e709a 100644 --- a/src/main/java/org/gridsuite/securityanalysis/server/dto/LimitViolationDTO.java +++ b/src/main/java/org/gridsuite/securityanalysis/server/dto/LimitViolationDTO.java @@ -24,7 +24,8 @@ public class LimitViolationDTO { private String limitName; private String nextLimitName; private ThreeSides side; - private int acceptableDuration; + private Integer acceptableDuration; + private Integer upcomingAcceptableDuration; private double limit; private Double patlLimit; private double limitReduction; @@ -39,7 +40,8 @@ public static LimitViolationDTO toDto(AbstractLimitViolationEntity limitViolatio .limitName(limitViolation.getLimitName()) .nextLimitName(limitViolation.getNextLimitName()) .side(limitViolation.getSide()) - .acceptableDuration((int) limitViolation.getAcceptableDuration()) + .acceptableDuration(limitViolation.getAcceptableDuration()) + .upcomingAcceptableDuration(limitViolation.getUpcomingAcceptableDuration()) .limit(limitViolation.getLimit()) .patlLimit(limitViolation.getPatlLimit()) .limitReduction(limitViolation.getLimitReduction()) diff --git a/src/main/java/org/gridsuite/securityanalysis/server/entities/AbstractLimitViolationEntity.java b/src/main/java/org/gridsuite/securityanalysis/server/entities/AbstractLimitViolationEntity.java index e3263195..5a2249b8 100644 --- a/src/main/java/org/gridsuite/securityanalysis/server/entities/AbstractLimitViolationEntity.java +++ b/src/main/java/org/gridsuite/securityanalysis/server/entities/AbstractLimitViolationEntity.java @@ -17,10 +17,7 @@ import lombok.experimental.FieldNameConstants; import lombok.experimental.SuperBuilder; -import java.util.Collection; -import java.util.Iterator; -import java.util.Optional; -import java.util.UUID; +import java.util.*; /** * @author Kevin Le Saulnier @@ -53,7 +50,9 @@ public abstract class AbstractLimitViolationEntity { @Enumerated(EnumType.STRING) private LimitViolationType limitType; - private long acceptableDuration; + private Integer acceptableDuration; + + private Integer upcomingAcceptableDuration; private double limitReduction; @@ -130,4 +129,42 @@ private static LoadingLimits.TemporaryLimit getNextTemporaryLimit(Branch bran return null; } + + protected static Integer calculateActualOverloadDuration(LimitViolation limitViolation, Network network) { + if (limitViolation.getValue() > limitViolation.getLimit()) { + return limitViolation.getAcceptableDuration(); + } else { + String equipmentId = limitViolation.getSubjectId(); + LoadingLimits.TemporaryLimit tempLimit = null; + + Branch branch = network.getBranch(equipmentId); + if (branch != null) { + tempLimit = getBranchLimitViolationAboveCurrentValue(branch, limitViolation); + } + return (tempLimit != null) ? tempLimit.getAcceptableDuration() : Integer.MAX_VALUE; + } + } + + protected static Integer calculateUpcomingOverloadDuration(LimitViolation limitViolation) { + if (limitViolation.getValue() < limitViolation.getLimit()) { + return limitViolation.getAcceptableDuration(); + } + return null; + } + + private static LoadingLimits.TemporaryLimit getBranchLimitViolationAboveCurrentValue(Branch branch, LimitViolation limitViolation) { + // limits are returned from the store by DESC duration / ASC value + Optional currentLimits = branch.getCurrentLimits(limitViolation.getSideAsTwoSides()); + if (currentLimits.isEmpty() || limitViolation.getValue() < currentLimits.get().getPermanentLimit()) { + return null; + } else { + Optional nextTemporaryLimit = currentLimits.get().getTemporaryLimits().stream() + .filter(tl -> limitViolation.getValue() < tl.getValue()) + .findFirst(); + if (nextTemporaryLimit.isPresent()) { + return nextTemporaryLimit.get(); + } + } + return null; + } } diff --git a/src/main/java/org/gridsuite/securityanalysis/server/entities/ContingencyLimitViolationEntity.java b/src/main/java/org/gridsuite/securityanalysis/server/entities/ContingencyLimitViolationEntity.java index 3bd83583..1bdd6079 100644 --- a/src/main/java/org/gridsuite/securityanalysis/server/entities/ContingencyLimitViolationEntity.java +++ b/src/main/java/org/gridsuite/securityanalysis/server/entities/ContingencyLimitViolationEntity.java @@ -46,7 +46,8 @@ public static ContingencyLimitViolationEntity toEntity(Network network, LimitVio .patlLimit(patlLimit) .patlLoading(computeLoading(limitViolation, patlLimit)) .nextLimitName(getNextLimitName(limitViolation, network)) - .acceptableDuration(limitViolation.getAcceptableDuration()) + .acceptableDuration(calculateActualOverloadDuration(limitViolation, network)) + .upcomingAcceptableDuration(calculateUpcomingOverloadDuration(limitViolation)) .build(); subjectLimitViolation.addContingencyLimitViolation(contingencyLimitViolationEntity); diff --git a/src/main/resources/db/changelog/changesets/changelog_20250908T143920Z.xml b/src/main/resources/db/changelog/changesets/changelog_20250908T143920Z.xml new file mode 100644 index 00000000..8e50b705 --- /dev/null +++ b/src/main/resources/db/changelog/changesets/changelog_20250908T143920Z.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + acceptable_duration < -2147483648 OR acceptable_duration > 2147483647 + + + + + + + + + acceptable_duration < -2147483648 OR acceptable_duration > 2147483647 + + + + + + diff --git a/src/main/resources/db/changelog/db.changelog-master.yaml b/src/main/resources/db/changelog/db.changelog-master.yaml index 366cada8..e1801e26 100644 --- a/src/main/resources/db/changelog/db.changelog-master.yaml +++ b/src/main/resources/db/changelog/db.changelog-master.yaml @@ -48,3 +48,6 @@ databaseChangeLog: - include: file: changesets/changelog_20250903T125946Z.xml relativeToChangelogFile: true + - include: + file: changesets/changelog_20250908T143920Z.xml + relativeToChangelogFile: true diff --git a/src/test/java/org/gridsuite/securityanalysis/server/ContingencyLimitViolationTest.java b/src/test/java/org/gridsuite/securityanalysis/server/ContingencyLimitViolationTest.java index 3a78c3d9..04cdb7d1 100644 --- a/src/test/java/org/gridsuite/securityanalysis/server/ContingencyLimitViolationTest.java +++ b/src/test/java/org/gridsuite/securityanalysis/server/ContingencyLimitViolationTest.java @@ -17,6 +17,8 @@ import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; +import static com.powsybl.iidm.network.test.EurostagTutorialExample1Factory.NGEN_NHV1; +import static com.powsybl.iidm.network.test.EurostagTutorialExample1Factory.NHV1_NHV2_1; import static org.junit.jupiter.api.Assertions.assertEquals; /** @@ -24,37 +26,81 @@ */ @SpringBootTest class ContingencyLimitViolationTest { - @Test void testContingencyLimitViolationEntityNewFields() { - testContingencyLimitViolationMapping("10'", 10 * 60, 1200, 1250, TwoSides.TWO, "1'", 1100); + testContingencyLimitViolationMapping("10'", 10 * 60, 1200, 1, 1250, TwoSides.TWO, "1'", 1100, 10 * 60, null); } @Test void testContingencyLimitViolationEntityNewFieldsWithPermanentLimitReached() { - testContingencyLimitViolationMapping(LimitViolationUtils.PERMANENT_LIMIT_NAME, Integer.MAX_VALUE, 1100, 1150, TwoSides.TWO, "10'", 1100); + testContingencyLimitViolationMapping(LimitViolationUtils.PERMANENT_LIMIT_NAME, Integer.MAX_VALUE, 1100, 1, 1150, TwoSides.TWO, "10'", 1100, Integer.MAX_VALUE, null); } @Test void testContingencyLimitViolationEntityNewFieldsWithPermanentLimitReachedAndNoTemporaryLimit() { - testContingencyLimitViolationMapping(LimitViolationUtils.PERMANENT_LIMIT_NAME, Integer.MAX_VALUE, 500, 1000, TwoSides.ONE, null, 500); + testContingencyLimitViolationMapping(LimitViolationUtils.PERMANENT_LIMIT_NAME, Integer.MAX_VALUE, 500, 1, 1000, TwoSides.ONE, null, 500, Integer.MAX_VALUE, null); } @Test void testContingencyLimitViolationEntityNewFieldsWithLastLimitReached() { - testContingencyLimitViolationMapping("N/A", 0, 1100, 3000, TwoSides.TWO, null, 1100); + testContingencyLimitViolationMapping("N/A", 0, 1100, 1, 3000, TwoSides.TWO, null, 1100, 0, null); + } + + @Test + void testContingencyLimitViolationEntityNewFieldsWithLimitReductionEffective() { + // for this test to be relevant, "value" needs to be less that "limit" + testContingencyLimitViolationMapping("10'", 60, 1200, 0.8, 1150, TwoSides.TWO, "1'", 1100, 10 * 60, 60); + } + + @Test + void test2wtContingencyLimitViolationEntityNewFieldsWithLimitReductionEffective() { + // for this test to be relevant, "value" needs to be less that "limit" + Network network = EurostagTutorialExample1Factory.createWithFixedCurrentLimits(); + // create limit set for two windings transformer + network.getTwoWindingsTransformer(NGEN_NHV1).getOrCreateSelectedOperationalLimitsGroup1().newCurrentLimits() + .setPermanentLimit(100) + .beginTemporaryLimit() + .setName("10'") + .setValue(200) + .setAcceptableDuration(60 * 10) + .endTemporaryLimit() + .beginTemporaryLimit() + .setName("1'") + .setValue(300) + .setAcceptableDuration(60) + .endTemporaryLimit() + .beginTemporaryLimit() + .setName("N/A") + .setValue(Double.MAX_VALUE) + .setAcceptableDuration(0) + .endTemporaryLimit() + .add(); + + LimitViolation limitViolation = new LimitViolation(NGEN_NHV1, "NGEN_NHV1_name", LimitViolationType.CURRENT, "10'", 60, 200, 0.8, 180, TwoSides.ONE); + + SubjectLimitViolationEntity subjectLimitViolationEntity = new SubjectLimitViolationEntity(NGEN_NHV1, "NGEN_NHV1_name"); + + ContingencyLimitViolationEntity contingencyLimitViolationEntity = ContingencyLimitViolationEntity.toEntity(network, limitViolation, subjectLimitViolationEntity); + + assertEquals("1'", contingencyLimitViolationEntity.getNextLimitName()); + assertEquals(100, contingencyLimitViolationEntity.getPatlLimit()); + assertEquals(60 * 10, contingencyLimitViolationEntity.getAcceptableDuration()); + assertEquals(60, contingencyLimitViolationEntity.getUpcomingAcceptableDuration()); + assertEquals(100 * limitViolation.getValue() / contingencyLimitViolationEntity.getPatlLimit(), contingencyLimitViolationEntity.getPatlLoading()); } - private void testContingencyLimitViolationMapping(String limitName, int acceptableDuration, double limit, double value, TwoSides side, String expectedNextLimitName, long expectedPatlLimit) { + private void testContingencyLimitViolationMapping(String limitName, int acceptableDuration, double limit, double limitReduction, double value, TwoSides side, String expectedNextLimitName, long expectedPatlLimit, Integer expectedAcceptableDuration, Integer expectedUpcomingAcceptableDuration) { Network network = EurostagTutorialExample1Factory.createWithFixedCurrentLimits(); - LimitViolation limitViolation = new LimitViolation("NHV1_NHV2_1", "NHV1_NHV2_1_name", LimitViolationType.CURRENT, limitName, acceptableDuration, limit, 1, value, side); + LimitViolation limitViolation = new LimitViolation(NHV1_NHV2_1, "NHV1_NHV2_1_name", LimitViolationType.CURRENT, limitName, acceptableDuration, limit, limitReduction, value, side); - SubjectLimitViolationEntity subjectLimitViolationEntity = new SubjectLimitViolationEntity("NHV1_NHV2_1", "NHV1_NHV2_1_name"); + SubjectLimitViolationEntity subjectLimitViolationEntity = new SubjectLimitViolationEntity(NHV1_NHV2_1, "NHV1_NHV2_1_name"); ContingencyLimitViolationEntity contingencyLimitViolationEntity = ContingencyLimitViolationEntity.toEntity(network, limitViolation, subjectLimitViolationEntity); assertEquals(expectedNextLimitName, contingencyLimitViolationEntity.getNextLimitName()); assertEquals(expectedPatlLimit, contingencyLimitViolationEntity.getPatlLimit()); + assertEquals(expectedAcceptableDuration, contingencyLimitViolationEntity.getAcceptableDuration()); + assertEquals(expectedUpcomingAcceptableDuration, contingencyLimitViolationEntity.getUpcomingAcceptableDuration()); assertEquals(100 * limitViolation.getValue() / contingencyLimitViolationEntity.getPatlLimit(), contingencyLimitViolationEntity.getPatlLoading()); } }