Skip to content
Original file line number Diff line number Diff line change
@@ -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 <quyet-thang.pham at rte-france.com>
*/
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;
}
}
Original file line number Diff line number Diff line change
@@ -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<DynamicModelConfig> 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 <quyet-thang.pham at rte-france.com>
*/
public class DynamicModelConfigsJsonSerializer extends StdSerializer<List<DynamicModelConfig>> {

public DynamicModelConfigsJsonSerializer() {
super((Class<List<DynamicModelConfig>>) (Class<?>) List.class);
}

@Override
public void serialize(List<DynamicModelConfig> configs,
JsonGenerator gen,
SerializerProvider provider) throws IOException {

gen.writeStartObject();

gen.writeFieldName("models");
writeDynamicModelConfigs(configs, gen);

gen.writeEndObject();
}

private static void writeDynamicModelConfigs(List<DynamicModelConfig> 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<Property> 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<String|int|double|boolean|TwoSides>
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<List<String|integer|double|boolean|TwoSides>>
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());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
*/
@RestController
@RequestMapping(value = "/" + API_VERSION)
@Tag(name = "Dynamic simulation server")
@Tag(name = "Dynamic security analysis server")
public class DynamicSecurityAnalysisController {

private final DynamicSecurityAnalysisService dynamicSecurityAnalysisService;
Expand Down Expand Up @@ -84,7 +84,7 @@ public ResponseEntity<UUID> run(@PathVariable("networkUuid") UUID networkUuid,

@GetMapping(value = "/results/{resultUuid}/status", produces = "application/json")
@Operation(summary = "Get the dynamic security analysis status from the database")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "The dynamic simulation status"),
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "The dynamic security analysis status"),
@ApiResponse(responseCode = "204", description = "Dynamic security analysis status is empty"),
@ApiResponse(responseCode = "404", description = "Dynamic security analysis result uuid has not been found")})
public ResponseEntity<DynamicSecurityAnalysisStatus> getStatus(@Parameter(description = "Result UUID") @PathVariable("resultUuid") UUID resultUuid) {
Expand All @@ -104,7 +104,7 @@ public ResponseEntity<List<UUID>> invalidateStatus(@Parameter(description = "Res

@DeleteMapping(value = "/results/{resultUuid}")
@Operation(summary = "Delete a dynamic security analysis result from the database")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "The dynamic simulation result has been deleted")})
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "The dynamic security analysis result has been deleted")})
public ResponseEntity<Void> deleteResult(@Parameter(description = "Result UUID") @PathVariable("resultUuid") UUID resultUuid) {
dynamicSecurityAnalysisResultService.delete(resultUuid);
return ResponseEntity.ok().build();
Expand All @@ -128,7 +128,7 @@ public ResponseEntity<Void> stop(@Parameter(description = "Result UUID") @PathVa
}

@GetMapping(value = "/providers", produces = APPLICATION_JSON_VALUE)
@Operation(summary = "Get all security analysis simulation providers")
@Operation(summary = "Get all dynamic security analysis providers")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "Dynamic security analysis providers have been found")})
public ResponseEntity<List<String>> getProviders() {
return ResponseEntity.ok().contentType(MediaType.APPLICATION_JSON)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import io.swagger.v3.oas.annotations.tags.Tag;
import org.gridsuite.dynamicsecurityanalysis.server.DynamicSecurityAnalysisApi;
import org.gridsuite.dynamicsecurityanalysis.server.dto.parameters.DynamicSecurityAnalysisParametersInfos;
import org.gridsuite.dynamicsecurityanalysis.server.dto.parameters.DynamicSecurityAnalysisParametersValues;
import org.gridsuite.dynamicsecurityanalysis.server.service.ParametersService;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
Expand All @@ -21,12 +22,14 @@
import java.util.Optional;
import java.util.UUID;

import static org.gridsuite.computation.service.AbstractResultContext.VARIANT_ID_HEADER;

/**
* @author Thang PHAM <quyet-thang.pham at rte-france.com>
*/
@RestController
@RequestMapping(value = "/" + DynamicSecurityAnalysisApi.API_VERSION + "/parameters")
@Tag(name = "Dynamic security analysis parameters")
@Tag(name = "Dynamic security analysis server - Parameters")
public class DynamicSecurityAnalysisParametersController {

private final ParametersService parametersService;
Expand Down Expand Up @@ -112,4 +115,15 @@ public ResponseEntity<Void> updateProvider(
return ResponseEntity.ok().build();
}

@GetMapping(value = "/{uuid}/values", produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Get parameters values")
@ApiResponse(responseCode = "200", description = "parameters were returned")
@ApiResponse(responseCode = "404", description = "parameters were not found")
public ResponseEntity<DynamicSecurityAnalysisParametersValues> getParametersValues(
@Parameter(description = "parameters UUID") @PathVariable("uuid") UUID parametersUuid,
@RequestParam(name = "networkUuid") UUID networkUuid,
@RequestParam(name = VARIANT_ID_HEADER, required = false) String variantId) {
return ResponseEntity.of(Optional.of(parametersService.getParametersValues(parametersUuid, networkUuid, variantId)));
}

}
Original file line number Diff line number Diff line change
@@ -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.dynamicsecurityanalysis.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 <quyet-thang.pham at rte-france.com>
*/
@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<Contingency> contingencies;
}
Loading