Skip to content
Open
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/**
* 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.study.server.controller.logicalcontrols;

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.gridsuite.study.server.StudyApi;
import org.gridsuite.study.server.nodeactivity.NodeActivityRunnerService;
import org.gridsuite.study.server.service.RootNetworkNodeInfoService;
import org.gridsuite.study.server.service.StudyService;
import org.gridsuite.study.server.service.logicalcontrols.LogicalControlsService;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

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

import static org.gridsuite.study.server.StudyConstants.HEADER_USER_ID;
import static org.gridsuite.study.server.nodeactivity.NodeActivityType.COMPUTE;

/**
* @author David Braquart <david.braquart at rte-france.com>
*/
@RestController
@RequestMapping(value = "/" + StudyApi.API_VERSION + "/studies/{studyUuid}/root-networks/{rootNetworkUuid}/nodes/{nodeUuid}/logical-controls")
@Tag(name = "Study server - Logical controls")
public class LogicalControlsController {
private final RootNetworkNodeInfoService rootNetworkNodeInfoService;
private final StudyService studyService;
private final LogicalControlsService logicalControlsService;
private final NodeActivityRunnerService nodeActivityRunnerService;

public LogicalControlsController(RootNetworkNodeInfoService rootNetworkNodeInfoService, StudyService studyService, LogicalControlsService logicalControlsService,
NodeActivityRunnerService nodeActivityRunnerService) {
this.rootNetworkNodeInfoService = rootNetworkNodeInfoService;
this.studyService = studyService;
this.logicalControlsService = logicalControlsService;
this.nodeActivityRunnerService = nodeActivityRunnerService;
}

@PostMapping(value = "/run")
@Operation(summary = "run logical controls on study")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "The logical controls has started")})
public ResponseEntity<Void> runLogicalControls(@Parameter(description = "studyUuid") @PathVariable("studyUuid") UUID studyUuid,
@PathVariable("rootNetworkUuid") UUID rootNetworkUuid,
@Parameter(description = "nodeUuid") @PathVariable("nodeUuid") UUID nodeUuid,
@RequestHeader(HEADER_USER_ID) String userId) {
studyService.assertIsNodeNotReadOnly(nodeUuid);
// no quota on this computation
nodeActivityRunnerService.runWith(COMPUTE, studyUuid, rootNetworkUuid, List.of(nodeUuid),
() -> logicalControlsService.runLogicalControls(studyUuid, nodeUuid, rootNetworkUuid, userId));
return ResponseEntity.ok().build();
}

@PutMapping(value = "/stop")
@Operation(summary = "stop logical controls on study")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "The logical controls has been stopped")})
public ResponseEntity<Void> stopLogicalControls(@Parameter(description = "Study uuid") @PathVariable("studyUuid") UUID studyUuid,
@PathVariable("rootNetworkUuid") UUID rootNetworkUuid,
@Parameter(description = "nodeUuid") @PathVariable("nodeUuid") UUID nodeUuid) {
rootNetworkNodeInfoService.stopLogicalControls(studyUuid, nodeUuid, rootNetworkUuid);
return ResponseEntity.ok().build();
}

@GetMapping(value = "/result")
@Operation(summary = "Get a logical controls result on study")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "The logical controls result"),
@ApiResponse(responseCode = "204", description = "No logical controls has been done yet"),
@ApiResponse(responseCode = "404", description = "The logical controls has not been found")})
public ResponseEntity<String> getLogicalControlsResult(@Parameter(description = "study UUID") @PathVariable("studyUuid") UUID studyUuid,
@Parameter(description = "rootNetwork Uuid") @PathVariable("rootNetworkUuid") UUID rootNetworkUuid,
@Parameter(description = "node Uuid") @PathVariable("nodeUuid") UUID nodeUuid) {
String result = rootNetworkNodeInfoService.getLogicalControlsResult(nodeUuid, rootNetworkUuid);
return result != null ? ResponseEntity.ok().body(result) :
ResponseEntity.noContent().build();
}

@GetMapping(value = "/status")
@Operation(summary = "Get the logical controls status on study")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "The logical controls status"),
@ApiResponse(responseCode = "204", description = "No logical controls has been done yet"),
@ApiResponse(responseCode = "404", description = "The logical controls status has not been found")})
public ResponseEntity<String> getLogicalControlsStatus(@Parameter(description = "Study UUID") @PathVariable("studyUuid") UUID studyUuid,
@PathVariable("rootNetworkUuid") UUID rootNetworkUuid,
@Parameter(description = "nodeUuid") @PathVariable("nodeUuid") UUID nodeUuid) {
String status = rootNetworkNodeInfoService.getLogicalControlsStatus(nodeUuid, rootNetworkUuid);
return status != null ? ResponseEntity.ok().body(status) : ResponseEntity.noContent().build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
import static org.gridsuite.study.server.StudyConstants.HEADER_USER_ID;
import static org.gridsuite.study.server.dto.ComputationType.STATE_ESTIMATION;
import static org.gridsuite.study.server.nodeactivity.NodeActivityType.COMPUTE;
import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;

/**
* @author Bassel El Cheikh <bassel.el-cheikh_externe at rte-france.com>
Expand Down Expand Up @@ -99,14 +98,4 @@ public ResponseEntity<Void> stopStateEstimation(@Parameter(description = "Study
rootNetworkNodeInfoService.stopStateEstimation(studyUuid, nodeUuid, rootNetworkUuid, userId);
return ResponseEntity.ok().build();
}

@PostMapping(value = "/logical-controls", produces = APPLICATION_JSON_VALUE)
@Operation(summary = "Run logical controls on a study node")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "The computation has been done and the results are returned")})
public ResponseEntity<String> computeLogicalControls(@Parameter(description = "studyUuid") @PathVariable("studyUuid") UUID studyUuid,
@PathVariable("rootNetworkUuid") UUID rootNetworkUuid,
@Parameter(description = "nodeUuid") @PathVariable("nodeUuid") UUID nodeUuid) {
// no quota on this small synchronous computation
return ResponseEntity.ok().body(stateEstimationService.computeLogicalControls(studyUuid, nodeUuid, rootNetworkUuid));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,11 @@ public enum ComputationType {
ASYMMETRICAL_LOAD("Asymmetrical load", "asymmetricalLoadResultUuid",
NotificationService.UPDATE_TYPE_ASYMMETRICAL_LOAD_STATUS,
NotificationService.UPDATE_TYPE_ASYMMETRICAL_LOAD_RESULT,
NotificationService.UPDATE_TYPE_ASYMMETRICAL_LOAD_FAILED);
NotificationService.UPDATE_TYPE_ASYMMETRICAL_LOAD_FAILED),
LOGICAL_CONTROLS("Logical controls", "logicalControlsResultUuid",
NotificationService.UPDATE_TYPE_LOGICAL_CONTROLS_STATUS,
NotificationService.UPDATE_TYPE_LOGICAL_CONTROLS_RESULT,
NotificationService.UPDATE_TYPE_LOGICAL_CONTROLS_FAILED);

private final String label; // used for logs
private final String resultUuidLabel;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ public void add(InvalidateNodeInfos invalidateNodeInfos) {
stateEstimationResultUuids.addAll(invalidateNodeInfos.getStateEstimationResultUuids());
pccMinResultUuids.addAll(invalidateNodeInfos.getPccMinResultUuids());
asymmetricalLoadResultUuids.addAll(invalidateNodeInfos.getAsymmetricalLoadResultUuids());
logicalControlsResultUuids.addAll(invalidateNodeInfos.getLogicalControlsResultUuids());
dynamicSimulationResultUuids.addAll(invalidateNodeInfos.getDynamicSimulationResultUuids());
dynamicSecurityAnalysisResultUuids.addAll(invalidateNodeInfos.getDynamicSecurityAnalysisResultUuids());
dynamicMarginCalculationResultUuids.addAll(invalidateNodeInfos.getDynamicMarginCalculationResultUuids());
Expand All @@ -88,6 +89,7 @@ public RemoteDeletionInfos toRemoteDeletionInfos(InvalidateNodeInfos invalidateN
infos.setStateEstimationResultUuids(new HashSet<>(invalidateNodeInfos.getStateEstimationResultUuids()));
infos.setPccMinResultUuids(new HashSet<>(invalidateNodeInfos.getPccMinResultUuids()));
infos.setAsymmetricalLoadResultUuids(new HashSet<>(invalidateNodeInfos.getAsymmetricalLoadResultUuids()));
infos.setLogicalControlsResultUuids(new HashSet<>(invalidateNodeInfos.getLogicalControlsResultUuids()));
return infos;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ public static QuotaType mapFromComputationType(ComputationType computationType)
case PCC_MIN -> QuotaType.PCC_MIN;
case SHORT_CIRCUIT, SHORT_CIRCUIT_ONE_BUS -> QuotaType.SHORT_CIRCUIT;
case ASYMMETRICAL_LOAD -> QuotaType.ASYMMETRICAL_LOAD;
case LOGICAL_CONTROLS -> null; // no quota for logical controls
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ public class RemoteDeletionInfos {
protected Set<UUID> stateEstimationResultUuids = new HashSet<>();
protected Set<UUID> pccMinResultUuids = new HashSet<>();
protected Set<UUID> asymmetricalLoadResultUuids = new HashSet<>();
protected Set<UUID> logicalControlsResultUuids = new HashSet<>();

public List<UUID> getReportUuids() {
return reportUuids.stream().toList();
Expand Down Expand Up @@ -74,6 +75,10 @@ public List<UUID> getAsymmetricalLoadResultUuids() {
return asymmetricalLoadResultUuids.stream().toList();
}

public List<UUID> getLogicalControlsResultUuids() {
return logicalControlsResultUuids.stream().toList();
}

public List<UUID> getDynamicSimulationResultUuids() {
return dynamicSimulationResultUuids.stream().toList();
}
Expand Down Expand Up @@ -138,4 +143,8 @@ public void addAsymmetricalLoadResultUuid(UUID asymmetricalLoadResultUuid) {
asymmetricalLoadResultUuids.add(asymmetricalLoadResultUuid);
}

public void addLogicalControlsResultUuid(UUID logicalControlsResultUuid) {
logicalControlsResultUuids.add(logicalControlsResultUuid);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ public class RootNetworkNodeInfo {

private UUID asymmetricalLoadResultUuid;

private UUID logicalControlsResultUuid;
Comment thread
coderabbitai[bot] marked this conversation as resolved.

private NodeBuildStatus nodeBuildStatus;

private List<NodeExportEmbeddable> nodeExportNetwork;
Expand All @@ -77,6 +79,7 @@ public RootNetworkNodeInfoEntity toEntity() {
.dynamicSecurityAnalysisResultUuid(dynamicSecurityAnalysisResultUuid)
.dynamicMarginCalculationResultUuid(dynamicMarginCalculationResultUuid)
.stateEstimationResultUuid(stateEstimationResultUuid)
.logicalControlsResultUuid(logicalControlsResultUuid)
.pccMinResultUuid(pccMinResultUuid)
.nodeBuildStatus(nodeBuildStatus.toEntity())
.build();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,9 @@ public class RootNetworkNodeInfoEntity {
@Column(name = "asymmetricalLoadResultUuid")
private UUID asymmetricalLoadResultUuid;

@Column(name = "logicalControlsResultUuid")
private UUID logicalControlsResultUuid;

@Embedded
@AttributeOverrides(value = {
@AttributeOverride(name = "localBuildStatus", column = @Column(name = "localBuildStatus", nullable = false)),
Expand All @@ -129,6 +132,7 @@ public RootNetworkNodeInfo toDto() {
.stateEstimationResultUuid(stateEstimationResultUuid)
.pccMinResultUuid(pccMinResultUuid)
.asymmetricalLoadResultUuid(asymmetricalLoadResultUuid)
.logicalControlsResultUuid(logicalControlsResultUuid)
.sensitivityAnalysisResultUuid(sensitivityAnalysisResultUuid)
.voltageInitResultUuid(voltageInitResultUuid)
.shortCircuitAnalysisResultUuid(shortCircuitAnalysisResultUuid)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,9 @@ public class NotificationService {
public static final String UPDATE_TYPE_ASYMMETRICAL_LOAD_FAILED = "asymmetricalLoad_failed";
public static final String UPDATE_TYPE_ASYMMETRICAL_LOAD_RESULT = "asymmetricalLoadResult";
public static final String UPDATE_TYPE_ASYMMETRICAL_LOAD_STATUS = "asymmetricalLoad_status";
public static final String UPDATE_TYPE_LOGICAL_CONTROLS_FAILED = "logicalControls_failed";
public static final String UPDATE_TYPE_LOGICAL_CONTROLS_RESULT = "logicalControlsResult";
public static final String UPDATE_TYPE_LOGICAL_CONTROLS_STATUS = "logicalControls_status";
public static final String UPDATE_TYPE_COMPUTATION_PARAMETERS = "computationParametersUpdated";
public static final String UPDATE_COMPUTATION_RESULT_COLUMN_FILTER = "computationResultColumnFilterUpdated";
public static final String UPDATE_COMPUTATION_RESULT_GLOBAL_FILTER = "computationResultGlobalFilterUpdated";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ public interface RootNetworkNodeInfoRepository extends JpaRepository<RootNetwork

List<RootNetworkNodeInfoEntity> findAllByAsymmetricalLoadResultUuidNotNull();

List<RootNetworkNodeInfoEntity> findAllByLogicalControlsResultUuidNotNull();

List<RootNetworkNodeInfoEntity> findAllByNodeInfoId(UUID nodeInfoId);

List<RootNetworkNodeInfoEntity> findAllByRootNetworkId(UUID rootNetworkUuid);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -407,10 +407,10 @@ public void consumeCalculationFailed(Message<String> msg, ComputationType comput
}

private void handleQuotaEnd(ComputationType computationType, String userId, UUID resultUuid) {
if (!shouldCheckOperationQuotas) {
QuotaType quotaType = QuotaType.mapFromComputationType(computationType);
if (!shouldCheckOperationQuotas || quotaType == null) {
return;
}
QuotaType quotaType = QuotaType.mapFromComputationType(computationType);
userAdminService.releaseQuota(userId, resultUuid);
notificationService.emitQuotaChange(userId, quotaType);
}
Expand Down Expand Up @@ -839,6 +839,26 @@ public Consumer<Message<String>> consumeAsymmetricalLoadFailed() {
return message -> consumeCalculationFailed(message, ASYMMETRICAL_LOAD);
}

@Bean
public Consumer<Message<String>> consumeLogicalControlsResult() {
return message -> consumeCalculationResult(message, LOGICAL_CONTROLS);
}

@Bean
public Consumer<Message<String>> consumeLogicalControlsRunning() {
return message -> consumeCalculationRunning(message, LOGICAL_CONTROLS);
}

@Bean
public Consumer<Message<String>> consumeLogicalControlsStopped() {
return message -> consumeCalculationStopped(message, LOGICAL_CONTROLS);
}

@Bean
public Consumer<Message<String>> consumeLogicalControlsFailed() {
return message -> consumeCalculationFailed(message, LOGICAL_CONTROLS);
}

public void consumeNetworkExportFinished(Message<String> msg) {
String receiverString = msg.getHeaders().get(HEADER_RECEIVER, String.class);
String s3Key = msg.getHeaders().get(HEADER_S3_KEY, String.class);
Expand Down
Loading
Loading