From ffff85501842dc68780ed7d9bd4af6667d561482 Mon Sep 17 00:00:00 2001 From: Thang PHAM Date: Tue, 23 Dec 2025 22:34:30 +0100 Subject: [PATCH 01/17] DMC - Functional implementation Signed-off-by: Thang PHAM --- pom.xml | 15 +- .../server/config/RestTemplateConfig.java | 45 ++++ .../DynamicMarginCalculationController.java | 155 +++++++++++ ...MarginCalculationParametersController.java | 115 +++++++++ ...namicMarginCalculationParametersInfos.java | 60 +++++ ...namicSecurityAnalysisParametersValues.java | 31 +++ .../DynamicSimulationParametersValues.java | 32 +++ .../dto/parameters/LoadsVariationInfos.java | 35 +++ ...amicMarginCalculationParametersEntity.java | 137 ++++++++++ .../parameters/LoadsVariationEntity.java | 68 +++++ .../result/FailedCriterionEmbeddable.java | 42 +++ .../result/LoadIncreaseResultEntity.java | 89 +++++++ .../result/MarginCalculationResultEntity.java | 54 ++++ .../entities/result/ScenarioResultEntity.java | 75 ++++++ ...micMarginCalculationBusinessErrorCode.java | 28 ++ .../DynamicMarginCalculationException.java | 31 +++ ...amicMarginCalculationExceptionHandler.java | 47 ++++ ...MarginCalculationParametersRepository.java | 21 ++ .../MarginCalculationResultRepository.java | 26 ++ .../DynamicMarginCalculationObserver.java | 41 +++ ...DynamicMarginCalculationResultService.java | 25 +- .../DynamicMarginCalculationService.java | 60 +++++ ...DynamicMarginCalculationWorkerService.java | 240 ++++++++++++++++++ .../server/service/ParametersService.java | 203 +++++++++++++++ .../service/client/AbstractRestClient.java | 38 +++ .../client/DynamicSecurityAnalysisClient.java | 55 ++++ .../client/DynamicSimulationClient.java | 55 ++++ .../server/service/client/FilterClient.java | 51 ++++ .../server/service/client/utils/UrlUtils.java | 51 ++++ ...DynamicMarginCalculationResultContext.java | 91 +++++++ .../DynamicMarginCalculationRunContext.java | 46 ++++ .../server/utils/GZipUtils.java | 62 +++++ src/main/resources/application-local.yml | 8 +- src/main/resources/config/application.yaml | 10 +- .../changesets/changelog_20251223T095053Z.xml | 130 ++++++++++ .../db/changelog/db.changelog-master.yaml | 4 + 36 files changed, 2265 insertions(+), 11 deletions(-) create mode 100644 src/main/java/org/gridsuite/dynamicmargincalculation/server/config/RestTemplateConfig.java create mode 100644 src/main/java/org/gridsuite/dynamicmargincalculation/server/controller/DynamicMarginCalculationController.java create mode 100644 src/main/java/org/gridsuite/dynamicmargincalculation/server/controller/DynamicMarginCalculationParametersController.java create mode 100644 src/main/java/org/gridsuite/dynamicmargincalculation/server/dto/parameters/DynamicMarginCalculationParametersInfos.java create mode 100644 src/main/java/org/gridsuite/dynamicmargincalculation/server/dto/parameters/DynamicSecurityAnalysisParametersValues.java create mode 100644 src/main/java/org/gridsuite/dynamicmargincalculation/server/dto/parameters/DynamicSimulationParametersValues.java create mode 100644 src/main/java/org/gridsuite/dynamicmargincalculation/server/dto/parameters/LoadsVariationInfos.java create mode 100644 src/main/java/org/gridsuite/dynamicmargincalculation/server/entities/parameters/DynamicMarginCalculationParametersEntity.java create mode 100644 src/main/java/org/gridsuite/dynamicmargincalculation/server/entities/parameters/LoadsVariationEntity.java create mode 100644 src/main/java/org/gridsuite/dynamicmargincalculation/server/entities/result/FailedCriterionEmbeddable.java create mode 100644 src/main/java/org/gridsuite/dynamicmargincalculation/server/entities/result/LoadIncreaseResultEntity.java create mode 100644 src/main/java/org/gridsuite/dynamicmargincalculation/server/entities/result/MarginCalculationResultEntity.java create mode 100644 src/main/java/org/gridsuite/dynamicmargincalculation/server/entities/result/ScenarioResultEntity.java create mode 100644 src/main/java/org/gridsuite/dynamicmargincalculation/server/error/DynamicMarginCalculationBusinessErrorCode.java create mode 100644 src/main/java/org/gridsuite/dynamicmargincalculation/server/error/DynamicMarginCalculationException.java create mode 100644 src/main/java/org/gridsuite/dynamicmargincalculation/server/error/DynamicMarginCalculationExceptionHandler.java create mode 100644 src/main/java/org/gridsuite/dynamicmargincalculation/server/repositories/DynamicMarginCalculationParametersRepository.java create mode 100644 src/main/java/org/gridsuite/dynamicmargincalculation/server/repositories/MarginCalculationResultRepository.java create mode 100644 src/main/java/org/gridsuite/dynamicmargincalculation/server/service/DynamicMarginCalculationObserver.java create mode 100644 src/main/java/org/gridsuite/dynamicmargincalculation/server/service/DynamicMarginCalculationService.java create mode 100644 src/main/java/org/gridsuite/dynamicmargincalculation/server/service/DynamicMarginCalculationWorkerService.java create mode 100644 src/main/java/org/gridsuite/dynamicmargincalculation/server/service/ParametersService.java create mode 100644 src/main/java/org/gridsuite/dynamicmargincalculation/server/service/client/AbstractRestClient.java create mode 100644 src/main/java/org/gridsuite/dynamicmargincalculation/server/service/client/DynamicSecurityAnalysisClient.java create mode 100644 src/main/java/org/gridsuite/dynamicmargincalculation/server/service/client/DynamicSimulationClient.java create mode 100644 src/main/java/org/gridsuite/dynamicmargincalculation/server/service/client/FilterClient.java create mode 100644 src/main/java/org/gridsuite/dynamicmargincalculation/server/service/client/utils/UrlUtils.java create mode 100644 src/main/java/org/gridsuite/dynamicmargincalculation/server/service/contexts/DynamicMarginCalculationResultContext.java create mode 100644 src/main/java/org/gridsuite/dynamicmargincalculation/server/service/contexts/DynamicMarginCalculationRunContext.java create mode 100644 src/main/java/org/gridsuite/dynamicmargincalculation/server/utils/GZipUtils.java create mode 100644 src/main/resources/db/changelog/changesets/changelog_20251223T095053Z.xml diff --git a/pom.xml b/pom.xml index 69a1858..64339fc 100644 --- a/pom.xml +++ b/pom.xml @@ -48,7 +48,7 @@ gridsuite org.gridsuite:dynamic-margin-calculation-server - 1.7.0 + 1.9.0 1.34.0 @@ -116,10 +116,6 @@ - - org.gridsuite - gridsuite-computation - org.springframework.boot spring-boot-starter-web @@ -162,6 +158,15 @@ powsybl-dynawo-margin-calculation + + org.gridsuite + gridsuite-computation + + + org.gridsuite + gridsuite-filter + + org.springframework.cloud diff --git a/src/main/java/org/gridsuite/dynamicmargincalculation/server/config/RestTemplateConfig.java b/src/main/java/org/gridsuite/dynamicmargincalculation/server/config/RestTemplateConfig.java new file mode 100644 index 0000000..99d6076 --- /dev/null +++ b/src/main/java/org/gridsuite/dynamicmargincalculation/server/config/RestTemplateConfig.java @@ -0,0 +1,45 @@ +/** + * 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.dynamicmargincalculation.server.config; + +import com.fasterxml.jackson.databind.MapperFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.powsybl.commons.report.ReportNodeJsonModule; +import com.powsybl.dynawo.margincalculation.json.MarginCalculationParametersJsonModule; +import org.gridsuite.computation.ComputationConfig; +import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer; +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; +import org.springframework.web.client.RestTemplate; + +/** + * @author Thang PHAM + */ +@Configuration +@Import(ComputationConfig.class) +public class RestTemplateConfig { + @Bean + public RestTemplate restTemplate(RestTemplateBuilder restTemplateBuilder, ObjectMapper objectMapper) { + MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter(objectMapper); + + return restTemplateBuilder + .additionalMessageConverters(messageConverter) + .build(); + } + + @Bean + public Jackson2ObjectMapperBuilderCustomizer appJsonCustomizer() { + return builder -> builder + .featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) + .featuresToEnable(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS) + .modulesToInstall(new MarginCalculationParametersJsonModule(), new ReportNodeJsonModule()); + } +} diff --git a/src/main/java/org/gridsuite/dynamicmargincalculation/server/controller/DynamicMarginCalculationController.java b/src/main/java/org/gridsuite/dynamicmargincalculation/server/controller/DynamicMarginCalculationController.java new file mode 100644 index 0000000..9ddef18 --- /dev/null +++ b/src/main/java/org/gridsuite/dynamicmargincalculation/server/controller/DynamicMarginCalculationController.java @@ -0,0 +1,155 @@ +/** + * 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.dynamicmargincalculation.server.controller; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.apache.commons.collections4.CollectionUtils; +import org.gridsuite.computation.dto.ReportInfos; +import org.gridsuite.dynamicmargincalculation.server.dto.DynamicMarginCalculationStatus; +import org.gridsuite.dynamicmargincalculation.server.service.DynamicMarginCalculationResultService; +import org.gridsuite.dynamicmargincalculation.server.service.DynamicMarginCalculationService; +import org.gridsuite.dynamicmargincalculation.server.service.ParametersService; +import org.gridsuite.dynamicmargincalculation.server.service.contexts.DynamicMarginCalculationRunContext; +import org.springframework.core.io.Resource; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.UUID; + +import static org.gridsuite.computation.service.AbstractResultContext.*; +import static org.gridsuite.computation.service.NotificationService.*; +import static org.gridsuite.dynamicmargincalculation.server.DynamicMarginCalculationApi.API_VERSION; +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; +import static org.springframework.http.MediaType.TEXT_PLAIN_VALUE; + +/** + * @author Thang PHAM + */ +@RestController +@RequestMapping(value = "/" + API_VERSION) +@Tag(name = "Dynamic margin calculation server") +public class DynamicMarginCalculationController { + + private final DynamicMarginCalculationService dynamicMarginCalculationService; + private final DynamicMarginCalculationResultService dynamicMarginCalculationResultService; + private final ParametersService parametersService; + + public DynamicMarginCalculationController(DynamicMarginCalculationService dynamicMarginCalculationService, + DynamicMarginCalculationResultService dynamicMarginCalculationResultService, + ParametersService parametersService) { + this.dynamicMarginCalculationService = dynamicMarginCalculationService; + this.dynamicMarginCalculationResultService = dynamicMarginCalculationResultService; + this.parametersService = parametersService; + } + + @PostMapping(value = "/networks/{networkUuid}/run", produces = "application/json") + @Operation(summary = "run the dynamic margin calculation") + @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "Run dynamic margin calculation")}) + public ResponseEntity run(@PathVariable("networkUuid") UUID networkUuid, + @RequestParam(name = VARIANT_ID_HEADER, required = false) String variantId, + @RequestParam(name = HEADER_RECEIVER, required = false) String receiver, + @RequestParam(name = "reportUuid", required = false) UUID reportId, + @RequestParam(name = REPORTER_ID_HEADER, required = false) String reportName, + @RequestParam(name = REPORT_TYPE_HEADER, required = false, defaultValue = "DynamicMarginCalculation") String reportType, + @RequestParam(name = HEADER_PROVIDER, required = false) String provider, + @RequestParam(name = HEADER_DEBUG, required = false, defaultValue = "false") boolean debug, + @RequestParam(name = "dynamicSecurityAnalysisParametersUuid") UUID dynamicSecurityAnalysisParametersUuid, + @RequestParam(name = "parametersUuid") UUID parametersUuid, + @RequestBody String dynamicSimulationParametersJson, + @RequestHeader(HEADER_USER_ID) String userId) { + + DynamicMarginCalculationRunContext dynamicMarginCalculationRunContext = parametersService.createRunContext( + networkUuid, + variantId, + receiver, + provider, + ReportInfos.builder().reportUuid(reportId).reporterId(reportName).computationType(reportType).build(), + userId, + dynamicSimulationParametersJson, + dynamicSecurityAnalysisParametersUuid, + parametersUuid, + debug); + + UUID resultUuid = dynamicMarginCalculationService.runAndSaveResult(dynamicMarginCalculationRunContext); + return ResponseEntity.ok().contentType(MediaType.APPLICATION_JSON).body(resultUuid); + } + + @GetMapping(value = "/results/{resultUuid}/status", produces = "application/json") + @Operation(summary = "Get the dynamic margin calculation status from the database") + @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "The dynamic margin calculation status"), + @ApiResponse(responseCode = "204", description = "Dynamic margin calculation status is empty"), + @ApiResponse(responseCode = "404", description = "Dynamic security analysis result uuid has not been found")}) + public ResponseEntity getStatus(@Parameter(description = "Result UUID") @PathVariable("resultUuid") UUID resultUuid) { + DynamicMarginCalculationStatus result = dynamicMarginCalculationService.getStatus(resultUuid); + return ResponseEntity.ok().body(result); + } + + @PutMapping(value = "/results/invalidate-status", produces = "application/json") + @Operation(summary = "Invalidate the dynamic margin calculation status from the database") + @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "The dynamic margin calculation result uuids have been invalidated"), + @ApiResponse(responseCode = "404", description = "Dynamic margin calculation result has not been found")}) + public ResponseEntity> invalidateStatus(@Parameter(description = "Result UUIDs") @RequestParam("resultUuid") List resultUuids) { + List result = dynamicMarginCalculationResultService.updateStatus(resultUuids, DynamicMarginCalculationStatus.NOT_DONE); + return CollectionUtils.isEmpty(result) ? ResponseEntity.notFound().build() : + ResponseEntity.ok().contentType(MediaType.APPLICATION_JSON).body(result); + } + + @DeleteMapping(value = "/results/{resultUuid}") + @Operation(summary = "Delete a dynamic margin calculation result from the database") + @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "The dynamic margin calculation result has been deleted")}) + public ResponseEntity deleteResult(@Parameter(description = "Result UUID") @PathVariable("resultUuid") UUID resultUuid) { + dynamicMarginCalculationResultService.delete(resultUuid); + return ResponseEntity.ok().build(); + } + + @DeleteMapping(value = "/results", produces = APPLICATION_JSON_VALUE) + @Operation(summary = "Delete dynamic margin calculation results from the database") + @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "Dynamic margin calculation results have been deleted")}) + public ResponseEntity deleteResults(@Parameter(description = "Results UUID") @RequestParam(value = "resultsUuids", required = false) List resultsUuids) { + dynamicMarginCalculationService.deleteResults(resultsUuids); + return ResponseEntity.ok().build(); + } + + @PutMapping(value = "/results/{resultUuid}/stop", produces = APPLICATION_JSON_VALUE) + @Operation(summary = "Stop a dynamic margin calculation computation") + @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "The dynamic margin calculation has been stopped")}) + public ResponseEntity stop(@Parameter(description = "Result UUID") @PathVariable("resultUuid") UUID resultUuid, + @Parameter(description = "Result receiver") @RequestParam(name = "receiver", required = false, defaultValue = "") String receiver) { + dynamicMarginCalculationService.stop(resultUuid, receiver); + return ResponseEntity.ok().build(); + } + + @GetMapping(value = "/providers", produces = APPLICATION_JSON_VALUE) + @Operation(summary = "Get all margin calculation providers") + @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "Dynamic margin calculation providers have been found")}) + public ResponseEntity> getProviders() { + return ResponseEntity.ok().contentType(MediaType.APPLICATION_JSON) + .body(dynamicMarginCalculationService.getProviders()); + } + + @GetMapping(value = "/default-provider", produces = TEXT_PLAIN_VALUE) + @Operation(summary = "Get dynamic margin calculation default provider") + @ApiResponses(@ApiResponse(responseCode = "200", description = "The dynamic margin calculation default provider has been found")) + public ResponseEntity getDefaultProvider() { + return ResponseEntity.ok().body(dynamicMarginCalculationService.getDefaultProvider()); + } + + @GetMapping(value = "/results/{resultUuid}/download-debug-file", produces = "application/json") + @Operation(summary = "Download a dynamic margin calculation debug file") + @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "Dynamic margin calculation debug file"), + @ApiResponse(responseCode = "404", description = "Dynamic margin calculation debug file has not been found")}) + public ResponseEntity downloadDebugFile(@Parameter(description = "Result UUID") @PathVariable("resultUuid") UUID resultUuid) { + return dynamicMarginCalculationService.downloadDebugFile(resultUuid); + } + +} diff --git a/src/main/java/org/gridsuite/dynamicmargincalculation/server/controller/DynamicMarginCalculationParametersController.java b/src/main/java/org/gridsuite/dynamicmargincalculation/server/controller/DynamicMarginCalculationParametersController.java new file mode 100644 index 0000000..6fb3947 --- /dev/null +++ b/src/main/java/org/gridsuite/dynamicmargincalculation/server/controller/DynamicMarginCalculationParametersController.java @@ -0,0 +1,115 @@ +/** + * 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.dynamicmargincalculation.server.controller; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.gridsuite.dynamicmargincalculation.server.DynamicMarginCalculationApi; +import org.gridsuite.dynamicmargincalculation.server.dto.parameters.DynamicMarginCalculationParametersInfos; +import org.gridsuite.dynamicmargincalculation.server.service.ParametersService; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +/** + * @author Thang PHAM + */ +@RestController +@RequestMapping(value = "/" + DynamicMarginCalculationApi.API_VERSION + "/parameters") +@Tag(name = "Dynamic security analysis server - Parameters") +public class DynamicMarginCalculationParametersController { + + private final ParametersService parametersService; + + public DynamicMarginCalculationParametersController(ParametersService parametersService) { + this.parametersService = parametersService; + } + + @PostMapping(value = "", consumes = MediaType.APPLICATION_JSON_VALUE) + @Operation(summary = "Create parameters") + @ApiResponse(responseCode = "200", description = "parameters were created") + public ResponseEntity createParameters( + @RequestBody DynamicMarginCalculationParametersInfos parametersInfos) { + return ResponseEntity.ok(parametersService.createParameters(parametersInfos)); + } + + @PostMapping(value = "/default") + @Operation(summary = "Create default parameters") + @ApiResponse(responseCode = "200", description = "Default parameters were created") + public ResponseEntity createDefaultParameters() { + return ResponseEntity.ok(parametersService.createDefaultParameters()); + } + + @PostMapping(value = "", params = "duplicateFrom", produces = MediaType.APPLICATION_JSON_VALUE) + @Operation(summary = "Duplicate parameters") + @ApiResponse(responseCode = "200", description = "parameters were duplicated") + public ResponseEntity duplicateParameters( + @Parameter(description = "source parameters UUID") @RequestParam("duplicateFrom") UUID sourceParametersUuid) { + return ResponseEntity.of(Optional.of(parametersService.duplicateParameters(sourceParametersUuid))); + } + + @GetMapping(value = "/{uuid}", produces = MediaType.APPLICATION_JSON_VALUE) + @Operation(summary = "Get parameters") + @ApiResponse(responseCode = "200", description = "parameters were returned") + @ApiResponse(responseCode = "404", description = "parameters were not found") + public ResponseEntity getParameters( + @Parameter(description = "parameters UUID") @PathVariable("uuid") UUID parametersUuid) { + return ResponseEntity.of(Optional.of(parametersService.getParameters(parametersUuid))); + } + + @GetMapping(value = "", produces = MediaType.APPLICATION_JSON_VALUE) + @Operation(summary = "Get all parameters") + @ApiResponse(responseCode = "200", description = "The list of all parameters was returned") + public ResponseEntity> getAllParameters() { + return ResponseEntity.ok().body(parametersService.getAllParameters()); + } + + @PutMapping(value = "/{uuid}", consumes = MediaType.APPLICATION_JSON_VALUE) + @Operation(summary = "Update parameters") + @ApiResponse(responseCode = "200", description = "parameters were updated") + public ResponseEntity updateParameters( + @Parameter(description = "parameters UUID") @PathVariable("uuid") UUID parametersUuid, + @RequestBody(required = false) DynamicMarginCalculationParametersInfos parametersInfos) { + parametersService.updateParameters(parametersUuid, parametersInfos); + return ResponseEntity.ok().build(); + } + + @DeleteMapping(value = "/{uuid}") + @Operation(summary = "Delete parameters") + @ApiResponse(responseCode = "200", description = "parameters were deleted") + public ResponseEntity deleteParameters( + @Parameter(description = "parameters UUID") @PathVariable("uuid") UUID parametersUuid) { + parametersService.deleteParameters(parametersUuid); + return ResponseEntity.ok().build(); + } + + @GetMapping(value = "/{uuid}/provider", produces = MediaType.APPLICATION_JSON_VALUE) + @Operation(summary = "Get provider") + @ApiResponse(responseCode = "200", description = "provider were returned") + @ApiResponse(responseCode = "404", description = "provider were not found") + public ResponseEntity getProvider( + @Parameter(description = "parameters UUID") @PathVariable("uuid") UUID parametersUuid) { + return ResponseEntity.of(Optional.of(parametersService.getParameters(parametersUuid).getProvider())); + } + + @PutMapping(value = "/{uuid}/provider") + @Operation(summary = "Update provider") + @ApiResponse(responseCode = "200", description = "provider was updated") + public ResponseEntity updateProvider( + @Parameter(description = "parameters UUID") @PathVariable("uuid") UUID parametersUuid, + @RequestBody(required = false) String provider) { + parametersService.updateProvider(parametersUuid, provider); + return ResponseEntity.ok().build(); + } + +} diff --git a/src/main/java/org/gridsuite/dynamicmargincalculation/server/dto/parameters/DynamicMarginCalculationParametersInfos.java b/src/main/java/org/gridsuite/dynamicmargincalculation/server/dto/parameters/DynamicMarginCalculationParametersInfos.java new file mode 100644 index 0000000..f247e7c --- /dev/null +++ b/src/main/java/org/gridsuite/dynamicmargincalculation/server/dto/parameters/DynamicMarginCalculationParametersInfos.java @@ -0,0 +1,60 @@ +/* + * 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.dynamicmargincalculation.server.dto.parameters; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.powsybl.dynawo.margincalculation.MarginCalculationParameters.CalculationType; +import com.powsybl.dynawo.margincalculation.MarginCalculationParameters.LoadModelsRule; +import lombok.*; + +import java.util.List; +import java.util.UUID; + +/** + * @author Thang PHAM + */ +@NoArgsConstructor +@AllArgsConstructor +@Getter +@Setter +@Builder +@JsonIgnoreProperties(ignoreUnknown = true) +public class DynamicMarginCalculationParametersInfos { + private UUID id; + + private String provider; + + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private Double startTime; + + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private Double stopTime; + + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private Double marginCalculationStartTime; + + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private Double loadIncreaseStartTime; + + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private Double loadIncreaseStopTime; + + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private CalculationType calculationType; + + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private Integer accuracy; + + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private LoadModelsRule loadModelsRule; + + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private List loadsVariations; + +} diff --git a/src/main/java/org/gridsuite/dynamicmargincalculation/server/dto/parameters/DynamicSecurityAnalysisParametersValues.java b/src/main/java/org/gridsuite/dynamicmargincalculation/server/dto/parameters/DynamicSecurityAnalysisParametersValues.java new file mode 100644 index 0000000..8fa9437 --- /dev/null +++ b/src/main/java/org/gridsuite/dynamicmargincalculation/server/dto/parameters/DynamicSecurityAnalysisParametersValues.java @@ -0,0 +1,31 @@ +/* + * 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.dynamicmargincalculation.server.dto.parameters; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.powsybl.contingency.Contingency; +import lombok.*; + +import java.util.List; + +/** + * @author Thang PHAM + */ +@NoArgsConstructor +@AllArgsConstructor +@Getter +@Setter +@Builder +@JsonIgnoreProperties(ignoreUnknown = true) +public class DynamicSecurityAnalysisParametersValues { + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private Double contingenciesStartTime; + + @JsonInclude(JsonInclude.Include.NON_EMPTY) + List contingencies; +} diff --git a/src/main/java/org/gridsuite/dynamicmargincalculation/server/dto/parameters/DynamicSimulationParametersValues.java b/src/main/java/org/gridsuite/dynamicmargincalculation/server/dto/parameters/DynamicSimulationParametersValues.java new file mode 100644 index 0000000..5c627a3 --- /dev/null +++ b/src/main/java/org/gridsuite/dynamicmargincalculation/server/dto/parameters/DynamicSimulationParametersValues.java @@ -0,0 +1,32 @@ +/* + * 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.dynamicmargincalculation.server.dto.parameters; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.powsybl.dynawo.DynawoSimulationParameters; +import com.powsybl.dynawo.suppliers.dynamicmodels.DynamicModelConfig; +import lombok.*; + +import java.util.List; + +/** + * @author Thang PHAM + */ +@NoArgsConstructor +@AllArgsConstructor +@Getter +@Setter +@Builder +@JsonIgnoreProperties(ignoreUnknown = true) +public class DynamicSimulationParametersValues { + @JsonInclude(JsonInclude.Include.NON_EMPTY) + List dynamicModel; + + @JsonInclude(JsonInclude.Include.NON_EMPTY) + DynawoSimulationParameters dynawoParameters; +} diff --git a/src/main/java/org/gridsuite/dynamicmargincalculation/server/dto/parameters/LoadsVariationInfos.java b/src/main/java/org/gridsuite/dynamicmargincalculation/server/dto/parameters/LoadsVariationInfos.java new file mode 100644 index 0000000..480377c --- /dev/null +++ b/src/main/java/org/gridsuite/dynamicmargincalculation/server/dto/parameters/LoadsVariationInfos.java @@ -0,0 +1,35 @@ +/* + * 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.dynamicmargincalculation.server.dto.parameters; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.*; + +import java.util.List; +import java.util.UUID; + +/** + * @author Thang PHAM + */ +@NoArgsConstructor +@AllArgsConstructor +@Getter +@Setter +@Builder +@JsonIgnoreProperties(ignoreUnknown = true) +public class LoadsVariationInfos { + + private UUID id; + + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private List loadFilterUuids; + + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private Double variation; +} diff --git a/src/main/java/org/gridsuite/dynamicmargincalculation/server/entities/parameters/DynamicMarginCalculationParametersEntity.java b/src/main/java/org/gridsuite/dynamicmargincalculation/server/entities/parameters/DynamicMarginCalculationParametersEntity.java new file mode 100644 index 0000000..1d9ca3f --- /dev/null +++ b/src/main/java/org/gridsuite/dynamicmargincalculation/server/entities/parameters/DynamicMarginCalculationParametersEntity.java @@ -0,0 +1,137 @@ +/* + * 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.dynamicmargincalculation.server.entities.parameters; + +import com.powsybl.dynawo.margincalculation.MarginCalculationParameters.CalculationType; +import com.powsybl.dynawo.margincalculation.MarginCalculationParameters.LoadModelsRule; +import jakarta.persistence.*; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.gridsuite.dynamicmargincalculation.server.dto.parameters.DynamicMarginCalculationParametersInfos; +import org.gridsuite.dynamicmargincalculation.server.dto.parameters.LoadsVariationInfos; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.stream.Collectors; + +import static jakarta.persistence.CascadeType.ALL; + +/** + * @author Thang PHAM + */ +@NoArgsConstructor +@Getter +@Setter +@Entity +@Table(name = "dynamic_margin_calculation_parameters") +public class DynamicMarginCalculationParametersEntity { + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + @Column(name = "id") + private UUID id; + + @Column(name = "provider") + private String provider; + + @Column(name = "start_time") + private Double startTime; + + @Column(name = "stop_time") + private Double stopTime; + + @Column(name = "margin_calculation_start_time") + private Double marginCalculationStartTime; + + @Column(name = "load_increase_start_time") + private Double loadIncreaseStartTime; + + @Column(name = "load_increase_stop_time") + private Double loadIncreaseStopTime; + + @Column(name = "calculation_type") + @Enumerated(EnumType.STRING) + private CalculationType calculationType; + + @Column(name = "accuracy") + private Integer accuracy; + + @Column(name = "load_models_rule") + @Enumerated(EnumType.STRING) + private LoadModelsRule loadModelsRule; + + @OneToMany(fetch = FetchType.EAGER, cascade = ALL, orphanRemoval = true) + @JoinColumn(name = "dynamic_margin_calculation_parameters_id", foreignKey = @ForeignKey(name = "dynamic_margin_calculation_parameters_id_fk")) + @OrderColumn(name = "pos") + private List loadsVariations = new ArrayList<>(); + + public DynamicMarginCalculationParametersEntity(DynamicMarginCalculationParametersInfos parametersInfos) { + assignAttributes(parametersInfos); + } + + private void assignAttributes(DynamicMarginCalculationParametersInfos parametersInfos) { + if (id == null) { + id = UUID.randomUUID(); + parametersInfos.setId(id); + } + provider = parametersInfos.getProvider(); + startTime = parametersInfos.getStartTime(); + stopTime = parametersInfos.getStopTime(); + marginCalculationStartTime = parametersInfos.getMarginCalculationStartTime(); + loadIncreaseStartTime = parametersInfos.getLoadIncreaseStartTime(); + loadIncreaseStopTime = parametersInfos.getLoadIncreaseStopTime(); + calculationType = parametersInfos.getCalculationType(); + accuracy = parametersInfos.getAccuracy(); + loadModelsRule = parametersInfos.getLoadModelsRule(); + // assign loads variations + assignLoadsVariations(parametersInfos.getLoadsVariations()); + } + + private void assignLoadsVariations(List loadsVariationInfosList) { + // build existing loads variation Map + Map loadsVariationsByIdMap = loadsVariations.stream().collect(Collectors.toMap(LoadsVariationEntity::getId, loadVariationEntity -> loadVariationEntity)); + + // merge existing and add new loads variations + List mergedLoadsVariations = new ArrayList<>(); + for (LoadsVariationInfos loadsVariationInfos : loadsVariationInfosList) { + if (loadsVariationInfos.getId() != null) { + LoadsVariationEntity existingEntity = loadsVariationsByIdMap.get(loadsVariationInfos.getId()); + existingEntity.update(loadsVariationInfos); + mergedLoadsVariations.add(existingEntity); + } else { + mergedLoadsVariations.add(new LoadsVariationEntity(loadsVariationInfos)); + } + } + + // by clear/addAll, existing elements that are not present in the new list will be removed systematically + loadsVariations.clear(); + loadsVariations.addAll(mergedLoadsVariations); + } + + public void update(DynamicMarginCalculationParametersInfos parametersInfos) { + assignAttributes(parametersInfos); + } + + public DynamicMarginCalculationParametersInfos toDto(boolean toDuplicate) { + return DynamicMarginCalculationParametersInfos.builder() + .id(toDuplicate ? null : id) + .provider(provider) + .startTime(startTime) + .stopTime(stopTime) + .marginCalculationStartTime(marginCalculationStartTime) + .loadIncreaseStartTime(loadIncreaseStartTime) + .loadIncreaseStopTime(loadIncreaseStopTime) + .calculationType(calculationType) + .accuracy(accuracy) + .loadModelsRule(loadModelsRule) + .loadsVariations(loadsVariations.stream().map(loadsVariationEntity -> loadsVariationEntity.toDto(toDuplicate)).toList()) + .build(); + } +} diff --git a/src/main/java/org/gridsuite/dynamicmargincalculation/server/entities/parameters/LoadsVariationEntity.java b/src/main/java/org/gridsuite/dynamicmargincalculation/server/entities/parameters/LoadsVariationEntity.java new file mode 100644 index 0000000..b001fd3 --- /dev/null +++ b/src/main/java/org/gridsuite/dynamicmargincalculation/server/entities/parameters/LoadsVariationEntity.java @@ -0,0 +1,68 @@ +/* + * 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.dynamicmargincalculation.server.entities.parameters; + +import jakarta.persistence.*; +import lombok.*; +import org.gridsuite.dynamicmargincalculation.server.dto.parameters.LoadsVariationInfos; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +/** + * @author Thang PHAM + */ +@NoArgsConstructor +@AllArgsConstructor +@Getter +@Setter +@Builder +@Entity +@Table(name = "loads_variation") +public class LoadsVariationEntity { + @Id + @Column(name = "id") + private UUID id; + + @ElementCollection(fetch = FetchType.EAGER) + @CollectionTable( + name = "loads_variation_load_filter", + joinColumns = @JoinColumn(name = "loads_variation_id"), + foreignKey = @ForeignKey(name = "loads_variation_id_fk") + ) + @Column(name = "load_filter_id", nullable = false) + private List loadFilterIds = new ArrayList<>(); + + private Double variation; + + LoadsVariationEntity(LoadsVariationInfos loadsVariationInfos) { + assignAttributes(loadsVariationInfos); + } + + public void assignAttributes(LoadsVariationInfos loadsVariationInfos) { + if (id == null) { + id = UUID.randomUUID(); + loadsVariationInfos.setId(id); + } + variation = loadsVariationInfos.getVariation(); + loadFilterIds = loadsVariationInfos.getLoadFilterUuids(); + } + + void update(LoadsVariationInfos loadsVariationInfos) { + assignAttributes(loadsVariationInfos); + } + + LoadsVariationInfos toDto(boolean toDuplicate) { + return LoadsVariationInfos.builder() + .id(toDuplicate ? null : id) + .variation(variation) + .loadFilterUuids(loadFilterIds) + .build(); + } +} diff --git a/src/main/java/org/gridsuite/dynamicmargincalculation/server/entities/result/FailedCriterionEmbeddable.java b/src/main/java/org/gridsuite/dynamicmargincalculation/server/entities/result/FailedCriterionEmbeddable.java new file mode 100644 index 0000000..7c1fa39 --- /dev/null +++ b/src/main/java/org/gridsuite/dynamicmargincalculation/server/entities/result/FailedCriterionEmbeddable.java @@ -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.dynamicmargincalculation.server.entities.result; + +import com.powsybl.dynawo.contingency.results.FailedCriterion; +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** + * @author Thang PHAM + */ +@NoArgsConstructor +@Getter +@Setter +@Embeddable +public class FailedCriterionEmbeddable { + + @Column(name = "description") + private String description; + + @Column(name = "time") + private double time; + + public static FailedCriterionEmbeddable fromDomain(FailedCriterion failedCriterion) { + FailedCriterionEmbeddable embeddable = new FailedCriterionEmbeddable(); + embeddable.setDescription(failedCriterion.description()); + embeddable.setTime(failedCriterion.time()); + return embeddable; + } + + public FailedCriterion toDto() { + return new FailedCriterion(description, time); + } +} diff --git a/src/main/java/org/gridsuite/dynamicmargincalculation/server/entities/result/LoadIncreaseResultEntity.java b/src/main/java/org/gridsuite/dynamicmargincalculation/server/entities/result/LoadIncreaseResultEntity.java new file mode 100644 index 0000000..bf7db42 --- /dev/null +++ b/src/main/java/org/gridsuite/dynamicmargincalculation/server/entities/result/LoadIncreaseResultEntity.java @@ -0,0 +1,89 @@ +/* + * 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.dynamicmargincalculation.server.entities.result; + +import com.powsybl.dynawo.contingency.results.FailedCriterion; +import com.powsybl.dynawo.contingency.results.ScenarioResult; +import com.powsybl.dynawo.contingency.results.Status; +import com.powsybl.dynawo.margincalculation.results.LoadIncreaseResult; +import jakarta.persistence.*; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +/** + * @author Thang PHAM + */ +@NoArgsConstructor +@Getter +@Setter +@Entity +@Table(name = "load_increase_result", indexes = {@Index(name = "load_increase_result_idx", columnList = "dynamic_margin_calculation_result_uuid")}) +public class LoadIncreaseResultEntity { + @Id + @Column(name = "id") + private UUID id; + + @Column(name = "load_level") + double loadLevel; + + @Column(name = "status") + @Enumerated(EnumType.STRING) + Status status; + + @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true) + @JoinColumn( + name = "load_increase_result_id", + referencedColumnName = "id", + foreignKey = @ForeignKey(name = "scenario_result_load_increase_result_id_fk") + ) + @OrderColumn(name = "pos") + private List scenarioResults = new ArrayList<>(); + + @ElementCollection + @CollectionTable( + name = "load_increase_result_failed_criteria", + joinColumns = @JoinColumn( + name = "load_increase_result_id", + referencedColumnName = "id", + foreignKey = @ForeignKey(name = "load_increase_result_failed_criteria_load_increase_result_id_fk") + ), + indexes = { + @Index(name = "load_increase_result_failed_criteria_idx", columnList = "load_increase_result_id") + } + ) + @OrderColumn(name = "pos") + private List failedCriteria = new ArrayList<>(); + + public static LoadIncreaseResultEntity fromDomain(LoadIncreaseResult loadIncreaseResult) { + LoadIncreaseResultEntity entity = new LoadIncreaseResultEntity(); + entity.setId(UUID.randomUUID()); + entity.setLoadLevel(loadIncreaseResult.loadLevel()); + entity.setStatus(loadIncreaseResult.status()); + + List scenarioResults = loadIncreaseResult.scenarioResults(); + entity.setScenarioResults(scenarioResults.stream().map(ScenarioResultEntity::fromDomain).toList()); + + List failedCriteria = loadIncreaseResult.failedCriteria(); + entity.setFailedCriteria(failedCriteria.stream().map(FailedCriterionEmbeddable::fromDomain).toList()); + + return entity; + } + + public LoadIncreaseResult toDto() { + List scenarioResultList = scenarioResults.stream().map(ScenarioResultEntity::toDto).toList(); + List failedCriterionList = failedCriteria.stream().map(FailedCriterionEmbeddable::toDto).toList(); + + return new LoadIncreaseResult(loadLevel, status, scenarioResultList, failedCriterionList); + } +} + diff --git a/src/main/java/org/gridsuite/dynamicmargincalculation/server/entities/result/MarginCalculationResultEntity.java b/src/main/java/org/gridsuite/dynamicmargincalculation/server/entities/result/MarginCalculationResultEntity.java new file mode 100644 index 0000000..5a9b6c3 --- /dev/null +++ b/src/main/java/org/gridsuite/dynamicmargincalculation/server/entities/result/MarginCalculationResultEntity.java @@ -0,0 +1,54 @@ +/* + * 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.dynamicmargincalculation.server.entities.result; + +import com.powsybl.dynawo.margincalculation.results.MarginCalculationResult; +import jakarta.persistence.*; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +/** + * @author Thang PHAM + */ +@Getter +@Setter +@Table(name = "dynamic_margin_calculation_result") +@NoArgsConstructor +@Entity +public class MarginCalculationResultEntity { + @Id + @Column(name = "result_uuid") + private UUID resultUuid; + + @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true) + @JoinColumn( + name = "dynamic_margin_calculation_result_uuid", + referencedColumnName = "result_uuid", + foreignKey = @ForeignKey(name = "load_increase_result_dynamic_margin_calculation_result_uuid_fk")) + @OrderColumn(name = "pos") + private List loadIncreaseResults = new ArrayList<>(); + + public static MarginCalculationResultEntity fromDomain(UUID resultUuid, MarginCalculationResult marginCalculationResult) { + MarginCalculationResultEntity entity = new MarginCalculationResultEntity(); + entity.setResultUuid(resultUuid); + + entity.setLoadIncreaseResults(marginCalculationResult.getLoadIncreaseResults().stream().map(LoadIncreaseResultEntity::fromDomain).toList()); + + return entity; + } + + public MarginCalculationResult toDto() { + return new MarginCalculationResult(loadIncreaseResults.stream().map(LoadIncreaseResultEntity::toDto).toList()); + } + +} diff --git a/src/main/java/org/gridsuite/dynamicmargincalculation/server/entities/result/ScenarioResultEntity.java b/src/main/java/org/gridsuite/dynamicmargincalculation/server/entities/result/ScenarioResultEntity.java new file mode 100644 index 0000000..f43ebb0 --- /dev/null +++ b/src/main/java/org/gridsuite/dynamicmargincalculation/server/entities/result/ScenarioResultEntity.java @@ -0,0 +1,75 @@ +/* + * 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.dynamicmargincalculation.server.entities.result; + +import com.powsybl.dynawo.contingency.results.FailedCriterion; +import com.powsybl.dynawo.contingency.results.ScenarioResult; +import com.powsybl.dynawo.contingency.results.Status; +import jakarta.persistence.*; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +/** + * @author Thang PHAM + */ +@NoArgsConstructor +@Getter +@Setter +@Entity +@Table(name = "scenario_result", indexes = @Index(name = "scenario_result_idx", columnList = "load_increase_result_id") +) +public class ScenarioResultEntity { + + @Id + @Column(name = "id") + private UUID id; + + @Column(name = "equipment_id") + private String equipmentId; + + @Column(name = "status") + @Enumerated(EnumType.STRING) + private Status status; + + @ElementCollection + @CollectionTable( + name = "scenario_result_failed_criteria", + joinColumns = @JoinColumn( + name = "scenario_result_id", + referencedColumnName = "id", + foreignKey = @ForeignKey(name = "scenario_result_failed_criteria_scenario_result_id_fk") + ), + indexes = { + @Index(name = "scenario_result_failed_criteria_idx", columnList = "scenario_result_id") + } + ) + @OrderColumn(name = "pos") + private List failedCriteria = new ArrayList<>(); + + public static ScenarioResultEntity fromDomain(ScenarioResult scenarioResult) { + ScenarioResultEntity embeddable = new ScenarioResultEntity(); + embeddable.setId(UUID.randomUUID()); + embeddable.setStatus(scenarioResult.status()); + + List failedCriteriaList = scenarioResult.failedCriteria(); + embeddable.setFailedCriteria(failedCriteriaList.stream().map(FailedCriterionEmbeddable::fromDomain).toList()); + + return embeddable; + } + + public ScenarioResult toDto() { + List failedCriterionList = failedCriteria.stream().map(FailedCriterionEmbeddable::toDto).toList(); + + return new ScenarioResult(equipmentId, status, failedCriterionList); + } +} diff --git a/src/main/java/org/gridsuite/dynamicmargincalculation/server/error/DynamicMarginCalculationBusinessErrorCode.java b/src/main/java/org/gridsuite/dynamicmargincalculation/server/error/DynamicMarginCalculationBusinessErrorCode.java new file mode 100644 index 0000000..afc6a82 --- /dev/null +++ b/src/main/java/org/gridsuite/dynamicmargincalculation/server/error/DynamicMarginCalculationBusinessErrorCode.java @@ -0,0 +1,28 @@ +/** + * 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.dynamicmargincalculation.server.error; + +import com.powsybl.ws.commons.error.BusinessErrorCode; + +/** + * @author Thang PHAM + */ +public enum DynamicMarginCalculationBusinessErrorCode implements BusinessErrorCode { + PROVIDER_NOT_FOUND("dynamicMarginCalculation.providerNotFound"), + CONTINGENCIES_NOT_FOUND("dynamicMarginCalculation.contingenciesNotFound"), + CONTINGENCY_LIST_EMPTY("dynamicMarginCalculation.contingencyListEmpty"); + + private final String code; + + DynamicMarginCalculationBusinessErrorCode(String code) { + this.code = code; + } + + public String value() { + return code; + } +} diff --git a/src/main/java/org/gridsuite/dynamicmargincalculation/server/error/DynamicMarginCalculationException.java b/src/main/java/org/gridsuite/dynamicmargincalculation/server/error/DynamicMarginCalculationException.java new file mode 100644 index 0000000..68b45a7 --- /dev/null +++ b/src/main/java/org/gridsuite/dynamicmargincalculation/server/error/DynamicMarginCalculationException.java @@ -0,0 +1,31 @@ +/** + * 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.dynamicmargincalculation.server.error; + +import com.powsybl.ws.commons.error.AbstractBusinessException; +import lombok.Getter; +import lombok.NonNull; + +/** + * @author Thang PHAM + */ +@Getter +public class DynamicMarginCalculationException extends AbstractBusinessException { + + private final DynamicMarginCalculationBusinessErrorCode errorCode; + + @NonNull + @Override + public DynamicMarginCalculationBusinessErrorCode getBusinessErrorCode() { + return errorCode; + } + + public DynamicMarginCalculationException(DynamicMarginCalculationBusinessErrorCode errorCode, String message) { + super(message); + this.errorCode = errorCode; + } +} diff --git a/src/main/java/org/gridsuite/dynamicmargincalculation/server/error/DynamicMarginCalculationExceptionHandler.java b/src/main/java/org/gridsuite/dynamicmargincalculation/server/error/DynamicMarginCalculationExceptionHandler.java new file mode 100644 index 0000000..74ccf36 --- /dev/null +++ b/src/main/java/org/gridsuite/dynamicmargincalculation/server/error/DynamicMarginCalculationExceptionHandler.java @@ -0,0 +1,47 @@ +/** + * 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.dynamicmargincalculation.server.error; + +import com.powsybl.ws.commons.error.AbstractBusinessExceptionHandler; +import com.powsybl.ws.commons.error.PowsyblWsProblemDetail; +import com.powsybl.ws.commons.error.ServerNameProvider; +import jakarta.servlet.http.HttpServletRequest; +import lombok.NonNull; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; + +/** + * @author Thang PHAM + */ +@ControllerAdvice +public class DynamicMarginCalculationExceptionHandler extends AbstractBusinessExceptionHandler { + + protected DynamicMarginCalculationExceptionHandler(ServerNameProvider serverNameProvider) { + super(serverNameProvider); + } + + @Override + protected @NonNull DynamicMarginCalculationBusinessErrorCode getBusinessCode(DynamicMarginCalculationException e) { + return e.getBusinessErrorCode(); + } + + protected HttpStatus mapStatus(DynamicMarginCalculationBusinessErrorCode businessErrorCode) { + return switch (businessErrorCode) { + case PROVIDER_NOT_FOUND, + CONTINGENCIES_NOT_FOUND -> HttpStatus.NOT_FOUND; + case CONTINGENCY_LIST_EMPTY -> HttpStatus.INTERNAL_SERVER_ERROR; + }; + } + + @ExceptionHandler(DynamicMarginCalculationException.class) + public ResponseEntity handleDynamicSecurityAnalysisException(DynamicMarginCalculationException exception, HttpServletRequest request) { + return super.handleDomainException(exception, request); + } + +} diff --git a/src/main/java/org/gridsuite/dynamicmargincalculation/server/repositories/DynamicMarginCalculationParametersRepository.java b/src/main/java/org/gridsuite/dynamicmargincalculation/server/repositories/DynamicMarginCalculationParametersRepository.java new file mode 100644 index 0000000..c10cd9e --- /dev/null +++ b/src/main/java/org/gridsuite/dynamicmargincalculation/server/repositories/DynamicMarginCalculationParametersRepository.java @@ -0,0 +1,21 @@ +/* + * 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.dynamicmargincalculation.server.repositories; + +import org.gridsuite.dynamicmargincalculation.server.entities.parameters.DynamicMarginCalculationParametersEntity; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.UUID; + +/** + * @author Thang PHAM + */ +@Repository +public interface DynamicMarginCalculationParametersRepository extends JpaRepository { +} diff --git a/src/main/java/org/gridsuite/dynamicmargincalculation/server/repositories/MarginCalculationResultRepository.java b/src/main/java/org/gridsuite/dynamicmargincalculation/server/repositories/MarginCalculationResultRepository.java new file mode 100644 index 0000000..f470adf --- /dev/null +++ b/src/main/java/org/gridsuite/dynamicmargincalculation/server/repositories/MarginCalculationResultRepository.java @@ -0,0 +1,26 @@ +/* + * 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.dynamicmargincalculation.server.repositories; + +import org.gridsuite.dynamicmargincalculation.server.entities.result.MarginCalculationResultEntity; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.Optional; +import java.util.UUID; + +/** + * @author Thang PHAM + */ +@Repository +public interface MarginCalculationResultRepository extends JpaRepository { + + Optional findByResultUuid(UUID resultUuid); + + void deleteByResultUuid(UUID resultUuid); +} diff --git a/src/main/java/org/gridsuite/dynamicmargincalculation/server/service/DynamicMarginCalculationObserver.java b/src/main/java/org/gridsuite/dynamicmargincalculation/server/service/DynamicMarginCalculationObserver.java new file mode 100644 index 0000000..84b0117 --- /dev/null +++ b/src/main/java/org/gridsuite/dynamicmargincalculation/server/service/DynamicMarginCalculationObserver.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.dynamicmargincalculation.server.service; + +import com.powsybl.dynawo.contingency.results.Status; +import com.powsybl.dynawo.margincalculation.results.MarginCalculationResult; +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.observation.ObservationRegistry; +import lombok.NonNull; +import org.gridsuite.computation.service.AbstractComputationObserver; +import org.gridsuite.dynamicmargincalculation.server.dto.parameters.DynamicMarginCalculationParametersInfos; +import org.springframework.stereotype.Service; + +/** + * @author Thang PHAM + */ +@Service +public class DynamicMarginCalculationObserver extends AbstractComputationObserver { + + private static final String COMPUTATION_TYPE = "dynamicmargincalculation"; + + public DynamicMarginCalculationObserver(@NonNull ObservationRegistry observationRegistry, @NonNull MeterRegistry meterRegistry) { + super(observationRegistry, meterRegistry); + } + + @Override + protected String getComputationType() { + return COMPUTATION_TYPE; + } + + @Override + protected String getResultStatus(MarginCalculationResult res) { + return res != null && res.getLoadIncreaseResults().stream() + .noneMatch(loadIncreaseResult -> loadIncreaseResult.status() == Status.EXECUTION_PROBLEM) ? "OK" : "NOK"; + } +} diff --git a/src/main/java/org/gridsuite/dynamicmargincalculation/server/service/DynamicMarginCalculationResultService.java b/src/main/java/org/gridsuite/dynamicmargincalculation/server/service/DynamicMarginCalculationResultService.java index 244bb7f..014b267 100644 --- a/src/main/java/org/gridsuite/dynamicmargincalculation/server/service/DynamicMarginCalculationResultService.java +++ b/src/main/java/org/gridsuite/dynamicmargincalculation/server/service/DynamicMarginCalculationResultService.java @@ -7,12 +7,15 @@ package org.gridsuite.dynamicmargincalculation.server.service; +import com.powsybl.dynawo.margincalculation.results.MarginCalculationResult; import jakarta.transaction.Transactional; import org.gridsuite.computation.error.ComputationException; import org.gridsuite.computation.service.AbstractComputationResultService; import org.gridsuite.dynamicmargincalculation.server.dto.DynamicMarginCalculationStatus; import org.gridsuite.dynamicmargincalculation.server.entities.DynamicMarginCalculationStatusEntity; +import org.gridsuite.dynamicmargincalculation.server.entities.result.MarginCalculationResultEntity; import org.gridsuite.dynamicmargincalculation.server.repositories.DynamicMarginCalculationStatusRepository; +import org.gridsuite.dynamicmargincalculation.server.repositories.MarginCalculationResultRepository; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; @@ -33,9 +36,12 @@ public class DynamicMarginCalculationResultService extends AbstractComputationRe public static final String MSG_RESULT_UUID_NOT_FOUND = "Result uuid not found: "; private final DynamicMarginCalculationStatusRepository statusRepository; + private final MarginCalculationResultRepository resultRepository; - public DynamicMarginCalculationResultService(DynamicMarginCalculationStatusRepository statusRepository) { + public DynamicMarginCalculationResultService(DynamicMarginCalculationStatusRepository statusRepository, + MarginCalculationResultRepository resultRepository) { this.statusRepository = statusRepository; + this.resultRepository = resultRepository; } @Override @@ -56,25 +62,31 @@ public List updateStatus(List resultUuids, DynamicMarginCalculationS return statusRepository.saveAllAndFlush(resultEntities).stream().map(DynamicMarginCalculationStatusEntity::getResultUuid).toList(); } - @Transactional - public void updateStatus(UUID resultUuid, DynamicMarginCalculationStatus status) { + private void internalUpdateStatus(UUID resultUuid, DynamicMarginCalculationStatus status) { LOGGER.debug("Update margin calculation status [resultUuid={}, status={}", resultUuid, status); DynamicMarginCalculationStatusEntity resultEntity = statusRepository.findByResultUuid(resultUuid) .orElseThrow(() -> new ComputationException(RESULT_NOT_FOUND, MSG_RESULT_UUID_NOT_FOUND + resultUuid)); resultEntity.setStatus(status); } + @Transactional + public void updateStatus(UUID resultUuid, DynamicMarginCalculationStatus status) { + internalUpdateStatus(resultUuid, status); + } + @Override @Transactional public void delete(UUID resultUuid) { Objects.requireNonNull(resultUuid); statusRepository.deleteByResultUuid(resultUuid); + resultRepository.deleteByResultUuid(resultUuid); } @Override @Transactional public void deleteAll() { statusRepository.deleteAll(); + resultRepository.deleteAll(); } @Override @@ -84,4 +96,11 @@ public DynamicMarginCalculationStatus findStatus(UUID resultUuid) { .map(DynamicMarginCalculationStatusEntity::getStatus) .orElse(null); } + + @Transactional + public void insertResult(UUID resultUuid, MarginCalculationResult result, DynamicMarginCalculationStatus status) { + internalUpdateStatus(resultUuid, status); + MarginCalculationResultEntity resultEntity = MarginCalculationResultEntity.fromDomain(resultUuid, result); + resultRepository.save(resultEntity); + } } diff --git a/src/main/java/org/gridsuite/dynamicmargincalculation/server/service/DynamicMarginCalculationService.java b/src/main/java/org/gridsuite/dynamicmargincalculation/server/service/DynamicMarginCalculationService.java new file mode 100644 index 0000000..c31ee3f --- /dev/null +++ b/src/main/java/org/gridsuite/dynamicmargincalculation/server/service/DynamicMarginCalculationService.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.dynamicmargincalculation.server.service; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.powsybl.dynawo.margincalculation.MarginCalculation; +import com.powsybl.network.store.client.NetworkStoreService; +import org.gridsuite.computation.s3.ComputationS3Service; +import org.gridsuite.computation.service.AbstractComputationService; +import org.gridsuite.computation.service.NotificationService; +import org.gridsuite.computation.service.UuidGeneratorService; +import org.gridsuite.dynamicmargincalculation.server.dto.DynamicMarginCalculationStatus; +import org.gridsuite.dynamicmargincalculation.server.service.contexts.DynamicMarginCalculationResultContext; +import org.gridsuite.dynamicmargincalculation.server.service.contexts.DynamicMarginCalculationRunContext; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.messaging.Message; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.UUID; + +/** + * @author Thang PHAM + */ +@Service +@ComponentScan(basePackageClasses = {NetworkStoreService.class, NotificationService.class}) +public class DynamicMarginCalculationService extends AbstractComputationService { + public static final String COMPUTATION_TYPE = "dynamic margin calculation"; + + public DynamicMarginCalculationService( + NotificationService notificationService, + ObjectMapper objectMapper, + UuidGeneratorService uuidGeneratorService, + DynamicMarginCalculationResultService dynamicSecurityAnalysisResultService, + ComputationS3Service computationS3Service, + @Value("${dynamic-margin-calculation.default-provider}") String defaultProvider) { + super(notificationService, dynamicSecurityAnalysisResultService, computationS3Service, objectMapper, uuidGeneratorService, defaultProvider); + } + + @Override + public UUID runAndSaveResult(DynamicMarginCalculationRunContext runContext) { + // insert a new result entity with running status + UUID resultUuid = uuidGeneratorService.generate(); + resultService.insertStatus(List.of(resultUuid), DynamicMarginCalculationStatus.RUNNING); + + // emit a message to launch the dynamic security analysis by the worker service + Message message = new DynamicMarginCalculationResultContext(resultUuid, runContext).toMessage(objectMapper); + notificationService.sendRunMessage(message); + return resultUuid; + } + + public List getProviders() { + return List.of(MarginCalculation.getRunner().getName()); + } +} diff --git a/src/main/java/org/gridsuite/dynamicmargincalculation/server/service/DynamicMarginCalculationWorkerService.java b/src/main/java/org/gridsuite/dynamicmargincalculation/server/service/DynamicMarginCalculationWorkerService.java new file mode 100644 index 0000000..2a87998 --- /dev/null +++ b/src/main/java/org/gridsuite/dynamicmargincalculation/server/service/DynamicMarginCalculationWorkerService.java @@ -0,0 +1,240 @@ +/** + * 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.dynamicmargincalculation.server.service; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.powsybl.commons.report.ReportNode; +import com.powsybl.computation.ComputationManager; +import com.powsybl.contingency.ContingenciesProvider; +import com.powsybl.contingency.Contingency; +import com.powsybl.dynamicsimulation.DynamicModelsSupplier; +import com.powsybl.dynawo.DynawoSimulationParameters; +import com.powsybl.dynawo.contingency.results.Status; +import com.powsybl.dynawo.margincalculation.MarginCalculation; +import com.powsybl.dynawo.margincalculation.MarginCalculationParameters; +import com.powsybl.dynawo.margincalculation.MarginCalculationRunParameters; +import com.powsybl.dynawo.margincalculation.loadsvariation.LoadsVariation; +import com.powsybl.dynawo.margincalculation.loadsvariation.supplier.LoadsVariationSupplier; +import com.powsybl.dynawo.margincalculation.results.MarginCalculationResult; +import com.powsybl.dynawo.suppliers.dynamicmodels.DynamicModelConfig; +import com.powsybl.dynawo.suppliers.dynamicmodels.DynawoModelsSupplier; +import com.powsybl.iidm.network.Network; +import com.powsybl.network.store.client.NetworkStoreService; +import org.apache.commons.collections4.CollectionUtils; +import org.gridsuite.computation.s3.ComputationS3Service; +import org.gridsuite.computation.service.*; +import org.gridsuite.dynamicmargincalculation.server.PropertyServerNameProvider; +import org.gridsuite.dynamicmargincalculation.server.dto.DynamicMarginCalculationStatus; +import org.gridsuite.dynamicmargincalculation.server.dto.parameters.DynamicMarginCalculationParametersInfos; +import org.gridsuite.dynamicmargincalculation.server.dto.parameters.DynamicSecurityAnalysisParametersValues; +import org.gridsuite.dynamicmargincalculation.server.dto.parameters.DynamicSimulationParametersValues; +import org.gridsuite.dynamicmargincalculation.server.dto.parameters.LoadsVariationInfos; +import org.gridsuite.dynamicmargincalculation.server.error.DynamicMarginCalculationException; +import org.gridsuite.dynamicmargincalculation.server.service.client.DynamicSecurityAnalysisClient; +import org.gridsuite.dynamicmargincalculation.server.service.client.DynamicSimulationClient; +import org.gridsuite.dynamicmargincalculation.server.service.contexts.DynamicMarginCalculationResultContext; +import org.gridsuite.dynamicmargincalculation.server.service.contexts.DynamicMarginCalculationRunContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.messaging.Message; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Consumer; + +import static org.gridsuite.dynamicmargincalculation.server.error.DynamicMarginCalculationBusinessErrorCode.CONTINGENCIES_NOT_FOUND; +import static org.gridsuite.dynamicmargincalculation.server.service.DynamicMarginCalculationService.COMPUTATION_TYPE; + +/** + * @author Thang PHAM + */ +@ComponentScan(basePackageClasses = {NetworkStoreService.class, NotificationService.class}) +@Service +public class DynamicMarginCalculationWorkerService extends AbstractWorkerService { + + private static final Logger LOGGER = LoggerFactory.getLogger(DynamicMarginCalculationWorkerService.class); + + private final DynamicSimulationClient dynamicSimulationClient; + private final DynamicSecurityAnalysisClient dynamicSecurityAnalysisClient; + private final ParametersService parametersService; + + public DynamicMarginCalculationWorkerService(NetworkStoreService networkStoreService, + NotificationService notificationService, + ReportService reportService, + ExecutionService executionService, + DynamicMarginCalculationObserver observer, + ObjectMapper objectMapper, + DynamicMarginCalculationResultService dynamicSecurityAnalysisResultService, + ComputationS3Service computationS3Service, + DynamicSimulationClient dynamicSimulationClient, + DynamicSecurityAnalysisClient dynamicSecurityAnalysisClient, + ParametersService parametersService, + PropertyServerNameProvider propertyServerNameProvider) { + super(networkStoreService, notificationService, reportService, dynamicSecurityAnalysisResultService, computationS3Service, executionService, observer, objectMapper, propertyServerNameProvider); + this.dynamicSimulationClient = Objects.requireNonNull(dynamicSimulationClient); + this.dynamicSecurityAnalysisClient = Objects.requireNonNull(dynamicSecurityAnalysisClient); + this.parametersService = Objects.requireNonNull(parametersService); + } + + /** + * Use this method to mock with DockerLocalComputationManager in case of integration tests with test container + * + * @return a computation manager + */ + public ComputationManager getComputationManager() { + return executionService.getComputationManager(); + } + + @Override + protected DynamicMarginCalculationResultContext fromMessage(Message message) { + return DynamicMarginCalculationResultContext.fromMessage(message, objectMapper); + } + + public void updateResult(UUID resultUuid, MarginCalculationResult result) { + Objects.requireNonNull(resultUuid); + DynamicMarginCalculationStatus status = result.getLoadIncreaseResults().stream() + .anyMatch(loadIncreaseResult -> loadIncreaseResult.status() == Status.EXECUTION_PROBLEM) ? + DynamicMarginCalculationStatus.FAILED : + DynamicMarginCalculationStatus.SUCCEED; + + resultService.insertResult(resultUuid, result, status); + } + + @Override + protected void saveResult(Network network, AbstractResultContext resultContext, MarginCalculationResult result) { + updateResult(resultContext.getResultUuid(), result); + } + + @Override + protected String getComputationType() { + return COMPUTATION_TYPE; + } + + // open the visibility from protected to public to mock in a test where the stop arrives early + @Override + public void preRun(DynamicMarginCalculationRunContext runContext) { + super.preRun(runContext); + + // get evaluated contingencies from the dynamic security analysis server + DynamicSecurityAnalysisParametersValues dynamicSecurityAnalysisParametersValues = + dynamicSecurityAnalysisClient.getParametersValues(runContext.getDynamicSecurityAnalysisParametersUuid(), + runContext.getNetworkUuid(), runContext.getVariantId()); + List contingencies = dynamicSecurityAnalysisParametersValues.getContingencies(); + if (CollectionUtils.isEmpty(contingencies)) { + throw new DynamicMarginCalculationException(CONTINGENCIES_NOT_FOUND, "No contingencies"); + } + + // get evaluated parameters values from the dynamic simulation server + DynamicSimulationParametersValues dynamicSimulationParametersValues = + dynamicSimulationClient.getParametersValues(runContext.getDynamicSimulationParametersJson(), + runContext.getNetworkUuid(), runContext.getVariantId()); + + // get dynamic model list from dynamic simulation server + List dynamicModel = dynamicSimulationParametersValues.getDynamicModel(); + + // get dynawo parameters from the dynamic simulation server + DynawoSimulationParameters dynawoParameters = dynamicSimulationParametersValues.getDynawoParameters(); + + DynamicMarginCalculationParametersInfos parametersInfos = runContext.getParameters(); + // create new margin calculation parameters + MarginCalculationParameters.Builder parametersBuilder = MarginCalculationParameters.builder(); + if (runContext.getDebugDir() != null) { + parametersBuilder.setDebugDir(runContext.getDebugDir().toString()); + } + parametersBuilder.setDynawoParameters(dynawoParameters); + + // set start and stop times + parametersBuilder.setStartTime(parametersInfos.getStartTime()); + parametersBuilder.setStopTime(parametersInfos.getStopTime()); + // set margin calculation start time + parametersBuilder.setMarginCalculationStartTime(parametersInfos.getMarginCalculationStartTime()); + // set load increase start and stop times + parametersBuilder.setLoadIncreaseStartTime(parametersInfos.getLoadIncreaseStartTime()); + parametersBuilder.setLoadIncreaseStopTime(parametersInfos.getLoadIncreaseStopTime()); + // set other parameters + parametersBuilder.setCalculationType(parametersInfos.getCalculationType()); + parametersBuilder.setAccuracy(parametersInfos.getAccuracy()); + parametersBuilder.setLoadModelsRule(parametersInfos.getLoadModelsRule()); + + // set contingency start time + parametersBuilder.setContingenciesStartTime(dynamicSecurityAnalysisParametersValues.getContingenciesStartTime()); + + // evaluate loads variation list + List loadsVariationInfosList = parametersInfos.getLoadsVariations(); + List loadsVariations = parametersService.getLoadsVariations(loadsVariationInfosList, runContext.getNetwork()); + + // enrich runContext + runContext.setDynamicModel(dynamicModel); + runContext.setMarginCalculationParameters(parametersBuilder.build()); + runContext.setContingencies(contingencies); + runContext.setLoadsVariations(loadsVariations); + } + + @Override + public CompletableFuture getCompletableFuture(DynamicMarginCalculationRunContext runContext, String provider, UUID resultUuid) { + + DynamicModelsSupplier dynamicModelsSupplier = new DynawoModelsSupplier(runContext.getDynamicModel()); + + List contingencies = runContext.getContingencies(); + ContingenciesProvider contingenciesProvider = network -> contingencies; + + LoadsVariationSupplier loadsVariationSupplier = (n, r) -> runContext.getLoadsVariations(); + + MarginCalculationParameters parameters = runContext.getMarginCalculationParameters(); + LOGGER.info("Run margin calculation on network {}, startTime {}, stopTime {}, marginCalculationStartTime {}", + runContext.getNetworkUuid(), parameters.getStartTime(), + parameters.getStopTime(), + parameters.getMarginCalculationStartTime()); + + MarginCalculationRunParameters runParameters = new MarginCalculationRunParameters() + .setComputationManager(getComputationManager()) + .setMarginCalculationParameters(parameters) + .setReportNode(runContext.getReportNode()); + + MarginCalculation.Runner runner = MarginCalculation.getRunner(); + + return runner.runAsync(runContext.getNetwork(), + dynamicModelsSupplier, + contingenciesProvider, + loadsVariationSupplier, + runParameters + ); + } + + @Override + protected void handleNonCancellationException(AbstractResultContext resultContext, Exception exception, AtomicReference rootReporter) { + super.handleNonCancellationException(resultContext, exception, rootReporter); + // try to get report nodes at powsybl level + List computationReportNodes = Optional.ofNullable(resultContext.getRunContext().getReportNode()).map(ReportNode::getChildren).orElse(null); + if (CollectionUtils.isNotEmpty(computationReportNodes)) { // means computing has started at powsybl level + // re-inject result table since it has been removed by handling exception in the super + resultService.insertStatus(List.of(resultContext.getResultUuid()), DynamicMarginCalculationStatus.FAILED); + // continue sending report for tracing reason + super.postRun(resultContext.getRunContext(), rootReporter, null); + } + } + + @Bean + @Override + public Consumer> consumeRun() { + return super.consumeRun(); + } + + @Bean + @Override + public Consumer> consumeCancel() { + return super.consumeCancel(); + } + +} diff --git a/src/main/java/org/gridsuite/dynamicmargincalculation/server/service/ParametersService.java b/src/main/java/org/gridsuite/dynamicmargincalculation/server/service/ParametersService.java new file mode 100644 index 0000000..313c818 --- /dev/null +++ b/src/main/java/org/gridsuite/dynamicmargincalculation/server/service/ParametersService.java @@ -0,0 +1,203 @@ +/** + * 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.dynamicmargincalculation.server.service; + +import com.powsybl.dynawo.margincalculation.MarginCalculation; +import com.powsybl.dynawo.margincalculation.MarginCalculationParameters; +import com.powsybl.dynawo.margincalculation.loadsvariation.LoadsVariation; +import com.powsybl.iidm.network.Load; +import com.powsybl.iidm.network.Network; +import jakarta.transaction.Transactional; +import org.gridsuite.computation.dto.ReportInfos; +import org.gridsuite.computation.error.ComputationException; +import org.gridsuite.dynamicmargincalculation.server.dto.parameters.DynamicMarginCalculationParametersInfos; +import org.gridsuite.dynamicmargincalculation.server.dto.parameters.LoadsVariationInfos; +import org.gridsuite.dynamicmargincalculation.server.entities.parameters.DynamicMarginCalculationParametersEntity; +import org.gridsuite.dynamicmargincalculation.server.error.DynamicMarginCalculationException; +import org.gridsuite.dynamicmargincalculation.server.repositories.DynamicMarginCalculationParametersRepository; +import org.gridsuite.dynamicmargincalculation.server.service.client.FilterClient; +import org.gridsuite.dynamicmargincalculation.server.service.contexts.DynamicMarginCalculationRunContext; +import org.gridsuite.filter.expertfilter.ExpertFilter; +import org.gridsuite.filter.expertfilter.expertrule.FilterUuidExpertRule; +import org.gridsuite.filter.utils.EquipmentType; +import org.gridsuite.filter.utils.FiltersUtils; +import org.gridsuite.filter.utils.expertfilter.FieldType; +import org.gridsuite.filter.utils.expertfilter.OperatorType; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Optional; +import java.util.UUID; +import java.util.stream.Collectors; + +import static org.gridsuite.computation.error.ComputationBusinessErrorCode.PARAMETERS_NOT_FOUND; +import static org.gridsuite.dynamicmargincalculation.server.error.DynamicMarginCalculationBusinessErrorCode.PROVIDER_NOT_FOUND; + +/** + * @author Thang PHAM + */ +@Service +public class ParametersService { + + public static final String MSG_PARAMETERS_UUID_NOT_FOUND = "Parameters uuid not found: "; + + private final String defaultProvider; + + private final DynamicMarginCalculationParametersRepository dynamicMarginCalculationParametersRepository; + + private final FilterClient filterClient; + + @Autowired + public ParametersService(@Value("${dynamic-margin-calculation.default-provider}") String defaultProvider, + DynamicMarginCalculationParametersRepository dynamicMarginCalculationParametersRepository, + FilterClient filterClient) { + this.defaultProvider = defaultProvider; + this.dynamicMarginCalculationParametersRepository = dynamicMarginCalculationParametersRepository; + this.filterClient = filterClient; + } + + public DynamicMarginCalculationRunContext createRunContext(UUID networkUuid, String variantId, String receiver, + String provider, ReportInfos reportInfos, String userId, + // should be UUID dynamicSimulationParametersUuid after moving dynamic simulation parameters to its server, + String dynamicSimulationParametersJson, + UUID dynamicSecurityAnalysisParametersUuid, + UUID dynamicMarginCalculationParametersUuid, + boolean debug) { + + // get parameters from the local database + DynamicMarginCalculationParametersInfos dynamicMarginCalculationParametersInfos = getParameters(dynamicMarginCalculationParametersUuid); + + // build run context + DynamicMarginCalculationRunContext runContext = DynamicMarginCalculationRunContext.builder() + .networkUuid(networkUuid) + .variantId(variantId) + .receiver(receiver) + .reportInfos(reportInfos) + .userId(userId) + .parameters(dynamicMarginCalculationParametersInfos) + .debug(debug) + .build(); + runContext.setDynamicSimulationParametersJson(dynamicSimulationParametersJson); + runContext.setDynamicSecurityAnalysisParametersUuid(dynamicSecurityAnalysisParametersUuid); + + // set provider for run context + String providerToUse = provider; + if (providerToUse == null) { + providerToUse = Optional.ofNullable(runContext.getParameters().getProvider()).orElse(defaultProvider); + } + + runContext.setProvider(providerToUse); + + // check provider + if (!MarginCalculation.getRunner().getName().equals(runContext.getProvider())) { + throw new DynamicMarginCalculationException(PROVIDER_NOT_FOUND, "Dynamic margin calculation provider not found: " + runContext.getProvider()); + } + + return runContext; + } + + // --- Dynamic security analysis parameters related methods --- // + + public DynamicMarginCalculationParametersInfos getParameters(UUID parametersUuid) { + DynamicMarginCalculationParametersEntity entity = dynamicMarginCalculationParametersRepository.findById(parametersUuid) + .orElseThrow(() -> new ComputationException(PARAMETERS_NOT_FOUND, MSG_PARAMETERS_UUID_NOT_FOUND + parametersUuid)); + + return DynamicMarginCalculationParametersInfos.builder() + .id(parametersUuid) + .provider(entity.getProvider()) + .startTime(entity.getStartTime()) + .stopTime(entity.getStopTime()) + .marginCalculationStartTime(entity.getMarginCalculationStartTime()) + .calculationType(entity.getCalculationType()) + .build(); + } + + public UUID createParameters(DynamicMarginCalculationParametersInfos parametersInfos) { + return dynamicMarginCalculationParametersRepository.save(new DynamicMarginCalculationParametersEntity(parametersInfos)).getId(); + } + + public UUID createDefaultParameters() { + DynamicMarginCalculationParametersInfos defaultParametersInfos = getDefaultParametersValues(defaultProvider); + return createParameters(defaultParametersInfos); + } + + public DynamicMarginCalculationParametersInfos getDefaultParametersValues(String provider) { + MarginCalculationParameters defaultConfigParameters = MarginCalculationParameters.load(); + return DynamicMarginCalculationParametersInfos.builder() + .provider(provider) + .startTime(defaultConfigParameters.getStartTime()) + .stopTime(defaultConfigParameters.getStopTime()) + .marginCalculationStartTime(defaultConfigParameters.getMarginCalculationStartTime()) + .loadIncreaseStartTime(defaultConfigParameters.getLoadIncreaseStartTime()) + .loadIncreaseStopTime(defaultConfigParameters.getLoadIncreaseStopTime()) + .calculationType(defaultConfigParameters.getCalculationType()) + .accuracy(defaultConfigParameters.getAccuracy()) + .loadModelsRule(defaultConfigParameters.getLoadModelsRule()) + .build(); + } + + @Transactional + public UUID duplicateParameters(UUID sourceParametersUuid) { + DynamicMarginCalculationParametersEntity entity = dynamicMarginCalculationParametersRepository.findById(sourceParametersUuid) + .orElseThrow(() -> new ComputationException(PARAMETERS_NOT_FOUND, MSG_PARAMETERS_UUID_NOT_FOUND + sourceParametersUuid)); + DynamicMarginCalculationParametersInfos duplicatedParametersInfos = entity.toDto(true); + duplicatedParametersInfos.setId(null); + return createParameters(duplicatedParametersInfos); + } + + public List getAllParameters() { + return dynamicMarginCalculationParametersRepository.findAll().stream() + .map(paramsEntity -> paramsEntity.toDto(false)) + .toList(); + } + + @Transactional + public void updateParameters(UUID parametersUuid, DynamicMarginCalculationParametersInfos parametersInfos) { + DynamicMarginCalculationParametersEntity entity = dynamicMarginCalculationParametersRepository.findById(parametersUuid) + .orElseThrow(() -> new ComputationException(PARAMETERS_NOT_FOUND, MSG_PARAMETERS_UUID_NOT_FOUND + parametersUuid)); + if (parametersInfos == null) { + // if the parameter is null, it means it's a reset to defaultValues, but we need to keep the provider because it's updated separately + entity.update(getDefaultParametersValues(Optional.ofNullable(entity.getProvider()).orElse(defaultProvider))); + } else { + entity.update(parametersInfos); + } + } + + public void deleteParameters(UUID parametersUuid) { + dynamicMarginCalculationParametersRepository.deleteById(parametersUuid); + } + + @Transactional + public void updateProvider(UUID parametersUuid, String provider) { + DynamicMarginCalculationParametersEntity entity = dynamicMarginCalculationParametersRepository.findById(parametersUuid) + .orElseThrow(() -> new ComputationException(PARAMETERS_NOT_FOUND, MSG_PARAMETERS_UUID_NOT_FOUND + parametersUuid)); + entity.setProvider(provider != null ? provider : defaultProvider); + } + + public List getLoadsVariations(List loadsVariationInfosList, Network network) { + List loadsVariations = loadsVariationInfosList.stream().map(loadsVariationInfos -> { + // build as a unique IS_PART_OF expert-filter then evaluate + ExpertFilter filter = ExpertFilter.builder() + .equipmentType(EquipmentType.LOAD) + .rules(FilterUuidExpertRule.builder() + .field(FieldType.ID) + .operator(OperatorType.IS_PART_OF) + .values(loadsVariationInfos.getLoadFilterUuids().stream().map(UUID::toString).collect(Collectors.toSet())) + .build()) + .build(); + + List loads = FiltersUtils.getIdentifiables(filter, network, filterClient::getFilters).stream() + .map(Load.class::cast).toList(); + return new LoadsVariation(loads, loadsVariationInfos.getVariation()); + }).toList(); + + return loadsVariations; + } + +} diff --git a/src/main/java/org/gridsuite/dynamicmargincalculation/server/service/client/AbstractRestClient.java b/src/main/java/org/gridsuite/dynamicmargincalculation/server/service/client/AbstractRestClient.java new file mode 100644 index 0000000..a440479 --- /dev/null +++ b/src/main/java/org/gridsuite/dynamicmargincalculation/server/service/client/AbstractRestClient.java @@ -0,0 +1,38 @@ +/* + * 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.dynamicmargincalculation.server.service.client; + +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.Getter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.client.RestTemplate; + +/** + * @author Thang PHAM + */ +public abstract class AbstractRestClient { + + protected final Logger logger = LoggerFactory.getLogger(this.getClass()); + + @Getter + private final RestTemplate restTemplate; + + @Getter + private final String baseUri; + + @Getter + private final ObjectMapper objectMapper; + + protected AbstractRestClient(String baseUri, RestTemplate restTemplate, ObjectMapper objectMapper) { + this.baseUri = baseUri; + this.restTemplate = restTemplate; + this.objectMapper = objectMapper; + } + +} diff --git a/src/main/java/org/gridsuite/dynamicmargincalculation/server/service/client/DynamicSecurityAnalysisClient.java b/src/main/java/org/gridsuite/dynamicmargincalculation/server/service/client/DynamicSecurityAnalysisClient.java new file mode 100644 index 0000000..a983b57 --- /dev/null +++ b/src/main/java/org/gridsuite/dynamicmargincalculation/server/service/client/DynamicSecurityAnalysisClient.java @@ -0,0 +1,55 @@ +/* + * 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.dynamicmargincalculation.server.service.client; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.gridsuite.dynamicmargincalculation.server.dto.parameters.DynamicSecurityAnalysisParametersValues; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.util.UriComponents; +import org.springframework.web.util.UriComponentsBuilder; + +import java.util.UUID; + +import static org.gridsuite.dynamicmargincalculation.server.service.client.utils.UrlUtils.buildEndPointUrl; + +/** + * @author Thang PHAM + */ +@Service +public class DynamicSecurityAnalysisClient extends AbstractRestClient { + + public static final String API_VERSION = "v1"; + public static final String DYNAMIC_SECURITY_ANALYSIS_REST_API_CALLED_SUCCESSFULLY_MESSAGE = "Dynamic security analysis REST API called successfully {}"; + + public static final String DYNAMIC_SECURITY_ANALYSIS_END_POINT_PARAMETERS = "parameters"; + + @Autowired + public DynamicSecurityAnalysisClient(@Value("${gridsuite.services.dynamic-security-analysis-server.base-uri:http://dynamic-security-analysis-server/}") String baseUri, + RestTemplate restTemplate, ObjectMapper objectMapper) { + super(baseUri, restTemplate, objectMapper); + } + + public DynamicSecurityAnalysisParametersValues getParametersValues(UUID dynamicSecurityAnalysisParametersUuid, UUID networkUuid, String variant) { + String endPointUrl = buildEndPointUrl(getBaseUri(), API_VERSION, DYNAMIC_SECURITY_ANALYSIS_END_POINT_PARAMETERS); + + UriComponents uriComponents = UriComponentsBuilder.fromUriString(endPointUrl + "/{parametersUuid}/values") + .queryParam("networkUuid", networkUuid) + .queryParam("variant", variant) + .buildAndExpand(dynamicSecurityAnalysisParametersUuid); + + // call dynamic security analysis REST API + String url = uriComponents.toUriString(); + DynamicSecurityAnalysisParametersValues result = getRestTemplate().getForObject(url, DynamicSecurityAnalysisParametersValues.class); + logger.debug(DYNAMIC_SECURITY_ANALYSIS_REST_API_CALLED_SUCCESSFULLY_MESSAGE, url); + return result; + } + +} diff --git a/src/main/java/org/gridsuite/dynamicmargincalculation/server/service/client/DynamicSimulationClient.java b/src/main/java/org/gridsuite/dynamicmargincalculation/server/service/client/DynamicSimulationClient.java new file mode 100644 index 0000000..e573fdf --- /dev/null +++ b/src/main/java/org/gridsuite/dynamicmargincalculation/server/service/client/DynamicSimulationClient.java @@ -0,0 +1,55 @@ +/* + * 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.dynamicmargincalculation.server.service.client; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.gridsuite.dynamicmargincalculation.server.dto.parameters.DynamicSimulationParametersValues; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.util.UriComponents; +import org.springframework.web.util.UriComponentsBuilder; + +import java.util.UUID; + +import static org.gridsuite.dynamicmargincalculation.server.service.client.utils.UrlUtils.buildEndPointUrl; + +/** + * @author Thang PHAM + */ +@Service +public class DynamicSimulationClient extends AbstractRestClient { + + public static final String API_VERSION = "v1"; + public static final String DYNAMIC_SIMULATION_REST_API_CALLED_SUCCESSFULLY_MESSAGE = "Dynamic simulation REST API called successfully {}"; + + public static final String DYNAMIC_SIMULATION_END_POINT_PARAMETERS = "parameters"; + + @Autowired + public DynamicSimulationClient(@Value("${gridsuite.services.dynamic-simulation-server.base-uri:http://dynamic-simulation-server/}") String baseUri, + RestTemplate restTemplate, ObjectMapper objectMapper) { + super(baseUri, restTemplate, objectMapper); + } + + public DynamicSimulationParametersValues getParametersValues(String dynamicSimulationParametersJson, UUID networkUuid, String variant) { + String endPointUrl = buildEndPointUrl(getBaseUri(), API_VERSION, DYNAMIC_SIMULATION_END_POINT_PARAMETERS); + + // TODO should use GET instead of POST after moving dynamic simulation parameters to its server + UriComponents uriComponents = UriComponentsBuilder.fromUriString(endPointUrl + "/values") + .queryParam("networkUuid", networkUuid) + .queryParam("variant", variant) + .build(); + + // call dynamic simulation REST API + String url = uriComponents.toUriString(); + DynamicSimulationParametersValues result = getRestTemplate().postForObject(url, dynamicSimulationParametersJson, DynamicSimulationParametersValues.class); + logger.debug(DYNAMIC_SIMULATION_REST_API_CALLED_SUCCESSFULLY_MESSAGE, url); + return result; + } +} diff --git a/src/main/java/org/gridsuite/dynamicmargincalculation/server/service/client/FilterClient.java b/src/main/java/org/gridsuite/dynamicmargincalculation/server/service/client/FilterClient.java new file mode 100644 index 0000000..9c0684f --- /dev/null +++ b/src/main/java/org/gridsuite/dynamicmargincalculation/server/service/client/FilterClient.java @@ -0,0 +1,51 @@ +package org.gridsuite.dynamicmargincalculation.server.service.client; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.commons.collections4.CollectionUtils; +import org.gridsuite.filter.AbstractFilter; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpMethod; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.util.UriComponentsBuilder; + +import java.util.Collections; +import java.util.List; +import java.util.UUID; + +import static org.gridsuite.dynamicmargincalculation.server.service.client.utils.UrlUtils.URL_DELIMITER; +import static org.gridsuite.dynamicmargincalculation.server.service.client.utils.UrlUtils.buildEndPointUrl; + +@Service +public class FilterClient extends AbstractRestClient { + + public static final String API_VERSION = "v1"; + public static final String FILTERS_BASE_ENDPOINT = "filters"; + public static final String FILTERS_GET_ENDPOINT = FILTERS_BASE_ENDPOINT + URL_DELIMITER + "metadata"; + + protected FilterClient( + @Value("${gridsuite.services.filter-server.base-uri:http://filter-server/}") String baseUri, + RestTemplate restTemplate, ObjectMapper objectMapper) { + super(baseUri, restTemplate, objectMapper); + } + + public List getFilters(List filterUuids) { + if (CollectionUtils.isEmpty(filterUuids)) { + return Collections.emptyList(); + } + + String endPointUrl = buildEndPointUrl(getBaseUri(), API_VERSION, FILTERS_GET_ENDPOINT); + + UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder.fromUriString(endPointUrl); + uriComponentsBuilder.queryParam("ids", filterUuids); + + // call filter server Rest API + return getRestTemplate().exchange( + uriComponentsBuilder.build().toUriString(), + HttpMethod.GET, + null, + new ParameterizedTypeReference>() { + }).getBody(); + } +} diff --git a/src/main/java/org/gridsuite/dynamicmargincalculation/server/service/client/utils/UrlUtils.java b/src/main/java/org/gridsuite/dynamicmargincalculation/server/service/client/utils/UrlUtils.java new file mode 100644 index 0000000..4dd6392 --- /dev/null +++ b/src/main/java/org/gridsuite/dynamicmargincalculation/server/service/client/utils/UrlUtils.java @@ -0,0 +1,51 @@ +/* + * 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.dynamicmargincalculation.server.service.client.utils; + +import com.powsybl.commons.exceptions.UncheckedUriSyntaxException; +import org.apache.logging.log4j.util.Strings; + +import java.net.URI; +import java.net.URISyntaxException; + +/** + * @author Thang PHAM + */ +public final class UrlUtils { + + public static final String URL_DELIMITER = "/"; + + private UrlUtils() { + throw new AssertionError("Utility class should not be instantiated"); + } + + /** + * Build endpoint url + * @param baseUri base uri with "http://domain:port" or empty + * @param apiVersion for example "v1" or empty + * @param endPoint root endpoint + * @return a normalized completed url to endpoint + */ + public static String buildEndPointUrl(String baseUri, String apiVersion, String endPoint) { + try { + var sb = new StringBuilder(baseUri); + if (Strings.isNotBlank(apiVersion)) { + sb.append(URL_DELIMITER).append(apiVersion); + } + if (Strings.isNotBlank(endPoint)) { + sb.append(URL_DELIMITER).append(endPoint); + } + var url = sb.toString(); + + // normalize before return + return new URI(url).normalize().toString(); + } catch (URISyntaxException e) { + throw new UncheckedUriSyntaxException(e); + } + } +} diff --git a/src/main/java/org/gridsuite/dynamicmargincalculation/server/service/contexts/DynamicMarginCalculationResultContext.java b/src/main/java/org/gridsuite/dynamicmargincalculation/server/service/contexts/DynamicMarginCalculationResultContext.java new file mode 100644 index 0000000..fdf53c2 --- /dev/null +++ b/src/main/java/org/gridsuite/dynamicmargincalculation/server/service/contexts/DynamicMarginCalculationResultContext.java @@ -0,0 +1,91 @@ +/* + * 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.dynamicmargincalculation.server.service.contexts; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.gridsuite.computation.dto.ReportInfos; +import org.gridsuite.computation.service.AbstractResultContext; +import org.gridsuite.dynamicmargincalculation.server.dto.parameters.DynamicMarginCalculationParametersInfos; +import org.gridsuite.dynamicmargincalculation.server.utils.GZipUtils; +import org.springframework.messaging.Message; +import org.springframework.messaging.MessageHeaders; + +import java.io.UncheckedIOException; +import java.util.Map; +import java.util.Objects; +import java.util.UUID; + +import static org.gridsuite.computation.service.NotificationService.*; +import static org.gridsuite.computation.utils.MessageUtils.getNonNullHeader; + +/** + * @author Thang PHAM + */ +public class DynamicMarginCalculationResultContext extends AbstractResultContext { + + private static final String HEADER_DYNAMIC_SIMULATION_PARAMETERS_JSON_UUID = "dynamicSimulationParametersJson"; + private static final String HEADER_DYNAMIC_SECURITY_ANALYSIS_PARAMETERS_UUID = "dynamicSecurityAnalysisParametersUuid"; + + public DynamicMarginCalculationResultContext(UUID resultUuid, DynamicMarginCalculationRunContext runContext) { + super(resultUuid, runContext); + } + + public static DynamicMarginCalculationResultContext fromMessage(Message message, ObjectMapper objectMapper) { + Objects.requireNonNull(message); + + // decode the parameters values + DynamicMarginCalculationParametersInfos parametersInfos; + try { + parametersInfos = objectMapper.readValue(message.getPayload(), DynamicMarginCalculationParametersInfos.class); + } catch (JsonProcessingException e) { + throw new UncheckedIOException(e); + } + + MessageHeaders headers = message.getHeaders(); + UUID resultUuid = UUID.fromString(getNonNullHeader(headers, RESULT_UUID_HEADER)); + String provider = getNonNullHeader(headers, HEADER_PROVIDER); + String receiver = (String) headers.get(HEADER_RECEIVER); + UUID networkUuid = UUID.fromString(getNonNullHeader(headers, NETWORK_UUID_HEADER)); + String variantId = (String) headers.get(VARIANT_ID_HEADER); + String reportUuidStr = (String) headers.get(REPORT_UUID_HEADER); + UUID reportUuid = reportUuidStr != null ? UUID.fromString(reportUuidStr) : null; + String reporterId = (String) headers.get(REPORTER_ID_HEADER); + String reportType = (String) headers.get(REPORT_TYPE_HEADER); + String userId = (String) headers.get(HEADER_USER_ID); + Boolean debug = (Boolean) headers.get(HEADER_DEBUG); + + DynamicMarginCalculationRunContext runContext = DynamicMarginCalculationRunContext.builder() + .networkUuid(networkUuid) + .variantId(variantId) + .receiver(receiver) + .provider(provider) + .reportInfos(ReportInfos.builder().reportUuid(reportUuid).reporterId(reporterId).computationType(reportType).build()) + .userId(userId) + .parameters(parametersInfos) + .debug(debug) + .build(); + + // specific headers + UUID dynamicSecurityAnalysisParametersUuid = UUID.fromString(getNonNullHeader(headers, HEADER_DYNAMIC_SECURITY_ANALYSIS_PARAMETERS_UUID)); + runContext.setDynamicSecurityAnalysisParametersUuid(dynamicSecurityAnalysisParametersUuid); + // TODO : using directly uuid after moving dynamic simulation parameters to its server + String compressedJson = getNonNullHeader(headers, HEADER_DYNAMIC_SIMULATION_PARAMETERS_JSON_UUID); + runContext.setDynamicSimulationParametersJson(GZipUtils.decompress(compressedJson)); + + return new DynamicMarginCalculationResultContext(resultUuid, runContext); + } + + @Override + public Map getSpecificMsgHeaders(ObjectMapper objectMapper) { + // TODO : using directly uuid after moving dynamic simulation parameters to its server + String compressedJson = GZipUtils.compress(getRunContext().getDynamicSimulationParametersJson()); + return Map.of(HEADER_DYNAMIC_SECURITY_ANALYSIS_PARAMETERS_UUID, getRunContext().getDynamicSecurityAnalysisParametersUuid().toString(), + HEADER_DYNAMIC_SIMULATION_PARAMETERS_JSON_UUID, compressedJson); + } +} diff --git a/src/main/java/org/gridsuite/dynamicmargincalculation/server/service/contexts/DynamicMarginCalculationRunContext.java b/src/main/java/org/gridsuite/dynamicmargincalculation/server/service/contexts/DynamicMarginCalculationRunContext.java new file mode 100644 index 0000000..5c74bc5 --- /dev/null +++ b/src/main/java/org/gridsuite/dynamicmargincalculation/server/service/contexts/DynamicMarginCalculationRunContext.java @@ -0,0 +1,46 @@ +/* + * 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.dynamicmargincalculation.server.service.contexts; + +import com.powsybl.contingency.Contingency; +import com.powsybl.dynawo.margincalculation.MarginCalculationParameters; +import com.powsybl.dynawo.margincalculation.loadsvariation.LoadsVariation; +import com.powsybl.dynawo.suppliers.dynamicmodels.DynamicModelConfig; +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; +import org.gridsuite.computation.dto.ReportInfos; +import org.gridsuite.computation.service.AbstractComputationRunContext; +import org.gridsuite.dynamicmargincalculation.server.dto.parameters.DynamicMarginCalculationParametersInfos; + +import java.util.List; +import java.util.UUID; + +/** + * @author Thang PHAM + */ +@Getter +@Setter +public class DynamicMarginCalculationRunContext extends AbstractComputationRunContext { + + private String dynamicSimulationParametersJson; + private UUID dynamicSecurityAnalysisParametersUuid; + + // --- Fields which are enriched in worker service --- // + + private List dynamicModel; + private MarginCalculationParameters marginCalculationParameters; + private List contingencies; + private List loadsVariations; + + @Builder + public DynamicMarginCalculationRunContext(UUID networkUuid, String variantId, String receiver, String provider, + ReportInfos reportInfos, String userId, DynamicMarginCalculationParametersInfos parameters, Boolean debug) { + super(networkUuid, variantId, receiver, reportInfos, userId, provider, parameters, debug); + } +} + diff --git a/src/main/java/org/gridsuite/dynamicmargincalculation/server/utils/GZipUtils.java b/src/main/java/org/gridsuite/dynamicmargincalculation/server/utils/GZipUtils.java new file mode 100644 index 0000000..e8e9d3d --- /dev/null +++ b/src/main/java/org/gridsuite/dynamicmargincalculation/server/utils/GZipUtils.java @@ -0,0 +1,62 @@ +/* + * 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.dynamicmargincalculation.server.utils; + +import org.apache.logging.log4j.util.Strings; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import java.util.zip.GZIPInputStream; +import java.util.zip.GZIPOutputStream; + +/** + * @author Thang PHAM + */ +public final class GZipUtils { + private GZipUtils() { + throw new AssertionError("Utility class should not be instantiated"); + } + + /** + * Compresses a string using GZIP and encodes it to Base64. + */ + public static String compress(String str) { + if (Strings.isEmpty(str)) { + return str; + } + try (ByteArrayOutputStream out = new ByteArrayOutputStream(); + GZIPOutputStream gzip = new GZIPOutputStream(out)) { + gzip.write(str.getBytes(StandardCharsets.UTF_8)); + return Base64.getEncoder().encodeToString(out.toByteArray()); + } catch (IOException e) { + throw new UncheckedIOException("Failed to compress string", e); + } + } + + /** + * Decodes a Base64 string and decompresses it using GZIP. + */ + public static String decompress(String compressedStr) { + if (Strings.isEmpty(compressedStr)) { + return compressedStr; + } + try { + byte[] compressed = Base64.getDecoder().decode(compressedStr); + try (ByteArrayInputStream in = new ByteArrayInputStream(compressed); + GZIPInputStream gzip = new GZIPInputStream(in)) { + return new String(gzip.readAllBytes(), StandardCharsets.UTF_8); + } + } catch (IOException e) { + throw new UncheckedIOException("Failed to decompress string", e); + } + } +} diff --git a/src/main/resources/application-local.yml b/src/main/resources/application-local.yml index 13f935c..eb8d202 100644 --- a/src/main/resources/application-local.yml +++ b/src/main/resources/application-local.yml @@ -16,7 +16,11 @@ powsybl: gridsuite: services: - dynamic-mapping-server: - base-uri: http://localhost:5036 + filter-server: + base-uri: http://localhost:5027 report-server: base-uri: http://localhost:5028 + dynamic-simulation-server: + base-uri: http://localhost:5032 + dynamic-security-analysis-server: + base-uri: http://localhost:5040 diff --git a/src/main/resources/config/application.yaml b/src/main/resources/config/application.yaml index 43bcde6..f71f747 100644 --- a/src/main/resources/config/application.yaml +++ b/src/main/resources/config/application.yaml @@ -15,6 +15,8 @@ spring: max-attempts: 1 publishRun-out-0: destination: ${powsybl-ws.rabbitmq.destination.prefix:}dmc.run + publishDebug-out-0: + destination: ${powsybl-ws.rabbitmq.destination.prefix:}dmc.debug publishResult-out-0: destination: ${powsybl-ws.rabbitmq.destination.prefix:}dmc.result consumeCancel-in-0: @@ -25,7 +27,7 @@ spring: destination: ${powsybl-ws.rabbitmq.destination.prefix:}dmc.stopped publishCancelFailed-out-0: destination: ${powsybl-ws.rabbitmq.destination.prefix:}dmc.cancelfailed - output-bindings: publishRun-out-0;publishResult-out-0;publishCancel-out-0;publishStopped-out-0;publishCancelFailed-out-0 + output-bindings: publishRun-out-0;publishDebug-out-0;publishResult-out-0;publishCancel-out-0;publishStopped-out-0;publishCancelFailed-out-0 rabbit: bindings: consumeRun-in-0: @@ -38,6 +40,12 @@ spring: enabled: true delivery-limit: 2 +computation: + s3: + enabled: true + +debug-subpath: debug + powsybl-ws: database: name: dynamicmargincalculation diff --git a/src/main/resources/db/changelog/changesets/changelog_20251223T095053Z.xml b/src/main/resources/db/changelog/changesets/changelog_20251223T095053Z.xml new file mode 100644 index 0000000..0b10698 --- /dev/null +++ b/src/main/resources/db/changelog/changesets/changelog_20251223T095053Z.xml @@ -0,0 +1,130 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/db/changelog/db.changelog-master.yaml b/src/main/resources/db/changelog/db.changelog-master.yaml index 462efc8..2d37ab3 100644 --- a/src/main/resources/db/changelog/db.changelog-master.yaml +++ b/src/main/resources/db/changelog/db.changelog-master.yaml @@ -3,3 +3,7 @@ databaseChangeLog: - include: file: changesets/changelog_20250922T104750Z.xml relativeToChangelogFile: true + + - include: + file: changesets/changelog_20251223T095053Z.xml + relativeToChangelogFile: true From 22f43127004dd3a222ff6b9ed1132bf6cc5e16c7 Mon Sep 17 00:00:00 2001 From: Thang PHAM Date: Thu, 29 Jan 2026 20:11:30 +0100 Subject: [PATCH 02/17] debug mode, correct params Signed-off-by: Thang PHAM --- pom.xml | 11 ++++- .../server/config/RestTemplateConfig.java | 5 ++- .../DynamicSimulationParametersValues.java | 3 ++ .../DynamicMarginCalculationStatusEntity.java | 5 +++ ...amicMarginCalculationParametersEntity.java | 5 ++- ...amicMarginCalculationStatusRepository.java | 8 ++++ ...DynamicMarginCalculationResultService.java | 21 +++++++++- .../server/service/ParametersService.java | 16 ++++---- .../client/DynamicSecurityAnalysisClient.java | 3 +- .../client/DynamicSimulationClient.java | 15 +++++-- ...DynamicMarginCalculationResultContext.java | 2 +- .../server/utils/GZipUtils.java | 1 + src/main/resources/application-local.yml | 3 ++ .../changesets/changelog_20251223T095053Z.xml | 41 +++++++++++-------- 14 files changed, 104 insertions(+), 35 deletions(-) diff --git a/pom.xml b/pom.xml index e3a473a..7a392f4 100644 --- a/pom.xml +++ b/pom.xml @@ -143,7 +143,16 @@ com.powsybl powsybl-dynawo-margin-calculation - + + com.powsybl + powsybl-dynawo-contingencies + 3.1.0 + + + com.powsybl + powsybl-dynamic-security-analysis + 7.1.1 + org.gridsuite gridsuite-computation diff --git a/src/main/java/org/gridsuite/dynamicmargincalculation/server/config/RestTemplateConfig.java b/src/main/java/org/gridsuite/dynamicmargincalculation/server/config/RestTemplateConfig.java index 99d6076..5198ca4 100644 --- a/src/main/java/org/gridsuite/dynamicmargincalculation/server/config/RestTemplateConfig.java +++ b/src/main/java/org/gridsuite/dynamicmargincalculation/server/config/RestTemplateConfig.java @@ -10,7 +10,9 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import com.powsybl.commons.report.ReportNodeJsonModule; +import com.powsybl.dynamicsimulation.json.DynamicSimulationParametersJsonModule; import com.powsybl.dynawo.margincalculation.json.MarginCalculationParametersJsonModule; +import com.powsybl.security.dynamic.json.DynamicSecurityAnalysisJsonModule; import org.gridsuite.computation.ComputationConfig; import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer; import org.springframework.boot.web.client.RestTemplateBuilder; @@ -40,6 +42,7 @@ public Jackson2ObjectMapperBuilderCustomizer appJsonCustomizer() { return builder -> builder .featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) .featuresToEnable(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS) - .modulesToInstall(new MarginCalculationParametersJsonModule(), new ReportNodeJsonModule()); + .modulesToInstall(new MarginCalculationParametersJsonModule(), new DynamicSecurityAnalysisJsonModule(), + new DynamicSimulationParametersJsonModule(), new ReportNodeJsonModule()); } } diff --git a/src/main/java/org/gridsuite/dynamicmargincalculation/server/dto/parameters/DynamicSimulationParametersValues.java b/src/main/java/org/gridsuite/dynamicmargincalculation/server/dto/parameters/DynamicSimulationParametersValues.java index 5c627a3..b33c0ca 100644 --- a/src/main/java/org/gridsuite/dynamicmargincalculation/server/dto/parameters/DynamicSimulationParametersValues.java +++ b/src/main/java/org/gridsuite/dynamicmargincalculation/server/dto/parameters/DynamicSimulationParametersValues.java @@ -8,8 +8,10 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.powsybl.dynawo.DynawoSimulationParameters; import com.powsybl.dynawo.suppliers.dynamicmodels.DynamicModelConfig; +import com.powsybl.dynawo.suppliers.dynamicmodels.DynamicModelConfigsJsonDeserializer; import lombok.*; import java.util.List; @@ -24,6 +26,7 @@ @Builder @JsonIgnoreProperties(ignoreUnknown = true) public class DynamicSimulationParametersValues { + @JsonDeserialize(using = DynamicModelConfigsJsonDeserializer.class) @JsonInclude(JsonInclude.Include.NON_EMPTY) List dynamicModel; diff --git a/src/main/java/org/gridsuite/dynamicmargincalculation/server/entities/DynamicMarginCalculationStatusEntity.java b/src/main/java/org/gridsuite/dynamicmargincalculation/server/entities/DynamicMarginCalculationStatusEntity.java index a495883..7e4fc26 100644 --- a/src/main/java/org/gridsuite/dynamicmargincalculation/server/entities/DynamicMarginCalculationStatusEntity.java +++ b/src/main/java/org/gridsuite/dynamicmargincalculation/server/entities/DynamicMarginCalculationStatusEntity.java @@ -8,6 +8,7 @@ package org.gridsuite.dynamicmargincalculation.server.entities; import jakarta.persistence.*; +import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @@ -22,6 +23,7 @@ @Setter @Table(name = "dynamic_margin_calculation_status") @NoArgsConstructor +@AllArgsConstructor @Entity public class DynamicMarginCalculationStatusEntity { @@ -38,4 +40,7 @@ public DynamicMarginCalculationStatusEntity(UUID resultUuid, DynamicMarginCalcul @Enumerated(EnumType.STRING) private DynamicMarginCalculationStatus status; + @Column(name = "debugFileLocation") + private String debugFileLocation; + } diff --git a/src/main/java/org/gridsuite/dynamicmargincalculation/server/entities/parameters/DynamicMarginCalculationParametersEntity.java b/src/main/java/org/gridsuite/dynamicmargincalculation/server/entities/parameters/DynamicMarginCalculationParametersEntity.java index 1d9ca3f..5a0b3e1 100644 --- a/src/main/java/org/gridsuite/dynamicmargincalculation/server/entities/parameters/DynamicMarginCalculationParametersEntity.java +++ b/src/main/java/org/gridsuite/dynamicmargincalculation/server/entities/parameters/DynamicMarginCalculationParametersEntity.java @@ -13,6 +13,7 @@ import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; +import org.apache.commons.collections4.CollectionUtils; import org.gridsuite.dynamicmargincalculation.server.dto.parameters.DynamicMarginCalculationParametersInfos; import org.gridsuite.dynamicmargincalculation.server.dto.parameters.LoadsVariationInfos; @@ -34,7 +35,6 @@ @Table(name = "dynamic_margin_calculation_parameters") public class DynamicMarginCalculationParametersEntity { @Id - @GeneratedValue(strategy = GenerationType.AUTO) @Column(name = "id") private UUID id; @@ -95,6 +95,9 @@ private void assignAttributes(DynamicMarginCalculationParametersInfos parameters } private void assignLoadsVariations(List loadsVariationInfosList) { + if (CollectionUtils.isEmpty(loadsVariationInfosList)) { + return; + } // build existing loads variation Map Map loadsVariationsByIdMap = loadsVariations.stream().collect(Collectors.toMap(LoadsVariationEntity::getId, loadVariationEntity -> loadVariationEntity)); diff --git a/src/main/java/org/gridsuite/dynamicmargincalculation/server/repositories/DynamicMarginCalculationStatusRepository.java b/src/main/java/org/gridsuite/dynamicmargincalculation/server/repositories/DynamicMarginCalculationStatusRepository.java index 4db5b2c..da638bb 100644 --- a/src/main/java/org/gridsuite/dynamicmargincalculation/server/repositories/DynamicMarginCalculationStatusRepository.java +++ b/src/main/java/org/gridsuite/dynamicmargincalculation/server/repositories/DynamicMarginCalculationStatusRepository.java @@ -9,6 +9,9 @@ import org.gridsuite.dynamicmargincalculation.server.entities.DynamicMarginCalculationStatusEntity; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; import java.util.Optional; @@ -23,4 +26,9 @@ public interface DynamicMarginCalculationStatusRepository extends JpaRepository< Optional findByResultUuid(UUID resultUuid); void deleteByResultUuid(UUID resultUuid); + + @Modifying + @Query("UPDATE DynamicMarginCalculationStatusEntity r SET r.debugFileLocation = :debugFileLocation WHERE r.resultUuid = :resultUuid") + int updateDebugFileLocation(@Param("resultUuid") UUID resultUuid, @Param("debugFileLocation") String debugFileLocation); + } diff --git a/src/main/java/org/gridsuite/dynamicmargincalculation/server/service/DynamicMarginCalculationResultService.java b/src/main/java/org/gridsuite/dynamicmargincalculation/server/service/DynamicMarginCalculationResultService.java index 014b267..6f313de 100644 --- a/src/main/java/org/gridsuite/dynamicmargincalculation/server/service/DynamicMarginCalculationResultService.java +++ b/src/main/java/org/gridsuite/dynamicmargincalculation/server/service/DynamicMarginCalculationResultService.java @@ -8,7 +8,6 @@ package org.gridsuite.dynamicmargincalculation.server.service; import com.powsybl.dynawo.margincalculation.results.MarginCalculationResult; -import jakarta.transaction.Transactional; import org.gridsuite.computation.error.ComputationException; import org.gridsuite.computation.service.AbstractComputationResultService; import org.gridsuite.dynamicmargincalculation.server.dto.DynamicMarginCalculationStatus; @@ -19,6 +18,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import java.util.List; import java.util.Objects; @@ -74,6 +74,15 @@ public void updateStatus(UUID resultUuid, DynamicMarginCalculationStatus status) internalUpdateStatus(resultUuid, status); } + @Override + @Transactional + public void saveDebugFileLocation(UUID resultUuid, String debugFilePath) { + statusRepository.findById(resultUuid).ifPresentOrElse( + (var resultEntity) -> statusRepository.updateDebugFileLocation(resultUuid, debugFilePath), + () -> statusRepository.save(new DynamicMarginCalculationStatusEntity(resultUuid, DynamicMarginCalculationStatus.NOT_DONE, debugFilePath)) + ); + } + @Override @Transactional public void delete(UUID resultUuid) { @@ -103,4 +112,14 @@ public void insertResult(UUID resultUuid, MarginCalculationResult result, Dynami MarginCalculationResultEntity resultEntity = MarginCalculationResultEntity.fromDomain(resultUuid, result); resultRepository.save(resultEntity); } + + @Override + @Transactional(readOnly = true) + public String findDebugFileLocation(UUID resultUuid) { + Objects.requireNonNull(resultUuid); + return statusRepository.findById(resultUuid) + .map(DynamicMarginCalculationStatusEntity::getDebugFileLocation) + .orElse(null); + } + } diff --git a/src/main/java/org/gridsuite/dynamicmargincalculation/server/service/ParametersService.java b/src/main/java/org/gridsuite/dynamicmargincalculation/server/service/ParametersService.java index 313c818..c432188 100644 --- a/src/main/java/org/gridsuite/dynamicmargincalculation/server/service/ParametersService.java +++ b/src/main/java/org/gridsuite/dynamicmargincalculation/server/service/ParametersService.java @@ -12,6 +12,7 @@ import com.powsybl.iidm.network.Load; import com.powsybl.iidm.network.Network; import jakarta.transaction.Transactional; +import org.apache.commons.collections4.CollectionUtils; import org.gridsuite.computation.dto.ReportInfos; import org.gridsuite.computation.error.ComputationException; import org.gridsuite.dynamicmargincalculation.server.dto.parameters.DynamicMarginCalculationParametersInfos; @@ -31,6 +32,7 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; +import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.UUID; @@ -108,20 +110,14 @@ public DynamicMarginCalculationParametersInfos getParameters(UUID parametersUuid DynamicMarginCalculationParametersEntity entity = dynamicMarginCalculationParametersRepository.findById(parametersUuid) .orElseThrow(() -> new ComputationException(PARAMETERS_NOT_FOUND, MSG_PARAMETERS_UUID_NOT_FOUND + parametersUuid)); - return DynamicMarginCalculationParametersInfos.builder() - .id(parametersUuid) - .provider(entity.getProvider()) - .startTime(entity.getStartTime()) - .stopTime(entity.getStopTime()) - .marginCalculationStartTime(entity.getMarginCalculationStartTime()) - .calculationType(entity.getCalculationType()) - .build(); + return entity.toDto(false); } public UUID createParameters(DynamicMarginCalculationParametersInfos parametersInfos) { return dynamicMarginCalculationParametersRepository.save(new DynamicMarginCalculationParametersEntity(parametersInfos)).getId(); } + @Transactional public UUID createDefaultParameters() { DynamicMarginCalculationParametersInfos defaultParametersInfos = getDefaultParametersValues(defaultProvider); return createParameters(defaultParametersInfos); @@ -181,6 +177,10 @@ public void updateProvider(UUID parametersUuid, String provider) { } public List getLoadsVariations(List loadsVariationInfosList, Network network) { + if (CollectionUtils.isEmpty(loadsVariationInfosList)) { + return Collections.emptyList(); + } + List loadsVariations = loadsVariationInfosList.stream().map(loadsVariationInfos -> { // build as a unique IS_PART_OF expert-filter then evaluate ExpertFilter filter = ExpertFilter.builder() diff --git a/src/main/java/org/gridsuite/dynamicmargincalculation/server/service/client/DynamicSecurityAnalysisClient.java b/src/main/java/org/gridsuite/dynamicmargincalculation/server/service/client/DynamicSecurityAnalysisClient.java index a983b57..e30b3e0 100644 --- a/src/main/java/org/gridsuite/dynamicmargincalculation/server/service/client/DynamicSecurityAnalysisClient.java +++ b/src/main/java/org/gridsuite/dynamicmargincalculation/server/service/client/DynamicSecurityAnalysisClient.java @@ -18,6 +18,7 @@ import java.util.UUID; +import static org.gridsuite.computation.service.AbstractResultContext.VARIANT_ID_HEADER; import static org.gridsuite.dynamicmargincalculation.server.service.client.utils.UrlUtils.buildEndPointUrl; /** @@ -42,7 +43,7 @@ public DynamicSecurityAnalysisParametersValues getParametersValues(UUID dynamicS UriComponents uriComponents = UriComponentsBuilder.fromUriString(endPointUrl + "/{parametersUuid}/values") .queryParam("networkUuid", networkUuid) - .queryParam("variant", variant) + .queryParam(VARIANT_ID_HEADER, variant) .buildAndExpand(dynamicSecurityAnalysisParametersUuid); // call dynamic security analysis REST API diff --git a/src/main/java/org/gridsuite/dynamicmargincalculation/server/service/client/DynamicSimulationClient.java b/src/main/java/org/gridsuite/dynamicmargincalculation/server/service/client/DynamicSimulationClient.java index e573fdf..80bfe31 100644 --- a/src/main/java/org/gridsuite/dynamicmargincalculation/server/service/client/DynamicSimulationClient.java +++ b/src/main/java/org/gridsuite/dynamicmargincalculation/server/service/client/DynamicSimulationClient.java @@ -11,6 +11,7 @@ import org.gridsuite.dynamicmargincalculation.server.dto.parameters.DynamicSimulationParametersValues; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.*; import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate; import org.springframework.web.util.UriComponents; @@ -18,6 +19,7 @@ import java.util.UUID; +import static org.gridsuite.computation.service.AbstractResultContext.VARIANT_ID_HEADER; import static org.gridsuite.dynamicmargincalculation.server.service.client.utils.UrlUtils.buildEndPointUrl; /** @@ -43,13 +45,20 @@ public DynamicSimulationParametersValues getParametersValues(String dynamicSimul // TODO should use GET instead of POST after moving dynamic simulation parameters to its server UriComponents uriComponents = UriComponentsBuilder.fromUriString(endPointUrl + "/values") .queryParam("networkUuid", networkUuid) - .queryParam("variant", variant) + .queryParam(VARIANT_ID_HEADER, variant) .build(); // call dynamic simulation REST API String url = uriComponents.toUriString(); - DynamicSimulationParametersValues result = getRestTemplate().postForObject(url, dynamicSimulationParametersJson, DynamicSimulationParametersValues.class); + + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + + HttpEntity requestEntity = new HttpEntity<>(dynamicSimulationParametersJson, headers); + + ResponseEntity result = getRestTemplate().exchange(url, HttpMethod.POST, requestEntity, DynamicSimulationParametersValues.class); + logger.debug(DYNAMIC_SIMULATION_REST_API_CALLED_SUCCESSFULLY_MESSAGE, url); - return result; + return result.getBody(); } } diff --git a/src/main/java/org/gridsuite/dynamicmargincalculation/server/service/contexts/DynamicMarginCalculationResultContext.java b/src/main/java/org/gridsuite/dynamicmargincalculation/server/service/contexts/DynamicMarginCalculationResultContext.java index fdf53c2..b145b70 100644 --- a/src/main/java/org/gridsuite/dynamicmargincalculation/server/service/contexts/DynamicMarginCalculationResultContext.java +++ b/src/main/java/org/gridsuite/dynamicmargincalculation/server/service/contexts/DynamicMarginCalculationResultContext.java @@ -75,7 +75,7 @@ public static DynamicMarginCalculationResultContext fromMessage(Message UUID dynamicSecurityAnalysisParametersUuid = UUID.fromString(getNonNullHeader(headers, HEADER_DYNAMIC_SECURITY_ANALYSIS_PARAMETERS_UUID)); runContext.setDynamicSecurityAnalysisParametersUuid(dynamicSecurityAnalysisParametersUuid); // TODO : using directly uuid after moving dynamic simulation parameters to its server - String compressedJson = getNonNullHeader(headers, HEADER_DYNAMIC_SIMULATION_PARAMETERS_JSON_UUID); + String compressedJson = headers.get(HEADER_DYNAMIC_SIMULATION_PARAMETERS_JSON_UUID).toString(); runContext.setDynamicSimulationParametersJson(GZipUtils.decompress(compressedJson)); return new DynamicMarginCalculationResultContext(resultUuid, runContext); diff --git a/src/main/java/org/gridsuite/dynamicmargincalculation/server/utils/GZipUtils.java b/src/main/java/org/gridsuite/dynamicmargincalculation/server/utils/GZipUtils.java index e8e9d3d..fef3cc2 100644 --- a/src/main/java/org/gridsuite/dynamicmargincalculation/server/utils/GZipUtils.java +++ b/src/main/java/org/gridsuite/dynamicmargincalculation/server/utils/GZipUtils.java @@ -36,6 +36,7 @@ public static String compress(String str) { try (ByteArrayOutputStream out = new ByteArrayOutputStream(); GZIPOutputStream gzip = new GZIPOutputStream(out)) { gzip.write(str.getBytes(StandardCharsets.UTF_8)); + gzip.finish(); return Base64.getEncoder().encodeToString(out.toByteArray()); } catch (IOException e) { throw new UncheckedIOException("Failed to compress string", e); diff --git a/src/main/resources/application-local.yml b/src/main/resources/application-local.yml index eb8d202..6272596 100644 --- a/src/main/resources/application-local.yml +++ b/src/main/resources/application-local.yml @@ -4,6 +4,9 @@ server: spring: rabbitmq: addresses: localhost + cloud: + aws: + endpoint: http://localhost:19000 powsybl-ws: database: diff --git a/src/main/resources/db/changelog/changesets/changelog_20251223T095053Z.xml b/src/main/resources/db/changelog/changesets/changelog_20251223T095053Z.xml index 0b10698..871d765 100644 --- a/src/main/resources/db/changelog/changesets/changelog_20251223T095053Z.xml +++ b/src/main/resources/db/changelog/changesets/changelog_20251223T095053Z.xml @@ -1,6 +1,6 @@ - + @@ -16,14 +16,14 @@ - + - + @@ -34,7 +34,7 @@ - + @@ -46,7 +46,7 @@ - + @@ -56,7 +56,7 @@ - + @@ -66,7 +66,7 @@ - + @@ -77,7 +77,7 @@ - + @@ -89,42 +89,47 @@ - + + + + + + - + - + - + - + - + - + - + - + - + From 693824d0e48a267953a605c19a03fe6c53751e6c Mon Sep 17 00:00:00 2001 From: Thang PHAM Date: Sat, 31 Jan 2026 02:24:10 +0100 Subject: [PATCH 03/17] add active field Signed-off-by: Thang PHAM --- .../dto/parameters/LoadsVariationInfos.java | 3 ++ .../parameters/LoadsVariationEntity.java | 4 ++ .../changesets/changelog_20251223T095053Z.xml | 39 ++++++++++--------- 3 files changed, 27 insertions(+), 19 deletions(-) diff --git a/src/main/java/org/gridsuite/dynamicmargincalculation/server/dto/parameters/LoadsVariationInfos.java b/src/main/java/org/gridsuite/dynamicmargincalculation/server/dto/parameters/LoadsVariationInfos.java index 480377c..55fb60e 100644 --- a/src/main/java/org/gridsuite/dynamicmargincalculation/server/dto/parameters/LoadsVariationInfos.java +++ b/src/main/java/org/gridsuite/dynamicmargincalculation/server/dto/parameters/LoadsVariationInfos.java @@ -32,4 +32,7 @@ public class LoadsVariationInfos { @JsonInclude(JsonInclude.Include.NON_EMPTY) private Double variation; + + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private Boolean active; } diff --git a/src/main/java/org/gridsuite/dynamicmargincalculation/server/entities/parameters/LoadsVariationEntity.java b/src/main/java/org/gridsuite/dynamicmargincalculation/server/entities/parameters/LoadsVariationEntity.java index b001fd3..f4a0e23 100644 --- a/src/main/java/org/gridsuite/dynamicmargincalculation/server/entities/parameters/LoadsVariationEntity.java +++ b/src/main/java/org/gridsuite/dynamicmargincalculation/server/entities/parameters/LoadsVariationEntity.java @@ -41,6 +41,8 @@ public class LoadsVariationEntity { private Double variation; + private Boolean active; // means this variation is used in calculation + LoadsVariationEntity(LoadsVariationInfos loadsVariationInfos) { assignAttributes(loadsVariationInfos); } @@ -51,6 +53,7 @@ public void assignAttributes(LoadsVariationInfos loadsVariationInfos) { loadsVariationInfos.setId(id); } variation = loadsVariationInfos.getVariation(); + active = loadsVariationInfos.getActive(); loadFilterIds = loadsVariationInfos.getLoadFilterUuids(); } @@ -62,6 +65,7 @@ LoadsVariationInfos toDto(boolean toDuplicate) { return LoadsVariationInfos.builder() .id(toDuplicate ? null : id) .variation(variation) + .active(active) .loadFilterUuids(loadFilterIds) .build(); } diff --git a/src/main/resources/db/changelog/changesets/changelog_20251223T095053Z.xml b/src/main/resources/db/changelog/changesets/changelog_20251223T095053Z.xml index 871d765..32a308c 100644 --- a/src/main/resources/db/changelog/changesets/changelog_20251223T095053Z.xml +++ b/src/main/resources/db/changelog/changesets/changelog_20251223T095053Z.xml @@ -1,6 +1,6 @@ - + @@ -16,14 +16,14 @@ - + - + @@ -34,7 +34,7 @@ - + @@ -46,17 +46,18 @@ - + + - + @@ -66,7 +67,7 @@ - + @@ -77,7 +78,7 @@ - + @@ -89,47 +90,47 @@ - + - + - + - + - + - + - + - + - + - + - + From a872b730fad315b5a971eb6a00b31e6ea643769b Mon Sep 17 00:00:00 2001 From: Thang PHAM Date: Sat, 31 Jan 2026 14:02:29 +0100 Subject: [PATCH 04/17] Filter none active load variations Signed-off-by: Thang PHAM --- .../server/service/ParametersService.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/org/gridsuite/dynamicmargincalculation/server/service/ParametersService.java b/src/main/java/org/gridsuite/dynamicmargincalculation/server/service/ParametersService.java index c432188..05e8b29 100644 --- a/src/main/java/org/gridsuite/dynamicmargincalculation/server/service/ParametersService.java +++ b/src/main/java/org/gridsuite/dynamicmargincalculation/server/service/ParametersService.java @@ -74,6 +74,10 @@ public DynamicMarginCalculationRunContext createRunContext(UUID networkUuid, Str // get parameters from the local database DynamicMarginCalculationParametersInfos dynamicMarginCalculationParametersInfos = getParameters(dynamicMarginCalculationParametersUuid); + // take only active load variations + dynamicMarginCalculationParametersInfos.setLoadsVariations(dynamicMarginCalculationParametersInfos.getLoadsVariations() + .stream() + .filter(LoadsVariationInfos::getActive).toList()); // build run context DynamicMarginCalculationRunContext runContext = DynamicMarginCalculationRunContext.builder() From 3bb8598a6bc06643389201bdc7281bb8d45d8ea1 Mon Sep 17 00:00:00 2001 From: Thang PHAM Date: Sun, 1 Feb 2026 23:03:03 +0100 Subject: [PATCH 05/17] enrich filter names from backend Signed-off-by: Thang PHAM --- ...MarginCalculationParametersController.java | 12 +-- .../server/dto/ElementAttributes.java | 29 +++++++ ...namicMarginCalculationParametersInfos.java | 4 + .../server/dto/parameters/IdNameInfos.java | 28 +++++++ .../dto/parameters/LoadsVariationInfos.java | 2 +- .../parameters/LoadsVariationEntity.java | 5 +- ...amicMarginCalculationExceptionHandler.java | 2 +- ...MarginCalculationParametersRepository.java | 6 ++ .../server/service/ParametersService.java | 56 +++++++++---- .../service/client/DirectoryClient.java | 79 +++++++++++++++++++ src/main/resources/application-local.yml | 2 + 11 files changed, 202 insertions(+), 23 deletions(-) create mode 100644 src/main/java/org/gridsuite/dynamicmargincalculation/server/dto/ElementAttributes.java create mode 100644 src/main/java/org/gridsuite/dynamicmargincalculation/server/dto/parameters/IdNameInfos.java create mode 100644 src/main/java/org/gridsuite/dynamicmargincalculation/server/service/client/DirectoryClient.java diff --git a/src/main/java/org/gridsuite/dynamicmargincalculation/server/controller/DynamicMarginCalculationParametersController.java b/src/main/java/org/gridsuite/dynamicmargincalculation/server/controller/DynamicMarginCalculationParametersController.java index 6fb3947..28a06aa 100644 --- a/src/main/java/org/gridsuite/dynamicmargincalculation/server/controller/DynamicMarginCalculationParametersController.java +++ b/src/main/java/org/gridsuite/dynamicmargincalculation/server/controller/DynamicMarginCalculationParametersController.java @@ -18,9 +18,10 @@ import org.springframework.web.bind.annotation.*; import java.util.List; -import java.util.Optional; import java.util.UUID; +import static org.gridsuite.computation.service.NotificationService.HEADER_USER_ID; + /** * @author Thang PHAM */ @@ -55,7 +56,7 @@ public ResponseEntity createDefaultParameters() { @ApiResponse(responseCode = "200", description = "parameters were duplicated") public ResponseEntity duplicateParameters( @Parameter(description = "source parameters UUID") @RequestParam("duplicateFrom") UUID sourceParametersUuid) { - return ResponseEntity.of(Optional.of(parametersService.duplicateParameters(sourceParametersUuid))); + return ResponseEntity.ok(parametersService.duplicateParameters(sourceParametersUuid)); } @GetMapping(value = "/{uuid}", produces = MediaType.APPLICATION_JSON_VALUE) @@ -63,8 +64,9 @@ public ResponseEntity duplicateParameters( @ApiResponse(responseCode = "200", description = "parameters were returned") @ApiResponse(responseCode = "404", description = "parameters were not found") public ResponseEntity getParameters( - @Parameter(description = "parameters UUID") @PathVariable("uuid") UUID parametersUuid) { - return ResponseEntity.of(Optional.of(parametersService.getParameters(parametersUuid))); + @Parameter(description = "parameters UUID") @PathVariable("uuid") UUID parametersUuid, + @RequestHeader(HEADER_USER_ID) String userId) { + return ResponseEntity.ok(parametersService.getParameters(parametersUuid, userId)); } @GetMapping(value = "", produces = MediaType.APPLICATION_JSON_VALUE) @@ -99,7 +101,7 @@ public ResponseEntity deleteParameters( @ApiResponse(responseCode = "404", description = "provider were not found") public ResponseEntity getProvider( @Parameter(description = "parameters UUID") @PathVariable("uuid") UUID parametersUuid) { - return ResponseEntity.of(Optional.of(parametersService.getParameters(parametersUuid).getProvider())); + return ResponseEntity.ok(parametersService.getProvider(parametersUuid)); } @PutMapping(value = "/{uuid}/provider") diff --git a/src/main/java/org/gridsuite/dynamicmargincalculation/server/dto/ElementAttributes.java b/src/main/java/org/gridsuite/dynamicmargincalculation/server/dto/ElementAttributes.java new file mode 100644 index 0000000..71f7a96 --- /dev/null +++ b/src/main/java/org/gridsuite/dynamicmargincalculation/server/dto/ElementAttributes.java @@ -0,0 +1,29 @@ +/* + * 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.dynamicmargincalculation.server.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.util.UUID; + +/** + * partial type from ElementAttributes (Directory-server) + * @author Thang PHAM + */ +@AllArgsConstructor +@NoArgsConstructor +public class ElementAttributes { + @Getter + private UUID elementUuid; + @Setter + @Getter + private String elementName; +} diff --git a/src/main/java/org/gridsuite/dynamicmargincalculation/server/dto/parameters/DynamicMarginCalculationParametersInfos.java b/src/main/java/org/gridsuite/dynamicmargincalculation/server/dto/parameters/DynamicMarginCalculationParametersInfos.java index f247e7c..e4ca2f3 100644 --- a/src/main/java/org/gridsuite/dynamicmargincalculation/server/dto/parameters/DynamicMarginCalculationParametersInfos.java +++ b/src/main/java/org/gridsuite/dynamicmargincalculation/server/dto/parameters/DynamicMarginCalculationParametersInfos.java @@ -14,6 +14,7 @@ import lombok.*; import java.util.List; +import java.util.Map; import java.util.UUID; /** @@ -57,4 +58,7 @@ public class DynamicMarginCalculationParametersInfos { @JsonInclude(JsonInclude.Include.NON_EMPTY) private List loadsVariations; + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private Map elementsUuidToName; + } diff --git a/src/main/java/org/gridsuite/dynamicmargincalculation/server/dto/parameters/IdNameInfos.java b/src/main/java/org/gridsuite/dynamicmargincalculation/server/dto/parameters/IdNameInfos.java new file mode 100644 index 0000000..361748b --- /dev/null +++ b/src/main/java/org/gridsuite/dynamicmargincalculation/server/dto/parameters/IdNameInfos.java @@ -0,0 +1,28 @@ +/* + * 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.dynamicmargincalculation.server.dto.parameters; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.util.UUID; + +/** + * @author Thang PHAM + */ +@AllArgsConstructor +@NoArgsConstructor +public class IdNameInfos { + @Getter + private UUID id; + @Setter + @Getter + private String name; +} diff --git a/src/main/java/org/gridsuite/dynamicmargincalculation/server/dto/parameters/LoadsVariationInfos.java b/src/main/java/org/gridsuite/dynamicmargincalculation/server/dto/parameters/LoadsVariationInfos.java index 55fb60e..46b9359 100644 --- a/src/main/java/org/gridsuite/dynamicmargincalculation/server/dto/parameters/LoadsVariationInfos.java +++ b/src/main/java/org/gridsuite/dynamicmargincalculation/server/dto/parameters/LoadsVariationInfos.java @@ -28,7 +28,7 @@ public class LoadsVariationInfos { private UUID id; @JsonInclude(JsonInclude.Include.NON_EMPTY) - private List loadFilterUuids; + private List loadFilters; @JsonInclude(JsonInclude.Include.NON_EMPTY) private Double variation; diff --git a/src/main/java/org/gridsuite/dynamicmargincalculation/server/entities/parameters/LoadsVariationEntity.java b/src/main/java/org/gridsuite/dynamicmargincalculation/server/entities/parameters/LoadsVariationEntity.java index f4a0e23..96c2f27 100644 --- a/src/main/java/org/gridsuite/dynamicmargincalculation/server/entities/parameters/LoadsVariationEntity.java +++ b/src/main/java/org/gridsuite/dynamicmargincalculation/server/entities/parameters/LoadsVariationEntity.java @@ -9,6 +9,7 @@ import jakarta.persistence.*; import lombok.*; +import org.gridsuite.dynamicmargincalculation.server.dto.parameters.IdNameInfos; import org.gridsuite.dynamicmargincalculation.server.dto.parameters.LoadsVariationInfos; import java.util.ArrayList; @@ -54,7 +55,7 @@ public void assignAttributes(LoadsVariationInfos loadsVariationInfos) { } variation = loadsVariationInfos.getVariation(); active = loadsVariationInfos.getActive(); - loadFilterIds = loadsVariationInfos.getLoadFilterUuids(); + loadFilterIds = loadsVariationInfos.getLoadFilters().stream().map(IdNameInfos::getId).toList(); } void update(LoadsVariationInfos loadsVariationInfos) { @@ -66,7 +67,7 @@ LoadsVariationInfos toDto(boolean toDuplicate) { .id(toDuplicate ? null : id) .variation(variation) .active(active) - .loadFilterUuids(loadFilterIds) + .loadFilters(loadFilterIds.stream().map(loadFilterId -> new IdNameInfos(loadFilterId, null)).toList()) .build(); } } diff --git a/src/main/java/org/gridsuite/dynamicmargincalculation/server/error/DynamicMarginCalculationExceptionHandler.java b/src/main/java/org/gridsuite/dynamicmargincalculation/server/error/DynamicMarginCalculationExceptionHandler.java index 74ccf36..930d579 100644 --- a/src/main/java/org/gridsuite/dynamicmargincalculation/server/error/DynamicMarginCalculationExceptionHandler.java +++ b/src/main/java/org/gridsuite/dynamicmargincalculation/server/error/DynamicMarginCalculationExceptionHandler.java @@ -40,7 +40,7 @@ protected HttpStatus mapStatus(DynamicMarginCalculationBusinessErrorCode busines } @ExceptionHandler(DynamicMarginCalculationException.class) - public ResponseEntity handleDynamicSecurityAnalysisException(DynamicMarginCalculationException exception, HttpServletRequest request) { + public ResponseEntity handleDynamicMarginCalculationException(DynamicMarginCalculationException exception, HttpServletRequest request) { return super.handleDomainException(exception, request); } diff --git a/src/main/java/org/gridsuite/dynamicmargincalculation/server/repositories/DynamicMarginCalculationParametersRepository.java b/src/main/java/org/gridsuite/dynamicmargincalculation/server/repositories/DynamicMarginCalculationParametersRepository.java index c10cd9e..6c9f254 100644 --- a/src/main/java/org/gridsuite/dynamicmargincalculation/server/repositories/DynamicMarginCalculationParametersRepository.java +++ b/src/main/java/org/gridsuite/dynamicmargincalculation/server/repositories/DynamicMarginCalculationParametersRepository.java @@ -9,8 +9,11 @@ import org.gridsuite.dynamicmargincalculation.server.entities.parameters.DynamicMarginCalculationParametersEntity; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; +import java.util.Optional; import java.util.UUID; /** @@ -18,4 +21,7 @@ */ @Repository public interface DynamicMarginCalculationParametersRepository extends JpaRepository { + + @Query("SELECT params.provider FROM DynamicMarginCalculationParametersEntity params WHERE params.id = :id") + Optional findProviderById(@Param("id") UUID id); } diff --git a/src/main/java/org/gridsuite/dynamicmargincalculation/server/service/ParametersService.java b/src/main/java/org/gridsuite/dynamicmargincalculation/server/service/ParametersService.java index 05e8b29..008af2b 100644 --- a/src/main/java/org/gridsuite/dynamicmargincalculation/server/service/ParametersService.java +++ b/src/main/java/org/gridsuite/dynamicmargincalculation/server/service/ParametersService.java @@ -13,13 +13,16 @@ import com.powsybl.iidm.network.Network; import jakarta.transaction.Transactional; import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; import org.gridsuite.computation.dto.ReportInfos; import org.gridsuite.computation.error.ComputationException; import org.gridsuite.dynamicmargincalculation.server.dto.parameters.DynamicMarginCalculationParametersInfos; +import org.gridsuite.dynamicmargincalculation.server.dto.parameters.IdNameInfos; import org.gridsuite.dynamicmargincalculation.server.dto.parameters.LoadsVariationInfos; import org.gridsuite.dynamicmargincalculation.server.entities.parameters.DynamicMarginCalculationParametersEntity; import org.gridsuite.dynamicmargincalculation.server.error.DynamicMarginCalculationException; import org.gridsuite.dynamicmargincalculation.server.repositories.DynamicMarginCalculationParametersRepository; +import org.gridsuite.dynamicmargincalculation.server.service.client.DirectoryClient; import org.gridsuite.dynamicmargincalculation.server.service.client.FilterClient; import org.gridsuite.dynamicmargincalculation.server.service.contexts.DynamicMarginCalculationRunContext; import org.gridsuite.filter.expertfilter.ExpertFilter; @@ -32,10 +35,7 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; -import java.util.Collections; -import java.util.List; -import java.util.Optional; -import java.util.UUID; +import java.util.*; import java.util.stream.Collectors; import static org.gridsuite.computation.error.ComputationBusinessErrorCode.PARAMETERS_NOT_FOUND; @@ -53,14 +53,18 @@ public class ParametersService { private final DynamicMarginCalculationParametersRepository dynamicMarginCalculationParametersRepository; + private final DirectoryClient directoryClient; + private final FilterClient filterClient; @Autowired public ParametersService(@Value("${dynamic-margin-calculation.default-provider}") String defaultProvider, DynamicMarginCalculationParametersRepository dynamicMarginCalculationParametersRepository, + DirectoryClient directoryClient, FilterClient filterClient) { this.defaultProvider = defaultProvider; this.dynamicMarginCalculationParametersRepository = dynamicMarginCalculationParametersRepository; + this.directoryClient = directoryClient; this.filterClient = filterClient; } @@ -73,7 +77,7 @@ public DynamicMarginCalculationRunContext createRunContext(UUID networkUuid, Str boolean debug) { // get parameters from the local database - DynamicMarginCalculationParametersInfos dynamicMarginCalculationParametersInfos = getParameters(dynamicMarginCalculationParametersUuid); + DynamicMarginCalculationParametersInfos dynamicMarginCalculationParametersInfos = getParameters(dynamicMarginCalculationParametersUuid, null); // take only active load variations dynamicMarginCalculationParametersInfos.setLoadsVariations(dynamicMarginCalculationParametersInfos.getLoadsVariations() .stream() @@ -110,11 +114,33 @@ public DynamicMarginCalculationRunContext createRunContext(UUID networkUuid, Str // --- Dynamic security analysis parameters related methods --- // - public DynamicMarginCalculationParametersInfos getParameters(UUID parametersUuid) { + public DynamicMarginCalculationParametersInfos getParameters(UUID parametersUuid, String userId) { DynamicMarginCalculationParametersEntity entity = dynamicMarginCalculationParametersRepository.findById(parametersUuid) .orElseThrow(() -> new ComputationException(PARAMETERS_NOT_FOUND, MSG_PARAMETERS_UUID_NOT_FOUND + parametersUuid)); - return entity.toDto(false); + DynamicMarginCalculationParametersInfos parameters = entity.toDto(false); + + // enrich parameters with names of directory elements inside parameters if userId is provided + if (StringUtils.isNotBlank(userId)) { + Map elementsUuidToName = directoryClient.getElementNames( + parameters.getLoadsVariations().stream() + .flatMap(elem -> elem.getLoadFilters().stream().map(IdNameInfos::getId)) + .distinct().toList(), + userId); + // enrich load filters with name + parameters.getLoadsVariations().forEach(loadsVariation -> + loadsVariation.getLoadFilters().forEach(loadFilter -> + loadFilter.setName(elementsUuidToName.get(loadFilter.getId()))) + ); + parameters.setElementsUuidToName(elementsUuidToName); + } + + return parameters; + } + + public String getProvider(UUID parametersUuid) { + return dynamicMarginCalculationParametersRepository.findProviderById(parametersUuid) + .orElseThrow(() -> new ComputationException(PARAMETERS_NOT_FOUND, MSG_PARAMETERS_UUID_NOT_FOUND + parametersUuid)); } public UUID createParameters(DynamicMarginCalculationParametersInfos parametersInfos) { @@ -188,13 +214,15 @@ public List getLoadsVariations(List loadsVa List loadsVariations = loadsVariationInfosList.stream().map(loadsVariationInfos -> { // build as a unique IS_PART_OF expert-filter then evaluate ExpertFilter filter = ExpertFilter.builder() - .equipmentType(EquipmentType.LOAD) - .rules(FilterUuidExpertRule.builder() - .field(FieldType.ID) - .operator(OperatorType.IS_PART_OF) - .values(loadsVariationInfos.getLoadFilterUuids().stream().map(UUID::toString).collect(Collectors.toSet())) - .build()) - .build(); + .equipmentType(EquipmentType.LOAD) + .rules(FilterUuidExpertRule.builder() + .field(FieldType.ID) + .operator(OperatorType.IS_PART_OF) + .values(loadsVariationInfos.getLoadFilters().stream() + .map(IdNameInfos::getId) + .map(UUID::toString).collect(Collectors.toSet())) + .build()) + .build(); List loads = FiltersUtils.getIdentifiables(filter, network, filterClient::getFilters).stream() .map(Load.class::cast).toList(); diff --git a/src/main/java/org/gridsuite/dynamicmargincalculation/server/service/client/DirectoryClient.java b/src/main/java/org/gridsuite/dynamicmargincalculation/server/service/client/DirectoryClient.java new file mode 100644 index 0000000..7571cac --- /dev/null +++ b/src/main/java/org/gridsuite/dynamicmargincalculation/server/service/client/DirectoryClient.java @@ -0,0 +1,79 @@ +/* + * 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.dynamicmargincalculation.server.service.client; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; +import org.gridsuite.dynamicmargincalculation.server.dto.ElementAttributes; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.util.UriComponentsBuilder; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.stream.Collectors; + +import static org.gridsuite.computation.service.NotificationService.HEADER_USER_ID; +import static org.gridsuite.dynamicmargincalculation.server.service.client.utils.UrlUtils.buildEndPointUrl; + +/** + * + * @author Thang PHAM + */ +@Service +public class DirectoryClient extends AbstractRestClient { + public static final String API_VERSION = "v1"; + public static final String ELEMENT_END_POINT_INFOS = "elements"; + public static final String QUERY_PARAM_IDS = "ids"; + public static final String QUERY_PARAM_STRICT_MODE = "strictMode"; + + protected DirectoryClient( + @Value("${gridsuite.services.directory-server.base-uri:http://directory-server/}") String baseUri, + RestTemplate restTemplate, + ObjectMapper objectMapper + ) { + super(baseUri, restTemplate, objectMapper); + } + + public Map getElementNames(List ids, String userId) { + if (CollectionUtils.isEmpty(ids)) { + return Collections.emptyMap(); + } + + String endPointUrl = buildEndPointUrl(getBaseUri(), API_VERSION, ELEMENT_END_POINT_INFOS); + + UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder.fromUriString(endPointUrl); + uriComponentsBuilder.queryParam(QUERY_PARAM_IDS, ids); + uriComponentsBuilder.queryParam(QUERY_PARAM_STRICT_MODE, false); // to ignore non existing elements error + + HttpHeaders headers = new HttpHeaders(); + if (StringUtils.isNotBlank(userId)) { + headers.set(HEADER_USER_ID, userId); + } + + List elementAttributes = getRestTemplate() + .exchange( + uriComponentsBuilder.build().toUriString(), + HttpMethod.GET, + new HttpEntity<>(headers), + new ParameterizedTypeReference>() { } + ).getBody(); + + return elementAttributes == null ? + Collections.emptyMap() : + elementAttributes.stream().collect(Collectors.toMap(ElementAttributes::getElementUuid, ElementAttributes::getElementName)); + } +} diff --git a/src/main/resources/application-local.yml b/src/main/resources/application-local.yml index 6272596..84573ae 100644 --- a/src/main/resources/application-local.yml +++ b/src/main/resources/application-local.yml @@ -19,6 +19,8 @@ powsybl: gridsuite: services: + directory-server: + base-uri: http://localhost:5026 filter-server: base-uri: http://localhost:5027 report-server: From 1be4f65bc4142078fc4f37cb5452e5c3608f022f Mon Sep 17 00:00:00 2001 From: Thang PHAM Date: Mon, 2 Feb 2026 18:40:21 +0100 Subject: [PATCH 06/17] check load filter existence Signed-off-by: Thang PHAM --- ...micMarginCalculationBusinessErrorCode.java | 3 +- .../DynamicMarginCalculationException.java | 19 +++++++ ...amicMarginCalculationExceptionHandler.java | 3 +- ...DynamicMarginCalculationWorkerService.java | 5 -- .../server/service/FilterService.java | 17 +++++++ .../server/service/ParametersService.java | 23 +++++++-- .../server/service/client/FilterClient.java | 51 ------------------- 7 files changed, 56 insertions(+), 65 deletions(-) create mode 100644 src/main/java/org/gridsuite/dynamicmargincalculation/server/service/FilterService.java delete mode 100644 src/main/java/org/gridsuite/dynamicmargincalculation/server/service/client/FilterClient.java diff --git a/src/main/java/org/gridsuite/dynamicmargincalculation/server/error/DynamicMarginCalculationBusinessErrorCode.java b/src/main/java/org/gridsuite/dynamicmargincalculation/server/error/DynamicMarginCalculationBusinessErrorCode.java index afc6a82..91b7bcc 100644 --- a/src/main/java/org/gridsuite/dynamicmargincalculation/server/error/DynamicMarginCalculationBusinessErrorCode.java +++ b/src/main/java/org/gridsuite/dynamicmargincalculation/server/error/DynamicMarginCalculationBusinessErrorCode.java @@ -13,8 +13,7 @@ */ public enum DynamicMarginCalculationBusinessErrorCode implements BusinessErrorCode { PROVIDER_NOT_FOUND("dynamicMarginCalculation.providerNotFound"), - CONTINGENCIES_NOT_FOUND("dynamicMarginCalculation.contingenciesNotFound"), - CONTINGENCY_LIST_EMPTY("dynamicMarginCalculation.contingencyListEmpty"); + LOAD_FILTERS_NOT_FOUND("dynamicMarginCalculation.loadFilterNotFound"); private final String code; diff --git a/src/main/java/org/gridsuite/dynamicmargincalculation/server/error/DynamicMarginCalculationException.java b/src/main/java/org/gridsuite/dynamicmargincalculation/server/error/DynamicMarginCalculationException.java index 68b45a7..7b3ec44 100644 --- a/src/main/java/org/gridsuite/dynamicmargincalculation/server/error/DynamicMarginCalculationException.java +++ b/src/main/java/org/gridsuite/dynamicmargincalculation/server/error/DynamicMarginCalculationException.java @@ -10,6 +10,9 @@ import lombok.Getter; import lombok.NonNull; +import java.util.Map; +import java.util.Objects; + /** * @author Thang PHAM */ @@ -18,14 +21,30 @@ public class DynamicMarginCalculationException extends AbstractBusinessException private final DynamicMarginCalculationBusinessErrorCode errorCode; + private final transient Map businessErrorValues; + @NonNull @Override public DynamicMarginCalculationBusinessErrorCode getBusinessErrorCode() { return errorCode; } + @NonNull + @Override + public Map getBusinessErrorValues() { + return businessErrorValues; + } + public DynamicMarginCalculationException(DynamicMarginCalculationBusinessErrorCode errorCode, String message) { super(message); this.errorCode = errorCode; + this.businessErrorValues = Map.of(); + } + + public DynamicMarginCalculationException(DynamicMarginCalculationBusinessErrorCode errorCode, String message, Map businessErrorValues) { + super(Objects.requireNonNull(message, "message must not be null")); + this.errorCode = Objects.requireNonNull(errorCode, "errorCode must not be null"); + this.businessErrorValues = businessErrorValues != null ? Map.copyOf(businessErrorValues) : Map.of(); } + } diff --git a/src/main/java/org/gridsuite/dynamicmargincalculation/server/error/DynamicMarginCalculationExceptionHandler.java b/src/main/java/org/gridsuite/dynamicmargincalculation/server/error/DynamicMarginCalculationExceptionHandler.java index 930d579..27b4bf1 100644 --- a/src/main/java/org/gridsuite/dynamicmargincalculation/server/error/DynamicMarginCalculationExceptionHandler.java +++ b/src/main/java/org/gridsuite/dynamicmargincalculation/server/error/DynamicMarginCalculationExceptionHandler.java @@ -34,8 +34,7 @@ protected DynamicMarginCalculationExceptionHandler(ServerNameProvider serverName protected HttpStatus mapStatus(DynamicMarginCalculationBusinessErrorCode businessErrorCode) { return switch (businessErrorCode) { case PROVIDER_NOT_FOUND, - CONTINGENCIES_NOT_FOUND -> HttpStatus.NOT_FOUND; - case CONTINGENCY_LIST_EMPTY -> HttpStatus.INTERNAL_SERVER_ERROR; + LOAD_FILTERS_NOT_FOUND -> HttpStatus.NOT_FOUND; }; } diff --git a/src/main/java/org/gridsuite/dynamicmargincalculation/server/service/DynamicMarginCalculationWorkerService.java b/src/main/java/org/gridsuite/dynamicmargincalculation/server/service/DynamicMarginCalculationWorkerService.java index 2a87998..b306f0a 100644 --- a/src/main/java/org/gridsuite/dynamicmargincalculation/server/service/DynamicMarginCalculationWorkerService.java +++ b/src/main/java/org/gridsuite/dynamicmargincalculation/server/service/DynamicMarginCalculationWorkerService.java @@ -33,7 +33,6 @@ import org.gridsuite.dynamicmargincalculation.server.dto.parameters.DynamicSecurityAnalysisParametersValues; import org.gridsuite.dynamicmargincalculation.server.dto.parameters.DynamicSimulationParametersValues; import org.gridsuite.dynamicmargincalculation.server.dto.parameters.LoadsVariationInfos; -import org.gridsuite.dynamicmargincalculation.server.error.DynamicMarginCalculationException; import org.gridsuite.dynamicmargincalculation.server.service.client.DynamicSecurityAnalysisClient; import org.gridsuite.dynamicmargincalculation.server.service.client.DynamicSimulationClient; import org.gridsuite.dynamicmargincalculation.server.service.contexts.DynamicMarginCalculationResultContext; @@ -53,7 +52,6 @@ import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; -import static org.gridsuite.dynamicmargincalculation.server.error.DynamicMarginCalculationBusinessErrorCode.CONTINGENCIES_NOT_FOUND; import static org.gridsuite.dynamicmargincalculation.server.service.DynamicMarginCalculationService.COMPUTATION_TYPE; /** @@ -131,9 +129,6 @@ public void preRun(DynamicMarginCalculationRunContext runContext) { dynamicSecurityAnalysisClient.getParametersValues(runContext.getDynamicSecurityAnalysisParametersUuid(), runContext.getNetworkUuid(), runContext.getVariantId()); List contingencies = dynamicSecurityAnalysisParametersValues.getContingencies(); - if (CollectionUtils.isEmpty(contingencies)) { - throw new DynamicMarginCalculationException(CONTINGENCIES_NOT_FOUND, "No contingencies"); - } // get evaluated parameters values from the dynamic simulation server DynamicSimulationParametersValues dynamicSimulationParametersValues = diff --git a/src/main/java/org/gridsuite/dynamicmargincalculation/server/service/FilterService.java b/src/main/java/org/gridsuite/dynamicmargincalculation/server/service/FilterService.java new file mode 100644 index 0000000..9185556 --- /dev/null +++ b/src/main/java/org/gridsuite/dynamicmargincalculation/server/service/FilterService.java @@ -0,0 +1,17 @@ +package org.gridsuite.dynamicmargincalculation.server.service; + +import com.powsybl.network.store.client.NetworkStoreService; +import org.gridsuite.computation.service.AbstractFilterService; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.stereotype.Service; + +@Service +public class FilterService extends AbstractFilterService { + + public FilterService(RestTemplateBuilder restTemplateBuilder, + NetworkStoreService networkStoreService, + @Value("${gridsuite.services.filter-server.base-uri:http://filter-server/}") String filterServerBaseUri) { + super(restTemplateBuilder, networkStoreService, filterServerBaseUri); + } +} diff --git a/src/main/java/org/gridsuite/dynamicmargincalculation/server/service/ParametersService.java b/src/main/java/org/gridsuite/dynamicmargincalculation/server/service/ParametersService.java index 008af2b..07492ce 100644 --- a/src/main/java/org/gridsuite/dynamicmargincalculation/server/service/ParametersService.java +++ b/src/main/java/org/gridsuite/dynamicmargincalculation/server/service/ParametersService.java @@ -23,8 +23,8 @@ import org.gridsuite.dynamicmargincalculation.server.error.DynamicMarginCalculationException; import org.gridsuite.dynamicmargincalculation.server.repositories.DynamicMarginCalculationParametersRepository; import org.gridsuite.dynamicmargincalculation.server.service.client.DirectoryClient; -import org.gridsuite.dynamicmargincalculation.server.service.client.FilterClient; import org.gridsuite.dynamicmargincalculation.server.service.contexts.DynamicMarginCalculationRunContext; +import org.gridsuite.filter.AbstractFilter; import org.gridsuite.filter.expertfilter.ExpertFilter; import org.gridsuite.filter.expertfilter.expertrule.FilterUuidExpertRule; import org.gridsuite.filter.utils.EquipmentType; @@ -39,6 +39,7 @@ import java.util.stream.Collectors; import static org.gridsuite.computation.error.ComputationBusinessErrorCode.PARAMETERS_NOT_FOUND; +import static org.gridsuite.dynamicmargincalculation.server.error.DynamicMarginCalculationBusinessErrorCode.LOAD_FILTERS_NOT_FOUND; import static org.gridsuite.dynamicmargincalculation.server.error.DynamicMarginCalculationBusinessErrorCode.PROVIDER_NOT_FOUND; /** @@ -55,17 +56,17 @@ public class ParametersService { private final DirectoryClient directoryClient; - private final FilterClient filterClient; + private final FilterService filterService; @Autowired public ParametersService(@Value("${dynamic-margin-calculation.default-provider}") String defaultProvider, DynamicMarginCalculationParametersRepository dynamicMarginCalculationParametersRepository, DirectoryClient directoryClient, - FilterClient filterClient) { + FilterService filterService) { this.defaultProvider = defaultProvider; this.dynamicMarginCalculationParametersRepository = dynamicMarginCalculationParametersRepository; this.directoryClient = directoryClient; - this.filterClient = filterClient; + this.filterService = filterService; } public DynamicMarginCalculationRunContext createRunContext(UUID networkUuid, String variantId, String receiver, @@ -211,6 +212,18 @@ public List getLoadsVariations(List loadsVa return Collections.emptyList(); } + // check none-existing load filters + List loadFilerUuids = loadsVariationInfosList.stream().flatMap(loadsVariationInfos -> loadsVariationInfos.getLoadFilters().stream()) + .distinct() + .map(IdNameInfos::getId) + .toList(); + List loadFilters = filterService.getFilters(loadFilerUuids); + Map filterByUuidMap = loadFilters.stream().collect(Collectors.toMap(AbstractFilter::getId, filter -> filter)); + List missingFilterUuids = loadFilerUuids.stream().filter(uuid -> filterByUuidMap.get(uuid) == null).map(Objects::toString).toList(); + if (CollectionUtils.isNotEmpty(missingFilterUuids)) { + throw new DynamicMarginCalculationException(LOAD_FILTERS_NOT_FOUND, "Some load filters do not exist", Map.of("filterUuids", " [" + String.join(", ", missingFilterUuids) + "]")); + } + List loadsVariations = loadsVariationInfosList.stream().map(loadsVariationInfos -> { // build as a unique IS_PART_OF expert-filter then evaluate ExpertFilter filter = ExpertFilter.builder() @@ -224,7 +237,7 @@ public List getLoadsVariations(List loadsVa .build()) .build(); - List loads = FiltersUtils.getIdentifiables(filter, network, filterClient::getFilters).stream() + List loads = FiltersUtils.getIdentifiables(filter, network, filterService::getFilters).stream() .map(Load.class::cast).toList(); return new LoadsVariation(loads, loadsVariationInfos.getVariation()); }).toList(); diff --git a/src/main/java/org/gridsuite/dynamicmargincalculation/server/service/client/FilterClient.java b/src/main/java/org/gridsuite/dynamicmargincalculation/server/service/client/FilterClient.java deleted file mode 100644 index 9c0684f..0000000 --- a/src/main/java/org/gridsuite/dynamicmargincalculation/server/service/client/FilterClient.java +++ /dev/null @@ -1,51 +0,0 @@ -package org.gridsuite.dynamicmargincalculation.server.service.client; - -import com.fasterxml.jackson.databind.ObjectMapper; -import org.apache.commons.collections4.CollectionUtils; -import org.gridsuite.filter.AbstractFilter; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.core.ParameterizedTypeReference; -import org.springframework.http.HttpMethod; -import org.springframework.stereotype.Service; -import org.springframework.web.client.RestTemplate; -import org.springframework.web.util.UriComponentsBuilder; - -import java.util.Collections; -import java.util.List; -import java.util.UUID; - -import static org.gridsuite.dynamicmargincalculation.server.service.client.utils.UrlUtils.URL_DELIMITER; -import static org.gridsuite.dynamicmargincalculation.server.service.client.utils.UrlUtils.buildEndPointUrl; - -@Service -public class FilterClient extends AbstractRestClient { - - public static final String API_VERSION = "v1"; - public static final String FILTERS_BASE_ENDPOINT = "filters"; - public static final String FILTERS_GET_ENDPOINT = FILTERS_BASE_ENDPOINT + URL_DELIMITER + "metadata"; - - protected FilterClient( - @Value("${gridsuite.services.filter-server.base-uri:http://filter-server/}") String baseUri, - RestTemplate restTemplate, ObjectMapper objectMapper) { - super(baseUri, restTemplate, objectMapper); - } - - public List getFilters(List filterUuids) { - if (CollectionUtils.isEmpty(filterUuids)) { - return Collections.emptyList(); - } - - String endPointUrl = buildEndPointUrl(getBaseUri(), API_VERSION, FILTERS_GET_ENDPOINT); - - UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder.fromUriString(endPointUrl); - uriComponentsBuilder.queryParam("ids", filterUuids); - - // call filter server Rest API - return getRestTemplate().exchange( - uriComponentsBuilder.build().toUriString(), - HttpMethod.GET, - null, - new ParameterizedTypeReference>() { - }).getBody(); - } -} From 61809f28a2bbb80fe4823b899b0ad2a36dbd8fa0 Mon Sep 17 00:00:00 2001 From: Thang PHAM Date: Tue, 3 Feb 2026 14:11:13 +0100 Subject: [PATCH 07/17] clear loads variations when dto is empty Signed-off-by: Thang PHAM --- .../parameters/DynamicMarginCalculationParametersEntity.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/org/gridsuite/dynamicmargincalculation/server/entities/parameters/DynamicMarginCalculationParametersEntity.java b/src/main/java/org/gridsuite/dynamicmargincalculation/server/entities/parameters/DynamicMarginCalculationParametersEntity.java index 5a0b3e1..50b0489 100644 --- a/src/main/java/org/gridsuite/dynamicmargincalculation/server/entities/parameters/DynamicMarginCalculationParametersEntity.java +++ b/src/main/java/org/gridsuite/dynamicmargincalculation/server/entities/parameters/DynamicMarginCalculationParametersEntity.java @@ -96,6 +96,7 @@ private void assignAttributes(DynamicMarginCalculationParametersInfos parameters private void assignLoadsVariations(List loadsVariationInfosList) { if (CollectionUtils.isEmpty(loadsVariationInfosList)) { + loadsVariations.clear(); return; } // build existing loads variation Map From 4d9aec9212ee5af1025def508c159289a960835a Mon Sep 17 00:00:00 2001 From: Thang PHAM Date: Fri, 13 Feb 2026 00:04:46 +0100 Subject: [PATCH 08/17] Add tests Signed-off-by: Thang PHAM --- .../DynamicMarginCalculationController.java | 10 +- ...MarginCalculationParametersController.java | 4 +- ...namicMarginCalculationParametersInfos.java | 4 - .../server/dto/parameters/IdNameInfos.java | 6 +- ...amicMarginCalculationParametersEntity.java | 1 - .../parameters/LoadsVariationEntity.java | 1 - ...DynamicMarginCalculationResultService.java | 7 +- .../server/service/FilterService.java | 9 + .../server/service/ParametersService.java | 22 +- ...ynamicMarginCalculationControllerTest.java | 156 ++++++ .../server/controller/AbstractDynawoTest.java | 46 ++ ...MarginCalculationControllerIEEE14Test.java | 211 ++++++++ ...ynamicMarginCalculationControllerTest.java | 480 ++++++++++++++++++ ...inCalculationParametersControllerTest.java | 275 ++++++++++ .../utils/DynamicModelConfigJsonUtils.java | 35 ++ .../server/controller/utils/TestUtils.java | 43 ++ .../com/powsybl/config/test/config.yml | 9 + .../com/powsybl/config/test/filelist.txt | 1 + src/test/resources/data/ieee14/IEEE14.iidm | 232 +++++++++ .../data/ieee14/input/dynamicModel.dmp | 1 + .../input/dynamicSimulationParameters.dmp | 1 + 21 files changed, 1529 insertions(+), 25 deletions(-) create mode 100644 src/test/java/org/gridsuite/dynamicmargincalculation/server/controller/AbstractDynamicMarginCalculationControllerTest.java create mode 100644 src/test/java/org/gridsuite/dynamicmargincalculation/server/controller/AbstractDynawoTest.java create mode 100644 src/test/java/org/gridsuite/dynamicmargincalculation/server/controller/DynamicMarginCalculationControllerIEEE14Test.java create mode 100644 src/test/java/org/gridsuite/dynamicmargincalculation/server/controller/DynamicMarginCalculationControllerTest.java create mode 100644 src/test/java/org/gridsuite/dynamicmargincalculation/server/controller/DynamicMarginCalculationParametersControllerTest.java create mode 100644 src/test/java/org/gridsuite/dynamicmargincalculation/server/controller/utils/DynamicModelConfigJsonUtils.java create mode 100644 src/test/java/org/gridsuite/dynamicmargincalculation/server/controller/utils/TestUtils.java create mode 100644 src/test/resources/com/powsybl/config/test/config.yml create mode 100644 src/test/resources/com/powsybl/config/test/filelist.txt create mode 100644 src/test/resources/data/ieee14/IEEE14.iidm create mode 100644 src/test/resources/data/ieee14/input/dynamicModel.dmp create mode 100644 src/test/resources/data/ieee14/input/dynamicSimulationParameters.dmp diff --git a/src/main/java/org/gridsuite/dynamicmargincalculation/server/controller/DynamicMarginCalculationController.java b/src/main/java/org/gridsuite/dynamicmargincalculation/server/controller/DynamicMarginCalculationController.java index 9ddef18..36e77e9 100644 --- a/src/main/java/org/gridsuite/dynamicmargincalculation/server/controller/DynamicMarginCalculationController.java +++ b/src/main/java/org/gridsuite/dynamicmargincalculation/server/controller/DynamicMarginCalculationController.java @@ -1,5 +1,5 @@ /** - * 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/. @@ -52,7 +52,7 @@ public DynamicMarginCalculationController(DynamicMarginCalculationService dynami this.parametersService = parametersService; } - @PostMapping(value = "/networks/{networkUuid}/run", produces = "application/json") + @PostMapping(value = "/networks/{networkUuid}/run", produces = APPLICATION_JSON_VALUE) @Operation(summary = "run the dynamic margin calculation") @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "Run dynamic margin calculation")}) public ResponseEntity run(@PathVariable("networkUuid") UUID networkUuid, @@ -84,7 +84,7 @@ public ResponseEntity run(@PathVariable("networkUuid") UUID networkUuid, return ResponseEntity.ok().contentType(MediaType.APPLICATION_JSON).body(resultUuid); } - @GetMapping(value = "/results/{resultUuid}/status", produces = "application/json") + @GetMapping(value = "/results/{resultUuid}/status", produces = APPLICATION_JSON_VALUE) @Operation(summary = "Get the dynamic margin calculation status from the database") @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "The dynamic margin calculation status"), @ApiResponse(responseCode = "204", description = "Dynamic margin calculation status is empty"), @@ -94,7 +94,7 @@ public ResponseEntity getStatus(@Parameter(descr return ResponseEntity.ok().body(result); } - @PutMapping(value = "/results/invalidate-status", produces = "application/json") + @PutMapping(value = "/results/invalidate-status", produces = APPLICATION_JSON_VALUE) @Operation(summary = "Invalidate the dynamic margin calculation status from the database") @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "The dynamic margin calculation result uuids have been invalidated"), @ApiResponse(responseCode = "404", description = "Dynamic margin calculation result has not been found")}) @@ -144,7 +144,7 @@ public ResponseEntity getDefaultProvider() { return ResponseEntity.ok().body(dynamicMarginCalculationService.getDefaultProvider()); } - @GetMapping(value = "/results/{resultUuid}/download-debug-file", produces = "application/json") + @GetMapping(value = "/results/{resultUuid}/download-debug-file", produces = APPLICATION_JSON_VALUE) @Operation(summary = "Download a dynamic margin calculation debug file") @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "Dynamic margin calculation debug file"), @ApiResponse(responseCode = "404", description = "Dynamic margin calculation debug file has not been found")}) diff --git a/src/main/java/org/gridsuite/dynamicmargincalculation/server/controller/DynamicMarginCalculationParametersController.java b/src/main/java/org/gridsuite/dynamicmargincalculation/server/controller/DynamicMarginCalculationParametersController.java index 28a06aa..8f4cd20 100644 --- a/src/main/java/org/gridsuite/dynamicmargincalculation/server/controller/DynamicMarginCalculationParametersController.java +++ b/src/main/java/org/gridsuite/dynamicmargincalculation/server/controller/DynamicMarginCalculationParametersController.java @@ -1,5 +1,5 @@ /** - * 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/. @@ -95,7 +95,7 @@ public ResponseEntity deleteParameters( return ResponseEntity.ok().build(); } - @GetMapping(value = "/{uuid}/provider", produces = MediaType.APPLICATION_JSON_VALUE) + @GetMapping(value = "/{uuid}/provider", produces = MediaType.TEXT_PLAIN_VALUE) @Operation(summary = "Get provider") @ApiResponse(responseCode = "200", description = "provider were returned") @ApiResponse(responseCode = "404", description = "provider were not found") diff --git a/src/main/java/org/gridsuite/dynamicmargincalculation/server/dto/parameters/DynamicMarginCalculationParametersInfos.java b/src/main/java/org/gridsuite/dynamicmargincalculation/server/dto/parameters/DynamicMarginCalculationParametersInfos.java index e4ca2f3..f247e7c 100644 --- a/src/main/java/org/gridsuite/dynamicmargincalculation/server/dto/parameters/DynamicMarginCalculationParametersInfos.java +++ b/src/main/java/org/gridsuite/dynamicmargincalculation/server/dto/parameters/DynamicMarginCalculationParametersInfos.java @@ -14,7 +14,6 @@ import lombok.*; import java.util.List; -import java.util.Map; import java.util.UUID; /** @@ -58,7 +57,4 @@ public class DynamicMarginCalculationParametersInfos { @JsonInclude(JsonInclude.Include.NON_EMPTY) private List loadsVariations; - @JsonInclude(JsonInclude.Include.NON_EMPTY) - private Map elementsUuidToName; - } diff --git a/src/main/java/org/gridsuite/dynamicmargincalculation/server/dto/parameters/IdNameInfos.java b/src/main/java/org/gridsuite/dynamicmargincalculation/server/dto/parameters/IdNameInfos.java index 361748b..c3afa28 100644 --- a/src/main/java/org/gridsuite/dynamicmargincalculation/server/dto/parameters/IdNameInfos.java +++ b/src/main/java/org/gridsuite/dynamicmargincalculation/server/dto/parameters/IdNameInfos.java @@ -7,10 +7,7 @@ package org.gridsuite.dynamicmargincalculation.server.dto.parameters; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; +import lombok.*; import java.util.UUID; @@ -19,6 +16,7 @@ */ @AllArgsConstructor @NoArgsConstructor +@Builder public class IdNameInfos { @Getter private UUID id; diff --git a/src/main/java/org/gridsuite/dynamicmargincalculation/server/entities/parameters/DynamicMarginCalculationParametersEntity.java b/src/main/java/org/gridsuite/dynamicmargincalculation/server/entities/parameters/DynamicMarginCalculationParametersEntity.java index 50b0489..5f819f0 100644 --- a/src/main/java/org/gridsuite/dynamicmargincalculation/server/entities/parameters/DynamicMarginCalculationParametersEntity.java +++ b/src/main/java/org/gridsuite/dynamicmargincalculation/server/entities/parameters/DynamicMarginCalculationParametersEntity.java @@ -79,7 +79,6 @@ public DynamicMarginCalculationParametersEntity(DynamicMarginCalculationParamete private void assignAttributes(DynamicMarginCalculationParametersInfos parametersInfos) { if (id == null) { id = UUID.randomUUID(); - parametersInfos.setId(id); } provider = parametersInfos.getProvider(); startTime = parametersInfos.getStartTime(); diff --git a/src/main/java/org/gridsuite/dynamicmargincalculation/server/entities/parameters/LoadsVariationEntity.java b/src/main/java/org/gridsuite/dynamicmargincalculation/server/entities/parameters/LoadsVariationEntity.java index 96c2f27..3670150 100644 --- a/src/main/java/org/gridsuite/dynamicmargincalculation/server/entities/parameters/LoadsVariationEntity.java +++ b/src/main/java/org/gridsuite/dynamicmargincalculation/server/entities/parameters/LoadsVariationEntity.java @@ -51,7 +51,6 @@ public class LoadsVariationEntity { public void assignAttributes(LoadsVariationInfos loadsVariationInfos) { if (id == null) { id = UUID.randomUUID(); - loadsVariationInfos.setId(id); } variation = loadsVariationInfos.getVariation(); active = loadsVariationInfos.getActive(); diff --git a/src/main/java/org/gridsuite/dynamicmargincalculation/server/service/DynamicMarginCalculationResultService.java b/src/main/java/org/gridsuite/dynamicmargincalculation/server/service/DynamicMarginCalculationResultService.java index 6f313de..a26959c 100644 --- a/src/main/java/org/gridsuite/dynamicmargincalculation/server/service/DynamicMarginCalculationResultService.java +++ b/src/main/java/org/gridsuite/dynamicmargincalculation/server/service/DynamicMarginCalculationResultService.java @@ -62,7 +62,7 @@ public List updateStatus(List resultUuids, DynamicMarginCalculationS return statusRepository.saveAllAndFlush(resultEntities).stream().map(DynamicMarginCalculationStatusEntity::getResultUuid).toList(); } - private void internalUpdateStatus(UUID resultUuid, DynamicMarginCalculationStatus status) { + private void doUpdateStatus(UUID resultUuid, DynamicMarginCalculationStatus status) { LOGGER.debug("Update margin calculation status [resultUuid={}, status={}", resultUuid, status); DynamicMarginCalculationStatusEntity resultEntity = statusRepository.findByResultUuid(resultUuid) .orElseThrow(() -> new ComputationException(RESULT_NOT_FOUND, MSG_RESULT_UUID_NOT_FOUND + resultUuid)); @@ -71,7 +71,7 @@ private void internalUpdateStatus(UUID resultUuid, DynamicMarginCalculationStatu @Transactional public void updateStatus(UUID resultUuid, DynamicMarginCalculationStatus status) { - internalUpdateStatus(resultUuid, status); + doUpdateStatus(resultUuid, status); } @Override @@ -99,6 +99,7 @@ public void deleteAll() { } @Override + @Transactional public DynamicMarginCalculationStatus findStatus(UUID resultUuid) { Objects.requireNonNull(resultUuid); return statusRepository.findByResultUuid(resultUuid) @@ -108,7 +109,7 @@ public DynamicMarginCalculationStatus findStatus(UUID resultUuid) { @Transactional public void insertResult(UUID resultUuid, MarginCalculationResult result, DynamicMarginCalculationStatus status) { - internalUpdateStatus(resultUuid, status); + doUpdateStatus(resultUuid, status); MarginCalculationResultEntity resultEntity = MarginCalculationResultEntity.fromDomain(resultUuid, result); resultRepository.save(resultEntity); } diff --git a/src/main/java/org/gridsuite/dynamicmargincalculation/server/service/FilterService.java b/src/main/java/org/gridsuite/dynamicmargincalculation/server/service/FilterService.java index 9185556..e320ff6 100644 --- a/src/main/java/org/gridsuite/dynamicmargincalculation/server/service/FilterService.java +++ b/src/main/java/org/gridsuite/dynamicmargincalculation/server/service/FilterService.java @@ -1,3 +1,9 @@ +/** + * 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.dynamicmargincalculation.server.service; import com.powsybl.network.store.client.NetworkStoreService; @@ -6,6 +12,9 @@ import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.stereotype.Service; +/** + * @author Thang PHAM + */ @Service public class FilterService extends AbstractFilterService { diff --git a/src/main/java/org/gridsuite/dynamicmargincalculation/server/service/ParametersService.java b/src/main/java/org/gridsuite/dynamicmargincalculation/server/service/ParametersService.java index 07492ce..d5cef81 100644 --- a/src/main/java/org/gridsuite/dynamicmargincalculation/server/service/ParametersService.java +++ b/src/main/java/org/gridsuite/dynamicmargincalculation/server/service/ParametersService.java @@ -11,7 +11,6 @@ import com.powsybl.dynawo.margincalculation.loadsvariation.LoadsVariation; import com.powsybl.iidm.network.Load; import com.powsybl.iidm.network.Network; -import jakarta.transaction.Transactional; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.gridsuite.computation.dto.ReportInfos; @@ -34,6 +33,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import java.util.*; import java.util.stream.Collectors; @@ -69,6 +69,7 @@ public ParametersService(@Value("${dynamic-margin-calculation.default-provider}" this.filterService = filterService; } + @Transactional(readOnly = true) public DynamicMarginCalculationRunContext createRunContext(UUID networkUuid, String variantId, String receiver, String provider, ReportInfos reportInfos, String userId, // should be UUID dynamicSimulationParametersUuid after moving dynamic simulation parameters to its server, @@ -78,7 +79,7 @@ public DynamicMarginCalculationRunContext createRunContext(UUID networkUuid, Str boolean debug) { // get parameters from the local database - DynamicMarginCalculationParametersInfos dynamicMarginCalculationParametersInfos = getParameters(dynamicMarginCalculationParametersUuid, null); + DynamicMarginCalculationParametersInfos dynamicMarginCalculationParametersInfos = doGetParameters(dynamicMarginCalculationParametersUuid, null); // take only active load variations dynamicMarginCalculationParametersInfos.setLoadsVariations(dynamicMarginCalculationParametersInfos.getLoadsVariations() .stream() @@ -115,7 +116,12 @@ public DynamicMarginCalculationRunContext createRunContext(UUID networkUuid, Str // --- Dynamic security analysis parameters related methods --- // + @Transactional(readOnly = true) public DynamicMarginCalculationParametersInfos getParameters(UUID parametersUuid, String userId) { + return doGetParameters(parametersUuid, userId); + } + + private DynamicMarginCalculationParametersInfos doGetParameters(UUID parametersUuid, String userId) { DynamicMarginCalculationParametersEntity entity = dynamicMarginCalculationParametersRepository.findById(parametersUuid) .orElseThrow(() -> new ComputationException(PARAMETERS_NOT_FOUND, MSG_PARAMETERS_UUID_NOT_FOUND + parametersUuid)); @@ -133,25 +139,30 @@ public DynamicMarginCalculationParametersInfos getParameters(UUID parametersUuid loadsVariation.getLoadFilters().forEach(loadFilter -> loadFilter.setName(elementsUuidToName.get(loadFilter.getId()))) ); - parameters.setElementsUuidToName(elementsUuidToName); } return parameters; } + @Transactional(readOnly = true) public String getProvider(UUID parametersUuid) { return dynamicMarginCalculationParametersRepository.findProviderById(parametersUuid) .orElseThrow(() -> new ComputationException(PARAMETERS_NOT_FOUND, MSG_PARAMETERS_UUID_NOT_FOUND + parametersUuid)); } + @Transactional public UUID createParameters(DynamicMarginCalculationParametersInfos parametersInfos) { + return doCreateParameters(parametersInfos); + } + + private UUID doCreateParameters(DynamicMarginCalculationParametersInfos parametersInfos) { return dynamicMarginCalculationParametersRepository.save(new DynamicMarginCalculationParametersEntity(parametersInfos)).getId(); } @Transactional public UUID createDefaultParameters() { DynamicMarginCalculationParametersInfos defaultParametersInfos = getDefaultParametersValues(defaultProvider); - return createParameters(defaultParametersInfos); + return doCreateParameters(defaultParametersInfos); } public DynamicMarginCalculationParametersInfos getDefaultParametersValues(String provider) { @@ -175,7 +186,7 @@ public UUID duplicateParameters(UUID sourceParametersUuid) { .orElseThrow(() -> new ComputationException(PARAMETERS_NOT_FOUND, MSG_PARAMETERS_UUID_NOT_FOUND + sourceParametersUuid)); DynamicMarginCalculationParametersInfos duplicatedParametersInfos = entity.toDto(true); duplicatedParametersInfos.setId(null); - return createParameters(duplicatedParametersInfos); + return doCreateParameters(duplicatedParametersInfos); } public List getAllParameters() { @@ -196,6 +207,7 @@ public void updateParameters(UUID parametersUuid, DynamicMarginCalculationParame } } + @Transactional public void deleteParameters(UUID parametersUuid) { dynamicMarginCalculationParametersRepository.deleteById(parametersUuid); } diff --git a/src/test/java/org/gridsuite/dynamicmargincalculation/server/controller/AbstractDynamicMarginCalculationControllerTest.java b/src/test/java/org/gridsuite/dynamicmargincalculation/server/controller/AbstractDynamicMarginCalculationControllerTest.java new file mode 100644 index 0000000..4dadee0 --- /dev/null +++ b/src/test/java/org/gridsuite/dynamicmargincalculation/server/controller/AbstractDynamicMarginCalculationControllerTest.java @@ -0,0 +1,156 @@ +/** + * 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.dynamicmargincalculation.server.controller; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.powsybl.network.store.client.NetworkStoreService; +import lombok.SneakyThrows; +import org.gridsuite.computation.service.ReportService; +import org.gridsuite.dynamicmargincalculation.server.DynamicMarginCalculationApplication; +import org.gridsuite.dynamicmargincalculation.server.controller.utils.TestUtils; +import org.gridsuite.dynamicmargincalculation.server.dto.DynamicMarginCalculationStatus; +import org.gridsuite.dynamicmargincalculation.server.repositories.DynamicMarginCalculationParametersRepository; +import org.gridsuite.dynamicmargincalculation.server.service.DynamicMarginCalculationWorkerService; +import org.gridsuite.dynamicmargincalculation.server.service.FilterService; +import org.gridsuite.dynamicmargincalculation.server.service.ParametersService; +import org.gridsuite.dynamicmargincalculation.server.service.client.DirectoryClient; +import org.gridsuite.dynamicmargincalculation.server.service.client.DynamicSecurityAnalysisClient; +import org.gridsuite.dynamicmargincalculation.server.service.client.DynamicSimulationClient; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.mockito.Mockito; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.stream.binder.test.OutputDestination; +import org.springframework.cloud.stream.binder.test.TestChannelBinderConfiguration; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.bean.override.mockito.MockitoBean; +import org.springframework.test.context.bean.override.mockito.MockitoSpyBean; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.MvcResult; + +import java.io.IOException; +import java.util.List; +import java.util.UUID; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +/** + * Shared setup for DynamicMarginCalculationController integration tests (MockMvc + test binder + Dynawo computation manager). + * + * @author Thang PHAM + */ +@AutoConfigureMockMvc +@SpringBootTest +@ContextConfiguration(classes = {DynamicMarginCalculationApplication.class, TestChannelBinderConfiguration.class}) +public abstract class AbstractDynamicMarginCalculationControllerTest extends AbstractDynawoTest { + + protected final Logger logger = LoggerFactory.getLogger(this.getClass()); + + protected final String dmcDebugDestination = "dmc.debug.destination"; + protected final String dmcResultDestination = "dmc.result.destination"; + protected final String dmcStoppedDestination = "dmc.stopped.destination"; + protected final String dmcCancelFailedDestination = "dmc.cancelfailed.destination"; + + @Autowired + protected MockMvc mockMvc; + + @Autowired + protected ObjectMapper objectMapper; + + @MockitoBean + protected ReportService reportService; + + @MockitoBean + protected DirectoryClient directoryClient; + + @MockitoBean + protected FilterService filterClient; + + @MockitoBean + protected DynamicSecurityAnalysisClient dynamicSecurityAnalysisClient; + + @MockitoBean + protected DynamicSimulationClient dynamicSimulationClient; + + @MockitoBean + protected NetworkStoreService networkStoreClient; + + @MockitoBean + protected DynamicMarginCalculationParametersRepository dynamicMarginCalculationParametersRepository; + + @MockitoSpyBean + protected ParametersService parametersService; + + @MockitoSpyBean + protected DynamicMarginCalculationWorkerService dynamicMarginCalculationWorkerService; + + @BeforeEach + @Override + public void setUp() throws IOException { + super.setUp(); + initDynamicMarginCalculationWorkerServiceSpy(); + initParametersRepositoryMock(); + initExternalClientsMocks(); + } + + @SneakyThrows + @AfterEach + @Override + public void tearDown() { + super.tearDown(); + + // delete all results + mockMvc.perform(delete("/v1/results")) + .andExpect(status().isOk()); + + // ensure queues are empty then clear + OutputDestination output = getOutputDestination(); + List destinations = List.of(dmcDebugDestination, dmcResultDestination, dmcStoppedDestination, dmcCancelFailedDestination); + TestUtils.assertQueuesEmptyThenClear(destinations, output); + } + + protected abstract OutputDestination getOutputDestination(); + + /** + * Provide a computation manager for the worker service so that computation can run. + */ + protected void initDynamicMarginCalculationWorkerServiceSpy() { + Mockito.when(dynamicMarginCalculationWorkerService.getComputationManager()).thenReturn(computationManager); + } + + /** + * Concrete test classes must set up repository responses for parameters UUIDs they use. + */ + protected abstract void initParametersRepositoryMock(); + + /** + * Concrete test classes may stub directory/filter calls if their parameters require it. + */ + protected abstract void initExternalClientsMocks(); + + // --- utility methods --- // + + protected void assertResultStatus(UUID runUuid, DynamicMarginCalculationStatus expectedStatus) throws Exception { + MvcResult result = mockMvc.perform(get("/v1/results/{resultUuid}/status", runUuid)) + .andExpect(status().isOk()) + .andReturn(); + + DynamicMarginCalculationStatus statusValue = null; + if (!result.getResponse().getContentAsString().isEmpty()) { + statusValue = objectMapper.readValue(result.getResponse().getContentAsString(), DynamicMarginCalculationStatus.class); + } + + assertThat(statusValue).isSameAs(expectedStatus); + } +} diff --git a/src/test/java/org/gridsuite/dynamicmargincalculation/server/controller/AbstractDynawoTest.java b/src/test/java/org/gridsuite/dynamicmargincalculation/server/controller/AbstractDynawoTest.java new file mode 100644 index 0000000..2dffe17 --- /dev/null +++ b/src/test/java/org/gridsuite/dynamicmargincalculation/server/controller/AbstractDynawoTest.java @@ -0,0 +1,46 @@ +/** + * 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.dynamicmargincalculation.server.controller; + +import com.powsybl.computation.ComputationManager; +import com.powsybl.computation.local.test.ComputationDockerConfig; +import com.powsybl.computation.local.test.DockerLocalComputationManager; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.io.TempDir; + +import java.io.IOException; +import java.nio.file.Path; + +/** + * Base class to run Dynawo-based computations in integration tests (docker local computation manager). + * + * @author Thang PHAM + */ +public abstract class AbstractDynawoTest { + + private static final String JAVA_DYNAWO_VERSION = "3.1.0"; + private static final String DOCKER_IMAGE_ID = "powsybl/java-dynawo:" + JAVA_DYNAWO_VERSION; + + @TempDir + public Path localDir; + + protected ComputationManager computationManager; + + @BeforeEach + public void setUp() throws IOException { + Path dockerDir = Path.of("/home/powsybl"); + ComputationDockerConfig config = new ComputationDockerConfig() + .setDockerImageId(DOCKER_IMAGE_ID); + computationManager = new DockerLocalComputationManager(localDir, dockerDir, config); + } + + @AfterEach + public void tearDown() { + computationManager.close(); + } +} diff --git a/src/test/java/org/gridsuite/dynamicmargincalculation/server/controller/DynamicMarginCalculationControllerIEEE14Test.java b/src/test/java/org/gridsuite/dynamicmargincalculation/server/controller/DynamicMarginCalculationControllerIEEE14Test.java new file mode 100644 index 0000000..132f3fa --- /dev/null +++ b/src/test/java/org/gridsuite/dynamicmargincalculation/server/controller/DynamicMarginCalculationControllerIEEE14Test.java @@ -0,0 +1,211 @@ +/** + * 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.dynamicmargincalculation.server.controller; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.powsybl.commons.datasource.ReadOnlyDataSource; +import com.powsybl.commons.datasource.ResourceDataSource; +import com.powsybl.commons.datasource.ResourceSet; +import com.powsybl.contingency.Contingency; +import com.powsybl.dynamicsimulation.DynamicSimulationParameters; +import com.powsybl.dynawo.DynawoSimulationParameters; +import com.powsybl.dynawo.suppliers.dynamicmodels.DynamicModelConfig; +import com.powsybl.iidm.network.Importers; +import com.powsybl.iidm.network.Network; +import com.powsybl.iidm.network.VariantManagerConstants; +import com.powsybl.network.store.client.PreloadingStrategy; +import org.gridsuite.dynamicmargincalculation.server.controller.utils.DynamicModelConfigJsonUtils; +import org.gridsuite.dynamicmargincalculation.server.dto.DynamicMarginCalculationStatus; +import org.gridsuite.dynamicmargincalculation.server.dto.parameters.*; +import org.gridsuite.dynamicmargincalculation.server.entities.parameters.DynamicMarginCalculationParametersEntity; +import org.gridsuite.filter.expertfilter.ExpertFilter; +import org.gridsuite.filter.expertfilter.expertrule.CombinatorExpertRule; +import org.gridsuite.filter.expertfilter.expertrule.StringExpertRule; +import org.gridsuite.filter.utils.EquipmentType; +import org.gridsuite.filter.utils.expertfilter.CombinatorType; +import org.gridsuite.filter.utils.expertfilter.FieldType; +import org.gridsuite.filter.utils.expertfilter.OperatorType; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cloud.stream.binder.test.OutputDestination; +import org.springframework.messaging.Message; +import org.springframework.test.web.servlet.MvcResult; + +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.gridsuite.computation.service.AbstractResultContext.VARIANT_ID_HEADER; +import static org.gridsuite.computation.service.NotificationService.HEADER_RESULT_UUID; +import static org.gridsuite.computation.service.NotificationService.HEADER_USER_ID; +import static org.gridsuite.dynamicmargincalculation.server.controller.utils.TestUtils.RESOURCE_PATH_DELIMITER; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.when; +import static org.springframework.http.MediaType.APPLICATION_JSON; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +/** + * IEEE14 controller test for DynamicMarginCalculationController + * + * This test focuses on: + * - calling the /v1/networks/{uuid}/run endpoint + * - asserting a result notification is emitted + * - asserting the persisted status ends as SUCCEED + * + * @author Thang PHAM + */ +public class DynamicMarginCalculationControllerIEEE14Test extends AbstractDynamicMarginCalculationControllerTest { + + // directories + public static final String DATA_IEEE14_BASE_DIR = RESOURCE_PATH_DELIMITER + "data" + RESOURCE_PATH_DELIMITER + "ieee14"; + public static final String INPUT = "input"; + + public static final String DYNAMIC_MODEL_DUMP_FILE = "dynamicModel.dmp"; + public static final String DYNAMIC_SIMULATION_PARAMETERS_DUMP_FILE = "dynamicSimulationParameters.dmp"; + + public static final String NETWORK_FILE = "IEEE14.iidm"; + private static final UUID NETWORK_UUID = UUID.randomUUID(); + private static final String VARIANT_1_ID = "variant_1"; + + private static final UUID DSA_PARAMETERS_UUID = UUID.randomUUID(); + private static final UUID PARAMETERS_UUID = UUID.randomUUID(); + private static final UUID FILTER_UUID = UUID.randomUUID(); + + @Autowired + private OutputDestination output; + + @Override + public OutputDestination getOutputDestination() { + return output; + } + + @Override + protected void initParametersRepositoryMock() { + // Use defaults from service to keep test stable across parameter schema changes + DynamicMarginCalculationParametersInfos params = parametersService.getDefaultParametersValues("Dynawo"); + params.setLoadsVariations(List.of( + LoadsVariationInfos.builder() + .loadFilters(List.of(IdNameInfos.builder().id(FILTER_UUID).build())) + .variation(10.0) + .active(true) + .build() + )); + + DynamicMarginCalculationParametersEntity entity = new DynamicMarginCalculationParametersEntity(params); + given(dynamicMarginCalculationParametersRepository.findById(PARAMETERS_UUID)).willReturn(Optional.of(entity)); + } + + @Override + protected void initExternalClientsMocks() { + // Mock for network + ReadOnlyDataSource dataSource = new ResourceDataSource("IEEE14", + new ResourceSet(DATA_IEEE14_BASE_DIR, NETWORK_FILE)); + Network network = Importers.importData("XIIDM", dataSource, null); + network.getVariantManager().cloneVariant(VariantManagerConstants.INITIAL_VARIANT_ID, VARIANT_1_ID); + given(networkStoreClient.getNetwork(NETWORK_UUID, PreloadingStrategy.COLLECTION)).willReturn(network); + + // Mock for the dynamic simulation client + initDynamicSimulationClientsMock(); + + // Mock for the dynamic security analysis client + initDynamicSecurityAnalysisClientMock(); + + // Mock for the filer client + when(filterClient.getFilters(eq(List.of(FILTER_UUID)))) + .thenReturn( + List.of( + ExpertFilter.builder() + .id(FILTER_UUID) + .rules(CombinatorExpertRule.builder() + .combinator(CombinatorType.AND) + .rules(List.of( + StringExpertRule.builder() + .field(FieldType.ID) + .operator(OperatorType.IS) + .value("_LOAD__11_EC") + .build() + )) + .build()) + .equipmentType(EquipmentType.LOAD) + .build()) + ); + + } + + private void initDynamicSimulationClientsMock() { + + try { + String inputDir = DATA_IEEE14_BASE_DIR + RESOURCE_PATH_DELIMITER + INPUT; + + // load dynamicModel.dmp + String dynamicModelFilePath = inputDir + RESOURCE_PATH_DELIMITER + DYNAMIC_MODEL_DUMP_FILE; + InputStream dynamicModelIS = getClass().getResourceAsStream(dynamicModelFilePath); + assert dynamicModelIS != null; + // temporal : use custom ObjectMapper provided by powsybl-dynawo to deserialize dynamic model + ObjectMapper dynamicModelConfigObjectMapper = DynamicModelConfigJsonUtils.createObjectMapper(); + List dynamicModel = dynamicModelConfigObjectMapper.readValue(dynamicModelIS, new TypeReference<>() { }); + + // load dynamicSimulationParameters.dmp + String dynamicSimulationParametersFilePath = inputDir + RESOURCE_PATH_DELIMITER + DYNAMIC_SIMULATION_PARAMETERS_DUMP_FILE; + InputStream dynamicSimulationParametersIS = getClass().getResourceAsStream(dynamicSimulationParametersFilePath); + assert dynamicSimulationParametersIS != null; + DynamicSimulationParameters dynamicSimulationParameters = objectMapper.readValue(dynamicSimulationParametersIS, DynamicSimulationParameters.class); + + // Mock for dynamic simulation server + when(dynamicSimulationClient.getParametersValues(anyString(), eq(NETWORK_UUID), any())) + .thenReturn(DynamicSimulationParametersValues.builder() + .dynamicModel(dynamicModel) + .dynawoParameters(dynamicSimulationParameters.getExtension(DynawoSimulationParameters.class)) + .build()); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + private void initDynamicSecurityAnalysisClientMock() { + when(dynamicSecurityAnalysisClient.getParametersValues(eq(DSA_PARAMETERS_UUID), eq(NETWORK_UUID), any())) + .thenReturn(DynamicSecurityAnalysisParametersValues.builder() + .contingenciesStartTime(105d) + .contingencies(List.of(Contingency.load("_LOAD__11_EC"))) + .build()); + } + + @Test + void test01IEEE14() throws Exception { + // The controller requires a request body string: dynamicSimulationParametersJson. + String dynamicSimulationParametersJson = "{}"; + + // run dynamic margin calculation on a specific variant + MvcResult result = mockMvc.perform( + post("/v1/networks/{networkUuid}/run", NETWORK_UUID.toString()) + .param(VARIANT_ID_HEADER, VARIANT_1_ID) + .param("dynamicSecurityAnalysisParametersUuid", DSA_PARAMETERS_UUID.toString()) + .param("parametersUuid", PARAMETERS_UUID.toString()) + .contentType(APPLICATION_JSON) + .content(dynamicSimulationParametersJson) + .header(HEADER_USER_ID, "testUserId") + ) + .andExpect(status().isOk()) + .andReturn(); + + UUID runUuid = objectMapper.readValue(result.getResponse().getContentAsString(), UUID.class); + + Message messageSwitch = output.receive(1000, dmcResultDestination); + assertThat(messageSwitch).isNotNull(); + assertThat(messageSwitch.getHeaders()).containsEntry(HEADER_RESULT_UUID, runUuid.toString()); + + // --- CHECK result --- // + assertResultStatus(runUuid, DynamicMarginCalculationStatus.SUCCEED); + } +} diff --git a/src/test/java/org/gridsuite/dynamicmargincalculation/server/controller/DynamicMarginCalculationControllerTest.java b/src/test/java/org/gridsuite/dynamicmargincalculation/server/controller/DynamicMarginCalculationControllerTest.java new file mode 100644 index 0000000..634355d --- /dev/null +++ b/src/test/java/org/gridsuite/dynamicmargincalculation/server/controller/DynamicMarginCalculationControllerTest.java @@ -0,0 +1,480 @@ +/** + * 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.dynamicmargincalculation.server.controller; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.powsybl.commons.datasource.ReadOnlyDataSource; +import com.powsybl.commons.datasource.ResourceDataSource; +import com.powsybl.commons.datasource.ResourceSet; +import com.powsybl.contingency.Contingency; +import com.powsybl.dynawo.contingency.results.FailedCriterion; +import com.powsybl.dynawo.contingency.results.ScenarioResult; +import com.powsybl.dynawo.margincalculation.MarginCalculation; +import com.powsybl.dynawo.margincalculation.results.LoadIncreaseResult; +import com.powsybl.dynawo.margincalculation.results.MarginCalculationResult; +import com.powsybl.iidm.network.Importers; +import com.powsybl.iidm.network.Network; +import com.powsybl.iidm.network.VariantManagerConstants; +import com.powsybl.network.store.client.PreloadingStrategy; +import org.gridsuite.computation.service.NotificationService; +import org.gridsuite.dynamicmargincalculation.server.dto.DynamicMarginCalculationStatus; +import org.gridsuite.dynamicmargincalculation.server.dto.parameters.DynamicMarginCalculationParametersInfos; +import org.gridsuite.dynamicmargincalculation.server.dto.parameters.DynamicSecurityAnalysisParametersValues; +import org.gridsuite.dynamicmargincalculation.server.dto.parameters.DynamicSimulationParametersValues; +import org.gridsuite.dynamicmargincalculation.server.entities.parameters.DynamicMarginCalculationParametersEntity; +import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cloud.stream.binder.test.OutputDestination; +import org.springframework.messaging.Message; +import org.springframework.test.context.bean.override.mockito.MockitoSpyBean; +import org.springframework.test.web.servlet.MvcResult; +import software.amazon.awssdk.core.ResponseInputStream; +import software.amazon.awssdk.core.sync.RequestBody; +import software.amazon.awssdk.http.AbortableInputStream; +import software.amazon.awssdk.services.s3.S3Client; +import software.amazon.awssdk.services.s3.model.GetObjectRequest; +import software.amazon.awssdk.services.s3.model.GetObjectResponse; +import software.amazon.awssdk.services.s3.model.PutObjectRequest; +import software.amazon.awssdk.services.s3.model.PutObjectResponse; + +import java.io.ByteArrayInputStream; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; + +import static com.powsybl.dynawo.contingency.results.Status.CONVERGENCE; +import static com.powsybl.dynawo.contingency.results.Status.CRITERIA_NON_RESPECTED; +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; +import static org.gridsuite.computation.s3.ComputationS3Service.METADATA_FILE_NAME; +import static org.gridsuite.computation.service.AbstractResultContext.REPORTER_ID_HEADER; +import static org.gridsuite.computation.service.AbstractResultContext.VARIANT_ID_HEADER; +import static org.gridsuite.computation.service.NotificationService.*; +import static org.gridsuite.dynamicmargincalculation.server.controller.utils.TestUtils.RESOURCE_PATH_DELIMITER; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.*; +import static org.springframework.http.MediaType.APPLICATION_JSON; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +/** + * @author Thang PHAM + */ +public class DynamicMarginCalculationControllerTest extends AbstractDynamicMarginCalculationControllerTest { + + // directories + public static final String DATA_IEEE14_BASE_DIR = RESOURCE_PATH_DELIMITER + "data" + RESOURCE_PATH_DELIMITER + "ieee14"; + + private static final UUID NETWORK_UUID = UUID.randomUUID(); + private static final String VARIANT_1_ID = "variant_1"; + private static final String NETWORK_FILE = "IEEE14.iidm"; + + private static final UUID PARAMETERS_UUID = UUID.randomUUID(); + private static final UUID DSA_PARAMETERS_UUID = UUID.randomUUID(); + + private static final String LINE_ID = "_BUS____1-BUS____5-1_AC"; + private static final String GEN_ID = "_GEN____2_SM"; + + @Autowired + private OutputDestination output; + + @Autowired + ObjectMapper objectMapper; + + @MockitoSpyBean + private NotificationService notificationService; + + @MockitoSpyBean + private S3Client s3Client; + + @Override + public OutputDestination getOutputDestination() { + return output; + } + + @Override + protected void initParametersRepositoryMock() { + DynamicMarginCalculationParametersInfos params = parametersService.getDefaultParametersValues("Dynawo"); + params.setLoadsVariations(List.of()); // keep test independent from directory/filter enrichment + + DynamicMarginCalculationParametersEntity entity = new DynamicMarginCalculationParametersEntity(params); + given(dynamicMarginCalculationParametersRepository.findById(PARAMETERS_UUID)).willReturn(Optional.of(entity)); + } + + @Override + protected void initExternalClientsMocks() { + // Mock for network + ReadOnlyDataSource dataSource = new ResourceDataSource("IEEE14", + new ResourceSet(DATA_IEEE14_BASE_DIR, NETWORK_FILE)); + Network network = Importers.importData("XIIDM", dataSource, null); + network.getVariantManager().cloneVariant(VariantManagerConstants.INITIAL_VARIANT_ID, VARIANT_1_ID); + given(networkStoreClient.getNetwork(NETWORK_UUID, PreloadingStrategy.COLLECTION)).willReturn(network); + + // Mock for dynamic security analysis + when(dynamicSecurityAnalysisClient.getParametersValues(eq(DSA_PARAMETERS_UUID), eq(NETWORK_UUID), any())) + .thenReturn(DynamicSecurityAnalysisParametersValues.builder() + .contingenciesStartTime(105d) + .contingencies(List.of(Contingency.load("_LOAD__11_EC"))) + .build()); + + // Mock for dynamic simulation server + when(dynamicSimulationClient.getParametersValues(anyString(), eq(NETWORK_UUID), any())) + .thenReturn(DynamicSimulationParametersValues.builder().build()); + } + + @Test + void testResult() throws Exception { + + // mock DynamicMarginCalculationWorkerService + doReturn(CompletableFuture.completedFuture(MarginCalculationResult.empty())) + .when(dynamicMarginCalculationWorkerService).getCompletableFuture(any(), any(), any()); + + // mock s3 client for run with debug + doReturn(PutObjectResponse.builder().build()) + .when(s3Client).putObject(any(PutObjectRequest.class), any(RequestBody.class)); + + doReturn(new ResponseInputStream<>( + GetObjectResponse.builder() + .metadata(Map.of(METADATA_FILE_NAME, "debugFile")) + .contentLength(100L).build(), + AbortableInputStream.create(new ByteArrayInputStream("s3 debug file content".getBytes())) + )).when(s3Client).getObject(any(GetObjectRequest.class)); + + // run with debug (body is the dynamicSimulationParametersJson string) + String dynamicSimulationParametersJson = "{}"; + MvcResult result = mockMvc.perform( + post("/v1/networks/{networkUuid}/run", NETWORK_UUID) + .param(VARIANT_ID_HEADER, VARIANT_1_ID) + .param("dynamicSecurityAnalysisParametersUuid", DSA_PARAMETERS_UUID.toString()) + .param("parametersUuid", PARAMETERS_UUID.toString()) + .param(HEADER_DEBUG, "true") + .contentType(APPLICATION_JSON) + .content(dynamicSimulationParametersJson) + .header(HEADER_USER_ID, "testUserId") + ) + .andExpect(status().isOk()) + .andReturn(); + + UUID runUuid = objectMapper.readValue(result.getResponse().getContentAsString(), UUID.class); + + // check notification of result + Message messageSwitch = output.receive(10_000, dmcResultDestination); + assertThat(messageSwitch).isNotNull(); + assertThat(messageSwitch.getHeaders()).containsEntry(HEADER_RESULT_UUID, runUuid.toString()); + + // check notification of debug + messageSwitch = output.receive(10_000, dmcDebugDestination); + assertThat(messageSwitch).isNotNull(); + assertThat(messageSwitch.getHeaders()).containsEntry(HEADER_RESULT_UUID, runUuid.toString()); + + // download debug zip file is ok + mockMvc.perform(get("/v1/results/{resultUuid}/download-debug-file", runUuid)) + .andExpect(status().isOk()); + + // check interaction with s3 client + verify(s3Client, times(1)).putObject(any(PutObjectRequest.class), any(RequestBody.class)); + verify(s3Client, times(1)).getObject(any(GetObjectRequest.class)); + + // run on implicit default variant (variantId omitted) + result = mockMvc.perform( + post("/v1/networks/{networkUuid}/run", NETWORK_UUID) + .param("dynamicSecurityAnalysisParametersUuid", DSA_PARAMETERS_UUID.toString()) + .param("parametersUuid", PARAMETERS_UUID.toString()) + .contentType(APPLICATION_JSON) + .content(dynamicSimulationParametersJson) + .header(HEADER_USER_ID, "testUserId") + ) + .andExpect(status().isOk()) + .andReturn(); + + runUuid = objectMapper.readValue(result.getResponse().getContentAsString(), UUID.class); + + messageSwitch = output.receive(10_000, dmcResultDestination); + assertThat(messageSwitch).isNotNull(); + assertThat(messageSwitch.getHeaders()).containsEntry(HEADER_RESULT_UUID, runUuid.toString()); + + // get status (depending on timing, could be RUNNING or SUCCEED) + result = mockMvc.perform(get("/v1/results/{resultUuid}/status", runUuid)) + .andExpect(status().isOk()) + .andReturn(); + + DynamicMarginCalculationStatus statusValue = objectMapper.readValue( + result.getResponse().getContentAsString(), + DynamicMarginCalculationStatus.class + ); + assertThat(statusValue).isIn(DynamicMarginCalculationStatus.SUCCEED, DynamicMarginCalculationStatus.RUNNING); + + // status of non-existing result => empty (mapped to null in helper) + assertResultStatus(UUID.randomUUID(), null); + + // invalidate status => set NOT_DONE + mockMvc.perform(put("/v1/results/invalidate-status") + .param("resultUuid", runUuid.toString())) + .andExpect(status().isOk()); + + result = mockMvc.perform(get("/v1/results/{resultUuid}/status", runUuid)) + .andExpect(status().isOk()) + .andReturn(); + + DynamicMarginCalculationStatus statusAfterInvalidate = objectMapper.readValue( + result.getResponse().getContentAsString(), + DynamicMarginCalculationStatus.class + ); + assertThat(statusAfterInvalidate).isSameAs(DynamicMarginCalculationStatus.NOT_DONE); + + // invalidate status for unknown result => 404 (controller returns notFound when update list is empty) + mockMvc.perform(put("/v1/results/invalidate-status") + .param("resultUuid", UUID.randomUUID().toString())) + .andExpect(status().isNotFound()); + + // delete one result + mockMvc.perform(delete("/v1/results/{resultUuid}", runUuid)) + .andExpect(status().isOk()); + + // verify deleted => status becomes empty (null) + assertResultStatus(runUuid, null); + + // delete non-existing => ok + mockMvc.perform(delete("/v1/results/{resultUuid}", UUID.randomUUID())) + .andExpect(status().isOk()); + + // delete all results => ok + mockMvc.perform(delete("/v1/results")) + .andExpect(status().isOk()); + } + + @Test + void testRunWithSynchronousExceptions() throws Exception { + String dynamicSimulationParametersJson = "{}"; + + // provider not found + mockMvc.perform( + post("/v1/networks/{networkUuid}/run", NETWORK_UUID) + .param(HEADER_PROVIDER, "notFoundProvider") + .param(VARIANT_ID_HEADER, VARIANT_1_ID) + .param("dynamicSecurityAnalysisParametersUuid", DSA_PARAMETERS_UUID.toString()) + .param("parametersUuid", PARAMETERS_UUID.toString()) + .contentType(APPLICATION_JSON) + .content(dynamicSimulationParametersJson) + .header(HEADER_USER_ID, "testUserId") + ) + .andExpect(status().isNotFound()); + + // parameters not found + mockMvc.perform( + post("/v1/networks/{networkUuid}/run", NETWORK_UUID) + .param(VARIANT_ID_HEADER, VARIANT_1_ID) + .param("dynamicSecurityAnalysisParametersUuid", DSA_PARAMETERS_UUID.toString()) + .param("parametersUuid", UUID.randomUUID().toString()) + .contentType(APPLICATION_JSON) + .content(dynamicSimulationParametersJson) + .header(HEADER_USER_ID, "testUserId") + ) + .andExpect(status().isNotFound()); + } + + @Test + void testRunWithResult() throws Exception { + doAnswer(invocation -> null).when(reportService).deleteReport(any()); + doAnswer(invocation -> null).when(reportService).sendReport(any(), any()); + + doReturn(CompletableFuture.completedFuture(new MarginCalculationResult(List.of( + new LoadIncreaseResult(100, CRITERIA_NON_RESPECTED, List.of(), + List.of(new FailedCriterion("total load power = 207.704MW > 200MW (criteria id: Risque protection)", 56.929320))), + new LoadIncreaseResult(0, CONVERGENCE, + List.of(new ScenarioResult(LINE_ID, CONVERGENCE), + new ScenarioResult(GEN_ID, CONVERGENCE))) + )))) + .when(dynamicMarginCalculationWorkerService).getCompletableFuture(any(), any(), any()); + String dynamicSimulationParametersJson = "{}"; + + MvcResult result = mockMvc.perform( + post("/v1/networks/{networkUuid}/run", NETWORK_UUID) + .param(VARIANT_ID_HEADER, VARIANT_1_ID) + .param("dynamicSecurityAnalysisParametersUuid", DSA_PARAMETERS_UUID.toString()) + .param("parametersUuid", PARAMETERS_UUID.toString()) + .param("reportUuid", UUID.randomUUID().toString()) + .param(REPORTER_ID_HEADER, "dmc") + .contentType(APPLICATION_JSON) + .content(dynamicSimulationParametersJson) + .header(HEADER_USER_ID, "testUserId") + ) + .andExpect(status().isOk()) + .andReturn(); + + UUID runUuid = objectMapper.readValue(result.getResponse().getContentAsString(), UUID.class); + + Message messageSwitch = output.receive(1000, dmcResultDestination); + assertThat(messageSwitch).isNotNull(); + assertThat(messageSwitch.getHeaders()).containsEntry(HEADER_RESULT_UUID, runUuid.toString()); + } + + // --- BEGIN Test cancelling a running computation ---// + + private void mockSendRunMessage(Supplier> runAsyncMock) { + // In test environment, the test binder calls consumers directly in the caller thread, i.e. the controller thread. + // By consequence, a real asynchronous Producer/Consumer can not be simulated like prod. + // So mocking producer in a separated thread differing from the controller thread (same pattern as the selected test). + doAnswer(invocation -> CompletableFuture.runAsync(() -> { + // static mock must be in the same thread of the consumer + // see : https://stackoverflow.com/questions/76406935/mock-static-method-in-spring-boot-integration-test + try (MockedStatic marginCalculationMockedStatic = mockStatic(MarginCalculation.class)) { + MarginCalculation.Runner runner = mock(MarginCalculation.Runner.class); + marginCalculationMockedStatic.when(MarginCalculation::getRunner).thenReturn(runner); + + // This gives us deterministic control over "long" vs "short" computations. + doAnswer(invocation2 -> runAsyncMock.get()) + .when(runner).runAsync(any(), any(), any(), any(), any()); + + // call real method sendRunMessage + try { + invocation.callRealMethod(); + } catch (Throwable e) { + throw new RuntimeException("Error while wrapping sendRunMessage in a separated thread", e); + } + } + })) + .when(notificationService).sendRunMessage(any()); + } + + private UUID runAndCancel(CountDownLatch cancelLatch, int cancelDelayMs) throws Exception { + String dynamicSimulationParametersJson = "{}"; + + // run the dynamic margin calculation + MvcResult result = mockMvc.perform( + post("/v1/networks/{networkUuid}/run", NETWORK_UUID) + .param(VARIANT_ID_HEADER, VARIANT_1_ID) + .param("dynamicSecurityAnalysisParametersUuid", DSA_PARAMETERS_UUID.toString()) + .param("parametersUuid", PARAMETERS_UUID.toString()) + .contentType(APPLICATION_JSON) + .content(dynamicSimulationParametersJson) + .header(HEADER_USER_ID, "testUserId")) + .andExpect(status().isOk()) + .andReturn(); + + UUID runUuid = objectMapper.readValue(result.getResponse().getContentAsString(), UUID.class); + + // Should be running quickly after creation + assertResultStatus(runUuid, DynamicMarginCalculationStatus.RUNNING); + + // stop, with a timeout to avoid test hangs if an exception occurs before latch countdown + boolean completed = cancelLatch.await(5, TimeUnit.SECONDS); + if (!completed) { + throw new AssertionError("Timed out waiting for cancelLatch, something might have crashed before latch countdown happens."); + } + + // optional extra wait (simulate user cancelling early/late) + await().pollDelay(cancelDelayMs, TimeUnit.MILLISECONDS).until(() -> true); + + mockMvc.perform(put("/v1/results/{resultUuid}/stop", runUuid)) + .andExpect(status().isOk()); + + return runUuid; + } + + @Test + void testStopOnTime() throws Exception { + CountDownLatch cancelLatch = new CountDownLatch(1); + + // Emit messages in separate threads, like in production. + mockSendRunMessage(() -> { + // trigger stop at the beginning of computation + cancelLatch.countDown(); + + // fake a long computation (1s) + return CompletableFuture.supplyAsync( + () -> null, + CompletableFuture.delayedExecutor(1000, TimeUnit.MILLISECONDS) + ); + }); + + UUID runUuid = runAndCancel(cancelLatch, 0); + + // Must have a cancel message in the stop queue + Message message = output.receive(1000, dmcStoppedDestination); + assertThat(message).isNotNull(); + assertThat(message.getHeaders()) + .containsEntry(HEADER_RESULT_UUID, runUuid.toString()) + .containsKey(HEADER_MESSAGE); + + // result has been deleted by cancel so status is empty (null) + assertResultStatus(runUuid, null); + } + + @Test + void testStopEarly() throws Exception { + CountDownLatch cancelLatch = new CountDownLatch(1); + + // Emit messages in separate threads, like in production. + mockSendRunMessage(() -> CompletableFuture.supplyAsync(() -> null)); + + // Delay before the computation starts to simulate "cancel too early" + doAnswer(invocation -> { + Object object = invocation.callRealMethod(); + + cancelLatch.countDown(); + + await().pollDelay(1000, TimeUnit.MILLISECONDS).until(() -> true); + return object; + }).when(dynamicMarginCalculationWorkerService).preRun(any()); + + UUID runUuid = runAndCancel(cancelLatch, 0); + + // Must have a cancel failed message + Message message = output.receive(1000, dmcCancelFailedDestination); + assertThat(message).isNotNull(); + assertThat(message.getHeaders()) + .containsEntry(HEADER_RESULT_UUID, runUuid.toString()) + .containsKey(HEADER_MESSAGE); + + // cancel failed so result still exists (status remains RUNNING in this behaviour) + assertResultStatus(runUuid, DynamicMarginCalculationStatus.RUNNING); + } + + @Test + void testStopLately() throws Exception { + CountDownLatch cancelLatch = new CountDownLatch(1); + + // Emit messages in separate threads, like in production. + mockSendRunMessage(() -> { + // using latch to trigger stop dynamic margin calculation at the beginning of computation + cancelLatch.countDown(); + + // fake a short computation + return CompletableFuture.supplyAsync(MarginCalculationResult::empty); + }); + + // test run then cancel + UUID runUuid = runAndCancel(cancelLatch, 1000); + + // check result + // Computation finished quickly => must have a result message + Message message = output.receive(1000, dmcResultDestination); + assertThat(message).isNotNull(); + assertThat(message.getHeaders()) + .containsEntry(HEADER_RESULT_UUID, runUuid.toString()); + + // Stop arrives after end => cancel failed message + message = output.receive(1000, dmcCancelFailedDestination); + assertThat(message).isNotNull(); + assertThat(message.getHeaders()) + .containsEntry(HEADER_RESULT_UUID, runUuid.toString()) + .containsKey(HEADER_MESSAGE); + + // cancel failed so results are not deleted + assertResultStatus(runUuid, DynamicMarginCalculationStatus.SUCCEED); + } + + // --- END Test cancelling a running computation ---// +} diff --git a/src/test/java/org/gridsuite/dynamicmargincalculation/server/controller/DynamicMarginCalculationParametersControllerTest.java b/src/test/java/org/gridsuite/dynamicmargincalculation/server/controller/DynamicMarginCalculationParametersControllerTest.java new file mode 100644 index 0000000..8a13657 --- /dev/null +++ b/src/test/java/org/gridsuite/dynamicmargincalculation/server/controller/DynamicMarginCalculationParametersControllerTest.java @@ -0,0 +1,275 @@ +/** + * 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.dynamicmargincalculation.server.controller; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.gridsuite.dynamicmargincalculation.server.DynamicMarginCalculationApplication; +import org.gridsuite.dynamicmargincalculation.server.dto.parameters.DynamicMarginCalculationParametersInfos; +import org.gridsuite.dynamicmargincalculation.server.dto.parameters.IdNameInfos; +import org.gridsuite.dynamicmargincalculation.server.dto.parameters.LoadsVariationInfos; +import org.gridsuite.dynamicmargincalculation.server.entities.parameters.DynamicMarginCalculationParametersEntity; +import org.gridsuite.dynamicmargincalculation.server.repositories.DynamicMarginCalculationParametersRepository; +import org.gridsuite.dynamicmargincalculation.server.service.FilterService; +import org.gridsuite.dynamicmargincalculation.server.service.ParametersService; +import org.gridsuite.dynamicmargincalculation.server.service.client.DirectoryClient; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.bean.override.mockito.MockitoBean; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.MvcResult; + +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.gridsuite.computation.service.NotificationService.HEADER_USER_ID; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.*; +import static org.springframework.http.MediaType.APPLICATION_JSON; +import static org.springframework.http.MediaType.TEXT_PLAIN_VALUE; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +/** + * @author Thang PHAM + */ +@AutoConfigureMockMvc +@SpringBootTest +@ContextConfiguration(classes = {DynamicMarginCalculationApplication.class}) +class DynamicMarginCalculationParametersControllerTest { + + private static final String USER_ID = "userId"; + private static final UUID LOAD_FILTER_UUID_1 = UUID.fromString("fff118fa-12ff-4fe1-965d-1d81a45d8ef8"); + private static final UUID LOAD_FILTER_UUID_2 = UUID.fromString("96d0097b-ec80-4ac9-827a-4a38095972a0"); + + @Autowired + MockMvc mockMvc; + + @Autowired + ObjectMapper objectMapper; + + @Autowired + ParametersService parametersService; + + @Autowired + DynamicMarginCalculationParametersRepository parametersRepository; + + @MockitoBean + DirectoryClient directoryClient; + + @MockitoBean + FilterService filterService; + + @AfterEach + void tearDown() { + parametersRepository.deleteAll(); + reset(directoryClient, filterService); + } + + private DynamicMarginCalculationParametersInfos newParametersInfos() { + // Keep the DTO minimal and stable for CRUD tests (no need to involve filter evaluation). + DynamicMarginCalculationParametersInfos infos = parametersService.getDefaultParametersValues("Dynawo"); + infos.setLoadsVariations(List.of()); // make explicit to avoid null handling differences + return infos; + } + + private DynamicMarginCalculationParametersInfos newParametersInfosWithLoadFilters() { + DynamicMarginCalculationParametersInfos infos = parametersService.getDefaultParametersValues("Dynawo"); + + LoadsVariationInfos loadsVariationInfos = LoadsVariationInfos.builder() + .active(true) + .variation(10.0) + .loadFilters(List.of( + IdNameInfos.builder().id(LOAD_FILTER_UUID_1).build(), + IdNameInfos.builder().id(LOAD_FILTER_UUID_2).build() + )) + .build(); + + infos.setLoadsVariations(List.of(loadsVariationInfos)); + return infos; + } + + @Test + void testCreateParameters() throws Exception { + DynamicMarginCalculationParametersInfos parametersInfos = newParametersInfos(); + + MvcResult result = mockMvc.perform(post("/v1/parameters") + .contentType(APPLICATION_JSON) + .content(objectMapper.writeValueAsString(parametersInfos))) + .andExpect(status().isOk()) + .andReturn(); + + UUID parametersUuid = objectMapper.readValue(result.getResponse().getContentAsString(), UUID.class); + + Optional entityOpt = parametersRepository.findById(parametersUuid); + assertThat(entityOpt).isPresent(); + + DynamicMarginCalculationParametersInfos resultParametersInfos = entityOpt.get().toDto(true); + + assertThat(resultParametersInfos).usingRecursiveComparison().isEqualTo(parametersInfos); + } + + @Test + void testCreateDefaultParameters() throws Exception { + DynamicMarginCalculationParametersInfos defaultParametersInfos = newParametersInfos(); + + MvcResult result = mockMvc.perform(post("/v1/parameters/default")) + .andExpect(status().isOk()) + .andReturn(); + + UUID parametersUuid = objectMapper.readValue(result.getResponse().getContentAsString(), UUID.class); + + Optional entityOpt = parametersRepository.findById(parametersUuid); + assertThat(entityOpt).isPresent(); + assertThat(entityOpt.get().getProvider()).isEqualTo("Dynawo"); + DynamicMarginCalculationParametersInfos resultParametersInfos = entityOpt.get().toDto(true); + assertThat(resultParametersInfos).usingRecursiveComparison().isEqualTo(defaultParametersInfos); + } + + @Test + void testDuplicateParameters() throws Exception { + DynamicMarginCalculationParametersInfos originalInfos = newParametersInfos(); + UUID originalUuid = parametersRepository.save(new DynamicMarginCalculationParametersEntity(originalInfos)).getId(); + + MvcResult result = mockMvc.perform(post("/v1/parameters") + .param("duplicateFrom", originalUuid.toString())) + .andExpect(status().isOk()) + .andReturn(); + + UUID duplicatedUuid = objectMapper.readValue(result.getResponse().getContentAsString(), UUID.class); + + Optional duplicatedOpt = parametersRepository.findById(duplicatedUuid); + assertThat(duplicatedOpt).isPresent(); + + DynamicMarginCalculationParametersInfos duplicatedInfos = duplicatedOpt.get().toDto(true); + + assertThat(duplicatedInfos).usingRecursiveComparison().isEqualTo(originalInfos); + } + + @Test + void testGetParametersRequiresUserHeaderAndEnrichesLoadFilterNames() throws Exception { + // --- Setup: persist parameters with load filters (names missing before enrichment) --- // + DynamicMarginCalculationParametersInfos infos = newParametersInfosWithLoadFilters(); + UUID parametersUuid = parametersRepository.save(new DynamicMarginCalculationParametersEntity(infos)).getId(); + + // service enrichment uses DirectoryClient only when userId is provided + when(directoryClient.getElementNames(eq(List.of(LOAD_FILTER_UUID_1, LOAD_FILTER_UUID_2)), eq(USER_ID))) + .thenReturn(Map.of( + LOAD_FILTER_UUID_1, "Filter 1", + LOAD_FILTER_UUID_2, "Filter 2" + )); + + // --- Execute --- // + MvcResult result = mockMvc.perform(get("/v1/parameters/{uuid}", parametersUuid) + .header(HEADER_USER_ID, USER_ID)) + .andExpect(status().isOk()) + .andReturn(); + + DynamicMarginCalculationParametersInfos returned = + objectMapper.readValue(result.getResponse().getContentAsString(), DynamicMarginCalculationParametersInfos.class); + + // --- Verify --- // + assertThat(returned.getId()).isEqualTo(parametersUuid); + assertThat(returned.getLoadsVariations()).hasSize(1); + assertThat(returned.getLoadsVariations().getFirst().getLoadFilters()).hasSize(2); + + assertThat(returned.getLoadsVariations().getFirst().getLoadFilters().getFirst().getName()).isEqualTo("Filter 1"); + assertThat(returned.getLoadsVariations().getFirst().getLoadFilters().get(1).getName()).isEqualTo("Filter 2"); + + verify(directoryClient, times(1)).getElementNames(eq(List.of(LOAD_FILTER_UUID_1, LOAD_FILTER_UUID_2)), eq(USER_ID)); + } + + @Test + void testGetAllParameters() throws Exception { + DynamicMarginCalculationParametersInfos infos = newParametersInfos(); + parametersRepository.saveAll(List.of( + new DynamicMarginCalculationParametersEntity(infos), + new DynamicMarginCalculationParametersEntity(infos) + )); + + MvcResult result = mockMvc.perform(get("/v1/parameters")) + .andExpect(status().isOk()) + .andReturn(); + + List returned = objectMapper.readValue( + result.getResponse().getContentAsString(), + new TypeReference<>() { } + ); + + assertThat(returned).hasSize(2); + assertThat(returned.get(0).getId()).isNotNull(); + assertThat(returned.get(1).getId()).isNotNull(); + } + + @Test + void testUpdateParameters() throws Exception { + DynamicMarginCalculationParametersInfos infos = newParametersInfos(); + UUID parametersUuid = parametersRepository.save(new DynamicMarginCalculationParametersEntity(infos)).getId(); + + DynamicMarginCalculationParametersInfos updatedInfos = newParametersInfos(); + updatedInfos.setAccuracy(1); // change a field to ensure update persists + + mockMvc.perform(put("/v1/parameters/{uuid}", parametersUuid) + .contentType(APPLICATION_JSON) + .content(objectMapper.writeValueAsString(updatedInfos))) + .andExpect(status().isOk()); + + Optional entityOpt = parametersRepository.findById(parametersUuid); + assertThat(entityOpt).isPresent(); + + DynamicMarginCalculationParametersInfos persisted = entityOpt.get().toDto(false); + assertThat(persisted.getAccuracy()).isEqualTo(1); + } + + @Test + void testDeleteParameters() throws Exception { + DynamicMarginCalculationParametersInfos infos = newParametersInfos(); + UUID parametersUuid = parametersRepository.save(new DynamicMarginCalculationParametersEntity(infos)).getId(); + + mockMvc.perform(delete("/v1/parameters/{uuid}", parametersUuid)) + .andExpect(status().isOk()); + + assertThat(parametersRepository.findById(parametersUuid)).isEmpty(); + } + + @Test + void testGetProvider() throws Exception { + DynamicMarginCalculationParametersInfos infos = newParametersInfos(); + infos.setProvider("Dynawo"); + UUID parametersUuid = parametersRepository.save(new DynamicMarginCalculationParametersEntity(infos)).getId(); + + MvcResult result = mockMvc.perform(get("/v1/parameters/{uuid}/provider", parametersUuid)) + .andExpect(status().isOk()) + .andReturn(); + String provider = result.getResponse().getContentAsString(); + assertThat(provider).isEqualTo("Dynawo"); + } + + @Test + void testUpdateProvider() throws Exception { + DynamicMarginCalculationParametersInfos infos = newParametersInfos(); + UUID parametersUuid = parametersRepository.save(new DynamicMarginCalculationParametersEntity(infos)).getId(); + + String newProvider = "Dynawo"; + + mockMvc.perform(put("/v1/parameters/{uuid}/provider", parametersUuid) + .contentType(TEXT_PLAIN_VALUE) + .content(newProvider)) + .andExpect(status().isOk()); + + Optional entityOpt = parametersRepository.findById(parametersUuid); + assertThat(entityOpt).isPresent(); + assertThat(entityOpt.get().getProvider()).isEqualTo(newProvider); + } +} diff --git a/src/test/java/org/gridsuite/dynamicmargincalculation/server/controller/utils/DynamicModelConfigJsonUtils.java b/src/test/java/org/gridsuite/dynamicmargincalculation/server/controller/utils/DynamicModelConfigJsonUtils.java new file mode 100644 index 0000000..b156540 --- /dev/null +++ b/src/test/java/org/gridsuite/dynamicmargincalculation/server/controller/utils/DynamicModelConfigJsonUtils.java @@ -0,0 +1,35 @@ +/** + * 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.dynamicmargincalculation.server.controller.utils; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.module.SimpleModule; +import com.powsybl.commons.json.JsonUtil; +import com.powsybl.dynawo.suppliers.dynamicmodels.DynamicModelConfigsJsonDeserializer; + +import java.util.List; + +/** + * @author Thang PHAM + */ +public final class DynamicModelConfigJsonUtils { + + private DynamicModelConfigJsonUtils() { + throw new AssertionError("Utility class should not be instantiated"); + } + + public static ObjectMapper createObjectMapper() { + ObjectMapper mapper = JsonUtil.createObjectMapper(); + + SimpleModule module = new SimpleModule("dynamic-model-configs"); + module.addDeserializer(List.class, new DynamicModelConfigsJsonDeserializer()); + + mapper.registerModule(module); + return mapper; + } +} diff --git a/src/test/java/org/gridsuite/dynamicmargincalculation/server/controller/utils/TestUtils.java b/src/test/java/org/gridsuite/dynamicmargincalculation/server/controller/utils/TestUtils.java new file mode 100644 index 0000000..b9a6e46 --- /dev/null +++ b/src/test/java/org/gridsuite/dynamicmargincalculation/server/controller/utils/TestUtils.java @@ -0,0 +1,43 @@ +/** + * 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.dynamicmargincalculation.server.controller.utils; + +import org.springframework.cloud.stream.binder.test.OutputDestination; + +import java.util.List; +import java.util.concurrent.TimeUnit; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.testcontainers.shaded.org.awaitility.Awaitility.await; + +/** + * @author Thang PHAM + */ +public final class TestUtils { + + public static final String RESOURCE_PATH_DELIMITER = "/"; + + private static final long TIMEOUT = 100; + + private TestUtils() { + throw new AssertionError("Utility class should not be instantiated"); + } + + public static void assertQueuesEmptyThenClear(List destinations, OutputDestination output) { + await().pollDelay(TIMEOUT, TimeUnit.MILLISECONDS).until(() -> { + try { + destinations.forEach(destination -> assertThat(output.receive(0, destination)).as("Should not be any messages in queue " + destination + " : ").isNull()); + } catch (NullPointerException e) { + // Ignoring + } finally { + output.clear(); // purge in order to not fail the other tests + } + return true; + }); + } +} diff --git a/src/test/resources/com/powsybl/config/test/config.yml b/src/test/resources/com/powsybl/config/test/config.yml new file mode 100644 index 0000000..ab45183 --- /dev/null +++ b/src/test/resources/com/powsybl/config/test/config.yml @@ -0,0 +1,9 @@ +dynawo: + # home dir references to directory inside the container java-dynawo + homeDir: /dynaflow-launcher + debug: true + +dynawo-algorithms: + # home dir references to directory inside the container java-dynawo + homeDir: /dynaflow-launcher + debug: true diff --git a/src/test/resources/com/powsybl/config/test/filelist.txt b/src/test/resources/com/powsybl/config/test/filelist.txt new file mode 100644 index 0000000..e9abc7f --- /dev/null +++ b/src/test/resources/com/powsybl/config/test/filelist.txt @@ -0,0 +1 @@ +config.yml \ No newline at end of file diff --git a/src/test/resources/data/ieee14/IEEE14.iidm b/src/test/resources/data/ieee14/IEEE14.iidm new file mode 100644 index 0000000..ef4c3fd --- /dev/null +++ b/src/test/resources/data/ieee14/IEEE14.iidm @@ -0,0 +1,232 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/resources/data/ieee14/input/dynamicModel.dmp b/src/test/resources/data/ieee14/input/dynamicModel.dmp new file mode 100644 index 0000000..73a7557 --- /dev/null +++ b/src/test/resources/data/ieee14/input/dynamicModel.dmp @@ -0,0 +1 @@ +{"models":[{"model":"GeneratorSynchronousThreeWindingsProportionalRegulations","group":"IEEE14","groupType":"PREFIX","properties":[{"name":"staticId","value":"_GEN____6_SM","type":"STRING"}]},{"model":"GeneratorSynchronousThreeWindingsProportionalRegulations","group":"IEEE14","groupType":"PREFIX","properties":[{"name":"staticId","value":"_GEN____8_SM","type":"STRING"}]},{"model":"GeneratorSynchronousFourWindingsProportionalRegulations","group":"IEEE14","groupType":"PREFIX","properties":[{"name":"staticId","value":"_GEN____1_SM","type":"STRING"}]},{"model":"GeneratorSynchronousFourWindingsProportionalRegulations","group":"IEEE14","groupType":"PREFIX","properties":[{"name":"staticId","value":"_GEN____2_SM","type":"STRING"}]},{"model":"GeneratorSynchronousFourWindingsProportionalRegulations","group":"IEEE14","groupType":"PREFIX","properties":[{"name":"staticId","value":"_GEN____3_SM","type":"STRING"}]},{"model":"LoadAlphaBeta","group":"LAB","groupType":"FIXED","properties":[{"name":"staticId","value":"_LOAD__10_EC","type":"STRING"}]},{"model":"LoadAlphaBeta","group":"LAB","groupType":"FIXED","properties":[{"name":"staticId","value":"_LOAD__11_EC","type":"STRING"}]},{"model":"LoadAlphaBeta","group":"LAB","groupType":"FIXED","properties":[{"name":"staticId","value":"_LOAD__12_EC","type":"STRING"}]},{"model":"LoadAlphaBeta","group":"LAB","groupType":"FIXED","properties":[{"name":"staticId","value":"_LOAD__13_EC","type":"STRING"}]},{"model":"LoadAlphaBeta","group":"LAB","groupType":"FIXED","properties":[{"name":"staticId","value":"_LOAD__14_EC","type":"STRING"}]},{"model":"LoadAlphaBeta","group":"LAB","groupType":"FIXED","properties":[{"name":"staticId","value":"_LOAD___2_EC","type":"STRING"}]},{"model":"LoadAlphaBeta","group":"LAB","groupType":"FIXED","properties":[{"name":"staticId","value":"_LOAD___3_EC","type":"STRING"}]},{"model":"LoadAlphaBeta","group":"LAB","groupType":"FIXED","properties":[{"name":"staticId","value":"_LOAD___9_EC","type":"STRING"}]},{"model":"LoadAlphaBeta","group":"LAB","groupType":"FIXED","properties":[{"name":"staticId","value":"_LOAD___4_EC","type":"STRING"}]},{"model":"LoadAlphaBeta","group":"LAB","groupType":"FIXED","properties":[{"name":"staticId","value":"_LOAD___6_EC","type":"STRING"}]},{"model":"LoadAlphaBeta","group":"LAB","groupType":"FIXED","properties":[{"name":"staticId","value":"_LOAD___5_EC","type":"STRING"}]},{"model":"StaticVarCompensator","group":"SVarCT","groupType":"FIXED","properties":[{"name":"staticId","value":"SVC2","type":"STRING"}]},{"model":"OverloadManagementSystem","group":"CLA_2_4","groupType":"FIXED","properties":[{"name":"dynamicModelId","value":"CLA_1","type":"STRING"},{"name":"iMeasurement","value":"_BUS____2-BUS____4-1_AC","type":"STRING"},{"name":"iMeasurementSide","value":"TWO","type":"TWO_SIDES"},{"name":"controlledBranch","value":"_BUS____2-BUS____4-1_AC","type":"STRING"}]},{"model":"OverloadManagementSystem","group":"CLA_2_5","groupType":"FIXED","properties":[{"name":"dynamicModelId","value":"CLA_2","type":"STRING"},{"name":"iMeasurement","value":"_BUS____2-BUS____5-1_AC","type":"STRING"},{"name":"iMeasurementSide","value":"TWO","type":"TWO_SIDES"},{"name":"controlledBranch","value":"_BUS____2-BUS____5-1_AC","type":"STRING"}]},{"model":"TapChangerBlockingAutomaton","group":"TCB_2_4","groupType":"FIXED","properties":[{"name":"dynamicModelId","value":"TCB_1","type":"STRING"},{"name":"uMeasurements","values":["_BUS___11_TN","_BUS___12_TN"],"type":"STRING"},{"name":"transformers","values":["_LOAD__11_EC","_LOAD__12_EC"],"type":"STRING"}]},{"model":"TapChangerBlockingAutomaton","group":"TCB_2_5","groupType":"FIXED","properties":[{"name":"dynamicModelId","value":"TCB_2","type":"STRING"},{"name":"uMeasurements","value":"_BUS____4_TN","type":"STRING"},{"name":"transformers","value":"_BUS____4-BUS____9-1_PT","type":"STRING"}]}]} \ No newline at end of file diff --git a/src/test/resources/data/ieee14/input/dynamicSimulationParameters.dmp b/src/test/resources/data/ieee14/input/dynamicSimulationParameters.dmp new file mode 100644 index 0000000..d6a5299 --- /dev/null +++ b/src/test/resources/data/ieee14/input/dynamicSimulationParameters.dmp @@ -0,0 +1 @@ +{"version":"1.1","startTime":0.0,"stopTime":50.0,"extensions":{"DynawoSimulationParameters":{"networkParameters":{"id":"NETWORK","parameters":{"capacitor_no_reclosing_delay":{"name":"capacitor_no_reclosing_delay","type":"DOUBLE","value":"300.0"},"dangling_line_currentLimit_maxTimeOperation":{"name":"dangling_line_currentLimit_maxTimeOperation","type":"DOUBLE","value":"240.0"},"line_currentLimit_maxTimeOperation":{"name":"line_currentLimit_maxTimeOperation","type":"DOUBLE","value":"240.0"},"load_Tp":{"name":"load_Tp","type":"DOUBLE","value":"90.0"},"load_Tq":{"name":"load_Tq","type":"DOUBLE","value":"90.0"},"load_alpha":{"name":"load_alpha","type":"DOUBLE","value":"1.0"},"load_alphaLong":{"name":"load_alphaLong","type":"DOUBLE","value":"0.0"},"load_beta":{"name":"load_beta","type":"DOUBLE","value":"2.0"},"load_betaLong":{"name":"load_betaLong","type":"DOUBLE","value":"0.0"},"load_isControllable":{"name":"load_isControllable","type":"BOOL","value":"false"},"load_isRestorative":{"name":"load_isRestorative","type":"BOOL","value":"false"},"load_zPMax":{"name":"load_zPMax","type":"DOUBLE","value":"100.0"},"load_zQMax":{"name":"load_zQMax","type":"DOUBLE","value":"100.0"},"reactance_no_reclosing_delay":{"name":"reactance_no_reclosing_delay","type":"DOUBLE","value":"0.0"},"transformer_currentLimit_maxTimeOperation":{"name":"transformer_currentLimit_maxTimeOperation","type":"DOUBLE","value":"240.0"},"transformer_t1st_HT":{"name":"transformer_t1st_HT","type":"DOUBLE","value":"60.0"},"transformer_t1st_THT":{"name":"transformer_t1st_THT","type":"DOUBLE","value":"30.0"},"transformer_tNext_HT":{"name":"transformer_tNext_HT","type":"DOUBLE","value":"10.0"},"transformer_tNext_THT":{"name":"transformer_tNext_THT","type":"DOUBLE","value":"10.0"},"transformer_tolV":{"name":"transformer_tolV","type":"DOUBLE","value":"0.015"}},"references":[]},"solverParameters":{"id":"SIM","parameters":{"hMin":{"name":"hMin","type":"DOUBLE","value":"0.001"},"hMax":{"name":"hMax","type":"DOUBLE","value":"1.0"},"kReduceStep":{"name":"kReduceStep","type":"DOUBLE","value":"0.5"},"maxNewtonTry":{"name":"maxNewtonTry","type":"INT","value":"10"},"linearSolverName":{"name":"linearSolverName","type":"STRING","value":"KLU"},"fnormtol":{"name":"fnormtol","type":"DOUBLE","value":"0.001"},"initialaddtol":{"name":"initialaddtol","type":"DOUBLE","value":"1.0"},"scsteptol":{"name":"scsteptol","type":"DOUBLE","value":"0.001"},"mxnewtstep":{"name":"mxnewtstep","type":"DOUBLE","value":"10000.0"},"msbset":{"name":"msbset","type":"INT","value":"0"},"mxiter":{"name":"mxiter","type":"INT","value":"15"},"printfl":{"name":"printfl","type":"INT","value":"0"},"optimizeAlgebraicResidualsEvaluations":{"name":"optimizeAlgebraicResidualsEvaluations","type":"BOOL","value":"true"},"skipNRIfInitialGuessOK":{"name":"skipNRIfInitialGuessOK","type":"BOOL","value":"true"},"enableSilentZ":{"name":"enableSilentZ","type":"BOOL","value":"true"},"optimizeReinitAlgebraicResidualsEvaluations":{"name":"optimizeReinitAlgebraicResidualsEvaluations","type":"BOOL","value":"true"},"minimumModeChangeTypeForAlgebraicRestoration":{"name":"minimumModeChangeTypeForAlgebraicRestoration","type":"STRING","value":"ALGEBRAIC_J_UPDATE"},"minimumModeChangeTypeForAlgebraicRestorationInit":{"name":"minimumModeChangeTypeForAlgebraicRestorationInit","type":"STRING","value":"ALGEBRAIC_J_UPDATE"},"fnormtolAlg":{"name":"fnormtolAlg","type":"DOUBLE","value":"0.001"},"initialaddtolAlg":{"name":"initialaddtolAlg","type":"DOUBLE","value":"1.0"},"scsteptolAlg":{"name":"scsteptolAlg","type":"DOUBLE","value":"0.001"},"mxnewtstepAlg":{"name":"mxnewtstepAlg","type":"DOUBLE","value":"10000.0"},"msbsetAlg":{"name":"msbsetAlg","type":"INT","value":"5"},"mxiterAlg":{"name":"mxiterAlg","type":"INT","value":"30"},"printflAlg":{"name":"printflAlg","type":"INT","value":"0"},"fnormtolAlgJ":{"name":"fnormtolAlgJ","type":"DOUBLE","value":"0.001"},"initialaddtolAlgJ":{"name":"initialaddtolAlgJ","type":"DOUBLE","value":"1.0"},"scsteptolAlgJ":{"name":"scsteptolAlgJ","type":"DOUBLE","value":"0.001"},"mxnewtstepAlgJ":{"name":"mxnewtstepAlgJ","type":"DOUBLE","value":"10000.0"},"msbsetAlgJ":{"name":"msbsetAlgJ","type":"INT","value":"1"},"mxiterAlgJ":{"name":"mxiterAlgJ","type":"INT","value":"50"},"printflAlgJ":{"name":"printflAlgJ","type":"INT","value":"0"},"fnormtolAlgInit":{"name":"fnormtolAlgInit","type":"DOUBLE","value":"0.001"},"initialaddtolAlgInit":{"name":"initialaddtolAlgInit","type":"DOUBLE","value":"1.0"},"scsteptolAlgInit":{"name":"scsteptolAlgInit","type":"DOUBLE","value":"0.001"},"mxnewtstepAlgInit":{"name":"mxnewtstepAlgInit","type":"DOUBLE","value":"10000.0"},"msbsetAlgInit":{"name":"msbsetAlgInit","type":"INT","value":"1"},"mxiterAlgInit":{"name":"mxiterAlgInit","type":"INT","value":"50"},"printflAlgInit":{"name":"printflAlgInit","type":"INT","value":"0"},"maximumNumberSlowStepIncrease":{"name":"maximumNumberSlowStepIncrease","type":"INT","value":"40"},"minimalAcceptableStep":{"name":"minimalAcceptableStep","type":"DOUBLE","value":"0.001"}},"references":[]},"solverType":"SIM","mergeLoads":false,"modelSimplifiers":[],"dumpFileParameters":{"exportDumpFile":false,"useDumpFile":false,"dumpFileFolder":null,"dumpFile":null},"precision":1.0E-6,"timelineExportMode":"XML","logLevelFilter":"INFO","specificLogs":["NETWORK"],"criteriaFilePath":null,"additionalModelsPath":null,"modelsParameters":[{"id":"LAB","parameters":{"load_alpha":{"name":"load_alpha","type":"DOUBLE","value":"1"},"load_beta":{"name":"load_beta","type":"DOUBLE","value":"2"}},"references":[{"name":"load_P0Pu","type":"DOUBLE","origData":"IIDM","origName":"p_pu","componentId":null},{"name":"load_Q0Pu","type":"DOUBLE","origData":"IIDM","origName":"q_pu","componentId":null},{"name":"load_U0Pu","type":"DOUBLE","origData":"IIDM","origName":"v_pu","componentId":null},{"name":"load_UPhase0","type":"DOUBLE","origData":"IIDM","origName":"angle_pu","componentId":null}]},{"id":"IEEE14_GEN____6_SM","parameters":{"generator_UNom":{"name":"generator_UNom","type":"DOUBLE","value":"15"},"generator_SNom":{"name":"generator_SNom","type":"DOUBLE","value":"80"},"generator_PNomTurb":{"name":"generator_PNomTurb","type":"DOUBLE","value":"74.4"},"generator_PNomAlt":{"name":"generator_PNomAlt","type":"DOUBLE","value":"74.4"},"generator_ExcitationPu":{"name":"generator_ExcitationPu","type":"INT","value":"1"},"generator_MdPuEfd":{"name":"generator_MdPuEfd","type":"DOUBLE","value":"0"},"generator_H":{"name":"generator_H","type":"DOUBLE","value":"4.975"},"generator_DPu":{"name":"generator_DPu","type":"DOUBLE","value":"0"},"generator_SnTfo":{"name":"generator_SnTfo","type":"DOUBLE","value":"80"},"generator_UNomHV":{"name":"generator_UNomHV","type":"DOUBLE","value":"15"},"generator_UNomLV":{"name":"generator_UNomLV","type":"DOUBLE","value":"15"},"generator_UBaseHV":{"name":"generator_UBaseHV","type":"DOUBLE","value":"15"},"generator_UBaseLV":{"name":"generator_UBaseLV","type":"DOUBLE","value":"15"},"generator_RTfPu":{"name":"generator_RTfPu","type":"DOUBLE","value":"0.0"},"generator_XTfPu":{"name":"generator_XTfPu","type":"DOUBLE","value":"0.0"},"generator_RaPu":{"name":"generator_RaPu","type":"DOUBLE","value":"0.004"},"generator_XlPu":{"name":"generator_XlPu","type":"DOUBLE","value":"0.102"},"generator_XdPu":{"name":"generator_XdPu","type":"DOUBLE","value":"0.75"},"generator_XpdPu":{"name":"generator_XpdPu","type":"DOUBLE","value":"0.225"},"generator_XppdPu":{"name":"generator_XppdPu","type":"DOUBLE","value":"0.154"},"generator_Tpd0":{"name":"generator_Tpd0","type":"DOUBLE","value":"3"},"generator_Tppd0":{"name":"generator_Tppd0","type":"DOUBLE","value":"0.04"},"generator_XqPu":{"name":"generator_XqPu","type":"DOUBLE","value":"0.45"},"generator_XppqPu":{"name":"generator_XppqPu","type":"DOUBLE","value":"0.2"},"generator_Tppq0":{"name":"generator_Tppq0","type":"DOUBLE","value":"0.04"},"generator_md":{"name":"generator_md","type":"DOUBLE","value":"0.16"},"generator_mq":{"name":"generator_mq","type":"DOUBLE","value":"0.16"},"generator_nd":{"name":"generator_nd","type":"DOUBLE","value":"5.7"},"generator_nq":{"name":"generator_nq","type":"DOUBLE","value":"5.7"},"governor_KGover":{"name":"governor_KGover","type":"DOUBLE","value":"5"},"governor_PMin":{"name":"governor_PMin","type":"DOUBLE","value":"0"},"governor_PMax":{"name":"governor_PMax","type":"DOUBLE","value":"74.4"},"governor_PNom":{"name":"governor_PNom","type":"DOUBLE","value":"74.4"},"voltageRegulator_LagEfdMin":{"name":"voltageRegulator_LagEfdMin","type":"DOUBLE","value":"0"},"voltageRegulator_LagEfdMax":{"name":"voltageRegulator_LagEfdMax","type":"DOUBLE","value":"0"},"voltageRegulator_EfdMinPu":{"name":"voltageRegulator_EfdMinPu","type":"DOUBLE","value":"-5"},"voltageRegulator_EfdMaxPu":{"name":"voltageRegulator_EfdMaxPu","type":"DOUBLE","value":"5"},"voltageRegulator_UsRefMinPu":{"name":"voltageRegulator_UsRefMinPu","type":"DOUBLE","value":"0.8"},"voltageRegulator_UsRefMaxPu":{"name":"voltageRegulator_UsRefMaxPu","type":"DOUBLE","value":"1.2"},"voltageRegulator_Gain":{"name":"voltageRegulator_Gain","type":"DOUBLE","value":"20"},"URef_ValueIn":{"name":"URef_ValueIn","type":"DOUBLE","value":"0"},"Pm_ValueIn":{"name":"Pm_ValueIn","type":"DOUBLE","value":"0"},"generator_UseApproximation":{"name":"generator_UseApproximation","type":"BOOL","value":"true"}},"references":[{"name":"generator_P0Pu","type":"DOUBLE","origData":"IIDM","origName":"p_pu","componentId":null},{"name":"generator_Q0Pu","type":"DOUBLE","origData":"IIDM","origName":"q_pu","componentId":null},{"name":"generator_UPhase0","type":"DOUBLE","origData":"IIDM","origName":"angle_pu","componentId":null},{"name":"generator_U0Pu","type":"DOUBLE","origData":"IIDM","origName":"v_pu","componentId":null}]},{"id":"IEEE14_GEN____8_SM","parameters":{"generator_UNom":{"name":"generator_UNom","type":"DOUBLE","value":"18"},"generator_SNom":{"name":"generator_SNom","type":"DOUBLE","value":"250"},"generator_PNomTurb":{"name":"generator_PNomTurb","type":"DOUBLE","value":"228"},"generator_PNomAlt":{"name":"generator_PNomAlt","type":"DOUBLE","value":"228"},"generator_ExcitationPu":{"name":"generator_ExcitationPu","type":"INT","value":"1"},"generator_MdPuEfd":{"name":"generator_MdPuEfd","type":"DOUBLE","value":"0"},"generator_H":{"name":"generator_H","type":"DOUBLE","value":"2.748"},"generator_DPu":{"name":"generator_DPu","type":"DOUBLE","value":"0"},"generator_SnTfo":{"name":"generator_SnTfo","type":"DOUBLE","value":"250"},"generator_UNomHV":{"name":"generator_UNomHV","type":"DOUBLE","value":"13.8"},"generator_UNomLV":{"name":"generator_UNomLV","type":"DOUBLE","value":"18"},"generator_UBaseHV":{"name":"generator_UBaseHV","type":"DOUBLE","value":"13.8"},"generator_UBaseLV":{"name":"generator_UBaseLV","type":"DOUBLE","value":"18"},"generator_RTfPu":{"name":"generator_RTfPu","type":"DOUBLE","value":"0.0"},"generator_XTfPu":{"name":"generator_XTfPu","type":"DOUBLE","value":"0.1"},"generator_RaPu":{"name":"generator_RaPu","type":"DOUBLE","value":"0.004"},"generator_XlPu":{"name":"generator_XlPu","type":"DOUBLE","value":"0.11"},"generator_XdPu":{"name":"generator_XdPu","type":"DOUBLE","value":"1.53"},"generator_XpdPu":{"name":"generator_XpdPu","type":"DOUBLE","value":"0.31"},"generator_XppdPu":{"name":"generator_XppdPu","type":"DOUBLE","value":"0.275"},"generator_Tpd0":{"name":"generator_Tpd0","type":"DOUBLE","value":"8.4"},"generator_Tppd0":{"name":"generator_Tppd0","type":"DOUBLE","value":"0.096"},"generator_XqPu":{"name":"generator_XqPu","type":"DOUBLE","value":"0.99"},"generator_XppqPu":{"name":"generator_XppqPu","type":"DOUBLE","value":"0.58"},"generator_Tppq0":{"name":"generator_Tppq0","type":"DOUBLE","value":"0.56"},"generator_md":{"name":"generator_md","type":"DOUBLE","value":"0"},"generator_mq":{"name":"generator_mq","type":"DOUBLE","value":"0"},"generator_nd":{"name":"generator_nd","type":"DOUBLE","value":"0"},"generator_nq":{"name":"generator_nq","type":"DOUBLE","value":"0"},"governor_KGover":{"name":"governor_KGover","type":"DOUBLE","value":"5"},"governor_PMin":{"name":"governor_PMin","type":"DOUBLE","value":"0"},"governor_PMax":{"name":"governor_PMax","type":"DOUBLE","value":"228"},"governor_PNom":{"name":"governor_PNom","type":"DOUBLE","value":"228"},"voltageRegulator_LagEfdMin":{"name":"voltageRegulator_LagEfdMin","type":"DOUBLE","value":"0"},"voltageRegulator_LagEfdMax":{"name":"voltageRegulator_LagEfdMax","type":"DOUBLE","value":"0"},"voltageRegulator_EfdMinPu":{"name":"voltageRegulator_EfdMinPu","type":"DOUBLE","value":"-5"},"voltageRegulator_EfdMaxPu":{"name":"voltageRegulator_EfdMaxPu","type":"DOUBLE","value":"5"},"voltageRegulator_UsRefMinPu":{"name":"voltageRegulator_UsRefMinPu","type":"DOUBLE","value":"0.8"},"voltageRegulator_UsRefMaxPu":{"name":"voltageRegulator_UsRefMaxPu","type":"DOUBLE","value":"1.2"},"voltageRegulator_Gain":{"name":"voltageRegulator_Gain","type":"DOUBLE","value":"20"},"URef_ValueIn":{"name":"URef_ValueIn","type":"DOUBLE","value":"0"},"Pm_ValueIn":{"name":"Pm_ValueIn","type":"DOUBLE","value":"0"},"generator_UseApproximation":{"name":"generator_UseApproximation","type":"BOOL","value":"true"}},"references":[{"name":"generator_P0Pu","type":"DOUBLE","origData":"IIDM","origName":"p_pu","componentId":null},{"name":"generator_Q0Pu","type":"DOUBLE","origData":"IIDM","origName":"q_pu","componentId":null},{"name":"generator_UPhase0","type":"DOUBLE","origData":"IIDM","origName":"angle_pu","componentId":null},{"name":"generator_U0Pu","type":"DOUBLE","origData":"IIDM","origName":"v_pu","componentId":null}]},{"id":"IEEE14_GEN____1_SM","parameters":{"generator_UNom":{"name":"generator_UNom","type":"DOUBLE","value":"24"},"generator_SNom":{"name":"generator_SNom","type":"DOUBLE","value":"1211"},"generator_PNomTurb":{"name":"generator_PNomTurb","type":"DOUBLE","value":"1090"},"generator_PNomAlt":{"name":"generator_PNomAlt","type":"DOUBLE","value":"1090"},"generator_ExcitationPu":{"name":"generator_ExcitationPu","type":"INT","value":"1"},"generator_MdPuEfd":{"name":"generator_MdPuEfd","type":"DOUBLE","value":"0"},"generator_H":{"name":"generator_H","type":"DOUBLE","value":"5.4"},"generator_DPu":{"name":"generator_DPu","type":"DOUBLE","value":"0"},"generator_SnTfo":{"name":"generator_SnTfo","type":"DOUBLE","value":"1211"},"generator_UNomHV":{"name":"generator_UNomHV","type":"DOUBLE","value":"69"},"generator_UNomLV":{"name":"generator_UNomLV","type":"DOUBLE","value":"24"},"generator_UBaseHV":{"name":"generator_UBaseHV","type":"DOUBLE","value":"69"},"generator_UBaseLV":{"name":"generator_UBaseLV","type":"DOUBLE","value":"24"},"generator_RTfPu":{"name":"generator_RTfPu","type":"DOUBLE","value":"0.0"},"generator_XTfPu":{"name":"generator_XTfPu","type":"DOUBLE","value":"0.1"},"generator_RaPu":{"name":"generator_RaPu","type":"DOUBLE","value":"0.002796"},"generator_XlPu":{"name":"generator_XlPu","type":"DOUBLE","value":"0.202"},"generator_XdPu":{"name":"generator_XdPu","type":"DOUBLE","value":"2.22"},"generator_XpdPu":{"name":"generator_XpdPu","type":"DOUBLE","value":"0.384"},"generator_XppdPu":{"name":"generator_XppdPu","type":"DOUBLE","value":"0.264"},"generator_Tpd0":{"name":"generator_Tpd0","type":"DOUBLE","value":"8.094"},"generator_Tppd0":{"name":"generator_Tppd0","type":"DOUBLE","value":"0.08"},"generator_XqPu":{"name":"generator_XqPu","type":"DOUBLE","value":"2.22"},"generator_XppqPu":{"name":"generator_XppqPu","type":"DOUBLE","value":"0.262"},"generator_Tppq0":{"name":"generator_Tppq0","type":"DOUBLE","value":"0.084"},"generator_md":{"name":"generator_md","type":"DOUBLE","value":"0.215"},"generator_mq":{"name":"generator_mq","type":"DOUBLE","value":"0.215"},"generator_nd":{"name":"generator_nd","type":"DOUBLE","value":"6.995"},"generator_nq":{"name":"generator_nq","type":"DOUBLE","value":"6.995"},"generator_XpqPu":{"name":"generator_XpqPu","type":"DOUBLE","value":"0.393"},"generator_Tpq0":{"name":"generator_Tpq0","type":"DOUBLE","value":"1.572"},"governor_KGover":{"name":"governor_KGover","type":"DOUBLE","value":"5"},"governor_PMin":{"name":"governor_PMin","type":"DOUBLE","value":"0"},"governor_PMax":{"name":"governor_PMax","type":"DOUBLE","value":"1090"},"governor_PNom":{"name":"governor_PNom","type":"DOUBLE","value":"1090"},"voltageRegulator_LagEfdMin":{"name":"voltageRegulator_LagEfdMin","type":"DOUBLE","value":"0"},"voltageRegulator_LagEfdMax":{"name":"voltageRegulator_LagEfdMax","type":"DOUBLE","value":"0"},"voltageRegulator_EfdMinPu":{"name":"voltageRegulator_EfdMinPu","type":"DOUBLE","value":"-5"},"voltageRegulator_EfdMaxPu":{"name":"voltageRegulator_EfdMaxPu","type":"DOUBLE","value":"5"},"voltageRegulator_UsRefMinPu":{"name":"voltageRegulator_UsRefMinPu","type":"DOUBLE","value":"0.8"},"voltageRegulator_UsRefMaxPu":{"name":"voltageRegulator_UsRefMaxPu","type":"DOUBLE","value":"1.2"},"voltageRegulator_Gain":{"name":"voltageRegulator_Gain","type":"DOUBLE","value":"20"},"URef_ValueIn":{"name":"URef_ValueIn","type":"DOUBLE","value":"0"},"Pm_ValueIn":{"name":"Pm_ValueIn","type":"DOUBLE","value":"0"},"generator_UseApproximation":{"name":"generator_UseApproximation","type":"BOOL","value":"true"}},"references":[{"name":"generator_P0Pu","type":"DOUBLE","origData":"IIDM","origName":"p_pu","componentId":null},{"name":"generator_Q0Pu","type":"DOUBLE","origData":"IIDM","origName":"q_pu","componentId":null},{"name":"generator_UPhase0","type":"DOUBLE","origData":"IIDM","origName":"angle_pu","componentId":null},{"name":"generator_U0Pu","type":"DOUBLE","origData":"IIDM","origName":"v_pu","componentId":null}]},{"id":"IEEE14_GEN____2_SM","parameters":{"generator_UNom":{"name":"generator_UNom","type":"DOUBLE","value":"24"},"generator_SNom":{"name":"generator_SNom","type":"DOUBLE","value":"1120"},"generator_PNomTurb":{"name":"generator_PNomTurb","type":"DOUBLE","value":"1008"},"generator_PNomAlt":{"name":"generator_PNomAlt","type":"DOUBLE","value":"1008"},"generator_ExcitationPu":{"name":"generator_ExcitationPu","type":"INT","value":"1"},"generator_MdPuEfd":{"name":"generator_MdPuEfd","type":"DOUBLE","value":"0"},"generator_H":{"name":"generator_H","type":"DOUBLE","value":"6.3"},"generator_DPu":{"name":"generator_DPu","type":"DOUBLE","value":"0"},"generator_SnTfo":{"name":"generator_SnTfo","type":"DOUBLE","value":"1120"},"generator_UNomHV":{"name":"generator_UNomHV","type":"DOUBLE","value":"69"},"generator_UNomLV":{"name":"generator_UNomLV","type":"DOUBLE","value":"24"},"generator_UBaseHV":{"name":"generator_UBaseHV","type":"DOUBLE","value":"69"},"generator_UBaseLV":{"name":"generator_UBaseLV","type":"DOUBLE","value":"24"},"generator_RTfPu":{"name":"generator_RTfPu","type":"DOUBLE","value":"0.0"},"generator_XTfPu":{"name":"generator_XTfPu","type":"DOUBLE","value":"0.1"},"generator_RaPu":{"name":"generator_RaPu","type":"DOUBLE","value":"0.00357"},"generator_XlPu":{"name":"generator_XlPu","type":"DOUBLE","value":"0.219"},"generator_XdPu":{"name":"generator_XdPu","type":"DOUBLE","value":"2.57"},"generator_XpdPu":{"name":"generator_XpdPu","type":"DOUBLE","value":"0.407"},"generator_XppdPu":{"name":"generator_XppdPu","type":"DOUBLE","value":"0.3"},"generator_Tpd0":{"name":"generator_Tpd0","type":"DOUBLE","value":"9.651"},"generator_Tppd0":{"name":"generator_Tppd0","type":"DOUBLE","value":"0.058"},"generator_XqPu":{"name":"generator_XqPu","type":"DOUBLE","value":"2.57"},"generator_XppqPu":{"name":"generator_XppqPu","type":"DOUBLE","value":"0.301"},"generator_Tppq0":{"name":"generator_Tppq0","type":"DOUBLE","value":"0.06"},"generator_md":{"name":"generator_md","type":"DOUBLE","value":"0.084"},"generator_mq":{"name":"generator_mq","type":"DOUBLE","value":"0.084"},"generator_nd":{"name":"generator_nd","type":"DOUBLE","value":"5.57"},"generator_nq":{"name":"generator_nq","type":"DOUBLE","value":"5.57"},"generator_XpqPu":{"name":"generator_XpqPu","type":"DOUBLE","value":"0.454"},"generator_Tpq0":{"name":"generator_Tpq0","type":"DOUBLE","value":"1.009"},"governor_KGover":{"name":"governor_KGover","type":"DOUBLE","value":"5"},"governor_PMin":{"name":"governor_PMin","type":"DOUBLE","value":"0"},"governor_PMax":{"name":"governor_PMax","type":"DOUBLE","value":"1008"},"governor_PNom":{"name":"governor_PNom","type":"DOUBLE","value":"1008"},"voltageRegulator_LagEfdMin":{"name":"voltageRegulator_LagEfdMin","type":"DOUBLE","value":"0"},"voltageRegulator_LagEfdMax":{"name":"voltageRegulator_LagEfdMax","type":"DOUBLE","value":"0"},"voltageRegulator_EfdMinPu":{"name":"voltageRegulator_EfdMinPu","type":"DOUBLE","value":"-5"},"voltageRegulator_EfdMaxPu":{"name":"voltageRegulator_EfdMaxPu","type":"DOUBLE","value":"5"},"voltageRegulator_UsRefMinPu":{"name":"voltageRegulator_UsRefMinPu","type":"DOUBLE","value":"0.8"},"voltageRegulator_UsRefMaxPu":{"name":"voltageRegulator_UsRefMaxPu","type":"DOUBLE","value":"1.2"},"voltageRegulator_Gain":{"name":"voltageRegulator_Gain","type":"DOUBLE","value":"20"},"URef_ValueIn":{"name":"URef_ValueIn","type":"DOUBLE","value":"0"},"Pm_ValueIn":{"name":"Pm_ValueIn","type":"DOUBLE","value":"0"},"generator_UseApproximation":{"name":"generator_UseApproximation","type":"BOOL","value":"true"}},"references":[{"name":"generator_P0Pu","type":"DOUBLE","origData":"IIDM","origName":"p_pu","componentId":null},{"name":"generator_Q0Pu","type":"DOUBLE","origData":"IIDM","origName":"q_pu","componentId":null},{"name":"generator_UPhase0","type":"DOUBLE","origData":"IIDM","origName":"angle_pu","componentId":null},{"name":"generator_U0Pu","type":"DOUBLE","origData":"IIDM","origName":"v_pu","componentId":null}]},{"id":"IEEE14_GEN____3_SM","parameters":{"generator_UNom":{"name":"generator_UNom","type":"DOUBLE","value":"20"},"generator_SNom":{"name":"generator_SNom","type":"DOUBLE","value":"1650"},"generator_PNomTurb":{"name":"generator_PNomTurb","type":"DOUBLE","value":"1485"},"generator_PNomAlt":{"name":"generator_PNomAlt","type":"DOUBLE","value":"1485"},"generator_ExcitationPu":{"name":"generator_ExcitationPu","type":"INT","value":"1"},"generator_MdPuEfd":{"name":"generator_MdPuEfd","type":"DOUBLE","value":"0"},"generator_H":{"name":"generator_H","type":"DOUBLE","value":"5.625"},"generator_DPu":{"name":"generator_DPu","type":"DOUBLE","value":"0"},"generator_SnTfo":{"name":"generator_SnTfo","type":"DOUBLE","value":"1650"},"generator_UNomHV":{"name":"generator_UNomHV","type":"DOUBLE","value":"69"},"generator_UNomLV":{"name":"generator_UNomLV","type":"DOUBLE","value":"20"},"generator_UBaseHV":{"name":"generator_UBaseHV","type":"DOUBLE","value":"69"},"generator_UBaseLV":{"name":"generator_UBaseLV","type":"DOUBLE","value":"20"},"generator_RTfPu":{"name":"generator_RTfPu","type":"DOUBLE","value":"0.0"},"generator_XTfPu":{"name":"generator_XTfPu","type":"DOUBLE","value":"0.1"},"generator_RaPu":{"name":"generator_RaPu","type":"DOUBLE","value":"0.00316"},"generator_XlPu":{"name":"generator_XlPu","type":"DOUBLE","value":"0.256"},"generator_XdPu":{"name":"generator_XdPu","type":"DOUBLE","value":"2.81"},"generator_XpdPu":{"name":"generator_XpdPu","type":"DOUBLE","value":"0.509"},"generator_XppdPu":{"name":"generator_XppdPu","type":"DOUBLE","value":"0.354"},"generator_Tpd0":{"name":"generator_Tpd0","type":"DOUBLE","value":"10.041"},"generator_Tppd0":{"name":"generator_Tppd0","type":"DOUBLE","value":"0.065"},"generator_XqPu":{"name":"generator_XqPu","type":"DOUBLE","value":"2.62"},"generator_XppqPu":{"name":"generator_XppqPu","type":"DOUBLE","value":"0.377"},"generator_Tppq0":{"name":"generator_Tppq0","type":"DOUBLE","value":"0.094"},"generator_md":{"name":"generator_md","type":"DOUBLE","value":"0.05"},"generator_mq":{"name":"generator_mq","type":"DOUBLE","value":"0.05"},"generator_nd":{"name":"generator_nd","type":"DOUBLE","value":"9.285"},"generator_nq":{"name":"generator_nq","type":"DOUBLE","value":"9.285"},"generator_XpqPu":{"name":"generator_XpqPu","type":"DOUBLE","value":"0.601"},"generator_Tpq0":{"name":"generator_Tpq0","type":"DOUBLE","value":"1.22"},"governor_KGover":{"name":"governor_KGover","type":"DOUBLE","value":"5"},"governor_PMin":{"name":"governor_PMin","type":"DOUBLE","value":"0"},"governor_PMax":{"name":"governor_PMax","type":"DOUBLE","value":"1485"},"governor_PNom":{"name":"governor_PNom","type":"DOUBLE","value":"1485"},"voltageRegulator_LagEfdMin":{"name":"voltageRegulator_LagEfdMin","type":"DOUBLE","value":"0"},"voltageRegulator_LagEfdMax":{"name":"voltageRegulator_LagEfdMax","type":"DOUBLE","value":"0"},"voltageRegulator_EfdMinPu":{"name":"voltageRegulator_EfdMinPu","type":"DOUBLE","value":"-5"},"voltageRegulator_EfdMaxPu":{"name":"voltageRegulator_EfdMaxPu","type":"DOUBLE","value":"5"},"voltageRegulator_UsRefMinPu":{"name":"voltageRegulator_UsRefMinPu","type":"DOUBLE","value":"0.8"},"voltageRegulator_UsRefMaxPu":{"name":"voltageRegulator_UsRefMaxPu","type":"DOUBLE","value":"1.2"},"voltageRegulator_Gain":{"name":"voltageRegulator_Gain","type":"DOUBLE","value":"20"},"URef_ValueIn":{"name":"URef_ValueIn","type":"DOUBLE","value":"0"},"Pm_ValueIn":{"name":"Pm_ValueIn","type":"DOUBLE","value":"0"},"generator_UseApproximation":{"name":"generator_UseApproximation","type":"BOOL","value":"true"}},"references":[{"name":"generator_P0Pu","type":"DOUBLE","origData":"IIDM","origName":"p_pu","componentId":null},{"name":"generator_Q0Pu","type":"DOUBLE","origData":"IIDM","origName":"q_pu","componentId":null},{"name":"generator_UPhase0","type":"DOUBLE","origData":"IIDM","origName":"angle_pu","componentId":null},{"name":"generator_U0Pu","type":"DOUBLE","origData":"IIDM","origName":"v_pu","componentId":null}]},{"id":"GPQ","parameters":{"generator_AlphaPuPNom":{"name":"generator_AlphaPuPNom","type":"DOUBLE","value":"25"}},"references":[{"name":"generator_PNom","type":"DOUBLE","origData":"IIDM","origName":"pMax","componentId":null},{"name":"generator_PMin","type":"DOUBLE","origData":"IIDM","origName":"pMin","componentId":null},{"name":"generator_PMax","type":"DOUBLE","origData":"IIDM","origName":"pMax","componentId":null},{"name":"generator_P0Pu","type":"DOUBLE","origData":"IIDM","origName":"p_pu","componentId":null},{"name":"generator_Q0Pu","type":"DOUBLE","origData":"IIDM","origName":"q_pu","componentId":null},{"name":"generator_U0Pu","type":"DOUBLE","origData":"IIDM","origName":"v_pu","componentId":null},{"name":"generator_UPhase0","type":"DOUBLE","origData":"IIDM","origName":"angle_pu","componentId":null}]},{"id":"SVarCT","parameters":{"SVarC_BMaxPu":{"name":"SVarC_BMaxPu","type":"DOUBLE","value":"1.0678"},"SVarC_BMinPu":{"name":"SVarC_BMinPu","type":"DOUBLE","value":"-1.0466"},"SVarC_BShuntPu":{"name":"SVarC_BShuntPu","type":"DOUBLE","value":"0"},"SVarC_IMaxPu":{"name":"SVarC_IMaxPu","type":"DOUBLE","value":"1"},"SVarC_IMinPu":{"name":"SVarC_IMinPu","type":"DOUBLE","value":"-1"},"SVarC_KCurrentLimiter":{"name":"SVarC_KCurrentLimiter","type":"DOUBLE","value":"8"},"SVarC_Kp":{"name":"SVarC_Kp","type":"DOUBLE","value":"1.75"},"SVarC_Lambda":{"name":"SVarC_Lambda","type":"DOUBLE","value":"0.01"},"SVarC_SNom":{"name":"SVarC_SNom","type":"DOUBLE","value":"225"},"SVarC_Ti":{"name":"SVarC_Ti","type":"DOUBLE","value":"0.003428"},"SVarC_UBlock":{"name":"SVarC_UBlock","type":"DOUBLE","value":"5"},"SVarC_UNom":{"name":"SVarC_UNom","type":"DOUBLE","value":"250"},"SVarC_URefDown":{"name":"SVarC_URefDown","type":"DOUBLE","value":"220"},"SVarC_URefUp":{"name":"SVarC_URefUp","type":"DOUBLE","value":"230"},"SVarC_UThresholdDown":{"name":"SVarC_UThresholdDown","type":"DOUBLE","value":"218"},"SVarC_UThresholdUp":{"name":"SVarC_UThresholdUp","type":"DOUBLE","value":"240"},"SVarC_UUnblockDown":{"name":"SVarC_UUnblockDown","type":"DOUBLE","value":"180"},"SVarC_UUnblockUp":{"name":"SVarC_UUnblockUp","type":"DOUBLE","value":"270"},"SVarC_tThresholdDown":{"name":"SVarC_tThresholdDown","type":"DOUBLE","value":"0"},"SVarC_tThresholdUp":{"name":"SVarC_tThresholdUp","type":"DOUBLE","value":"60"}},"references":[{"name":"SVarC_Mode0","type":"INT","origData":"IIDM","origName":"regulatingMode","componentId":null},{"name":"SVarC_P0Pu","type":"DOUBLE","origData":"IIDM","origName":"p_pu","componentId":null},{"name":"SVarC_Q0Pu","type":"DOUBLE","origData":"IIDM","origName":"q_pu","componentId":null},{"name":"SVarC_U0Pu","type":"DOUBLE","origData":"IIDM","origName":"v_pu","componentId":null},{"name":"SVarC_UPhase0","type":"DOUBLE","origData":"IIDM","origName":"angle_pu","componentId":null}]},{"id":"CLA_2_4","parameters":{"currentLimitAutomaton_OrderToEmit":{"name":"currentLimitAutomaton_OrderToEmit","type":"INT","value":"3"},"currentLimitAutomaton_Running":{"name":"currentLimitAutomaton_Running","type":"BOOL","value":"true"},"currentLimitAutomaton_IMax":{"name":"currentLimitAutomaton_IMax","type":"DOUBLE","value":"1000"},"currentLimitAutomaton_tLagBeforeActing":{"name":"currentLimitAutomaton_tLagBeforeActing","type":"DOUBLE","value":"10"}},"references":[]},{"id":"CLA_2_5","parameters":{"currentLimitAutomaton_OrderToEmit":{"name":"currentLimitAutomaton_OrderToEmit","type":"INT","value":"1"},"currentLimitAutomaton_Running":{"name":"currentLimitAutomaton_Running","type":"BOOL","value":"true"},"currentLimitAutomaton_IMax":{"name":"currentLimitAutomaton_IMax","type":"DOUBLE","value":"600"},"currentLimitAutomaton_tLagBeforeActing":{"name":"currentLimitAutomaton_tLagBeforeActing","type":"DOUBLE","value":"5"}},"references":[]},{"id":"TCB_2_4","parameters":{"tapChangerBlocking_UMin":{"name":"tapChangerBlocking_UMin","type":"DOUBLE","value":"14"},"tapChangerBlocking_tLagBeforeBlocked":{"name":"tapChangerBlocking_tLagBeforeBlocked","type":"DOUBLE","value":"104"},"tapChangerBlocking_tLagTransBlockedD":{"name":"tapChangerBlocking_tLagTransBlockedD","type":"DOUBLE","value":"1004"},"tapChangerBlocking_tLagTransBlockedT":{"name":"tapChangerBlocking_tLagTransBlockedT","type":"DOUBLE","value":"14"}},"references":[]},{"id":"TCB_2_5","parameters":{"tapChangerBlocking_UMin":{"name":"tapChangerBlocking_UMin","type":"DOUBLE","value":"15"},"tapChangerBlocking_tLagBeforeBlocked":{"name":"tapChangerBlocking_tLagBeforeBlocked","type":"DOUBLE","value":"105"},"tapChangerBlocking_tLagTransBlockedD":{"name":"tapChangerBlocking_tLagTransBlockedD","type":"DOUBLE","value":"1005"},"tapChangerBlocking_tLagTransBlockedT":{"name":"tapChangerBlocking_tLagTransBlockedT","type":"DOUBLE","value":"15"}},"references":[]}]}}} \ No newline at end of file From 381b19400b8d8ba29627285e1b2473fc42429b01 Mon Sep 17 00:00:00 2001 From: Thang PHAM Date: Fri, 13 Feb 2026 00:11:52 +0100 Subject: [PATCH 09/17] Increase timeout to 2s Signed-off-by: Thang PHAM --- .../DynamicMarginCalculationControllerIEEE14Test.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/org/gridsuite/dynamicmargincalculation/server/controller/DynamicMarginCalculationControllerIEEE14Test.java b/src/test/java/org/gridsuite/dynamicmargincalculation/server/controller/DynamicMarginCalculationControllerIEEE14Test.java index 132f3fa..8162be9 100644 --- a/src/test/java/org/gridsuite/dynamicmargincalculation/server/controller/DynamicMarginCalculationControllerIEEE14Test.java +++ b/src/test/java/org/gridsuite/dynamicmargincalculation/server/controller/DynamicMarginCalculationControllerIEEE14Test.java @@ -201,7 +201,7 @@ void test01IEEE14() throws Exception { UUID runUuid = objectMapper.readValue(result.getResponse().getContentAsString(), UUID.class); - Message messageSwitch = output.receive(1000, dmcResultDestination); + Message messageSwitch = output.receive(2000, dmcResultDestination); assertThat(messageSwitch).isNotNull(); assertThat(messageSwitch.getHeaders()).containsEntry(HEADER_RESULT_UUID, runUuid.toString()); From c665b7d192ee53717b474a0abb6900641b115c05 Mon Sep 17 00:00:00 2001 From: Thang PHAM Date: Fri, 13 Feb 2026 10:49:43 +0100 Subject: [PATCH 10/17] try ignore IEEE14 test Signed-off-by: Thang PHAM --- .../DynamicMarginCalculationControllerIEEE14Test.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/test/java/org/gridsuite/dynamicmargincalculation/server/controller/DynamicMarginCalculationControllerIEEE14Test.java b/src/test/java/org/gridsuite/dynamicmargincalculation/server/controller/DynamicMarginCalculationControllerIEEE14Test.java index 8162be9..b5fd84e 100644 --- a/src/test/java/org/gridsuite/dynamicmargincalculation/server/controller/DynamicMarginCalculationControllerIEEE14Test.java +++ b/src/test/java/org/gridsuite/dynamicmargincalculation/server/controller/DynamicMarginCalculationControllerIEEE14Test.java @@ -30,6 +30,7 @@ import org.gridsuite.filter.utils.expertfilter.CombinatorType; import org.gridsuite.filter.utils.expertfilter.FieldType; import org.gridsuite.filter.utils.expertfilter.OperatorType; +import org.junit.Ignore; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.stream.binder.test.OutputDestination; @@ -181,6 +182,7 @@ private void initDynamicSecurityAnalysisClientMock() { .build()); } + @Ignore @Test void test01IEEE14() throws Exception { // The controller requires a request body string: dynamicSimulationParametersJson. From f3d18d5ac25ed3693044b60e7d08ec01e3f7fa8d Mon Sep 17 00:00:00 2001 From: Thang PHAM Date: Fri, 13 Feb 2026 11:07:58 +0100 Subject: [PATCH 11/17] try ignore IEEE14 test in Junit 5 Signed-off-by: Thang PHAM --- .../DynamicMarginCalculationControllerIEEE14Test.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/java/org/gridsuite/dynamicmargincalculation/server/controller/DynamicMarginCalculationControllerIEEE14Test.java b/src/test/java/org/gridsuite/dynamicmargincalculation/server/controller/DynamicMarginCalculationControllerIEEE14Test.java index b5fd84e..94b922d 100644 --- a/src/test/java/org/gridsuite/dynamicmargincalculation/server/controller/DynamicMarginCalculationControllerIEEE14Test.java +++ b/src/test/java/org/gridsuite/dynamicmargincalculation/server/controller/DynamicMarginCalculationControllerIEEE14Test.java @@ -30,7 +30,7 @@ import org.gridsuite.filter.utils.expertfilter.CombinatorType; import org.gridsuite.filter.utils.expertfilter.FieldType; import org.gridsuite.filter.utils.expertfilter.OperatorType; -import org.junit.Ignore; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.stream.binder.test.OutputDestination; @@ -182,7 +182,7 @@ private void initDynamicSecurityAnalysisClientMock() { .build()); } - @Ignore + @Disabled("To ignore test with container") @Test void test01IEEE14() throws Exception { // The controller requires a request body string: dynamicSimulationParametersJson. From baba3ea5f6b80868ffc41a506b6a7951262dc25c Mon Sep 17 00:00:00 2001 From: Thang PHAM Date: Fri, 13 Feb 2026 11:39:53 +0100 Subject: [PATCH 12/17] try ignore other tests except IEEE14 Signed-off-by: Thang PHAM --- .../server/PropertyServerNameProviderTest.java | 2 ++ .../DynamicMarginCalculationControllerIEEE14Test.java | 3 +-- .../controller/DynamicMarginCalculationControllerTest.java | 2 ++ .../DynamicMarginCalculationParametersControllerTest.java | 2 ++ .../server/controller/SupervisionControllerTest.java | 2 ++ .../service/DynamicMarginCalculationResultServiceTest.java | 2 ++ 6 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/test/java/org/gridsuite/dynamicmargincalculation/server/PropertyServerNameProviderTest.java b/src/test/java/org/gridsuite/dynamicmargincalculation/server/PropertyServerNameProviderTest.java index 7056fee..ba6b2dc 100644 --- a/src/test/java/org/gridsuite/dynamicmargincalculation/server/PropertyServerNameProviderTest.java +++ b/src/test/java/org/gridsuite/dynamicmargincalculation/server/PropertyServerNameProviderTest.java @@ -6,6 +6,7 @@ */ package org.gridsuite.dynamicmargincalculation.server; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; @@ -13,6 +14,7 @@ /** * @author Mohamed Ben-rejeb {@literal } */ +@Disabled class PropertyServerNameProviderTest { @Test diff --git a/src/test/java/org/gridsuite/dynamicmargincalculation/server/controller/DynamicMarginCalculationControllerIEEE14Test.java b/src/test/java/org/gridsuite/dynamicmargincalculation/server/controller/DynamicMarginCalculationControllerIEEE14Test.java index 94b922d..959376e 100644 --- a/src/test/java/org/gridsuite/dynamicmargincalculation/server/controller/DynamicMarginCalculationControllerIEEE14Test.java +++ b/src/test/java/org/gridsuite/dynamicmargincalculation/server/controller/DynamicMarginCalculationControllerIEEE14Test.java @@ -30,7 +30,6 @@ import org.gridsuite.filter.utils.expertfilter.CombinatorType; import org.gridsuite.filter.utils.expertfilter.FieldType; import org.gridsuite.filter.utils.expertfilter.OperatorType; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.stream.binder.test.OutputDestination; @@ -182,7 +181,7 @@ private void initDynamicSecurityAnalysisClientMock() { .build()); } - @Disabled("To ignore test with container") + // @Disabled("To ignore test with container") @Test void test01IEEE14() throws Exception { // The controller requires a request body string: dynamicSimulationParametersJson. diff --git a/src/test/java/org/gridsuite/dynamicmargincalculation/server/controller/DynamicMarginCalculationControllerTest.java b/src/test/java/org/gridsuite/dynamicmargincalculation/server/controller/DynamicMarginCalculationControllerTest.java index 634355d..646c46e 100644 --- a/src/test/java/org/gridsuite/dynamicmargincalculation/server/controller/DynamicMarginCalculationControllerTest.java +++ b/src/test/java/org/gridsuite/dynamicmargincalculation/server/controller/DynamicMarginCalculationControllerTest.java @@ -26,6 +26,7 @@ import org.gridsuite.dynamicmargincalculation.server.dto.parameters.DynamicSecurityAnalysisParametersValues; import org.gridsuite.dynamicmargincalculation.server.dto.parameters.DynamicSimulationParametersValues; import org.gridsuite.dynamicmargincalculation.server.entities.parameters.DynamicMarginCalculationParametersEntity; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.mockito.MockedStatic; import org.springframework.beans.factory.annotation.Autowired; @@ -71,6 +72,7 @@ /** * @author Thang PHAM */ +@Disabled public class DynamicMarginCalculationControllerTest extends AbstractDynamicMarginCalculationControllerTest { // directories diff --git a/src/test/java/org/gridsuite/dynamicmargincalculation/server/controller/DynamicMarginCalculationParametersControllerTest.java b/src/test/java/org/gridsuite/dynamicmargincalculation/server/controller/DynamicMarginCalculationParametersControllerTest.java index 8a13657..c771910 100644 --- a/src/test/java/org/gridsuite/dynamicmargincalculation/server/controller/DynamicMarginCalculationParametersControllerTest.java +++ b/src/test/java/org/gridsuite/dynamicmargincalculation/server/controller/DynamicMarginCalculationParametersControllerTest.java @@ -18,6 +18,7 @@ import org.gridsuite.dynamicmargincalculation.server.service.ParametersService; import org.gridsuite.dynamicmargincalculation.server.service.client.DirectoryClient; import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; @@ -44,6 +45,7 @@ /** * @author Thang PHAM */ +@Disabled @AutoConfigureMockMvc @SpringBootTest @ContextConfiguration(classes = {DynamicMarginCalculationApplication.class}) diff --git a/src/test/java/org/gridsuite/dynamicmargincalculation/server/controller/SupervisionControllerTest.java b/src/test/java/org/gridsuite/dynamicmargincalculation/server/controller/SupervisionControllerTest.java index 63e1c78..8756bcf 100644 --- a/src/test/java/org/gridsuite/dynamicmargincalculation/server/controller/SupervisionControllerTest.java +++ b/src/test/java/org/gridsuite/dynamicmargincalculation/server/controller/SupervisionControllerTest.java @@ -7,6 +7,7 @@ package org.gridsuite.dynamicmargincalculation.server.controller; import org.gridsuite.dynamicmargincalculation.server.DynamicMarginCalculationApi; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; @@ -20,6 +21,7 @@ /** * @author Thang PHAM */ +@Disabled @AutoConfigureMockMvc @SpringBootTest class SupervisionControllerTest { diff --git a/src/test/java/org/gridsuite/dynamicmargincalculation/server/service/DynamicMarginCalculationResultServiceTest.java b/src/test/java/org/gridsuite/dynamicmargincalculation/server/service/DynamicMarginCalculationResultServiceTest.java index 672944c..29dabf3 100644 --- a/src/test/java/org/gridsuite/dynamicmargincalculation/server/service/DynamicMarginCalculationResultServiceTest.java +++ b/src/test/java/org/gridsuite/dynamicmargincalculation/server/service/DynamicMarginCalculationResultServiceTest.java @@ -12,6 +12,7 @@ import org.gridsuite.dynamicmargincalculation.server.entities.DynamicMarginCalculationStatusEntity; import org.gridsuite.dynamicmargincalculation.server.repositories.DynamicMarginCalculationStatusRepository; import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -28,6 +29,7 @@ /** * @author Thang PHAM */ +@Disabled @SpringBootTest class DynamicMarginCalculationResultServiceTest { From 65f6c7f9dba092a2131f08993ffca6e0769cb59b Mon Sep 17 00:00:00 2001 From: Thang PHAM Date: Fri, 13 Feb 2026 14:09:54 +0100 Subject: [PATCH 13/17] override pom testcontainers to fix INFO org.testcontainers.DockerClientFactory - Testcontainers version: 1.20.6 ERROR org.testcontainers.dockerclient.DockerClientProviderStrategy - Could not find a valid Docker environment. Please check configuration. Attempted configurations were: UnixSocketClientProviderStrategy: failed with exception BadRequestException (Status 400: {"message":"client version 1.32 is too old. Minimum supported API version is 1.44, please upgrade your client to a newer version"} ) Signed-off-by: Thang PHAM --- pom.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pom.xml b/pom.xml index 7a392f4..4b0b539 100644 --- a/pom.xml +++ b/pom.xml @@ -44,6 +44,7 @@ 47.0.0 powsybl/java-dynawo:3.1.0 + 1.21.4 org.gridsuite.dynamicmargincalculation.server gridsuite org.gridsuite:dynamic-margin-calculation-server @@ -86,6 +87,11 @@ + + org.testcontainers + testcontainers + ${testcontainers.version} + From bc25bcfa42398a025d156eab4ab51a7e08cc88bb Mon Sep 17 00:00:00 2001 From: Thang PHAM Date: Fri, 13 Feb 2026 16:01:44 +0100 Subject: [PATCH 14/17] overide testcontainers 1.21.4 to fix bug github docker client Signed-off-by: Thang PHAM --- pom.xml | 1 + .../server/entities/parameters/LoadsVariationEntity.java | 1 + .../server/PropertyServerNameProviderTest.java | 2 -- .../controller/DynamicMarginCalculationControllerTest.java | 2 -- .../DynamicMarginCalculationParametersControllerTest.java | 2 -- .../server/controller/SupervisionControllerTest.java | 2 -- .../service/DynamicMarginCalculationResultServiceTest.java | 2 -- 7 files changed, 2 insertions(+), 10 deletions(-) diff --git a/pom.xml b/pom.xml index 4b0b539..384c70d 100644 --- a/pom.xml +++ b/pom.xml @@ -44,6 +44,7 @@ 47.0.0 powsybl/java-dynawo:3.1.0 + 1.21.4 org.gridsuite.dynamicmargincalculation.server gridsuite diff --git a/src/main/java/org/gridsuite/dynamicmargincalculation/server/entities/parameters/LoadsVariationEntity.java b/src/main/java/org/gridsuite/dynamicmargincalculation/server/entities/parameters/LoadsVariationEntity.java index 3670150..57eba4a 100644 --- a/src/main/java/org/gridsuite/dynamicmargincalculation/server/entities/parameters/LoadsVariationEntity.java +++ b/src/main/java/org/gridsuite/dynamicmargincalculation/server/entities/parameters/LoadsVariationEntity.java @@ -31,6 +31,7 @@ public class LoadsVariationEntity { @Column(name = "id") private UUID id; + @Builder.Default @ElementCollection(fetch = FetchType.EAGER) @CollectionTable( name = "loads_variation_load_filter", diff --git a/src/test/java/org/gridsuite/dynamicmargincalculation/server/PropertyServerNameProviderTest.java b/src/test/java/org/gridsuite/dynamicmargincalculation/server/PropertyServerNameProviderTest.java index ba6b2dc..7056fee 100644 --- a/src/test/java/org/gridsuite/dynamicmargincalculation/server/PropertyServerNameProviderTest.java +++ b/src/test/java/org/gridsuite/dynamicmargincalculation/server/PropertyServerNameProviderTest.java @@ -6,7 +6,6 @@ */ package org.gridsuite.dynamicmargincalculation.server; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; @@ -14,7 +13,6 @@ /** * @author Mohamed Ben-rejeb {@literal } */ -@Disabled class PropertyServerNameProviderTest { @Test diff --git a/src/test/java/org/gridsuite/dynamicmargincalculation/server/controller/DynamicMarginCalculationControllerTest.java b/src/test/java/org/gridsuite/dynamicmargincalculation/server/controller/DynamicMarginCalculationControllerTest.java index 646c46e..634355d 100644 --- a/src/test/java/org/gridsuite/dynamicmargincalculation/server/controller/DynamicMarginCalculationControllerTest.java +++ b/src/test/java/org/gridsuite/dynamicmargincalculation/server/controller/DynamicMarginCalculationControllerTest.java @@ -26,7 +26,6 @@ import org.gridsuite.dynamicmargincalculation.server.dto.parameters.DynamicSecurityAnalysisParametersValues; import org.gridsuite.dynamicmargincalculation.server.dto.parameters.DynamicSimulationParametersValues; import org.gridsuite.dynamicmargincalculation.server.entities.parameters.DynamicMarginCalculationParametersEntity; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.mockito.MockedStatic; import org.springframework.beans.factory.annotation.Autowired; @@ -72,7 +71,6 @@ /** * @author Thang PHAM */ -@Disabled public class DynamicMarginCalculationControllerTest extends AbstractDynamicMarginCalculationControllerTest { // directories diff --git a/src/test/java/org/gridsuite/dynamicmargincalculation/server/controller/DynamicMarginCalculationParametersControllerTest.java b/src/test/java/org/gridsuite/dynamicmargincalculation/server/controller/DynamicMarginCalculationParametersControllerTest.java index c771910..8a13657 100644 --- a/src/test/java/org/gridsuite/dynamicmargincalculation/server/controller/DynamicMarginCalculationParametersControllerTest.java +++ b/src/test/java/org/gridsuite/dynamicmargincalculation/server/controller/DynamicMarginCalculationParametersControllerTest.java @@ -18,7 +18,6 @@ import org.gridsuite.dynamicmargincalculation.server.service.ParametersService; import org.gridsuite.dynamicmargincalculation.server.service.client.DirectoryClient; import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; @@ -45,7 +44,6 @@ /** * @author Thang PHAM */ -@Disabled @AutoConfigureMockMvc @SpringBootTest @ContextConfiguration(classes = {DynamicMarginCalculationApplication.class}) diff --git a/src/test/java/org/gridsuite/dynamicmargincalculation/server/controller/SupervisionControllerTest.java b/src/test/java/org/gridsuite/dynamicmargincalculation/server/controller/SupervisionControllerTest.java index 8756bcf..63e1c78 100644 --- a/src/test/java/org/gridsuite/dynamicmargincalculation/server/controller/SupervisionControllerTest.java +++ b/src/test/java/org/gridsuite/dynamicmargincalculation/server/controller/SupervisionControllerTest.java @@ -7,7 +7,6 @@ package org.gridsuite.dynamicmargincalculation.server.controller; import org.gridsuite.dynamicmargincalculation.server.DynamicMarginCalculationApi; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; @@ -21,7 +20,6 @@ /** * @author Thang PHAM */ -@Disabled @AutoConfigureMockMvc @SpringBootTest class SupervisionControllerTest { diff --git a/src/test/java/org/gridsuite/dynamicmargincalculation/server/service/DynamicMarginCalculationResultServiceTest.java b/src/test/java/org/gridsuite/dynamicmargincalculation/server/service/DynamicMarginCalculationResultServiceTest.java index 29dabf3..672944c 100644 --- a/src/test/java/org/gridsuite/dynamicmargincalculation/server/service/DynamicMarginCalculationResultServiceTest.java +++ b/src/test/java/org/gridsuite/dynamicmargincalculation/server/service/DynamicMarginCalculationResultServiceTest.java @@ -12,7 +12,6 @@ import org.gridsuite.dynamicmargincalculation.server.entities.DynamicMarginCalculationStatusEntity; import org.gridsuite.dynamicmargincalculation.server.repositories.DynamicMarginCalculationStatusRepository; import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -29,7 +28,6 @@ /** * @author Thang PHAM */ -@Disabled @SpringBootTest class DynamicMarginCalculationResultServiceTest { From 35d352b68df8f9e034888b6b4473d9218380f6ab Mon Sep 17 00:00:00 2001 From: Thang PHAM Date: Sun, 15 Feb 2026 21:27:22 +0100 Subject: [PATCH 15/17] Add tests for client services Signed-off-by: Thang PHAM --- .../DynamicModelConfigJsonUtils.java | 35 ++++ .../DynamicModelConfigsJsonSerializer.java | 170 ++++++++++++++++++ .../DynamicSimulationParametersValues.java | 3 + .../service/client/DirectoryClient.java | 1 - ...DynamicModelConfigsJsonSerializerTest.java | 37 ++++ ...MarginCalculationControllerIEEE14Test.java | 11 +- ...ynamicMarginCalculationControllerTest.java | 6 +- .../client/AbstractRestClientTest.java | 64 +++++++ .../service/client/DirectoryClientTest.java | 119 ++++++++++++ .../DynamicSecurityAnalysisClientTest.java | 115 ++++++++++++ .../client/DynamicSimulationClientTest.java | 156 ++++++++++++++++ src/test/resources/data/dynamicModels.json | 37 ++++ 12 files changed, 744 insertions(+), 10 deletions(-) create mode 100644 src/main/java/com/powsybl/dynawo/suppliers/dynamicmodels/DynamicModelConfigJsonUtils.java create mode 100644 src/main/java/com/powsybl/dynawo/suppliers/dynamicmodels/DynamicModelConfigsJsonSerializer.java create mode 100644 src/test/java/com/powsybl/dynawo/suppliers/dynamicmodels/DynamicModelConfigsJsonSerializerTest.java create mode 100644 src/test/java/org/gridsuite/dynamicmargincalculation/server/service/client/AbstractRestClientTest.java create mode 100644 src/test/java/org/gridsuite/dynamicmargincalculation/server/service/client/DirectoryClientTest.java create mode 100644 src/test/java/org/gridsuite/dynamicmargincalculation/server/service/client/DynamicSecurityAnalysisClientTest.java create mode 100644 src/test/java/org/gridsuite/dynamicmargincalculation/server/service/client/DynamicSimulationClientTest.java create mode 100644 src/test/resources/data/dynamicModels.json diff --git a/src/main/java/com/powsybl/dynawo/suppliers/dynamicmodels/DynamicModelConfigJsonUtils.java b/src/main/java/com/powsybl/dynawo/suppliers/dynamicmodels/DynamicModelConfigJsonUtils.java new file mode 100644 index 0000000..f157391 --- /dev/null +++ b/src/main/java/com/powsybl/dynawo/suppliers/dynamicmodels/DynamicModelConfigJsonUtils.java @@ -0,0 +1,35 @@ +/** + * 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 com.powsybl.dynawo.suppliers.dynamicmodels; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.module.SimpleModule; +import com.powsybl.commons.json.JsonUtil; + +import java.util.List; + +/** + * @author Thang PHAM + */ +public final class DynamicModelConfigJsonUtils { + + private DynamicModelConfigJsonUtils() { + throw new AssertionError("Utility class should not be instantiated"); + } + + public static ObjectMapper createObjectMapper() { + ObjectMapper mapper = JsonUtil.createObjectMapper(); + + SimpleModule module = new SimpleModule("dynamic-model-configs"); + module.addSerializer(new DynamicModelConfigsJsonSerializer()); + module.addDeserializer(List.class, new DynamicModelConfigsJsonDeserializer()); + + mapper.registerModule(module); + return mapper; + } +} diff --git a/src/main/java/com/powsybl/dynawo/suppliers/dynamicmodels/DynamicModelConfigsJsonSerializer.java b/src/main/java/com/powsybl/dynawo/suppliers/dynamicmodels/DynamicModelConfigsJsonSerializer.java new file mode 100644 index 0000000..8bdc326 --- /dev/null +++ b/src/main/java/com/powsybl/dynawo/suppliers/dynamicmodels/DynamicModelConfigsJsonSerializer.java @@ -0,0 +1,170 @@ +/** + * 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 com.powsybl.dynawo.suppliers.dynamicmodels; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.ser.std.StdSerializer; +import com.powsybl.dynawo.suppliers.Property; +import com.powsybl.dynawo.suppliers.PropertyType; +import com.powsybl.iidm.network.TwoSides; + +import java.io.IOException; +import java.util.List; + +/** + * Serialize a List with "models" as root: + * + * { + * "models": [ + * { + * "model": "...", + * "group": "...", + * "groupType": "FIXED", + * "properties": [ ... ] + * } + * ] + * } + * + * This matches {@link DynamicModelConfigsJsonDeserializer}. + * + * TODO : to remove when available at powsybl-dynawo + * + * @author Thang PHAM + */ +public class DynamicModelConfigsJsonSerializer extends StdSerializer> { + + public DynamicModelConfigsJsonSerializer() { + super((Class>) (Class) List.class); + } + + @Override + public void serialize(List configs, + JsonGenerator gen, + SerializerProvider provider) throws IOException { + + gen.writeStartObject(); + + gen.writeFieldName("models"); + writeDynamicModelConfigs(configs, gen); + + gen.writeEndObject(); + } + + private static void writeDynamicModelConfigs(List configs, JsonGenerator gen) throws IOException { + gen.writeStartArray(); + if (configs != null) { + for (DynamicModelConfig cfg : configs) { + writeDynamicModelConfig(cfg, gen); + } + } + gen.writeEndArray(); + } + + private static void writeDynamicModelConfig(DynamicModelConfig cfg, + JsonGenerator gen) throws IOException { + + gen.writeStartObject(); + + gen.writeStringField("model", cfg.model()); + gen.writeStringField("group", cfg.group()); + + if (cfg.groupType() != null) { + gen.writeStringField("groupType", cfg.groupType().name()); + } + + gen.writeFieldName("properties"); + writeProperties(cfg.properties(), gen); + + gen.writeEndObject(); + } + + private static void writeProperties(List properties, JsonGenerator gen) throws IOException { + gen.writeStartArray(); + if (properties != null) { + for (Property p : properties) { + writeProperty(p, gen); + } + } + gen.writeEndArray(); + } + + /** + * See {@code PropertyParserUtils.parseProperty}: + * - always writes "name" + * - writes exactly one of: "value" (string value), "values" (string array), "arrays" (array of string arrays) + * - writes "type" when it can be inferred from propertyClass + */ + private static void writeProperty(Property property, JsonGenerator gen) throws IOException { + gen.writeStartObject(); + + gen.writeStringField("name", property.name()); + + Object value = property.value(); + if (value instanceof List list) { + if (!list.isEmpty()) { + // lists: List + gen.writeFieldName("values"); + gen.writeStartArray(); + for (Object v : list) { + gen.writeString(String.valueOf(v)); + } + gen.writeEndArray(); + writeOptionalType(gen, property); + } + } else if (value != null && value.getClass().isArray()) { + // arrays: List> + gen.writeFieldName("arrays"); + gen.writeStartArray(); + for (List row : (List[]) value) { + gen.writeStartArray(); + for (Object v : row) { + gen.writeString(String.valueOf(v)); + } + gen.writeEndArray(); + } + gen.writeEndArray(); + writeOptionalType(gen, property); + } else { + if (value instanceof TwoSides ts) { + gen.writeStringField("value", ts.name()); + writeOptionalType(gen, property); + } else if (value != null) { + gen.writeStringField("value", String.valueOf(value)); + writeOptionalType(gen, property); + } + } + + gen.writeEndObject(); + } + + /** + * Writes the "type" field if we can (or if Property already carries an explicit type via propertyClass()). + * + * This keeps compatibility with PropertyParserUtils: + * case "type" -> builder.type(PropertyType.valueOf(parser.nextTextValue())); + */ + private static void writeOptionalType(JsonGenerator gen, Property property) throws IOException { + PropertyType inferredType = PropertyType.STRING; + + Class propertyClass = property.propertyClass(); + if (propertyClass != null) { + if (propertyClass == double.class) { + inferredType = PropertyType.DOUBLE; + } else if (propertyClass == int.class) { + inferredType = PropertyType.INTEGER; + } else if (propertyClass == boolean.class) { + inferredType = PropertyType.BOOLEAN; + } else if (propertyClass == TwoSides.class) { + inferredType = PropertyType.TWO_SIDES; + } + } + + gen.writeStringField("type", inferredType.name()); + } +} diff --git a/src/main/java/org/gridsuite/dynamicmargincalculation/server/dto/parameters/DynamicSimulationParametersValues.java b/src/main/java/org/gridsuite/dynamicmargincalculation/server/dto/parameters/DynamicSimulationParametersValues.java index b33c0ca..433376d 100644 --- a/src/main/java/org/gridsuite/dynamicmargincalculation/server/dto/parameters/DynamicSimulationParametersValues.java +++ b/src/main/java/org/gridsuite/dynamicmargincalculation/server/dto/parameters/DynamicSimulationParametersValues.java @@ -9,9 +9,11 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.powsybl.dynawo.DynawoSimulationParameters; import com.powsybl.dynawo.suppliers.dynamicmodels.DynamicModelConfig; import com.powsybl.dynawo.suppliers.dynamicmodels.DynamicModelConfigsJsonDeserializer; +import com.powsybl.dynawo.suppliers.dynamicmodels.DynamicModelConfigsJsonSerializer; import lombok.*; import java.util.List; @@ -26,6 +28,7 @@ @Builder @JsonIgnoreProperties(ignoreUnknown = true) public class DynamicSimulationParametersValues { + @JsonSerialize(using = DynamicModelConfigsJsonSerializer.class) @JsonDeserialize(using = DynamicModelConfigsJsonDeserializer.class) @JsonInclude(JsonInclude.Include.NON_EMPTY) List dynamicModel; diff --git a/src/main/java/org/gridsuite/dynamicmargincalculation/server/service/client/DirectoryClient.java b/src/main/java/org/gridsuite/dynamicmargincalculation/server/service/client/DirectoryClient.java index 7571cac..27bb674 100644 --- a/src/main/java/org/gridsuite/dynamicmargincalculation/server/service/client/DirectoryClient.java +++ b/src/main/java/org/gridsuite/dynamicmargincalculation/server/service/client/DirectoryClient.java @@ -30,7 +30,6 @@ import static org.gridsuite.dynamicmargincalculation.server.service.client.utils.UrlUtils.buildEndPointUrl; /** - * * @author Thang PHAM */ @Service diff --git a/src/test/java/com/powsybl/dynawo/suppliers/dynamicmodels/DynamicModelConfigsJsonSerializerTest.java b/src/test/java/com/powsybl/dynawo/suppliers/dynamicmodels/DynamicModelConfigsJsonSerializerTest.java new file mode 100644 index 0000000..8139536 --- /dev/null +++ b/src/test/java/com/powsybl/dynawo/suppliers/dynamicmodels/DynamicModelConfigsJsonSerializerTest.java @@ -0,0 +1,37 @@ +/** + * 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 com.powsybl.dynawo.suppliers.dynamicmodels; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * @author Thang PHAM + */ +class DynamicModelConfigsJsonSerializerTest { + + @Test + void testModelConfigDeserializerSerializer() throws IOException { + + ObjectMapper objectMapper = DynamicModelConfigJsonUtils.createObjectMapper(); + List dynamicModelConfigs = objectMapper.readValue(getClass().getResourceAsStream("/data/dynamicModels.json"), new TypeReference<>() { }); + assertEquals(2, dynamicModelConfigs.size()); + + String dynamicModelConfigsJson = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(dynamicModelConfigs); + List dynamicModelConfigs2 = objectMapper.readValue(dynamicModelConfigsJson, new TypeReference<>() { }); + + Assertions.assertThat(dynamicModelConfigs2).usingRecursiveComparison().isEqualTo(dynamicModelConfigs); + } +} diff --git a/src/test/java/org/gridsuite/dynamicmargincalculation/server/controller/DynamicMarginCalculationControllerIEEE14Test.java b/src/test/java/org/gridsuite/dynamicmargincalculation/server/controller/DynamicMarginCalculationControllerIEEE14Test.java index 959376e..1a002fc 100644 --- a/src/test/java/org/gridsuite/dynamicmargincalculation/server/controller/DynamicMarginCalculationControllerIEEE14Test.java +++ b/src/test/java/org/gridsuite/dynamicmargincalculation/server/controller/DynamicMarginCalculationControllerIEEE14Test.java @@ -75,12 +75,12 @@ public class DynamicMarginCalculationControllerIEEE14Test extends AbstractDynami public static final String DYNAMIC_SIMULATION_PARAMETERS_DUMP_FILE = "dynamicSimulationParameters.dmp"; public static final String NETWORK_FILE = "IEEE14.iidm"; - private static final UUID NETWORK_UUID = UUID.randomUUID(); + private static final UUID NETWORK_UUID = UUID.fromString("43b0f54e-e8fc-4607-8f53-e1cab1075ab5"); private static final String VARIANT_1_ID = "variant_1"; - private static final UUID DSA_PARAMETERS_UUID = UUID.randomUUID(); - private static final UUID PARAMETERS_UUID = UUID.randomUUID(); - private static final UUID FILTER_UUID = UUID.randomUUID(); + private static final UUID DSA_PARAMETERS_UUID = UUID.fromString("2745666a-0abe-4c3a-8b91-a719c1d1f753"); + private static final UUID PARAMETERS_UUID = UUID.fromString("e786c4ca-64e7-4f44-b6a2-8f23b8b4334a"); + private static final UUID FILTER_UUID = UUID.fromString("b234ce92-23f2-422c-b239-ec69abc399bd"); @Autowired private OutputDestination output; @@ -122,7 +122,7 @@ protected void initExternalClientsMocks() { initDynamicSecurityAnalysisClientMock(); // Mock for the filer client - when(filterClient.getFilters(eq(List.of(FILTER_UUID)))) + when(filterClient.getFilters(List.of(FILTER_UUID))) .thenReturn( List.of( ExpertFilter.builder() @@ -181,7 +181,6 @@ private void initDynamicSecurityAnalysisClientMock() { .build()); } - // @Disabled("To ignore test with container") @Test void test01IEEE14() throws Exception { // The controller requires a request body string: dynamicSimulationParametersJson. diff --git a/src/test/java/org/gridsuite/dynamicmargincalculation/server/controller/DynamicMarginCalculationControllerTest.java b/src/test/java/org/gridsuite/dynamicmargincalculation/server/controller/DynamicMarginCalculationControllerTest.java index 634355d..18418b4 100644 --- a/src/test/java/org/gridsuite/dynamicmargincalculation/server/controller/DynamicMarginCalculationControllerTest.java +++ b/src/test/java/org/gridsuite/dynamicmargincalculation/server/controller/DynamicMarginCalculationControllerTest.java @@ -76,12 +76,12 @@ public class DynamicMarginCalculationControllerTest extends AbstractDynamicMargi // directories public static final String DATA_IEEE14_BASE_DIR = RESOURCE_PATH_DELIMITER + "data" + RESOURCE_PATH_DELIMITER + "ieee14"; - private static final UUID NETWORK_UUID = UUID.randomUUID(); + private static final UUID NETWORK_UUID = UUID.fromString("508a9a3a-cc8d-4bb2-97fa-1d38a5dd4ac1"); private static final String VARIANT_1_ID = "variant_1"; private static final String NETWORK_FILE = "IEEE14.iidm"; - private static final UUID PARAMETERS_UUID = UUID.randomUUID(); - private static final UUID DSA_PARAMETERS_UUID = UUID.randomUUID(); + private static final UUID PARAMETERS_UUID = UUID.fromString("34f54d14-92ab-4616-a705-e9711275fc3b"); + private static final UUID DSA_PARAMETERS_UUID = UUID.fromString("bb9f39cf-7146-4892-8005-cd7c3fa48333"); private static final String LINE_ID = "_BUS____1-BUS____5-1_AC"; private static final String GEN_ID = "_GEN____2_SM"; diff --git a/src/test/java/org/gridsuite/dynamicmargincalculation/server/service/client/AbstractRestClientTest.java b/src/test/java/org/gridsuite/dynamicmargincalculation/server/service/client/AbstractRestClientTest.java new file mode 100644 index 0000000..e1dd6b6 --- /dev/null +++ b/src/test/java/org/gridsuite/dynamicmargincalculation/server/service/client/AbstractRestClientTest.java @@ -0,0 +1,64 @@ +/* + * 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.dynamicmargincalculation.server.service.client; + +import com.github.tomakehurst.wiremock.WireMockServer; +import org.gridsuite.dynamicmargincalculation.server.DynamicMarginCalculationApplication; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.TestInstance; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.ContextHierarchy; + +/** + * @author Thang PHAM + */ +@SpringBootTest +@ContextHierarchy({@ContextConfiguration(classes = {DynamicMarginCalculationApplication.class})}) +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +public abstract class AbstractRestClientTest { + + public static final String ERROR_MESSAGE = "Something wrong in remote server"; + public static final String ERROR_MESSAGE_JSON = """ + {"message": "%s"} + """.formatted(ERROR_MESSAGE); + + public final Logger getLogger() { + return LoggerFactory.getLogger(this.getClass()); + } + + protected WireMockServer wireMockServer; + + protected String initMockWebServer(WireMockServer server) { + wireMockServer = server; + + wireMockServer.start(); + getLogger().info("Mock server started at port = {}", wireMockServer.port()); + + // get base URL + return wireMockServer.baseUrl(); + } + + @BeforeEach + void setup() { + // Clear stubs/requests between tests while keeping the same server instance + wireMockServer.resetAll(); + } + + @AfterAll + public void tearDown() { + try { + wireMockServer.shutdown(); + } catch (Exception e) { + getLogger().info("Can not shutdown the mock server {}", this.getClass().getSimpleName()); + } + } +} diff --git a/src/test/java/org/gridsuite/dynamicmargincalculation/server/service/client/DirectoryClientTest.java b/src/test/java/org/gridsuite/dynamicmargincalculation/server/service/client/DirectoryClientTest.java new file mode 100644 index 0000000..5d54891 --- /dev/null +++ b/src/test/java/org/gridsuite/dynamicmargincalculation/server/service/client/DirectoryClientTest.java @@ -0,0 +1,119 @@ +/* + * 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.dynamicmargincalculation.server.service.client; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.github.tomakehurst.wiremock.WireMockServer; +import com.github.tomakehurst.wiremock.client.WireMock; +import org.gridsuite.dynamicmargincalculation.server.dto.ElementAttributes; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.web.client.HttpServerErrorException; +import org.springframework.web.client.RestTemplate; + +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; +import static com.github.tomakehurst.wiremock.client.WireMock.havingExactly; +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.catchThrowableOfType; +import static org.gridsuite.computation.service.NotificationService.HEADER_USER_ID; +import static org.gridsuite.dynamicmargincalculation.server.service.client.DirectoryClient.*; +import static org.gridsuite.dynamicmargincalculation.server.service.client.utils.UrlUtils.buildEndPointUrl; + +/** + * @author Thang PHAM + */ +class DirectoryClientTest extends AbstractRestClientTest { + + private DirectoryClient directoryClient; + + @Autowired + private RestTemplate restTemplate; + + @Autowired + private ObjectMapper objectMapper; + + private String getBaseUrl() { + return buildEndPointUrl("", API_VERSION, ELEMENT_END_POINT_INFOS); + } + + @BeforeAll + void init() { + directoryClient = new DirectoryClient( + // use new WireMockServer(DIRECTORY_PORT) to test with local server if needed + initMockWebServer(new WireMockServer(wireMockConfig().dynamicPort())), + restTemplate, + objectMapper + ); + } + + @Test + void testGetElementNamesEmptyInput() { + Map result = directoryClient.getElementNames(List.of(), "userId"); + assertThat(result).isEmpty(); + } + + @Test + void testGetElementNames() throws Exception { + + // --- Setup --- // + UUID id1 = UUID.fromString("0d740ec6-15ca-4d81-9963-ffed732c3d36"); + UUID id2 = UUID.fromString("ad1f5f93-67ad-4ea8-a85a-b82a52cce0a2"); + String userId = "testUserId"; + + List responseBody = List.of( + new ElementAttributes(id1, "Element 1"), + new ElementAttributes(id2, "Element 2") + ); + + wireMockServer.stubFor(WireMock.get(WireMock.urlPathEqualTo(getBaseUrl())) + .withQueryParam(QUERY_PARAM_IDS, havingExactly(id1.toString(), id2.toString())) + .withQueryParam(QUERY_PARAM_STRICT_MODE, equalTo("false")) + .withHeader(HEADER_USER_ID, equalTo(userId)) + .willReturn(WireMock.ok() + .withBody(objectMapper.writeValueAsString(responseBody)) + .withHeader("Content-Type", MediaType.APPLICATION_JSON_VALUE) + )); + + // --- Execute --- // + Map result = directoryClient.getElementNames(List.of(id1, id2), userId); + + // --- Verify --- // + assertThat(result).containsExactlyInAnyOrderEntriesOf(Map.of( + id1, "Element 1", + id2, "Element 2" + )); + } + + @Test + void testGetElementNamesGivenException() { + + // --- Setup --- // + UUID id1 = UUID.fromString("a5efc25c-c836-423d-a8ba-0c6e74a2f8ae"); + + wireMockServer.stubFor(WireMock.get(WireMock.urlPathEqualTo(getBaseUrl())) + .withQueryParam(QUERY_PARAM_IDS, havingExactly(id1.toString())) + .withQueryParam(QUERY_PARAM_STRICT_MODE, equalTo("false")) + .willReturn(WireMock.serverError() + .withBody(ERROR_MESSAGE_JSON) + )); + + // --- Execute --- // + HttpServerErrorException exception = catchThrowableOfType(HttpServerErrorException.class, + () -> directoryClient.getElementNames(List.of(id1), null)); + + // --- Verify --- // + assertThat(exception.getMessage()).contains(ERROR_MESSAGE); + } +} diff --git a/src/test/java/org/gridsuite/dynamicmargincalculation/server/service/client/DynamicSecurityAnalysisClientTest.java b/src/test/java/org/gridsuite/dynamicmargincalculation/server/service/client/DynamicSecurityAnalysisClientTest.java new file mode 100644 index 0000000..c6b6630 --- /dev/null +++ b/src/test/java/org/gridsuite/dynamicmargincalculation/server/service/client/DynamicSecurityAnalysisClientTest.java @@ -0,0 +1,115 @@ +/* + * 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.dynamicmargincalculation.server.service.client; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.github.tomakehurst.wiremock.WireMockServer; +import com.github.tomakehurst.wiremock.client.WireMock; +import com.powsybl.contingency.Contingency; +import org.gridsuite.dynamicmargincalculation.server.dto.parameters.DynamicSecurityAnalysisParametersValues; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.web.client.HttpServerErrorException; +import org.springframework.web.client.RestTemplate; + +import java.util.List; +import java.util.UUID; + +import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.catchThrowableOfType; +import static org.gridsuite.computation.service.AbstractResultContext.VARIANT_ID_HEADER; +import static org.gridsuite.dynamicmargincalculation.server.service.client.DynamicSecurityAnalysisClient.API_VERSION; +import static org.gridsuite.dynamicmargincalculation.server.service.client.DynamicSecurityAnalysisClient.DYNAMIC_SECURITY_ANALYSIS_END_POINT_PARAMETERS; +import static org.gridsuite.dynamicmargincalculation.server.service.client.utils.UrlUtils.buildEndPointUrl; + +/** + * @author Thang PHAM + */ +class DynamicSecurityAnalysisClientTest extends AbstractRestClientTest { + + private DynamicSecurityAnalysisClient dynamicSecurityAnalysisClient; + + @Autowired + private RestTemplate restTemplate; + + @Autowired + private ObjectMapper objectMapper; + + @BeforeAll + void init() { + // use new WireMockServer(DYNAMIC_SECURITY_ANALYSIS_PORT) to test with local server if needed + dynamicSecurityAnalysisClient = new DynamicSecurityAnalysisClient( + initMockWebServer(new WireMockServer(wireMockConfig().dynamicPort())), + restTemplate, + objectMapper + ); + } + + @Test + void testGetParametersValues() throws Exception { + + // --- Setup --- // + UUID parametersUuid = UUID.fromString("f1be5de3-b8e5-4ab5-9521-62a1f8a66228"); + UUID networkUuid = UUID.fromString("f1be5de3-b8e5-4ab5-9521-62a1f8a66228"); + String variantId = "variant_1"; + + DynamicSecurityAnalysisParametersValues expected = DynamicSecurityAnalysisParametersValues.builder() + .contingenciesStartTime(105d) + .contingencies(List.of(Contingency.load("_LOAD__11_EC"))) + .build(); + String bodyJson = objectMapper.writeValueAsString(expected); + + String baseEndpoint = buildEndPointUrl("", API_VERSION, DYNAMIC_SECURITY_ANALYSIS_END_POINT_PARAMETERS); + String urlPath = baseEndpoint + "/" + parametersUuid + "/values"; + + wireMockServer.stubFor(WireMock.get(WireMock.urlPathEqualTo(urlPath)) + .withQueryParam("networkUuid", equalTo(networkUuid.toString())) + .withQueryParam(VARIANT_ID_HEADER, equalTo(variantId)) + .willReturn(WireMock.ok() + .withBody(bodyJson) + .withHeader("Content-Type", MediaType.APPLICATION_JSON_VALUE) + )); + + // --- Execute --- // + DynamicSecurityAnalysisParametersValues result = + dynamicSecurityAnalysisClient.getParametersValues(parametersUuid, networkUuid, variantId); + + // --- Verify --- // + assertThat(result).usingRecursiveComparison().isEqualTo(expected); + } + + @Test + void testGetParametersValuesGivenException() { + + // --- Setup --- // + UUID parametersUuid = UUID.fromString("eb4a62e4-0927-4e1e-9256-56a8494b0786"); + UUID networkUuid = UUID.fromString("eb4a62e4-0927-4e1e-9256-56a8494b0786"); + String variantId = "variant_1"; + + String baseEndpoint = buildEndPointUrl("", API_VERSION, DYNAMIC_SECURITY_ANALYSIS_END_POINT_PARAMETERS); + String urlPath = baseEndpoint + "/" + parametersUuid + "/values"; + + wireMockServer.stubFor(WireMock.get(WireMock.urlPathEqualTo(urlPath)) + .withQueryParam("networkUuid", equalTo(networkUuid.toString())) + .withQueryParam(VARIANT_ID_HEADER, equalTo(variantId)) + .willReturn(WireMock.serverError() + .withBody(ERROR_MESSAGE_JSON) + )); + + // --- Execute --- // + HttpServerErrorException exception = catchThrowableOfType(HttpServerErrorException.class, + () -> dynamicSecurityAnalysisClient.getParametersValues(parametersUuid, networkUuid, variantId)); + + // --- Verify --- // + assertThat(exception.getMessage()).contains(ERROR_MESSAGE); + } +} diff --git a/src/test/java/org/gridsuite/dynamicmargincalculation/server/service/client/DynamicSimulationClientTest.java b/src/test/java/org/gridsuite/dynamicmargincalculation/server/service/client/DynamicSimulationClientTest.java new file mode 100644 index 0000000..063f5f1 --- /dev/null +++ b/src/test/java/org/gridsuite/dynamicmargincalculation/server/service/client/DynamicSimulationClientTest.java @@ -0,0 +1,156 @@ +/* + * 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.dynamicmargincalculation.server.service.client; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.github.tomakehurst.wiremock.WireMockServer; +import com.github.tomakehurst.wiremock.client.WireMock; +import com.powsybl.dynawo.DynawoSimulationParameters; +import com.powsybl.dynawo.parameters.Parameter; +import com.powsybl.dynawo.parameters.ParameterType; +import com.powsybl.dynawo.parameters.ParametersSet; +import com.powsybl.dynawo.suppliers.PropertyBuilder; +import com.powsybl.dynawo.suppliers.PropertyType; +import com.powsybl.dynawo.suppliers.SetGroupType; +import com.powsybl.dynawo.suppliers.dynamicmodels.DynamicModelConfig; +import org.gridsuite.dynamicmargincalculation.server.dto.parameters.DynamicSimulationParametersValues; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.web.client.HttpServerErrorException; +import org.springframework.web.client.RestTemplate; + +import java.util.List; +import java.util.UUID; + +import static com.github.tomakehurst.wiremock.client.WireMock.containing; +import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.catchThrowableOfType; +import static org.gridsuite.computation.service.AbstractResultContext.VARIANT_ID_HEADER; +import static org.gridsuite.dynamicmargincalculation.server.service.client.DynamicSimulationClient.API_VERSION; +import static org.gridsuite.dynamicmargincalculation.server.service.client.DynamicSimulationClient.DYNAMIC_SIMULATION_END_POINT_PARAMETERS; +import static org.gridsuite.dynamicmargincalculation.server.service.client.utils.UrlUtils.buildEndPointUrl; + +/** + * @author Thang PHAM + */ +class DynamicSimulationClientTest extends AbstractRestClientTest { + + private DynamicSimulationClient dynamicSimulationClient; + + @Autowired + private RestTemplate restTemplate; + + @Autowired + private ObjectMapper objectMapper; + + @BeforeAll + void init() { + // use new WireMockServer(DYNAMIC_SIMULATION_PORT) to test with local server if needed + dynamicSimulationClient = new DynamicSimulationClient( + initMockWebServer(new WireMockServer(wireMockConfig().dynamicPort())), + restTemplate, + objectMapper + ); + } + + @Test + void testGetParametersValues() throws Exception { + + // --- Setup --- // + UUID networkUuid = UUID.fromString("e6516424-21c3-4bff-953d-9d0efc20ea08"); + String variantId = "variant_1"; + String dynamicSimulationParametersJson = "{}"; + + // prepare dynamic model + List dynamicModel = List.of(new DynamicModelConfig("LoadAlphaBeta", "_DM", SetGroupType.SUFFIX, List.of( + new PropertyBuilder() + .name("staticId") + .value("LOAD") + .type(PropertyType.STRING) + .build()))); + + // prepare dynawo simulation parameters + DynawoSimulationParameters dynawoParameters = DynawoSimulationParameters.load(); + + // network + ParametersSet networkParamsSet = new ParametersSet("network"); + networkParamsSet.addParameter(new Parameter("LoadBeta", ParameterType.DOUBLE, "2")); + networkParamsSet.addParameter(new Parameter("LoadAlpha", ParameterType.DOUBLE, "1")); + + dynawoParameters.setNetworkParameters(networkParamsSet); + + // model parameters set + dynawoParameters.setModelsParameters(List.of(new ParametersSet("LoadAlphaBeta"))); + + // solver parameters set + ParametersSet simSolverParamsSet = new ParametersSet("SIM"); + simSolverParamsSet.addParameter(new Parameter("HMin", ParameterType.DOUBLE, "0.001")); + simSolverParamsSet.addParameter(new Parameter("LinearSolverName", ParameterType.STRING, "KLU")); + + dynawoParameters.setSolverParameters(simSolverParamsSet); + + // build Dto to mock return + DynamicSimulationParametersValues expected = DynamicSimulationParametersValues.builder() + .dynamicModel(dynamicModel) + .dynawoParameters(dynawoParameters) + .build(); + + String bodyJson = objectMapper.writeValueAsString(expected); + + String baseEndpoint = buildEndPointUrl("", API_VERSION, DYNAMIC_SIMULATION_END_POINT_PARAMETERS); + String urlPath = baseEndpoint + "/values"; + + wireMockServer.stubFor(WireMock.post(WireMock.urlPathEqualTo(urlPath)) + .withQueryParam("networkUuid", equalTo(networkUuid.toString())) + .withQueryParam(VARIANT_ID_HEADER, equalTo(variantId)) + .withHeader(HttpHeaders.CONTENT_TYPE, containing(MediaType.APPLICATION_JSON_VALUE)) + .withRequestBody(WireMock.equalToJson(dynamicSimulationParametersJson)) + .willReturn(WireMock.ok() + .withBody(bodyJson) + .withHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) + )); + + // --- Execute --- // + DynamicSimulationParametersValues result = + dynamicSimulationClient.getParametersValues(dynamicSimulationParametersJson, networkUuid, variantId); + + // --- Verify --- // + assertThat(result).usingRecursiveComparison().isEqualTo(expected); + } + + @Test + void testGetParametersValuesGivenException() { + + // --- Setup --- // + UUID networkUuid = UUID.fromString("4b364ee1-2933-401a-ae3c-e79253b2de67"); + String variantId = "variant_1"; + String dynamicSimulationParametersJson = "{}"; + + String baseEndpoint = buildEndPointUrl("", API_VERSION, DYNAMIC_SIMULATION_END_POINT_PARAMETERS); + String urlPath = baseEndpoint + "/values"; + + wireMockServer.stubFor(WireMock.post(WireMock.urlPathEqualTo(urlPath)) + .withQueryParam("networkUuid", equalTo(networkUuid.toString())) + .withQueryParam(VARIANT_ID_HEADER, equalTo(variantId)) + .willReturn(WireMock.serverError() + .withBody(ERROR_MESSAGE_JSON) + )); + + // --- Execute --- // + HttpServerErrorException exception = catchThrowableOfType(HttpServerErrorException.class, + () -> dynamicSimulationClient.getParametersValues(dynamicSimulationParametersJson, networkUuid, variantId)); + + // --- Verify --- // + assertThat(exception.getMessage()).contains(ERROR_MESSAGE); + } +} diff --git a/src/test/resources/data/dynamicModels.json b/src/test/resources/data/dynamicModels.json new file mode 100644 index 0000000..71605fc --- /dev/null +++ b/src/test/resources/data/dynamicModels.json @@ -0,0 +1,37 @@ +{ + "models":[ + { + "model":"LoadAlphaBeta", + "group": "_DM", + "groupType": "SUFFIX", + "properties":[ + { + "name":"staticId", + "value":"LOAD", + "type":"STRING" + } + ] + }, + { + "model": "TapChangerBlockingAutomationSystem", + "group": "tcb_par", + "properties":[ + { + "name": "dynamicModelId", + "value": "TCB1", + "type": "STRING" + }, + { + "name": "transformers", + "values": ["NGEN_NHV1", "NHV2_NLOAD"], + "type": "STRING" + }, + { + "name": "uMeasurements", + "arrays": [["OldNGen", "NGEN"], ["NHV1", "NHV2"]], + "type": "STRING" + } + ] + } + ] +} \ No newline at end of file From eaa051bf33797b27b8ddecb128d0efa1f78571b1 Mon Sep 17 00:00:00 2001 From: Thang PHAM Date: Sun, 15 Feb 2026 21:35:56 +0100 Subject: [PATCH 16/17] FIX Sonar Signed-off-by: Thang PHAM --- ...DynamicMarginCalculationParametersControllerTest.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/test/java/org/gridsuite/dynamicmargincalculation/server/controller/DynamicMarginCalculationParametersControllerTest.java b/src/test/java/org/gridsuite/dynamicmargincalculation/server/controller/DynamicMarginCalculationParametersControllerTest.java index 8a13657..8431b22 100644 --- a/src/test/java/org/gridsuite/dynamicmargincalculation/server/controller/DynamicMarginCalculationParametersControllerTest.java +++ b/src/test/java/org/gridsuite/dynamicmargincalculation/server/controller/DynamicMarginCalculationParametersControllerTest.java @@ -34,7 +34,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.gridsuite.computation.service.NotificationService.HEADER_USER_ID; -import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.*; import static org.springframework.http.MediaType.APPLICATION_JSON; import static org.springframework.http.MediaType.TEXT_PLAIN_VALUE; @@ -164,7 +163,7 @@ void testGetParametersRequiresUserHeaderAndEnrichesLoadFilterNames() throws Exce UUID parametersUuid = parametersRepository.save(new DynamicMarginCalculationParametersEntity(infos)).getId(); // service enrichment uses DirectoryClient only when userId is provided - when(directoryClient.getElementNames(eq(List.of(LOAD_FILTER_UUID_1, LOAD_FILTER_UUID_2)), eq(USER_ID))) + when(directoryClient.getElementNames(List.of(LOAD_FILTER_UUID_1, LOAD_FILTER_UUID_2), USER_ID)) .thenReturn(Map.of( LOAD_FILTER_UUID_1, "Filter 1", LOAD_FILTER_UUID_2, "Filter 2" @@ -187,7 +186,7 @@ void testGetParametersRequiresUserHeaderAndEnrichesLoadFilterNames() throws Exce assertThat(returned.getLoadsVariations().getFirst().getLoadFilters().getFirst().getName()).isEqualTo("Filter 1"); assertThat(returned.getLoadsVariations().getFirst().getLoadFilters().get(1).getName()).isEqualTo("Filter 2"); - verify(directoryClient, times(1)).getElementNames(eq(List.of(LOAD_FILTER_UUID_1, LOAD_FILTER_UUID_2)), eq(USER_ID)); + verify(directoryClient, times(1)).getElementNames(List.of(LOAD_FILTER_UUID_1, LOAD_FILTER_UUID_2), USER_ID); } @Test @@ -218,7 +217,7 @@ void testUpdateParameters() throws Exception { UUID parametersUuid = parametersRepository.save(new DynamicMarginCalculationParametersEntity(infos)).getId(); DynamicMarginCalculationParametersInfos updatedInfos = newParametersInfos(); - updatedInfos.setAccuracy(1); // change a field to ensure update persists + updatedInfos.setAccuracy(2); // change a field to ensure update persists mockMvc.perform(put("/v1/parameters/{uuid}", parametersUuid) .contentType(APPLICATION_JSON) @@ -229,7 +228,7 @@ void testUpdateParameters() throws Exception { assertThat(entityOpt).isPresent(); DynamicMarginCalculationParametersInfos persisted = entityOpt.get().toDto(false); - assertThat(persisted.getAccuracy()).isEqualTo(1); + assertThat(persisted.getAccuracy()).isEqualTo(2); } @Test From 6aaba4079cc05c6564db4b6b938a674c1995c216 Mon Sep 17 00:00:00 2001 From: Thang PHAM Date: Sun, 15 Feb 2026 22:45:56 +0100 Subject: [PATCH 17/17] update indexes Signed-off-by: Thang PHAM --- .../parameters/LoadsVariationEntity.java | 6 +- .../result/LoadIncreaseResultEntity.java | 5 +- .../entities/result/ScenarioResultEntity.java | 4 +- .../changesets/changelog_20251223T095053Z.xml | 58 +++++++++++-------- 4 files changed, 43 insertions(+), 30 deletions(-) diff --git a/src/main/java/org/gridsuite/dynamicmargincalculation/server/entities/parameters/LoadsVariationEntity.java b/src/main/java/org/gridsuite/dynamicmargincalculation/server/entities/parameters/LoadsVariationEntity.java index 57eba4a..058f34c 100644 --- a/src/main/java/org/gridsuite/dynamicmargincalculation/server/entities/parameters/LoadsVariationEntity.java +++ b/src/main/java/org/gridsuite/dynamicmargincalculation/server/entities/parameters/LoadsVariationEntity.java @@ -25,7 +25,8 @@ @Setter @Builder @Entity -@Table(name = "loads_variation") +@Table(name = "loads_variation", indexes = @Index(name = "idx_loads_variation_dynamic_margin_calculation_parameters_id", + columnList = "dynamic_margin_calculation_parameters_id")) public class LoadsVariationEntity { @Id @Column(name = "id") @@ -36,7 +37,8 @@ public class LoadsVariationEntity { @CollectionTable( name = "loads_variation_load_filter", joinColumns = @JoinColumn(name = "loads_variation_id"), - foreignKey = @ForeignKey(name = "loads_variation_id_fk") + foreignKey = @ForeignKey(name = "loads_variation_id_fk"), + indexes = { @Index(name = "idx_loads_variation_load_filter_loads_variation_id", columnList = "loads_variation_id") } ) @Column(name = "load_filter_id", nullable = false) private List loadFilterIds = new ArrayList<>(); diff --git a/src/main/java/org/gridsuite/dynamicmargincalculation/server/entities/result/LoadIncreaseResultEntity.java b/src/main/java/org/gridsuite/dynamicmargincalculation/server/entities/result/LoadIncreaseResultEntity.java index bf7db42..6bfc6de 100644 --- a/src/main/java/org/gridsuite/dynamicmargincalculation/server/entities/result/LoadIncreaseResultEntity.java +++ b/src/main/java/org/gridsuite/dynamicmargincalculation/server/entities/result/LoadIncreaseResultEntity.java @@ -27,7 +27,8 @@ @Getter @Setter @Entity -@Table(name = "load_increase_result", indexes = {@Index(name = "load_increase_result_idx", columnList = "dynamic_margin_calculation_result_uuid")}) +@Table(name = "load_increase_result", indexes = {@Index(name = "idx_load_increase_result_dynamic_margin_calculation_result_uuid", + columnList = "dynamic_margin_calculation_result_uuid")}) public class LoadIncreaseResultEntity { @Id @Column(name = "id") @@ -58,7 +59,7 @@ public class LoadIncreaseResultEntity { foreignKey = @ForeignKey(name = "load_increase_result_failed_criteria_load_increase_result_id_fk") ), indexes = { - @Index(name = "load_increase_result_failed_criteria_idx", columnList = "load_increase_result_id") + @Index(name = "idx_load_increase_result_failed_criteria_load_increase_result_id", columnList = "load_increase_result_id") } ) @OrderColumn(name = "pos") diff --git a/src/main/java/org/gridsuite/dynamicmargincalculation/server/entities/result/ScenarioResultEntity.java b/src/main/java/org/gridsuite/dynamicmargincalculation/server/entities/result/ScenarioResultEntity.java index f43ebb0..d715d6c 100644 --- a/src/main/java/org/gridsuite/dynamicmargincalculation/server/entities/result/ScenarioResultEntity.java +++ b/src/main/java/org/gridsuite/dynamicmargincalculation/server/entities/result/ScenarioResultEntity.java @@ -26,7 +26,7 @@ @Getter @Setter @Entity -@Table(name = "scenario_result", indexes = @Index(name = "scenario_result_idx", columnList = "load_increase_result_id") +@Table(name = "scenario_result", indexes = @Index(name = "idx_scenario_result_load_increase_result_id", columnList = "load_increase_result_id") ) public class ScenarioResultEntity { @@ -50,7 +50,7 @@ public class ScenarioResultEntity { foreignKey = @ForeignKey(name = "scenario_result_failed_criteria_scenario_result_id_fk") ), indexes = { - @Index(name = "scenario_result_failed_criteria_idx", columnList = "scenario_result_id") + @Index(name = "idx_scenario_result_failed_criteria_scenario_result_id", columnList = "scenario_result_id") } ) @OrderColumn(name = "pos") diff --git a/src/main/resources/db/changelog/changesets/changelog_20251223T095053Z.xml b/src/main/resources/db/changelog/changesets/changelog_20251223T095053Z.xml index 32a308c..65cdf52 100644 --- a/src/main/resources/db/changelog/changesets/changelog_20251223T095053Z.xml +++ b/src/main/resources/db/changelog/changesets/changelog_20251223T095053Z.xml @@ -1,6 +1,6 @@ - + @@ -16,14 +16,14 @@ - + - + @@ -34,7 +34,7 @@ - + @@ -46,7 +46,7 @@ - + @@ -57,7 +57,7 @@ - + @@ -67,7 +67,7 @@ - + @@ -78,7 +78,7 @@ - + @@ -90,47 +90,57 @@ - + - - + + + + + + + - - - + + + + + + + + - - + + - - + + - + - + - + - + - + - +