From bfe5be5a37ee250066614ab4545dd879ee064ae8 Mon Sep 17 00:00:00 2001 From: Rehili Ghazwa Date: Tue, 19 May 2026 14:52:37 +0200 Subject: [PATCH] fix csv export --- .../server/SecurityAnalysisController.java | 2 + .../server/dto/ConnectivityResultDTO.java | 39 ++++++++++++++++++ .../server/dto/ContingencyDTO.java | 2 + .../server/dto/ContingencyResultDTO.java | 10 +++-- .../server/dto/LimitViolationDTO.java | 10 +---- .../ConnectivityResultEmbeddable.java | 41 +++++++++++++++++++ .../server/entities/ContingencyEntity.java | 10 ++++- .../SecurityAnalysisResultService.java | 4 +- .../server/util/CsvExportUtils.java | 8 ++++ .../changesets/changelog_20260519T125101Z.xml | 13 ++++++ .../db/changelog/db.changelog-master.yaml | 3 ++ .../server/SecurityAnalysisProviderMock.java | 22 +++++----- 12 files changed, 138 insertions(+), 26 deletions(-) create mode 100644 src/main/java/org/gridsuite/securityanalysis/server/dto/ConnectivityResultDTO.java create mode 100644 src/main/java/org/gridsuite/securityanalysis/server/entities/ConnectivityResultEmbeddable.java create mode 100644 src/main/resources/db/changelog/changesets/changelog_20260519T125101Z.xml diff --git a/src/main/java/org/gridsuite/securityanalysis/server/SecurityAnalysisController.java b/src/main/java/org/gridsuite/securityanalysis/server/SecurityAnalysisController.java index 73edbb29..f2bc1a65 100644 --- a/src/main/java/org/gridsuite/securityanalysis/server/SecurityAnalysisController.java +++ b/src/main/java/org/gridsuite/securityanalysis/server/SecurityAnalysisController.java @@ -198,6 +198,7 @@ public ResponseEntity getNmKContingenciesResultZippedCsv(@Parameter(desc @Parameter(description = "variant Id") @RequestParam(name = "variantId", required = false) String variantId, @Parameter(description = "Filters") @RequestParam(name = "filters", required = false) String filters, @Parameter(description = "Global Filters") @RequestParam(name = "globalFilters", required = false) String globalFilters, + @Parameter(description = "Power cut-off") @RequestParam(name = "isPowerCutOffView", required = false) boolean isPowerCutOffView, @Parameter(description = "Translation properties") @RequestBody CsvTranslationDTO csvTranslations, @Parameter(description = "Sort parameters") Sort sort) { return ResponseEntity.ok() @@ -208,6 +209,7 @@ public ResponseEntity getNmKContingenciesResultZippedCsv(@Parameter(desc variantId, filters, globalFilters, + isPowerCutOffView, sort, csvTranslations )); diff --git a/src/main/java/org/gridsuite/securityanalysis/server/dto/ConnectivityResultDTO.java b/src/main/java/org/gridsuite/securityanalysis/server/dto/ConnectivityResultDTO.java new file mode 100644 index 00000000..cc8acfd2 --- /dev/null +++ b/src/main/java/org/gridsuite/securityanalysis/server/dto/ConnectivityResultDTO.java @@ -0,0 +1,39 @@ +/** + * Copyright (c) 2026, 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.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.gridsuite.securityanalysis.server.entities.ConnectivityResultEmbeddable; + +/** + * @author Ghazwa Rehili + */ + +@Getter +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class ConnectivityResultDTO { + private Double disconnectedLoadActivePower; + private Double disconnectedGenerationActivePower; + + public static ConnectivityResultDTO toDto(ConnectivityResultEmbeddable connectivityResult) { + if (connectivityResult == null) { + return ConnectivityResultDTO.builder() + .disconnectedLoadActivePower(null) + .disconnectedGenerationActivePower(null) + .build(); + } + return ConnectivityResultDTO.builder() + .disconnectedLoadActivePower(connectivityResult.getDisconnectedLoadActivePower()) + .disconnectedGenerationActivePower(connectivityResult.getDisconnectedGenerationActivePower()) + .build(); + } +} diff --git a/src/main/java/org/gridsuite/securityanalysis/server/dto/ContingencyDTO.java b/src/main/java/org/gridsuite/securityanalysis/server/dto/ContingencyDTO.java index 90bf6c8a..dc6fdeec 100644 --- a/src/main/java/org/gridsuite/securityanalysis/server/dto/ContingencyDTO.java +++ b/src/main/java/org/gridsuite/securityanalysis/server/dto/ContingencyDTO.java @@ -23,12 +23,14 @@ public class ContingencyDTO { private String contingencyId; private String status; private List elements; + private ConnectivityResultDTO connectivityResult; public static ContingencyDTO toDto(ContingencyEntity contingency) { return ContingencyDTO.builder() .contingencyId(contingency.getContingencyId()) .status(contingency.getStatus()) .elements(contingency.getContingencyElements().stream().map(ContingencyElementDTO::toDto).collect(Collectors.toList())) + .connectivityResult(ConnectivityResultDTO.toDto(contingency.getConnectivityResult())) .build(); } } diff --git a/src/main/java/org/gridsuite/securityanalysis/server/dto/ContingencyResultDTO.java b/src/main/java/org/gridsuite/securityanalysis/server/dto/ContingencyResultDTO.java index da0888cd..a0bc2b60 100644 --- a/src/main/java/org/gridsuite/securityanalysis/server/dto/ContingencyResultDTO.java +++ b/src/main/java/org/gridsuite/securityanalysis/server/dto/ContingencyResultDTO.java @@ -41,15 +41,19 @@ public static ContingencyResultDTO toDto(ContingencyEntity contingency) { } // each contingencyResultDto will return multiple line (one for each limitViolation) - public List> toCsvRows(Map translations, String language) { + public List> toCsvRows(Map translations, String language, boolean isPowerCutOffView) { return this.getSubjectLimitViolations().stream().map(lm -> { List csvRow = new ArrayList<>(); csvRow.add(this.getContingency().getContingencyId()); csvRow.add(CsvExportUtils.translate(this.getContingency().getStatus(), translations)); + if (isPowerCutOffView && this.getContingency().getConnectivityResult().getDisconnectedLoadActivePower() != null + && this.getContingency().getConnectivityResult().getDisconnectedGenerationActivePower() != null) { + csvRow.add(CsvExportUtils.convertDoubleToLocale(this.getContingency().getConnectivityResult().getDisconnectedLoadActivePower(), language)); + csvRow.add(CsvExportUtils.convertDoubleToLocale(this.getContingency().getConnectivityResult().getDisconnectedGenerationActivePower(), language)); + return csvRow; + } csvRow.add(lm.getSubjectId()); - csvRow.addAll(lm.getLimitViolation().toCsvRow(translations, language)); - return csvRow; }).toList(); 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 d07c5c75..b5bd0dac 100644 --- a/src/main/java/org/gridsuite/securityanalysis/server/dto/LimitViolationDTO.java +++ b/src/main/java/org/gridsuite/securityanalysis/server/dto/LimitViolationDTO.java @@ -9,12 +9,12 @@ import org.gridsuite.securityanalysis.server.entities.AbstractLimitViolationEntity; import org.gridsuite.securityanalysis.server.util.CsvExportUtils; -import java.text.NumberFormat; import java.util.ArrayList; import java.util.List; -import java.util.Locale; import java.util.Map; +import static org.gridsuite.securityanalysis.server.util.CsvExportUtils.convertDoubleToLocale; + @AllArgsConstructor @NoArgsConstructor @Builder @@ -54,12 +54,6 @@ public static LimitViolationDTO toDto(AbstractLimitViolationEntity limitViolatio .build(); } - private static String convertDoubleToLocale(Double value, String language) { - NumberFormat nf = NumberFormat.getInstance(language != null && language.equals("fr") ? Locale.FRENCH : Locale.US); - nf.setGroupingUsed(false); - return nf.format(value); - } - public List toCsvRow(Map translations, String language) { List csvRow = new ArrayList<>(); csvRow.add(this.getLimitType() != null ? CsvExportUtils.translate(this.getLimitType().name(), translations) : ""); diff --git a/src/main/java/org/gridsuite/securityanalysis/server/entities/ConnectivityResultEmbeddable.java b/src/main/java/org/gridsuite/securityanalysis/server/entities/ConnectivityResultEmbeddable.java new file mode 100644 index 00000000..39f77e57 --- /dev/null +++ b/src/main/java/org/gridsuite/securityanalysis/server/entities/ConnectivityResultEmbeddable.java @@ -0,0 +1,41 @@ +/** + * Copyright (c) 2026, 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.entities; + +import com.powsybl.security.results.ConnectivityResult; +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +/** + * @author Ghazwa Rehili + */ + +@Getter +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Embeddable +public class ConnectivityResultEmbeddable { + @Column + private Double disconnectedLoadActivePower; + + @Column + private Double disconnectedGenerationActivePower; + + public static ConnectivityResultEmbeddable toEntity(ConnectivityResult connectivityResult) { + if (connectivityResult.getDisconnectedLoadActivePower() == 0.0 && connectivityResult.getDisconnectedGenerationActivePower() == 0.0) { + return null; + } + return ConnectivityResultEmbeddable.builder() + .disconnectedGenerationActivePower(connectivityResult.getDisconnectedGenerationActivePower()) + .disconnectedLoadActivePower(connectivityResult.getDisconnectedLoadActivePower()) + .build(); + } +} diff --git a/src/main/java/org/gridsuite/securityanalysis/server/entities/ContingencyEntity.java b/src/main/java/org/gridsuite/securityanalysis/server/entities/ContingencyEntity.java index d9bddad7..cd6694d6 100644 --- a/src/main/java/org/gridsuite/securityanalysis/server/entities/ContingencyEntity.java +++ b/src/main/java/org/gridsuite/securityanalysis/server/entities/ContingencyEntity.java @@ -31,10 +31,12 @@ @Table(name = "contingency") public class ContingencyEntity { - public ContingencyEntity(String contingencyId, String status, List contingencyElements, List contingencyLimitViolations) { + public ContingencyEntity(String contingencyId, String status, List contingencyElements, ConnectivityResultEmbeddable connectivityResult, + List contingencyLimitViolations) { this.contingencyId = contingencyId; this.status = status; this.contingencyElements = contingencyElements; + this.connectivityResult = connectivityResult; setContingencyLimitViolations(contingencyLimitViolations); } @@ -54,6 +56,9 @@ public ContingencyEntity(String contingencyId, String status, List contingencyLimitViolations; + @Embedded + private ConnectivityResultEmbeddable connectivityResult; + /** * We keep a String as it could model LoadFlowResult.ComponentResult.Status or PostContingencyComputationStatus. */ @@ -72,6 +77,7 @@ public static ContingencyEntity toEntity(@Nullable Network network, PostContinge List contingencyLimitViolations = postContingencyResult.getLimitViolationsResult().getLimitViolations().stream() .map(limitViolation -> ContingencyLimitViolationEntity.toEntity(network, limitViolation, subjectLimitViolationsBySubjectId.get(limitViolation.getSubjectId()))) .collect(Collectors.toList()); - return new ContingencyEntity(postContingencyResult.getContingency().getId(), postContingencyResult.getStatus().name(), contingencyElements, contingencyLimitViolations); + ConnectivityResultEmbeddable connectivityResult = ConnectivityResultEmbeddable.toEntity(postContingencyResult.getConnectivityResult()); + return new ContingencyEntity(postContingencyResult.getContingency().getId(), postContingencyResult.getStatus().name(), contingencyElements, connectivityResult, contingencyLimitViolations); } } diff --git a/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisResultService.java b/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisResultService.java index 94a03cb6..befa2bd8 100644 --- a/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisResultService.java +++ b/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisResultService.java @@ -177,9 +177,9 @@ public List findNmKContingenciesResult(UUID resultUuid) { } @Transactional(readOnly = true) - public byte[] findNmKContingenciesResultZippedCsv(UUID resultUuid, UUID networkUuid, String variantId, String stringFilters, String stringGlobalFilters, Sort sort, CsvTranslationDTO csvTranslations) { + public byte[] findNmKContingenciesResultZippedCsv(UUID resultUuid, UUID networkUuid, String variantId, String stringFilters, String stringGlobalFilters, boolean isPowerCutOffView, Sort sort, CsvTranslationDTO csvTranslations) { List result = self.findNmKContingenciesPaged(resultUuid, networkUuid, variantId, stringFilters, stringGlobalFilters, Pageable.unpaged(sort)).getContent(); - return CsvExportUtils.csvRowsToZippedCsv(csvTranslations.headers(), csvTranslations.language(), result.stream().map(r -> r.toCsvRows(csvTranslations.enumValueTranslations(), csvTranslations.language())).flatMap(List::stream).toList()); + return CsvExportUtils.csvRowsToZippedCsv(csvTranslations.headers(), csvTranslations.language(), result.stream().map(r -> r.toCsvRows(csvTranslations.enumValueTranslations(), csvTranslations.language(), isPowerCutOffView)).flatMap(List::stream).toList()); } @Transactional(readOnly = true) diff --git a/src/main/java/org/gridsuite/securityanalysis/server/util/CsvExportUtils.java b/src/main/java/org/gridsuite/securityanalysis/server/util/CsvExportUtils.java index 5f38cd95..8ae01412 100644 --- a/src/main/java/org/gridsuite/securityanalysis/server/util/CsvExportUtils.java +++ b/src/main/java/org/gridsuite/securityanalysis/server/util/CsvExportUtils.java @@ -9,7 +9,9 @@ import java.io.OutputStream; import java.io.UncheckedIOException; import java.nio.charset.StandardCharsets; +import java.text.NumberFormat; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; @@ -70,4 +72,10 @@ public static String translate(String valueToTranslate, Map tran // if translated value is null, we keep original value (untranslated value) return translations.getOrDefault(valueToTranslate, valueToTranslate); } + + public static String convertDoubleToLocale(Double value, String language) { + NumberFormat nf = NumberFormat.getInstance(language != null && language.equals("fr") ? Locale.FRENCH : Locale.US); + nf.setGroupingUsed(false); + return nf.format(value); + } } diff --git a/src/main/resources/db/changelog/changesets/changelog_20260519T125101Z.xml b/src/main/resources/db/changelog/changesets/changelog_20260519T125101Z.xml new file mode 100644 index 00000000..a943bb6a --- /dev/null +++ b/src/main/resources/db/changelog/changesets/changelog_20260519T125101Z.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/db/changelog/db.changelog-master.yaml b/src/main/resources/db/changelog/db.changelog-master.yaml index 0ea419f9..80e73783 100644 --- a/src/main/resources/db/changelog/db.changelog-master.yaml +++ b/src/main/resources/db/changelog/db.changelog-master.yaml @@ -69,3 +69,6 @@ databaseChangeLog: - include: file: changesets/changelog_20260423T124334Z.xml relativeToChangelogFile: true + - include: + file: changesets/changelog_20260519T125101Z.xml + relativeToChangelogFile: true diff --git a/src/test/java/org/gridsuite/securityanalysis/server/SecurityAnalysisProviderMock.java b/src/test/java/org/gridsuite/securityanalysis/server/SecurityAnalysisProviderMock.java index 5399d998..10094989 100644 --- a/src/test/java/org/gridsuite/securityanalysis/server/SecurityAnalysisProviderMock.java +++ b/src/test/java/org/gridsuite/securityanalysis/server/SecurityAnalysisProviderMock.java @@ -118,9 +118,9 @@ public class SecurityAnalysisProviderMock implements SecurityAnalysisProvider { // CONTINGENCIES_WITHOUT_LIMIT_VIOLATION should not be contained here since it does not contain any LIMIT_VIOLATION // CONTINGENCIES_NOT_CONVERGED_WITHOUT_LIMIT_VIOLATION is here because status is not "CONVERGED" static final List RESULT_CONTINGENCIES = Stream.of( - CONTINGENCIES.stream().map(c -> toContingencyResultDTO(c.getContingency(), LoadFlowResult.ComponentResult.Status.CONVERGED.name(), RESULT_LIMIT_VIOLATIONS)), - FAILED_CONTINGENCIES.stream().map(c -> toContingencyResultDTO(c, LoadFlowResult.ComponentResult.Status.FAILED.name(), FAILED_LIMIT_VIOLATIONS)), - CONTINGENCIES_NOT_CONVERGED_WITHOUT_LIMIT_VIOLATION.stream().map(c -> toContingencyResultDTO(c, LoadFlowResult.ComponentResult.Status.FAILED.name(), List.of()))) + CONTINGENCIES.stream().map(c -> toContingencyResultDTO(c.getContingency(), LoadFlowResult.ComponentResult.Status.CONVERGED.name(), RESULT_LIMIT_VIOLATIONS, ConnectivityResultDTO.builder().disconnectedLoadActivePower(12.03).disconnectedGenerationActivePower(0.0).build())), + FAILED_CONTINGENCIES.stream().map(c -> toContingencyResultDTO(c, LoadFlowResult.ComponentResult.Status.FAILED.name(), FAILED_LIMIT_VIOLATIONS, ConnectivityResultDTO.builder().disconnectedLoadActivePower(0.0).disconnectedGenerationActivePower(12.03).build())), + CONTINGENCIES_NOT_CONVERGED_WITHOUT_LIMIT_VIOLATION.stream().map(c -> toContingencyResultDTO(c, LoadFlowResult.ComponentResult.Status.FAILED.name(), List.of(), ConnectivityResultDTO.builder().disconnectedLoadActivePower(0.0).disconnectedGenerationActivePower(0.0).build()))) .flatMap(Function.identity()).toList(); static List getResultContingenciesWithNestedFilter(Function filterMethod) { @@ -150,9 +150,9 @@ static List getResultContingenciesSorted(Comparator RESULT_CONSTRAINTS = Stream.concat( RESULT_LIMIT_VIOLATIONS.stream() - .map(limitViolation -> toSubjectLimitViolationResultDTO(limitViolation, CONTINGENCIES.stream().map(ContingencyInfos::getContingency).collect(Collectors.toList()), LoadFlowResult.ComponentResult.Status.CONVERGED)), + .map(limitViolation -> toSubjectLimitViolationResultDTO(limitViolation, CONTINGENCIES.stream().map(ContingencyInfos::getContingency).collect(Collectors.toList()), LoadFlowResult.ComponentResult.Status.CONVERGED, ConnectivityResultDTO.builder().disconnectedLoadActivePower(0.0).disconnectedGenerationActivePower(-852.36).build())), FAILED_LIMIT_VIOLATIONS.stream() - .map(limitViolation -> toSubjectLimitViolationResultDTO(limitViolation, FAILED_CONTINGENCIES, LoadFlowResult.ComponentResult.Status.FAILED)) + .map(limitViolation -> toSubjectLimitViolationResultDTO(limitViolation, FAILED_CONTINGENCIES, LoadFlowResult.ComponentResult.Status.FAILED, ConnectivityResultDTO.builder().disconnectedLoadActivePower(885.16).disconnectedGenerationActivePower(0.0).build())) ).toList(); static List getResultConstraintsWithNestedFilter(Function filterMethod) { @@ -251,10 +251,10 @@ private static SubjectLimitViolationDTO toSubjectLimitViolationDTO(LimitViolatio ); } - private static SubjectLimitViolationResultDTO toSubjectLimitViolationResultDTO(LimitViolation limitViolation, List convergedContingencies, LoadFlowResult.ComponentResult.Status status) { + private static SubjectLimitViolationResultDTO toSubjectLimitViolationResultDTO(LimitViolation limitViolation, List convergedContingencies, LoadFlowResult.ComponentResult.Status status, ConnectivityResultDTO connectivityResult) { return new SubjectLimitViolationResultDTO( limitViolation.getSubjectId(), - convergedContingencies.stream().map(c -> toContingencyLimitViolationDTO(c, limitViolation, status.name())).toList()); + convergedContingencies.stream().map(c -> toContingencyLimitViolationDTO(c, limitViolation, status.name(), connectivityResult)).toList()); } private static PreContingencyLimitViolationResultDTO toPreContingencyResultDTO(LimitViolation limitViolation, LoadFlowResult.ComponentResult.Status status) { @@ -277,12 +277,12 @@ private static PreContingencyLimitViolationResultDTO toPreContingencyResultDTO(L .build()); } - private static ContingencyResultDTO toContingencyResultDTO(Contingency contingency, String status, List limitViolations) { + private static ContingencyResultDTO toContingencyResultDTO(Contingency contingency, String status, List limitViolations, ConnectivityResultDTO connectivityResult) { return new ContingencyResultDTO( new ContingencyDTO( contingency.getId(), status, - contingency.getElements().stream().map(e -> new ContingencyElementDTO(e.getId(), e.getType())).toList() + contingency.getElements().stream().map(e -> new ContingencyElementDTO(e.getId(), e.getType())).toList(), connectivityResult ), limitViolations.stream() .map(SecurityAnalysisProviderMock::toSubjectLimitViolationDTO) @@ -290,7 +290,7 @@ private static ContingencyResultDTO toContingencyResultDTO(Contingency contingen ); } - private static ContingencyLimitViolationDTO toContingencyLimitViolationDTO(Contingency contingency, LimitViolation limitViolation, String status) { + private static ContingencyLimitViolationDTO toContingencyLimitViolationDTO(Contingency contingency, LimitViolation limitViolation, String status, ConnectivityResultDTO connectivityResult) { Double computedLoading = limitViolation.getLimitType().equals(LimitViolationType.CURRENT) ? (100 * limitViolation.getValue()) / (limitViolation.getLimit() * limitViolation.getLimitReduction()) : null; @@ -299,7 +299,7 @@ private static ContingencyLimitViolationDTO toContingencyLimitViolationDTO(Conti new ContingencyDTO( contingency.getId(), status, - contingency.getElements().stream().map(e -> new ContingencyElementDTO(e.getId(), e.getType())).toList() + contingency.getElements().stream().map(e -> new ContingencyElementDTO(e.getId(), e.getType())).toList(), connectivityResult ), LimitViolationDTO.builder() .limitType(limitViolation.getLimitType())