Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,47 @@ public ResponseEntity<Page<SubjectLimitViolationResultDTO>> getNmKConstraintsRes
: ResponseEntity.notFound().build();
}

@GetMapping(value = "/results/{resultUuid}/nmk-power-cut-off-result/paged", produces = APPLICATION_JSON_VALUE)
@Operation(summary = "Get a paged security analysis result from the database - NMK contingencies cut off power result")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "The security analysis result"),
@ApiResponse(responseCode = "404", description = "Security analysis result has not been found")})
public ResponseEntity<Page<ContingencyPowerCutOffDTO>> getNmKPowerCutOffResult(@Parameter(description = "Result UUID") @PathVariable("resultUuid") UUID resultUuid,
@Parameter(description = "network Uuid") @RequestParam(name = "networkUuid", required = false) UUID networkUuid,
@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 = "Pagination parameters") Pageable pageable) {
Page<ContingencyPowerCutOffDTO> result = securityAnalysisResultService.findNmKConnectivityResult(resultUuid, networkUuid, variantId, filters, globalFilters, pageable);

return result != null
? ResponseEntity.ok().contentType(MediaType.APPLICATION_JSON).body(result)
: ResponseEntity.notFound().build();
}

@PostMapping(value = "/results/{resultUuid}/nmk-power-cut-off-result/csv", produces = APPLICATION_OCTET_STREAM_VALUE, consumes = APPLICATION_JSON_VALUE)
@Operation(summary = "Get a security analysis result from the database - NMK contingencies result - CSV export")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "The security analysis result csv export"),
@ApiResponse(responseCode = "404", description = "Security analysis result has not been found")})
public ResponseEntity<byte[]> getNmKPowerCutOffResultZippedCsv(@Parameter(description = "Result UUID") @PathVariable("resultUuid") UUID resultUuid,
@Parameter(description = "network Uuid") @RequestParam(name = "networkUuid", required = false) UUID networkUuid,
@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 = "Translation properties") @RequestBody CsvTranslationDTO csvTranslations,
@Parameter(description = "Sort parameters") Sort sort) {
return ResponseEntity.ok()
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.body(securityAnalysisResultService.findNmKConnectivityResultResultZippedCsv(
resultUuid,
networkUuid,
variantId,
filters,
globalFilters,
sort,
csvTranslations
));
}

@PostMapping(value = "/results/{resultUuid}/nmk-constraints-result/csv", produces = APPLICATION_OCTET_STREAM_VALUE, consumes = APPLICATION_JSON_VALUE)
@Operation(summary = "Get a security analysis result from the database - NMK constraints result - CSV export")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "The security analysis result csv export"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,16 @@
@AllArgsConstructor
@NoArgsConstructor
public class ConnectivityResultDTO {
private double disconnectedLoadActivePower;
private double disconnectedGenerationActivePower;
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())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ public static ContingencyDTO toDto(ContingencyEntity contingency) {
.contingencyId(contingency.getContingencyId())
.status(contingency.getStatus())
.elements(contingency.getContingencyElements().stream().map(ContingencyElementDTO::toDto).collect(Collectors.toList()))
.connectivityResult(ConnectivityResultDTO.toDto(contingency.getConnectivityResult()))
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/**
* 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 org.gridsuite.securityanalysis.server.entities.ContingencyEntity;
import org.gridsuite.securityanalysis.server.util.CsvExportUtils;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
* @author Ghazwa Rehili <ghazwa.rehili at rte-france.com>
*/
public record ContingencyPowerCutOffDTO(
String contingencyId,
String status,
ConnectivityResultDTO connectivityResult
) {
public static ContingencyPowerCutOffDTO toDto(ContingencyEntity entity) {
return new ContingencyPowerCutOffDTO(
entity.getContingencyId(),
entity.getStatus(),
ConnectivityResultDTO.toDto(entity.getConnectivityResult())
);
}

public List<List<String>> toCsvRows(Map<String, String> translations, String language) {
List<String> csvRow = new ArrayList<>();
csvRow.add(contingencyId);
csvRow.add(CsvExportUtils.translate(status, translations));
if (connectivityResult != null) {
csvRow.add(CsvExportUtils.convertDoubleToLocale(connectivityResult.getDisconnectedLoadActivePower(), language));
csvRow.add(CsvExportUtils.convertDoubleToLocale(connectivityResult.getDisconnectedGenerationActivePower(), language));
}
return List.of(csvRow);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,6 @@ public List<List<String>> toCsvRows(Map<String, String> translations, String lan
csvRow.add(CsvExportUtils.translate(this.getContingency().getStatus(), translations));
csvRow.add(lm.getSubjectId());
csvRow.addAll(lm.getLimitViolation().toCsvRow(translations, language));
csvRow.add(CsvExportUtils.convertDoubleToLocale(this.getContingency().getConnectivityResult().getDisconnectedLoadActivePower(), language));
csvRow.add(CsvExportUtils.convertDoubleToLocale(this.getContingency().getConnectivityResult().getDisconnectedGenerationActivePower(), language));
return csvRow;
}).toList();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@
import java.util.List;
import java.util.Map;

import static org.gridsuite.securityanalysis.server.util.CsvExportUtils.convertDoubleToLocale;

/**
* @author Kevin Le Saulnier <kevin.lesaulnier at rte-france.com>
*/
Expand Down Expand Up @@ -50,8 +48,6 @@ public List<List<String>> toCsvRows(Map<String, String> translations, String lan
csvRow.add(contingency.getContingency().getContingencyId());
csvRow.add(CsvExportUtils.translate(contingency.getContingency().getStatus(), translations));
csvRow.addAll(contingency.getLimitViolation().toCsvRow(translations, language));
csvRow.add(convertDoubleToLocale(contingency.getContingency().getConnectivityResult().getDisconnectedLoadActivePower(), language));
csvRow.add(convertDoubleToLocale(contingency.getContingency().getConnectivityResult().getDisconnectedGenerationActivePower(), language));
return csvRow;
}).toList();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,24 +12,28 @@
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.experimental.FieldNameConstants;

/**
* @author Ghazwa Rehili <ghazwa.rehili at rte-france.com>
*/

@FieldNameConstants
@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Embeddable
public class ConnectivityResultEmbeddable {
@Column
private double disconnectedLoadActivePower;
private Double disconnectedLoadActivePower;

@Column
private double disconnectedGenerationActivePower;
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())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
package org.gridsuite.securityanalysis.server.entities;

import com.powsybl.iidm.network.Network;
import com.powsybl.security.results.ConnectivityResult;
import com.powsybl.security.results.PostContingencyResult;
import jakarta.persistence.*;
import lombok.Getter;
Expand Down Expand Up @@ -78,8 +77,7 @@ public static ContingencyEntity toEntity(@Nullable Network network, PostContinge
.map(limitViolation -> ContingencyLimitViolationEntity.toEntity(network, limitViolation, subjectLimitViolationsBySubjectId.get(limitViolation.getSubjectId())))
.collect(Collectors.toList());

ConnectivityResult cr = postContingencyResult.getConnectivityResult();
ConnectivityResultEmbeddable connectivityResult = cr != null ? ConnectivityResultEmbeddable.toEntity(cr) : null;
ConnectivityResultEmbeddable connectivityResult = ConnectivityResultEmbeddable.toEntity(postContingencyResult.getConnectivityResult());

return new ContingencyEntity(postContingencyResult.getContingency().getId(), postContingencyResult.getStatus().name(), contingencyElements, connectivityResult, contingencyLimitViolations);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@
import com.powsybl.security.LimitViolationType;
import com.powsybl.security.SecurityAnalysisResult;
import lombok.Getter;
import org.gridsuite.computation.error.ComputationException;
import org.gridsuite.computation.dto.GlobalFilter;
import org.gridsuite.computation.dto.ResourceFilterDTO;
import org.gridsuite.computation.error.ComputationException;
import org.gridsuite.computation.service.AbstractComputationResultService;
import org.gridsuite.computation.utils.SpecificationUtils;
import org.gridsuite.securityanalysis.server.dto.*;
Expand All @@ -39,6 +39,8 @@
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

import static org.gridsuite.computation.error.ComputationBusinessErrorCode.INVALID_SORT_FORMAT;
import static org.gridsuite.computation.error.ComputationBusinessErrorCode.RESULT_NOT_FOUND;
Expand Down Expand Up @@ -106,6 +108,13 @@ public class SecurityAnalysisResultService extends AbstractComputationResultServ
AbstractLimitViolationEntity.Fields.locationId
);

private static final List<String> ALLOWED_NMK_POWER_CUT_OFF_RESULT_SORT_PROPERTIES = List.of(
ContingencyEntity.Fields.contingencyId,
ContingencyEntity.Fields.status,
ContingencyEntity.Fields.connectivityResult + SpecificationUtils.FIELD_SEPARATOR + ConnectivityResultEmbeddable.Fields.disconnectedLoadActivePower,
ContingencyEntity.Fields.connectivityResult + SpecificationUtils.FIELD_SEPARATOR + ConnectivityResultEmbeddable.Fields.disconnectedGenerationActivePower
);

public SecurityAnalysisResultService(SecurityAnalysisResultRepository securityAnalysisResultRepository,
ContingencyRepository contingencyRepository,
PreContingencyLimitViolationRepository preContingencyLimitViolationRepository,
Expand Down Expand Up @@ -176,6 +185,27 @@ public List<ContingencyResultDTO> findNmKContingenciesResult(UUID resultUuid) {
return contingencies.stream().map(ContingencyResultDTO::toDto).toList();
}

@Transactional(readOnly = true)
public Page<ContingencyPowerCutOffDTO> findNmKConnectivityResult(UUID resultUuid, UUID networkUuid, String variantId, String stringFilters, String stringGlobalFilters, Pageable pageable) {
assertResultExists(resultUuid);
List<ResourceFilterDTO> allResourceFilters = getAllResourceFilters(stringFilters, stringGlobalFilters, globalFilter -> filterService.getResourceFilterContingencies(networkUuid, variantId, globalFilter));
Specification<ContingencyEntity> hasConnectivityResult = (root, query, cb) -> {
var cr = root.get(ContingencyEntity.Fields.connectivityResult);
return cb.or(
cb.isNotNull(cr.get(ConnectivityResultEmbeddable.Fields.disconnectedLoadActivePower)),
cb.isNotNull(cr.get(ConnectivityResultEmbeddable.Fields.disconnectedGenerationActivePower))
);
};
Page<ContingencyEntity> contingencyPage = self.findPowerCutOffContingenciesPage(resultUuid, allResourceFilters, hasConnectivityResult, pageable);
return contingencyPage.map(ContingencyPowerCutOffDTO::toDto);
}

@Transactional(readOnly = true)
public byte[] findNmKConnectivityResultResultZippedCsv(UUID resultUuid, UUID networkUuid, String variantId, String stringFilters, String stringGlobalFilters, Sort sort, CsvTranslationDTO csvTranslations) {
List<ContingencyPowerCutOffDTO> result = self.findNmKConnectivityResult(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());
}

@Transactional(readOnly = true)
public byte[] findNmKContingenciesResultZippedCsv(UUID resultUuid, UUID networkUuid, String variantId, String stringFilters, String stringGlobalFilters, Sort sort, CsvTranslationDTO csvTranslations) {
List<ContingencyResultDTO> result = self.findNmKContingenciesPaged(resultUuid, networkUuid, variantId, stringFilters, stringGlobalFilters, Pageable.unpaged(sort)).getContent();
Expand Down Expand Up @@ -231,6 +261,10 @@ private void assertSortAllowed(Sort sort, List<String> allowedSortProperties) {
}
}

private void assertNmKPowerCutOffSortAllowed(Sort sort) {
assertSortAllowed(sort, ALLOWED_NMK_POWER_CUT_OFF_RESULT_SORT_PROPERTIES);
}

public void assertResultExists(UUID resultUuid) {
if (securityAnalysisResultRepository.findById(resultUuid).isEmpty()) {
throw new ComputationException(RESULT_NOT_FOUND, "Result not found");
Expand Down Expand Up @@ -406,6 +440,44 @@ private Page<SubjectLimitViolationEntity> findSubjectLimitViolationsPage(UUID re
}
}

@Transactional(readOnly = true)
public Page<ContingencyEntity> findPowerCutOffContingenciesPage(UUID resultUuid, List<ResourceFilterDTO> resourceFilters, Specification<ContingencyEntity> extraSpecification, Pageable pageable) {
Objects.requireNonNull(resultUuid);
assertNmKPowerCutOffSortAllowed(pageable.getSort());
Pageable modifiedPageable = withDefaultSort(pageable);
Specification<ContingencyEntity> specification = contingencySpecificationBuilder.buildSpecification(resultUuid, resourceFilters).and(extraSpecification);

// Determine which properties to project based on sort fields
// When using DISTINCT, all ORDER BY columns must be in the SELECT list
Set<String> projectionProperties = new LinkedHashSet<>();
projectionProperties.add(ContingencyEntity.Fields.uuid);
modifiedPageable.getSort().forEach(order -> projectionProperties.add(order.getProperty()));

Page<ContingencyRepository.EntityUuid> uuidPage = contingencyRepository.findBy(specification, q ->
q.as(ContingencyRepository.EntityUuid.class)
.sortBy(modifiedPageable.getSort())
.project(projectionProperties.toArray(String[]::new))
.page(modifiedPageable)
);
if (!uuidPage.hasContent()) {
return Page.empty(pageable);
}

List<UUID> orderedUuids = uuidPage.map(ContingencyRepository.EntityUuid::getUuid).toList();
List<ContingencyEntity> contingencies = contingencyRepository.findAllByUuidIn(orderedUuids);
Map<UUID, Integer> positionByUuid = IntStream.range(0, orderedUuids.size()).boxed().collect(Collectors.toMap(orderedUuids::get, Function.identity()));
contingencies.sort(Comparator.comparingInt(c -> positionByUuid.get(c.getUuid())));
return new PageImpl<>(contingencies, pageable, uuidPage.getTotalElements());
}

private static Pageable withDefaultSort(Pageable pageable) {
if (pageable.isUnpaged() || pageable.getSort().getOrderFor(ContingencyEntity.Fields.uuid) != null) {
return pageable;
}
Sort stableSort = pageable.getSort().and(Sort.by(DEFAULT_SORT_DIRECTION, ContingencyEntity.Fields.uuid));
return PageRequest.of(pageable.getPageNumber(), pageable.getPageSize(), stableSort);
}

@Transactional(readOnly = true)
public List<LimitViolationType> findNResultLimitTypes(UUID resultUuid) {
Objects.requireNonNull(resultUuid);
Expand Down
Loading
Loading