Skip to content

Commit 6aa7369

Browse files
authored
New fields in SA results - limit violations (#197)
Signed-off-by: LE SAULNIER Kevin <[email protected]>
1 parent 7fcc484 commit 6aa7369

File tree

8 files changed

+212
-43
lines changed

8 files changed

+212
-43
lines changed

src/main/java/org/gridsuite/securityanalysis/server/dto/LimitViolationDTO.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,24 +22,30 @@
2222
public class LimitViolationDTO {
2323
private LimitViolationType limitType;
2424
private String limitName;
25+
private String nextLimitName;
2526
private ThreeSides side;
2627
private int acceptableDuration;
2728
private double limit;
29+
private Double patlLimit;
2830
private double limitReduction;
2931
private double value;
3032
private Double loading;
33+
private Double patlLoading;
3134
private String locationId;
3235

3336
public static LimitViolationDTO toDto(AbstractLimitViolationEntity limitViolation) {
3437
return LimitViolationDTO.builder()
3538
.limitType(limitViolation.getLimitType())
3639
.limitName(limitViolation.getLimitName())
40+
.nextLimitName(limitViolation.getNextLimitName())
3741
.side(limitViolation.getSide())
3842
.acceptableDuration((int) limitViolation.getAcceptableDuration())
3943
.limit(limitViolation.getLimit())
44+
.patlLimit(limitViolation.getPatlLimit())
4045
.limitReduction(limitViolation.getLimitReduction())
4146
.value(limitViolation.getValue())
4247
.loading(limitViolation.getLoading())
48+
.patlLoading(limitViolation.getPatlLoading())
4349
.locationId(limitViolation.getLocationId())
4450
.build();
4551
}

src/main/java/org/gridsuite/securityanalysis/server/entities/AbstractLimitViolationEntity.java

Lines changed: 68 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
*/
77
package org.gridsuite.securityanalysis.server.entities;
88

9-
import com.powsybl.iidm.network.ThreeSides;
9+
import com.powsybl.iidm.network.*;
10+
import com.powsybl.iidm.network.util.LimitViolationUtils;
1011
import com.powsybl.security.LimitViolation;
1112
import com.powsybl.security.LimitViolationType;
1213
import jakarta.persistence.*;
@@ -16,6 +17,9 @@
1617
import lombok.experimental.FieldNameConstants;
1718
import lombok.experimental.SuperBuilder;
1819

20+
import java.util.Collection;
21+
import java.util.Iterator;
22+
import java.util.Optional;
1923
import java.util.UUID;
2024

2125
/**
@@ -40,8 +44,12 @@ public abstract class AbstractLimitViolationEntity {
4044
@Column(name = "limitValue")
4145
private double limit;
4246

47+
private Double patlLimit;
48+
4349
private String limitName;
4450

51+
private String nextLimitName;
52+
4553
@Enumerated(EnumType.STRING)
4654
private LimitViolationType limitType;
4755

@@ -58,12 +66,68 @@ public abstract class AbstractLimitViolationEntity {
5866
@Column(name = "loading")
5967
private Double loading;
6068

69+
private Double patlLoading;
70+
6171
@Column
6272
private String locationId;
6373

64-
public static Double computeLoading(LimitViolation limitViolation) {
65-
return LimitViolationType.CURRENT.equals(limitViolation.getLimitType())
66-
? 100 * limitViolation.getValue() / limitViolation.getLimit()
74+
protected static Double computeLoading(LimitViolation limitViolation, Double limit) {
75+
return LimitViolationType.CURRENT.equals(limitViolation.getLimitType()) && limit != null
76+
? 100 * limitViolation.getValue() / limit
6777
: null;
6878
}
79+
80+
protected static Double getPatlLimit(LimitViolation limitViolation, Network network) {
81+
String equipmentId = limitViolation.getSubjectId();
82+
Branch<?> branch = network.getBranch(equipmentId);
83+
ThreeSides limitViolationSide = limitViolation.getSide();
84+
if (branch == null || limitViolationSide == null) {
85+
return null;
86+
}
87+
88+
Optional<CurrentLimits> currentLimits = branch.getCurrentLimits(limitViolationSide.toTwoSides());
89+
if (currentLimits.isPresent()) {
90+
return currentLimits.get().getPermanentLimit();
91+
}
92+
return null;
93+
}
94+
95+
protected static String getNextLimitName(LimitViolation limitViolation, Network network) {
96+
String equipmentId = limitViolation.getSubjectId();
97+
Branch<?> branch = network.getBranch(equipmentId);
98+
if (branch == null) {
99+
return null;
100+
}
101+
LoadingLimits.TemporaryLimit temporaryLimit = getNextTemporaryLimit(branch, limitViolation);
102+
return temporaryLimit != null ? temporaryLimit.getName() : null;
103+
}
104+
105+
private static LoadingLimits.TemporaryLimit getNextTemporaryLimit(Branch<?> branch, LimitViolation limitViolation) {
106+
// limits are returned from the store by DESC duration / ASC value
107+
ThreeSides limitViolationSide = limitViolation.getSide();
108+
String limitName = limitViolation.getLimitName();
109+
if (limitViolationSide == null || limitName == null) {
110+
return null;
111+
}
112+
113+
Optional<CurrentLimits> currentLimits = branch.getCurrentLimits(limitViolationSide.toTwoSides());
114+
if (currentLimits.isEmpty()) {
115+
return null;
116+
}
117+
118+
Collection<LoadingLimits.TemporaryLimit> temporaryLimits = currentLimits.get().getTemporaryLimits();
119+
if (limitName.equals(LimitViolationUtils.PERMANENT_LIMIT_NAME)) {
120+
return temporaryLimits.stream().findFirst().orElse(null);
121+
}
122+
123+
Iterator<LoadingLimits.TemporaryLimit> temporaryLimitIterator = temporaryLimits.iterator();
124+
while (temporaryLimitIterator.hasNext()) {
125+
LoadingLimits.TemporaryLimit currentTemporaryLimit = temporaryLimitIterator.next();
126+
if (currentTemporaryLimit.getName().equals(limitViolation.getLimitName())) {
127+
return temporaryLimitIterator.hasNext() ? temporaryLimitIterator.next() : null;
128+
}
129+
}
130+
131+
return null;
132+
}
69133
}

src/main/java/org/gridsuite/securityanalysis/server/entities/ContingencyLimitViolationEntity.java

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,14 @@
66
*/
77
package org.gridsuite.securityanalysis.server.entities;
88

9-
import com.powsybl.iidm.network.Network;
9+
import com.powsybl.iidm.network.*;
1010
import com.powsybl.security.LimitViolation;
1111
import org.gridsuite.computation.utils.ComputationResultUtils;
1212
import jakarta.persistence.*;
1313
import lombok.*;
1414
import lombok.experimental.FieldNameConstants;
1515
import lombok.experimental.SuperBuilder;
1616

17-
18-
1917
/**
2018
* @author Kevin Le Saulnier <kevin.lesaulnier at rte-france.com>
2119
*/
@@ -33,17 +31,22 @@ public class ContingencyLimitViolationEntity extends AbstractLimitViolationEntit
3331
private ContingencyEntity contingency;
3432

3533
public static ContingencyLimitViolationEntity toEntity(Network network, LimitViolation limitViolation, SubjectLimitViolationEntity subjectLimitViolation) {
34+
Double patlLimit = getPatlLimit(limitViolation, network);
35+
3636
ContingencyLimitViolationEntity contingencyLimitViolationEntity = ContingencyLimitViolationEntity.builder()
3737
.limit(limitViolation.getLimit())
3838
.limitName(limitViolation.getLimitName())
3939
.limitType(limitViolation.getLimitType())
40-
.acceptableDuration(limitViolation.getAcceptableDuration())
4140
.limitReduction(limitViolation.getLimitReduction())
4241
.value(limitViolation.getValue())
4342
.side(limitViolation.getSide())
44-
.loading(computeLoading(limitViolation))
43+
.loading(computeLoading(limitViolation, limitViolation.getLimit()))
4544
.locationId(ComputationResultUtils.getViolationLocationId(limitViolation, network))
4645
.subjectLimitViolation(subjectLimitViolation)
46+
.patlLimit(patlLimit)
47+
.patlLoading(computeLoading(limitViolation, patlLimit))
48+
.nextLimitName(getNextLimitName(limitViolation, network))
49+
.acceptableDuration(limitViolation.getAcceptableDuration())
4750
.build();
4851

4952
subjectLimitViolation.addContingencyLimitViolation(contingencyLimitViolationEntity);

src/main/java/org/gridsuite/securityanalysis/server/entities/PreContingencyLimitViolationEntity.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ public static PreContingencyLimitViolationEntity toEntity(Network network, Limit
5252
.limitReduction(limitViolation.getLimitReduction())
5353
.value(limitViolation.getValue())
5454
.side(limitViolation.getSide())
55-
.loading(computeLoading(limitViolation))
55+
.loading(computeLoading(limitViolation, limitViolation.getLimit()))
5656
.locationId(ComputationResultUtils.getViolationLocationId(limitViolation, network))
5757
.build();
5858
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
2+
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext" xmlns:pro="http://www.liquibase.org/xml/ns/pro" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog-ext http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd http://www.liquibase.org/xml/ns/pro http://www.liquibase.org/xml/ns/pro/liquibase-pro-latest.xsd http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd">
3+
<changeSet author="lesaulnierkev (generated)" id="1756904408172-1">
4+
<addColumn tableName="contingency_limit_violation">
5+
<column name="next_limit_name" type="varchar(255)"/>
6+
</addColumn>
7+
</changeSet>
8+
<changeSet author="lesaulnierkev (generated)" id="1756904408172-2">
9+
<addColumn tableName="pre_contingency_limit_violation">
10+
<column name="next_limit_name" type="varchar(255)"/>
11+
</addColumn>
12+
</changeSet>
13+
<changeSet author="lesaulnierkev (generated)" id="1756904408172-3">
14+
<addColumn tableName="contingency_limit_violation">
15+
<column name="patl_limit" type="float(53)"/>
16+
</addColumn>
17+
</changeSet>
18+
<changeSet author="lesaulnierkev (generated)" id="1756904408172-4">
19+
<addColumn tableName="pre_contingency_limit_violation">
20+
<column name="patl_limit" type="float(53)"/>
21+
</addColumn>
22+
</changeSet>
23+
<changeSet author="lesaulnierkev (generated)" id="1756904408172-7">
24+
<addColumn tableName="contingency_limit_violation">
25+
<column name="patl_loading" type="float(53)"/>
26+
</addColumn>
27+
</changeSet>
28+
<changeSet author="lesaulnierkev (generated)" id="1756904408172-8">
29+
<addColumn tableName="pre_contingency_limit_violation">
30+
<column name="patl_loading" type="float(53)"/>
31+
</addColumn>
32+
</changeSet>
33+
</databaseChangeLog>

src/main/resources/db/changelog/db.changelog-master.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,6 @@ databaseChangeLog:
4545
- include:
4646
file: changesets/changelog_20250827T095600Z.xml
4747
relativeToChangelogFile: true
48+
- include:
49+
file: changesets/changelog_20250903T125946Z.xml
50+
relativeToChangelogFile: true
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/**
2+
* Copyright (c) 2025, RTE (http://www.rte-france.com)
3+
* This Source Code Form is subject to the terms of the Mozilla Public
4+
* License, v. 2.0. If a copy of the MPL was not distributed with this
5+
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
6+
*/
7+
package org.gridsuite.securityanalysis.server;
8+
9+
import com.powsybl.iidm.network.Network;
10+
import com.powsybl.iidm.network.TwoSides;
11+
import com.powsybl.iidm.network.test.EurostagTutorialExample1Factory;
12+
import com.powsybl.iidm.network.util.LimitViolationUtils;
13+
import com.powsybl.security.LimitViolation;
14+
import com.powsybl.security.LimitViolationType;
15+
import org.gridsuite.securityanalysis.server.entities.ContingencyLimitViolationEntity;
16+
import org.gridsuite.securityanalysis.server.entities.SubjectLimitViolationEntity;
17+
import org.junit.jupiter.api.Test;
18+
import org.springframework.boot.test.context.SpringBootTest;
19+
20+
import static org.junit.jupiter.api.Assertions.assertEquals;
21+
22+
/**
23+
* @author Kevin Le Saulnier <kevin.lesaulnier at rte-france.com>
24+
*/
25+
@SpringBootTest
26+
class ContingencyLimitViolationTest {
27+
28+
@Test
29+
void testContingencyLimitViolationEntityNewFields() {
30+
testContingencyLimitViolationMapping("10'", 10 * 60, 1200, 1250, TwoSides.TWO, "1'", 1100);
31+
}
32+
33+
@Test
34+
void testContingencyLimitViolationEntityNewFieldsWithPermanentLimitReached() {
35+
testContingencyLimitViolationMapping(LimitViolationUtils.PERMANENT_LIMIT_NAME, Integer.MAX_VALUE, 1100, 1150, TwoSides.TWO, "10'", 1100);
36+
}
37+
38+
@Test
39+
void testContingencyLimitViolationEntityNewFieldsWithPermanentLimitReachedAndNoTemporaryLimit() {
40+
testContingencyLimitViolationMapping(LimitViolationUtils.PERMANENT_LIMIT_NAME, Integer.MAX_VALUE, 500, 1000, TwoSides.ONE, null, 500);
41+
}
42+
43+
@Test
44+
void testContingencyLimitViolationEntityNewFieldsWithLastLimitReached() {
45+
testContingencyLimitViolationMapping("N/A", 0, 1100, 3000, TwoSides.TWO, null, 1100);
46+
}
47+
48+
private void testContingencyLimitViolationMapping(String limitName, int acceptableDuration, double limit, double value, TwoSides side, String expectedNextLimitName, long expectedPatlLimit) {
49+
Network network = EurostagTutorialExample1Factory.createWithFixedCurrentLimits();
50+
LimitViolation limitViolation = new LimitViolation("NHV1_NHV2_1", "NHV1_NHV2_1_name", LimitViolationType.CURRENT, limitName, acceptableDuration, limit, 1, value, side);
51+
52+
SubjectLimitViolationEntity subjectLimitViolationEntity = new SubjectLimitViolationEntity("NHV1_NHV2_1", "NHV1_NHV2_1_name");
53+
54+
ContingencyLimitViolationEntity contingencyLimitViolationEntity = ContingencyLimitViolationEntity.toEntity(network, limitViolation, subjectLimitViolationEntity);
55+
56+
assertEquals(expectedNextLimitName, contingencyLimitViolationEntity.getNextLimitName());
57+
assertEquals(expectedPatlLimit, contingencyLimitViolationEntity.getPatlLimit());
58+
assertEquals(100 * limitViolation.getValue() / contingencyLimitViolationEntity.getPatlLimit(), contingencyLimitViolationEntity.getPatlLoading());
59+
}
60+
}

src/test/java/org/gridsuite/securityanalysis/server/SecurityAnalysisProviderMock.java

Lines changed: 33 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -231,17 +231,17 @@ private static SubjectLimitViolationDTO toSubjectLimitViolationDTO(LimitViolatio
231231

232232
return new SubjectLimitViolationDTO(
233233
limitViolation.getSubjectId(),
234-
new LimitViolationDTO(
235-
limitViolation.getLimitType(),
236-
limitViolation.getLimitName(),
237-
limitViolation.getSide(),
238-
limitViolation.getAcceptableDuration(),
239-
limitViolation.getLimit(),
240-
limitViolation.getLimitReduction(),
241-
limitViolation.getValue(),
242-
computedLoading,
243-
ComputationResultUtils.getViolationLocationId(limitViolation, getNetwork())
244-
)
234+
LimitViolationDTO.builder()
235+
.limitType(limitViolation.getLimitType())
236+
.limitName(limitViolation.getLimitName())
237+
.side(limitViolation.getSide())
238+
.acceptableDuration(limitViolation.getAcceptableDuration())
239+
.limit(limitViolation.getLimit())
240+
.limitReduction(limitViolation.getLimitReduction())
241+
.value(limitViolation.getValue())
242+
.loading(computedLoading)
243+
.locationId(ComputationResultUtils.getViolationLocationId(limitViolation, getNetwork()))
244+
.build()
245245
);
246246
}
247247

@@ -258,17 +258,17 @@ private static PreContingencyLimitViolationResultDTO toPreContingencyResultDTO(L
258258
return new PreContingencyLimitViolationResultDTO(
259259
limitViolation.getSubjectId(),
260260
status.name(),
261-
new LimitViolationDTO(
262-
limitViolation.getLimitType(),
263-
limitViolation.getLimitName(),
264-
limitViolation.getSide(),
265-
limitViolation.getAcceptableDuration(),
266-
limitViolation.getLimit(),
267-
limitViolation.getLimitReduction(),
268-
limitViolation.getValue(),
269-
computedLoading,
270-
ComputationResultUtils.getViolationLocationId(limitViolation, getNetwork())
271-
));
261+
LimitViolationDTO.builder()
262+
.limitType(limitViolation.getLimitType())
263+
.limitName(limitViolation.getLimitName())
264+
.side(limitViolation.getSide())
265+
.acceptableDuration(limitViolation.getAcceptableDuration())
266+
.limit(limitViolation.getLimit())
267+
.limitReduction(limitViolation.getLimitReduction())
268+
.value(limitViolation.getValue())
269+
.loading(computedLoading)
270+
.locationId(ComputationResultUtils.getViolationLocationId(limitViolation, getNetwork()))
271+
.build());
272272
}
273273

274274
private static ContingencyResultDTO toContingencyResultDTO(Contingency contingency, String status, List<LimitViolation> limitViolations) {
@@ -295,17 +295,17 @@ private static ContingencyLimitViolationDTO toContingencyLimitViolationDTO(Conti
295295
status,
296296
contingency.getElements().stream().map(e -> new ContingencyElementDTO(e.getId(), e.getType())).toList()
297297
),
298-
new LimitViolationDTO(
299-
limitViolation.getLimitType(),
300-
limitViolation.getLimitName(),
301-
limitViolation.getSide(),
302-
limitViolation.getAcceptableDuration(),
303-
limitViolation.getLimit(),
304-
limitViolation.getLimitReduction(),
305-
limitViolation.getValue(),
306-
computedLoading,
307-
ComputationResultUtils.getViolationLocationId(limitViolation, getNetwork())
308-
)
298+
LimitViolationDTO.builder()
299+
.limitType(limitViolation.getLimitType())
300+
.limitName(limitViolation.getLimitName())
301+
.side(limitViolation.getSide())
302+
.acceptableDuration(limitViolation.getAcceptableDuration())
303+
.limit(limitViolation.getLimit())
304+
.limitReduction(limitViolation.getLimitReduction())
305+
.value(limitViolation.getValue())
306+
.loading(computedLoading)
307+
.locationId(ComputationResultUtils.getViolationLocationId(limitViolation, getNetwork()))
308+
.build()
309309
);
310310
}
311311

0 commit comments

Comments
 (0)