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 3dbee102..bcd34264 100644 --- a/src/main/java/org/gridsuite/securityanalysis/server/dto/LimitViolationDTO.java +++ b/src/main/java/org/gridsuite/securityanalysis/server/dto/LimitViolationDTO.java @@ -22,24 +22,30 @@ public class LimitViolationDTO { private LimitViolationType limitType; private String limitName; + private String nextLimitName; private ThreeSides side; private int acceptableDuration; private double limit; + private Double patlLimit; private double limitReduction; private double value; private Double loading; + private Double patlLoading; private String locationId; public static LimitViolationDTO toDto(AbstractLimitViolationEntity limitViolation) { return LimitViolationDTO.builder() .limitType(limitViolation.getLimitType()) .limitName(limitViolation.getLimitName()) + .nextLimitName(limitViolation.getNextLimitName()) .side(limitViolation.getSide()) .acceptableDuration((int) limitViolation.getAcceptableDuration()) .limit(limitViolation.getLimit()) + .patlLimit(limitViolation.getPatlLimit()) .limitReduction(limitViolation.getLimitReduction()) .value(limitViolation.getValue()) .loading(limitViolation.getLoading()) + .patlLoading(limitViolation.getPatlLoading()) .locationId(limitViolation.getLocationId()) .build(); } 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 2bc0dc7c..e3263195 100644 --- a/src/main/java/org/gridsuite/securityanalysis/server/entities/AbstractLimitViolationEntity.java +++ b/src/main/java/org/gridsuite/securityanalysis/server/entities/AbstractLimitViolationEntity.java @@ -6,7 +6,8 @@ */ package org.gridsuite.securityanalysis.server.entities; -import com.powsybl.iidm.network.ThreeSides; +import com.powsybl.iidm.network.*; +import com.powsybl.iidm.network.util.LimitViolationUtils; import com.powsybl.security.LimitViolation; import com.powsybl.security.LimitViolationType; import jakarta.persistence.*; @@ -16,6 +17,9 @@ import lombok.experimental.FieldNameConstants; import lombok.experimental.SuperBuilder; +import java.util.Collection; +import java.util.Iterator; +import java.util.Optional; import java.util.UUID; /** @@ -40,8 +44,12 @@ public abstract class AbstractLimitViolationEntity { @Column(name = "limitValue") private double limit; + private Double patlLimit; + private String limitName; + private String nextLimitName; + @Enumerated(EnumType.STRING) private LimitViolationType limitType; @@ -58,12 +66,68 @@ public abstract class AbstractLimitViolationEntity { @Column(name = "loading") private Double loading; + private Double patlLoading; + @Column private String locationId; - public static Double computeLoading(LimitViolation limitViolation) { - return LimitViolationType.CURRENT.equals(limitViolation.getLimitType()) - ? 100 * limitViolation.getValue() / limitViolation.getLimit() + protected static Double computeLoading(LimitViolation limitViolation, Double limit) { + return LimitViolationType.CURRENT.equals(limitViolation.getLimitType()) && limit != null + ? 100 * limitViolation.getValue() / limit : null; } + + protected static Double getPatlLimit(LimitViolation limitViolation, Network network) { + String equipmentId = limitViolation.getSubjectId(); + Branch branch = network.getBranch(equipmentId); + ThreeSides limitViolationSide = limitViolation.getSide(); + if (branch == null || limitViolationSide == null) { + return null; + } + + Optional currentLimits = branch.getCurrentLimits(limitViolationSide.toTwoSides()); + if (currentLimits.isPresent()) { + return currentLimits.get().getPermanentLimit(); + } + return null; + } + + protected static String getNextLimitName(LimitViolation limitViolation, Network network) { + String equipmentId = limitViolation.getSubjectId(); + Branch branch = network.getBranch(equipmentId); + if (branch == null) { + return null; + } + LoadingLimits.TemporaryLimit temporaryLimit = getNextTemporaryLimit(branch, limitViolation); + return temporaryLimit != null ? temporaryLimit.getName() : null; + } + + private static LoadingLimits.TemporaryLimit getNextTemporaryLimit(Branch branch, LimitViolation limitViolation) { + // limits are returned from the store by DESC duration / ASC value + ThreeSides limitViolationSide = limitViolation.getSide(); + String limitName = limitViolation.getLimitName(); + if (limitViolationSide == null || limitName == null) { + return null; + } + + Optional currentLimits = branch.getCurrentLimits(limitViolationSide.toTwoSides()); + if (currentLimits.isEmpty()) { + return null; + } + + Collection temporaryLimits = currentLimits.get().getTemporaryLimits(); + if (limitName.equals(LimitViolationUtils.PERMANENT_LIMIT_NAME)) { + return temporaryLimits.stream().findFirst().orElse(null); + } + + Iterator temporaryLimitIterator = temporaryLimits.iterator(); + while (temporaryLimitIterator.hasNext()) { + LoadingLimits.TemporaryLimit currentTemporaryLimit = temporaryLimitIterator.next(); + if (currentTemporaryLimit.getName().equals(limitViolation.getLimitName())) { + return temporaryLimitIterator.hasNext() ? temporaryLimitIterator.next() : null; + } + } + + 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 2e5e0539..3bd83583 100644 --- a/src/main/java/org/gridsuite/securityanalysis/server/entities/ContingencyLimitViolationEntity.java +++ b/src/main/java/org/gridsuite/securityanalysis/server/entities/ContingencyLimitViolationEntity.java @@ -6,7 +6,7 @@ */ package org.gridsuite.securityanalysis.server.entities; -import com.powsybl.iidm.network.Network; +import com.powsybl.iidm.network.*; import com.powsybl.security.LimitViolation; import org.gridsuite.computation.utils.ComputationResultUtils; import jakarta.persistence.*; @@ -14,8 +14,6 @@ import lombok.experimental.FieldNameConstants; import lombok.experimental.SuperBuilder; - - /** * @author Kevin Le Saulnier */ @@ -33,17 +31,22 @@ public class ContingencyLimitViolationEntity extends AbstractLimitViolationEntit private ContingencyEntity contingency; public static ContingencyLimitViolationEntity toEntity(Network network, LimitViolation limitViolation, SubjectLimitViolationEntity subjectLimitViolation) { + Double patlLimit = getPatlLimit(limitViolation, network); + ContingencyLimitViolationEntity contingencyLimitViolationEntity = ContingencyLimitViolationEntity.builder() .limit(limitViolation.getLimit()) .limitName(limitViolation.getLimitName()) .limitType(limitViolation.getLimitType()) - .acceptableDuration(limitViolation.getAcceptableDuration()) .limitReduction(limitViolation.getLimitReduction()) .value(limitViolation.getValue()) .side(limitViolation.getSide()) - .loading(computeLoading(limitViolation)) + .loading(computeLoading(limitViolation, limitViolation.getLimit())) .locationId(ComputationResultUtils.getViolationLocationId(limitViolation, network)) .subjectLimitViolation(subjectLimitViolation) + .patlLimit(patlLimit) + .patlLoading(computeLoading(limitViolation, patlLimit)) + .nextLimitName(getNextLimitName(limitViolation, network)) + .acceptableDuration(limitViolation.getAcceptableDuration()) .build(); subjectLimitViolation.addContingencyLimitViolation(contingencyLimitViolationEntity); diff --git a/src/main/java/org/gridsuite/securityanalysis/server/entities/PreContingencyLimitViolationEntity.java b/src/main/java/org/gridsuite/securityanalysis/server/entities/PreContingencyLimitViolationEntity.java index 771d014d..710c6626 100644 --- a/src/main/java/org/gridsuite/securityanalysis/server/entities/PreContingencyLimitViolationEntity.java +++ b/src/main/java/org/gridsuite/securityanalysis/server/entities/PreContingencyLimitViolationEntity.java @@ -52,7 +52,7 @@ public static PreContingencyLimitViolationEntity toEntity(Network network, Limit .limitReduction(limitViolation.getLimitReduction()) .value(limitViolation.getValue()) .side(limitViolation.getSide()) - .loading(computeLoading(limitViolation)) + .loading(computeLoading(limitViolation, limitViolation.getLimit())) .locationId(ComputationResultUtils.getViolationLocationId(limitViolation, network)) .build(); } diff --git a/src/main/resources/db/changelog/changesets/changelog_20250903T125946Z.xml b/src/main/resources/db/changelog/changesets/changelog_20250903T125946Z.xml new file mode 100644 index 00000000..d1b680ca --- /dev/null +++ b/src/main/resources/db/changelog/changesets/changelog_20250903T125946Z.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/db/changelog/db.changelog-master.yaml b/src/main/resources/db/changelog/db.changelog-master.yaml index 4c87c5db..366cada8 100644 --- a/src/main/resources/db/changelog/db.changelog-master.yaml +++ b/src/main/resources/db/changelog/db.changelog-master.yaml @@ -45,3 +45,6 @@ databaseChangeLog: - include: file: changesets/changelog_20250827T095600Z.xml relativeToChangelogFile: true + - include: + file: changesets/changelog_20250903T125946Z.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 new file mode 100644 index 00000000..3a78c3d9 --- /dev/null +++ b/src/test/java/org/gridsuite/securityanalysis/server/ContingencyLimitViolationTest.java @@ -0,0 +1,60 @@ +/** + * Copyright (c) 2025, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.gridsuite.securityanalysis.server; + +import com.powsybl.iidm.network.Network; +import com.powsybl.iidm.network.TwoSides; +import com.powsybl.iidm.network.test.EurostagTutorialExample1Factory; +import com.powsybl.iidm.network.util.LimitViolationUtils; +import com.powsybl.security.LimitViolation; +import com.powsybl.security.LimitViolationType; +import org.gridsuite.securityanalysis.server.entities.ContingencyLimitViolationEntity; +import org.gridsuite.securityanalysis.server.entities.SubjectLimitViolationEntity; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * @author Kevin Le Saulnier + */ +@SpringBootTest +class ContingencyLimitViolationTest { + + @Test + void testContingencyLimitViolationEntityNewFields() { + testContingencyLimitViolationMapping("10'", 10 * 60, 1200, 1250, TwoSides.TWO, "1'", 1100); + } + + @Test + void testContingencyLimitViolationEntityNewFieldsWithPermanentLimitReached() { + testContingencyLimitViolationMapping(LimitViolationUtils.PERMANENT_LIMIT_NAME, Integer.MAX_VALUE, 1100, 1150, TwoSides.TWO, "10'", 1100); + } + + @Test + void testContingencyLimitViolationEntityNewFieldsWithPermanentLimitReachedAndNoTemporaryLimit() { + testContingencyLimitViolationMapping(LimitViolationUtils.PERMANENT_LIMIT_NAME, Integer.MAX_VALUE, 500, 1000, TwoSides.ONE, null, 500); + } + + @Test + void testContingencyLimitViolationEntityNewFieldsWithLastLimitReached() { + testContingencyLimitViolationMapping("N/A", 0, 1100, 3000, TwoSides.TWO, null, 1100); + } + + private void testContingencyLimitViolationMapping(String limitName, int acceptableDuration, double limit, double value, TwoSides side, String expectedNextLimitName, long expectedPatlLimit) { + Network network = EurostagTutorialExample1Factory.createWithFixedCurrentLimits(); + LimitViolation limitViolation = new LimitViolation("NHV1_NHV2_1", "NHV1_NHV2_1_name", LimitViolationType.CURRENT, limitName, acceptableDuration, limit, 1, value, side); + + 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(100 * limitViolation.getValue() / contingencyLimitViolationEntity.getPatlLimit(), contingencyLimitViolationEntity.getPatlLoading()); + } +} diff --git a/src/test/java/org/gridsuite/securityanalysis/server/SecurityAnalysisProviderMock.java b/src/test/java/org/gridsuite/securityanalysis/server/SecurityAnalysisProviderMock.java index 53bfaf91..2201d464 100644 --- a/src/test/java/org/gridsuite/securityanalysis/server/SecurityAnalysisProviderMock.java +++ b/src/test/java/org/gridsuite/securityanalysis/server/SecurityAnalysisProviderMock.java @@ -231,17 +231,17 @@ private static SubjectLimitViolationDTO toSubjectLimitViolationDTO(LimitViolatio return new SubjectLimitViolationDTO( limitViolation.getSubjectId(), - new LimitViolationDTO( - limitViolation.getLimitType(), - limitViolation.getLimitName(), - limitViolation.getSide(), - limitViolation.getAcceptableDuration(), - limitViolation.getLimit(), - limitViolation.getLimitReduction(), - limitViolation.getValue(), - computedLoading, - ComputationResultUtils.getViolationLocationId(limitViolation, getNetwork()) - ) + LimitViolationDTO.builder() + .limitType(limitViolation.getLimitType()) + .limitName(limitViolation.getLimitName()) + .side(limitViolation.getSide()) + .acceptableDuration(limitViolation.getAcceptableDuration()) + .limit(limitViolation.getLimit()) + .limitReduction(limitViolation.getLimitReduction()) + .value(limitViolation.getValue()) + .loading(computedLoading) + .locationId(ComputationResultUtils.getViolationLocationId(limitViolation, getNetwork())) + .build() ); } @@ -258,17 +258,17 @@ private static PreContingencyLimitViolationResultDTO toPreContingencyResultDTO(L return new PreContingencyLimitViolationResultDTO( limitViolation.getSubjectId(), status.name(), - new LimitViolationDTO( - limitViolation.getLimitType(), - limitViolation.getLimitName(), - limitViolation.getSide(), - limitViolation.getAcceptableDuration(), - limitViolation.getLimit(), - limitViolation.getLimitReduction(), - limitViolation.getValue(), - computedLoading, - ComputationResultUtils.getViolationLocationId(limitViolation, getNetwork()) - )); + LimitViolationDTO.builder() + .limitType(limitViolation.getLimitType()) + .limitName(limitViolation.getLimitName()) + .side(limitViolation.getSide()) + .acceptableDuration(limitViolation.getAcceptableDuration()) + .limit(limitViolation.getLimit()) + .limitReduction(limitViolation.getLimitReduction()) + .value(limitViolation.getValue()) + .loading(computedLoading) + .locationId(ComputationResultUtils.getViolationLocationId(limitViolation, getNetwork())) + .build()); } private static ContingencyResultDTO toContingencyResultDTO(Contingency contingency, String status, List limitViolations) { @@ -295,17 +295,17 @@ private static ContingencyLimitViolationDTO toContingencyLimitViolationDTO(Conti status, contingency.getElements().stream().map(e -> new ContingencyElementDTO(e.getId(), e.getType())).toList() ), - new LimitViolationDTO( - limitViolation.getLimitType(), - limitViolation.getLimitName(), - limitViolation.getSide(), - limitViolation.getAcceptableDuration(), - limitViolation.getLimit(), - limitViolation.getLimitReduction(), - limitViolation.getValue(), - computedLoading, - ComputationResultUtils.getViolationLocationId(limitViolation, getNetwork()) - ) + LimitViolationDTO.builder() + .limitType(limitViolation.getLimitType()) + .limitName(limitViolation.getLimitName()) + .side(limitViolation.getSide()) + .acceptableDuration(limitViolation.getAcceptableDuration()) + .limit(limitViolation.getLimit()) + .limitReduction(limitViolation.getLimitReduction()) + .value(limitViolation.getValue()) + .loading(computedLoading) + .locationId(ComputationResultUtils.getViolationLocationId(limitViolation, getNetwork())) + .build() ); }