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 @@ -67,7 +67,6 @@ public SecurityAnalysisController(SecurityAnalysisService securityAnalysisServic
schema = @Schema(implementation = SecurityAnalysisResult.class))})})
public ResponseEntity<SecurityAnalysisResult> run(@Parameter(description = "Network UUID") @PathVariable("networkUuid") UUID networkUuid,
@Parameter(description = "Variant Id") @RequestParam(name = "variantId", required = false) String variantId,
@Parameter(description = "Contingency list name") @RequestParam(name = "contingencyListName", required = false) List<String> contigencyListNames,
@Parameter(description = "Provider") @RequestParam(name = "provider", required = false) String provider,
@Parameter(description = "reportUuid") @RequestParam(name = "reportUuid", required = false) UUID reportUuid,
@Parameter(description = "reporterId") @RequestParam(name = "reporterId", required = false) String reporterId,
Expand All @@ -79,22 +78,21 @@ public ResponseEntity<SecurityAnalysisResult> run(@Parameter(description = "Netw
securityAnalysisParametersService.createRunContext(
networkUuid,
variantId,
new RunContextParametersInfos(contigencyListNames, parametersUuid, loadFlowParametersUuid),
new RunContextParametersInfos(parametersUuid, loadFlowParametersUuid),
null,
new ReportInfos(reportUuid, reporterId, reportType),
userId));
return ResponseEntity.ok().contentType(MediaType.APPLICATION_JSON).body(result);
}

@PostMapping(value = "/networks/{networkUuid}/run-and-save", produces = APPLICATION_JSON_VALUE, consumes = APPLICATION_JSON_VALUE)
@PostMapping(value = "/networks/{networkUuid}/run-and-save", produces = APPLICATION_JSON_VALUE)
@Operation(summary = "Run a security analysis on a network and save results in the database")
@ApiResponses(value = {@ApiResponse(responseCode = "200",
description = "The security analysis has been performed and results have been saved to database",
content = {@Content(mediaType = APPLICATION_JSON_VALUE,
schema = @Schema(implementation = SecurityAnalysisResult.class))})})
public ResponseEntity<UUID> runAndSave(@Parameter(description = "Network UUID") @PathVariable("networkUuid") UUID networkUuid,
@Parameter(description = "Variant Id") @RequestParam(name = "variantId", required = false) String variantId,
@Parameter(description = "Contingency list name") @RequestParam(name = "contingencyListName", required = false) List<String> contigencyListNames,
@Parameter(description = "Result receiver") @RequestParam(name = "receiver", required = false) String receiver,
@Parameter(description = "reportUuid") @RequestParam(name = "reportUuid", required = false) UUID reportUuid,
@Parameter(description = "reporterId") @RequestParam(name = "reporterId", required = false) String reporterId,
Expand All @@ -106,7 +104,7 @@ public ResponseEntity<UUID> runAndSave(@Parameter(description = "Network UUID")
securityAnalysisParametersService.createRunContext(
networkUuid,
variantId,
new RunContextParametersInfos(contigencyListNames, parametersUuid, loadFlowParametersUuid),
new RunContextParametersInfos(parametersUuid, loadFlowParametersUuid),
receiver,
new ReportInfos(reportUuid, reporterId, reportType),
userId
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package org.gridsuite.securityanalysis.server.dto;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
package org.gridsuite.securityanalysis.server.dto;
/**
* 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.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

import java.util.UUID;

/**
* @author Caroline Jeandat {@literal <caroline.jeandat at rte-france.com>}
*/
@NoArgsConstructor
@AllArgsConstructor
@Getter
@Setter
public class ParametersContingenciesDTO {
UUID id;
String name;
}
Comment on lines +17 to +20
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
public class ParametersContingenciesDTO {
UUID id;
String name;
}
public class IdNameInfos {
private UUID id;
private String name;
}

IMO, a simple name such as IdNameInfos is enough and generic to be reused if need, for example filters

Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/**
* Copyright (c) 2025, RTE (http://www.rte-france.com)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* Copyright (c) 2025, RTE (http://www.rte-france.com)
* 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.*;

import java.util.List;

/**
* @author Caroline Jeandat {@literal <caroline.jeandat at rte-france.com>}
*/
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
public class ParametersContingencyListDTO {
List<ParametersContingenciesDTO> contingencies;
String description;
boolean activated;
}
Comment on lines +20 to +24
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
public class ParametersContingencyListDTO {
List<ParametersContingenciesDTO> contingencies;
String description;
boolean activated;
}
public class ContingencyListsInfos {
private List<IdNameInfos> contingencyLists;
private String description;
private boolean activated;
}

IMO, we should move SecurityAnalysisParametersValues, LimitReductionsByVoltageLevel, ContingencyListsInfos, IdNameInfos to package dto/parameters

We can do the same for results but later when we work on results

Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
import lombok.AllArgsConstructor;
import lombok.Getter;

import java.util.List;
import java.util.UUID;

/**
Expand All @@ -20,8 +19,6 @@
@Getter
@Schema(description = "to help create a securityAnalysisRunContext")
public class RunContextParametersInfos {
private List<String> contingencyListNames;

private UUID securityAnalysisParametersUuid;

private UUID loadFlowParametersUuid;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@
import lombok.Builder;

import java.util.List;
import java.util.UUID;

@Builder
public record SecurityAnalysisParametersDTO(
SecurityAnalysisParameters securityAnalysisParameters,
List<UUID> contingencyListUuids,
List<List<Double>> limitReductions
) { }
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ public class SecurityAnalysisParametersValues {

private double flowProportionalThreshold;

private List<ParametersContingencyListDTO> contingencyLists;

private List<LimitReductionsByVoltageLevel> limitReductions;

public SecurityAnalysisParametersEntity toEntity() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ public ContingencyEntity(String contingencyId, String status, List<ContingencyEl
private UUID uuid;

private String contingencyId;

@Setter
@ManyToOne(fetch = FetchType.LAZY)
private SecurityAnalysisResultEntity result;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package org.gridsuite.securityanalysis.server.entities;

import jakarta.persistence.*;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

import java.util.List;
import java.util.UUID;

/**
* @author Caroline Jeandat {@literal <caroline.jeandat at rte-france.com>}
*/
@Getter
@Setter
@NoArgsConstructor
@Entity
@Table(name = "parameters_contingency_list", indexes = {@Index(name = "idx_security_analysis_parameters_id_contingency_lists", columnList = "security_analysis_parameters_id")})
public class ParametersContingencyListEntity {
Comment on lines +18 to +19
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
@Table(name = "parameters_contingency_list", indexes = {@Index(name = "idx_security_analysis_parameters_id_contingency_lists", columnList = "security_analysis_parameters_id")})
public class ParametersContingencyListEntity {
@Table(name = "parameters_contingency_lists", indexes = {@Index(name = "idx_parameters_contingency_lists_security_analysis_parameters_id", columnList = "security_analysis_parameters_id")})
public class ParametersContingencyListsEntity {


@Id
@GeneratedValue(strategy = GenerationType.UUID)
@Column(name = "contingency_list_id")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
@Column(name = "contingency_list_id")
@Column(name = "id")

private UUID id;

@ElementCollection
@CollectionTable(
name = "parameters_contingency_list_contingencies",
joinColumns = @JoinColumn(name = "contingency_list_id")
)
Comment on lines +27 to +30
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
@CollectionTable(
name = "parameters_contingency_list_contingencies",
joinColumns = @JoinColumn(name = "contingency_list_id")
)
@CollectionTable(
name = "parameters_contingency_lists_contingency_list",
joinColumns = @JoinColumn(name = "parameters_contingency_lists_id")
foreignKey = @ForeignKey(name = "parameters_contingency_lists_id_fk")
)

@Column(name = "contingencies_id")
private List<UUID> contingenciesIds;
Comment on lines +31 to +32
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
@Column(name = "contingencies_id")
private List<UUID> contingenciesIds;
@Column(name = "contingency_list_id")
private List<UUID> contingencyListIds;


@Column(name = "description")
private String description;

@Column(name = "activated")
private boolean activated;

@ManyToOne
@JoinColumn(name = "security_analysis_parameters_id")
private SecurityAnalysisParametersEntity securityAnalysisParameters;
Comment on lines +40 to +42
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO, I dont think we really need a reverse direction from child to parent


public ParametersContingencyListEntity(List<UUID> contingenciesIds, String description, boolean activated) {
this.contingenciesIds = contingenciesIds;
this.description = description;
this.activated = activated;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

import jakarta.persistence.*;
import lombok.*;
import org.gridsuite.securityanalysis.server.dto.ParametersContingenciesDTO;
import org.gridsuite.securityanalysis.server.dto.ParametersContingencyListDTO;
import org.gridsuite.securityanalysis.server.dto.SecurityAnalysisParametersValues;
import org.springframework.lang.Nullable;

Expand Down Expand Up @@ -55,11 +57,26 @@ public SecurityAnalysisParametersEntity(SecurityAnalysisParametersValues securit
@Column(name = "flowProportionalThreshold")
private double flowProportionalThreshold;

@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "securityAnalysisParameters")
@OrderColumn(name = "index")
@Builder.Default
private List<ParametersContingencyListEntity> contingencyLists = new ArrayList<>();

@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
@JoinColumn(name = "security_analysis_parameters_id", foreignKey = @ForeignKey(name = "securityAnalysisParametersEntity_limitReductions_fk"))
@OrderColumn(name = "index")
private List<LimitReductionEntity> limitReductions;

public List<UUID> getActivatedContingencyListUuids() {
if (contingencyLists == null) {
return List.of();
}
return this.contingencyLists.stream()
.filter(ParametersContingencyListEntity::isActivated)
.flatMap(contingencyList -> contingencyList.getContingenciesIds().stream())
.toList();
}

public List<List<Double>> toLimitReductionsValues() {
return this.limitReductions.stream().map(LimitReductionEntity::getReductions).map(ArrayList::new).collect(Collectors.toList());
}
Expand All @@ -75,9 +92,30 @@ private void assignAttributes(SecurityAnalysisParametersValues securityAnalysisP
this.highVoltageProportionalThreshold = securityAnalysisParametersValues.getHighVoltageProportionalThreshold();
this.lowVoltageAbsoluteThreshold = securityAnalysisParametersValues.getLowVoltageAbsoluteThreshold();
this.lowVoltageProportionalThreshold = securityAnalysisParametersValues.getLowVoltageProportionalThreshold();
assignContingencyLists(securityAnalysisParametersValues.getContingencyLists());
assignLimitReductions(securityAnalysisParametersValues.getLimitReductionsValues());
}

private void assignContingencyLists(List<ParametersContingencyListDTO> contingencyListsDTO) {
if (contingencyListsDTO == null) {
return;
}

List<ParametersContingencyListEntity> entities = contingencyListsDTO.stream()
.map(dto -> {
ParametersContingencyListEntity entity = new ParametersContingencyListEntity(
dto.getContingencies().stream().map(ParametersContingenciesDTO::getId).toList(),
dto.getDescription(),
dto.isActivated()
);
entity.setSecurityAnalysisParameters(this);
return entity;
})
.toList();
contingencyLists.clear();
contingencyLists.addAll(entities);
Comment on lines +115 to +116
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
contingencyLists.clear();
contingencyLists.addAll(entities);
if (contingencyLists == null) { // due to the use of @Builder.Default, the field is not initialized with an empty array
this.contingencyLists = entities;
} else {
contingencyLists.clear();
contingencyLists.addAll(entities);
}

}

private void assignLimitReductions(@Nullable List<List<Double>> values) {
if (values == null) {
return;
Expand All @@ -95,4 +133,3 @@ public void updateProvider(String provider) {
this.provider = provider;
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public ActionsService(
this.restTemplate = restTemplate;
}

public List<ContingencyInfos> getContingencyList(List<String> ids, UUID networkUuid, String variantId) {
public List<ContingencyInfos> getContingencyList(List<UUID> ids, UUID networkUuid, String variantId) {
Objects.requireNonNull(ids);
Objects.requireNonNull(networkUuid);
if (ids.isEmpty()) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package org.gridsuite.securityanalysis.server.service;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
package org.gridsuite.securityanalysis.server.service;
/**
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.service;


import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;

import java.net.URI;
import java.util.*;

/**
* @author Caroline Jeandat {@literal <caroline.jeandat at rte-france.com>}
*/
@Service
public class DirectoryService {
static final String DIRECTORY_API_VERSION = "v1";

private static final String DELIMITER = "/";

private String baseUri;

private RestTemplate restTemplate;

public void setDirectoryServiceBaseUri(String baseUri) {
this.baseUri = baseUri;
}

public DirectoryService(
@Value("${gridsuite.services.directory-server.base-uri:http://directory-server}") String baseUri,
RestTemplate restTemplate) {
this.baseUri = baseUri;
this.restTemplate = restTemplate;
}

public String getContingenciesName(UUID contingenciesId) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is better with a bulk fetch with a list of uuid and make the signature more generic, for example

Map<UUID, String> getElementNames(List elementUuids)

Objects.requireNonNull(contingenciesId);

URI path = UriComponentsBuilder
.fromPath(DELIMITER + DIRECTORY_API_VERSION + "/elements/{elementUuid}")
.buildAndExpand(contingenciesId)
.toUri();

try {
ResponseEntity<Map<String, Object>> response =
restTemplate.exchange(
baseUri + path,
HttpMethod.GET,
null,
new ParameterizedTypeReference<>() { }
);

Map<String, Object> responseBody = response.getBody();
return responseBody != null ? (String) responseBody.get("elementName") : null;

} catch (HttpClientErrorException.NotFound e) {
return null;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package org.gridsuite.securityanalysis.server.service;

import org.gridsuite.securityanalysis.server.dto.ParametersContingenciesDTO;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.UUID;

/**
* @author Caroline Jeandat {@literal <caroline.jeandat at rte-france.com>}
*/
@Service
public class ParametersContingenciesService {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure we need a dedicated service just for mapping


private final DirectoryService directoryService;

public ParametersContingenciesService(DirectoryService directoryService) {
this.directoryService = directoryService;
}

public List<ParametersContingenciesDTO> toDTO(List<UUID> contingenciesIds) {
return contingenciesIds == null ? null :
contingenciesIds.stream()
.map(id -> new ParametersContingenciesDTO(id, getContingenciesName(id)))
.toList();
}

public List<UUID> toUUIDs(List<ParametersContingenciesDTO> contingenciesDTOs) {
return contingenciesDTOs == null ? null :
contingenciesDTOs.stream()
.map(ParametersContingenciesDTO::getId)
.toList();
}

private String getContingenciesName(UUID contingencyId) {
return directoryService.getContingenciesName(contingencyId);
}
}
Loading
Loading