Skip to content

Commit

Permalink
feat(#447): RSR-1116 - update LN Mod.stVal based on COMPAS-LNodeStatus
Browse files Browse the repository at this point in the history
  • Loading branch information
massifben committed Dec 18, 2024
1 parent f1eeb3e commit f5f34ba
Show file tree
Hide file tree
Showing 8 changed files with 787 additions and 226 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ public Optional<DoLinkedToDa> findDoLinkedToDa(TDataTypeTemplates dtt, String lN
// Prepare DataObject
DataObject dataObject = new DataObject(tdo.getName(), tdoType.getCdc(), doLinkedToDaFilter.sdoNames());
// Search first DA from last DoType
return sdoOrDAService.findDA(lastDoType, tda1 -> tda1.getName().equals(doLinkedToDaFilter.daName()))
return sdoOrDAService.findDA(lastDoType, tda -> tda.getName().equals(doLinkedToDaFilter.daName()))
.flatMap(tda -> {
// Prepare DataAttribute
DataAttribute dataAttribute = new DataAttribute();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
// SPDX-FileCopyrightText: 2024 RTE FRANCE
//
// SPDX-License-Identifier: Apache-2.0

package org.lfenergy.compas.sct.commons;

import lombok.RequiredArgsConstructor;
import org.lfenergy.compas.scl2007b4.model.*;
import org.lfenergy.compas.sct.commons.domain.DataAttribute;
import org.lfenergy.compas.sct.commons.domain.DoLinkedToDa;
import org.lfenergy.compas.sct.commons.domain.DoLinkedToDaFilter;
import org.lfenergy.compas.sct.commons.dto.SclReportItem;
import org.lfenergy.compas.sct.commons.util.CommonConstants;
import org.lfenergy.compas.sct.commons.util.PrivateUtils;
import org.lfenergy.compas.sct.commons.util.SclConstructorHelper;

import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Stream;

@RequiredArgsConstructor
public class LNodeStatusService {

private static final String LNODE_STATUS_PRIVATE_TYPE = "COMPAS-LNodeStatus";
private final LdeviceService ldeviceService;
private final LnService lnService;
private final DataTypeTemplatesService dataTypeTemplatesService;

public List<SclReportItem> updateLnModStValBasedOnLNodeStatus(SCL scl) {
return scl.getSubstation().stream()
.flatMap(tSubstation -> tSubstation.getVoltageLevel().stream())
.flatMap(tVoltageLevel -> tVoltageLevel.getBay().stream())
.flatMap(tBay -> tBay.getFunction().stream())
.flatMap(tFunction -> tFunction.getLNode().stream())
.map(tlNode -> updateSingleLnModStValBasedOnLNodeStatus(scl, tlNode))
.filter(Objects::nonNull)
.toList();
}

private SclReportItem updateSingleLnModStValBasedOnLNodeStatus(SCL scl, TLNode tlNode) {
String lNodeLNS = PrivateUtils.extractStringPrivate(tlNode, LNODE_STATUS_PRIVATE_TYPE).orElse(null);
if (!"on".equals(lNodeLNS) && !"off".equals(lNodeLNS)) {
return SclReportItem.error(lNodePath(tlNode), "The private %s of the LNode has invalid value. Expecting one of [on, off] but got : %s".formatted(LNODE_STATUS_PRIVATE_TYPE, lNodeLNS));
}
TAnyLN anyLn = findLn(scl, tlNode.getIedName(), tlNode.getLdInst(), tlNode.getLnClass().getFirst(), tlNode.getLnInst(), tlNode.getPrefix()).orElse(null);
if (anyLn == null) {
return SclReportItem.error(lNodePath(tlNode), "LNode in Substation section does not have a matching LN in IED section");
}
String anyLnLNS = PrivateUtils.extractStringPrivate(anyLn, LNODE_STATUS_PRIVATE_TYPE).orElse(null);
if (anyLnLNS == null) {
return SclReportItem.error(lnPath(tlNode), "LN does not have a private %s".formatted(LNODE_STATUS_PRIVATE_TYPE));
}
if (!anyLnLNS.contains(lNodeLNS)) {
return SclReportItem.error(lnPath(tlNode), "Cannot set DAI Mod.stVal to %s, because LN private %s is set to %s".formatted(lNodeLNS, LNODE_STATUS_PRIVATE_TYPE, anyLnLNS));
}
TDAI daiModStVal = lnService.getDaiModStVal(anyLn).orElse(null);
if (daiModStVal == null) {
return null; // do nothing if DAI Mod.stVal is missing
}
List<String> modStValEnumValues = getModStValEnumValues(scl.getDataTypeTemplates(), anyLn.getLnType()).toList();
if (!modStValEnumValues.contains(lNodeLNS)) {
return SclReportItem.error(lnPath(tlNode), "Cannot set DAI Mod.stVal to '%s' because value is not in EnumType %s".formatted(lNodeLNS, modStValEnumValues));
}
daiModStVal.getVal().clear();
daiModStVal.getVal().add(SclConstructorHelper.newVal(lNodeLNS));
return null; // no error
}

private static String lnPath(TLNode tlNode) {
return "IED(%s)/LD(%s)/LN[%s,%s,%s]".formatted(tlNode.getIedName(), tlNode.getLdInst(), tlNode.getLnClass().getFirst(), tlNode.getLnInst(), tlNode.getPrefix());
}

private static String lNodePath(TLNode tlNode) {
return "LNode(iedName=%s, ldInst=%s, lnClass=%s, lnInst=%s, prefix=%s)".formatted(tlNode.getIedName(), tlNode.getLdInst(), tlNode.getLnClass().getFirst(), tlNode.getLnInst(), tlNode.getPrefix());
}

private Stream<String> getModStValEnumValues(TDataTypeTemplates dataTypeTemplates, String lnType) {
return dataTypeTemplatesService.findDoLinkedToDa(dataTypeTemplates, lnType, DoLinkedToDaFilter.from(CommonConstants.MOD_DO_NAME, CommonConstants.STVAL_DA_NAME))
.map(DoLinkedToDa::dataAttribute)
.filter(dataAttribute -> TPredefinedBasicTypeEnum.ENUM.equals(dataAttribute.getBType()))
.map(DataAttribute::getType)
.flatMap(enumId ->
dataTypeTemplates.getEnumType().stream()
.filter(tEnumType -> tEnumType.getId().equals(enumId))
.findFirst())
.stream()
.flatMap(tEnumType -> tEnumType.getEnumVal().stream())
.map(TEnumVal::getValue);
}

private Optional<TAnyLN> findLn(SCL scl, String iedName, String ldInst, String lnClass, String lnInst, String prefix) {
return scl.getIED().stream()
.filter(tied -> iedName.equals(tied.getName()))
.findFirst()
.flatMap(tied -> ldeviceService.findLdevice(tied, tlDevice -> ldInst.equals(tlDevice.getInst())))
.flatMap(tlDevice -> lnService.findAnyLn(tlDevice, tAnyLN -> lnService.matchesLn(tAnyLN, lnClass, lnInst, prefix)));

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import jakarta.xml.bind.JAXBElement;
import lombok.NonNull;
import lombok.experimental.UtilityClass;
import org.apache.commons.lang3.StringUtils;
import org.lfenergy.compas.scl2007b4.model.*;
import org.lfenergy.compas.sct.commons.dto.PrivateLinkedToStds;
import org.lfenergy.compas.sct.commons.exception.ScdException;
Expand Down Expand Up @@ -320,4 +321,13 @@ public static void copyCompasICDHeaderFromLNodePrivateIntoSTDPrivate(TPrivate st
}


public static Optional<String> extractStringPrivate(TBaseElement tBaseElement, String privateType) {
return tBaseElement.getPrivate().stream()
.filter(tPrivate -> privateType.equals(tPrivate.getType()))
.flatMap(tPrivate -> tPrivate.getContent().stream())
.filter(String.class::isInstance)
.map(String.class::cast)
.filter(StringUtils::isNotBlank)
.findFirst();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
// SPDX-FileCopyrightText: 2024 RTE FRANCE
//
// SPDX-License-Identifier: Apache-2.0

package org.lfenergy.compas.sct.commons;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.lfenergy.compas.scl2007b4.model.SCL;
import org.lfenergy.compas.scl2007b4.model.TAnyLN;
import org.lfenergy.compas.scl2007b4.model.TLN;
import org.lfenergy.compas.sct.commons.dto.SclReportItem;
import org.lfenergy.compas.sct.commons.testhelpers.SclHelper;
import org.lfenergy.compas.sct.commons.testhelpers.SclTestMarshaller;

import java.util.List;
import java.util.stream.Stream;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Named.named;
import static org.lfenergy.compas.sct.commons.testhelpers.SclHelper.*;

class LNodeStatusServiceTest {

private LNodeStatusService lNodeStatusService;

@BeforeEach
void setUp() {
lNodeStatusService = new LNodeStatusService(new LdeviceService(), new LnService(), new DataTypeTemplatesService());
}

@ParameterizedTest
@MethodSource("provideUpdateModStVal")
void updateLnStatusBasedOnPrivateLNodeStatus_should_update_Mod_stVal(String ldInst, String lnClass, String lnInst, String expected) {
// Given
SCL scl = SclTestMarshaller.getSCLFromFile("/scl-lnodestatus/lnodestatus.scd");
// When
List<SclReportItem> sclReportItems = lNodeStatusService.updateLnModStValBasedOnLNodeStatus(scl);
// Then
assertThat(sclReportItems).isEmpty();
assertThat(findDai(scl, "IED_NAME_1", ldInst, lnClass, lnInst, "", "Mod", "stVal"))
.map(SclHelper::getValue)
.hasValue(expected);
}

public static Stream<Arguments> provideUpdateModStVal() {
return Stream.of(
Arguments.of(named("LN 'on' à mettre à 'on'", "LDEVICE_1"), "PDIS", "1", "on"),
Arguments.of(named("LN 'off;on' à mettre à 'on'", "LDEVICE_1"), "PDIS", "2", "on"),
Arguments.of(named("LN 'off' à mettre à 'off'", "LDEVICE_1"), "PDIS", "3", "off"),
Arguments.of(named("LN 'off;on' à mettre à 'off'", "LDEVICE_1"), "PDIS", "3", "off"),
Arguments.of(named("LN0 'on' à mettre à 'on'", "LDEVICE_1"), "LLN0", "", "on"),
Arguments.of(named("LN0 'off;on' à mettre à 'on'", "LDEVICE_2"), "LLN0", "", "on"),
Arguments.of(named("LN0 'off' à mettre à 'off'", "LDEVICE_3"), "LLN0", "", "off"),
Arguments.of(named("LN0 'off;on' à mettre à 'off'", "LDEVICE_4"), "LLN0", "", "off")
);
}

@Test
void updateLnStatusBasedOnPrivateLNodeStatus_do_nothing_if_DAI_Mod_stVal_is_missing() {
// Given
SCL scl = SclTestMarshaller.getSCLFromFile("/scl-lnodestatus/lnodestatus_without_mod_stval.scd");
// When
List<SclReportItem> sclReportItems = lNodeStatusService.updateLnModStValBasedOnLNodeStatus(scl);
// Then
assertThat(sclReportItems).isEmpty();
assertThat(getLDevices(scl.getIED().getFirst())
.flatMap(tlDevice -> Stream.concat(Stream.of(tlDevice.getLN0()), tlDevice.getLN().stream())))
.flatMap(TAnyLN::getDOI)
.isEmpty();
}

@Test
void updateLnStatusBasedOnPrivateLNodeStatus_when_no_compasLNodeStatus_in_LNode_should_return_error() {
// Given
SCL scl = SclTestMarshaller.getSCLFromFile("/scl-lnodestatus/lnodestatus.scd");
scl.getSubstation().getFirst().getVoltageLevel().getFirst().getBay().getFirst().getFunction().getFirst().getLNode().getFirst()
.getPrivate().clear();
// When
List<SclReportItem> sclReportItems = lNodeStatusService.updateLnModStValBasedOnLNodeStatus(scl);
// Then
assertThat(sclReportItems).containsExactly(
SclReportItem.error("LNode(iedName=IED_NAME_1, ldInst=LDEVICE_1, lnClass=PDIS, lnInst=1, prefix=)",
"The private COMPAS-LNodeStatus of the LNode has invalid value. Expecting one of [on, off] but got : null")
);
}

@Test
void updateLnStatusBasedOnPrivateLNodeStatus_when_invalid_compasLNodeStatus_value_in_LNode_should_return_error() {
// Given
SCL scl = SclTestMarshaller.getSCLFromFile("/scl-lnodestatus/lnodestatus.scd");
scl.getSubstation().getFirst().getVoltageLevel().getFirst().getBay().getFirst().getFunction().getFirst().getLNode().getFirst()
.getPrivate().getFirst().getContent().set(0, "helloworld");
// When
List<SclReportItem> sclReportItems = lNodeStatusService.updateLnModStValBasedOnLNodeStatus(scl);
// Then
assertThat(sclReportItems).containsExactly(
SclReportItem.error("LNode(iedName=IED_NAME_1, ldInst=LDEVICE_1, lnClass=PDIS, lnInst=1, prefix=)",
"The private COMPAS-LNodeStatus of the LNode has invalid value. Expecting one of [on, off] but got : helloworld")
);
}

@Test
void updateLnStatusBasedOnPrivateLNodeStatus_when_LNode_does_not_match_any_LN_should_return_error() {
// Given
SCL scl = SclTestMarshaller.getSCLFromFile("/scl-lnodestatus/lnodestatus.scd");
((TLN) findAnyLn(scl, "IED_NAME_1", "LDEVICE_1", "PDIS", "1", ""))
.setPrefix("helloworld");
// When
List<SclReportItem> sclReportItems = lNodeStatusService.updateLnModStValBasedOnLNodeStatus(scl);
// Then
assertThat(sclReportItems).containsExactly(
SclReportItem.error("LNode(iedName=IED_NAME_1, ldInst=LDEVICE_1, lnClass=PDIS, lnInst=1, prefix=)",
"LNode in Substation section does not have a matching LN in IED section")
);
}

@Test
void updateLnStatusBasedOnPrivateLNodeStatus_when_no_compasLNodeStatus_in_LN_should_return_error() {
// Given
SCL scl = SclTestMarshaller.getSCLFromFile("/scl-lnodestatus/lnodestatus.scd");
findAnyLn(scl, "IED_NAME_1", "LDEVICE_1", "PDIS", "1", "")
.getPrivate().clear();
// When
List<SclReportItem> sclReportItems = lNodeStatusService.updateLnModStValBasedOnLNodeStatus(scl);
// Then
assertThat(sclReportItems).containsExactly(
SclReportItem.error("IED(IED_NAME_1)/LD(LDEVICE_1)/LN[PDIS,1,]",
"LN does not have a private COMPAS-LNodeStatus")
);
}

@Test
void updateLnStatusBasedOnPrivateLNodeStatus_when_compasLNodeStatus_is_on_in_LNode_but_off_in_LN_should_return_error() {
// Given
SCL scl = SclTestMarshaller.getSCLFromFile("/scl-lnodestatus/lnodestatus.scd");
findAnyLn(scl, "IED_NAME_1", "LDEVICE_1", "LLN0", "", "")
.getPrivate().getFirst().getContent().set(0, "off");
findAnyLn(scl, "IED_NAME_1", "LDEVICE_1", "PDIS", "1", "")
.getPrivate().getFirst().getContent().set(0, "off");
// When
List<SclReportItem> sclReportItems = lNodeStatusService.updateLnModStValBasedOnLNodeStatus(scl);
// Then
assertThat(sclReportItems).containsExactlyInAnyOrder(
SclReportItem.error("IED(IED_NAME_1)/LD(LDEVICE_1)/LN[LLN0,,]",
"Cannot set DAI Mod.stVal to on, because LN private COMPAS-LNodeStatus is set to off"),
SclReportItem.error("IED(IED_NAME_1)/LD(LDEVICE_1)/LN[PDIS,1,]",
"Cannot set DAI Mod.stVal to on, because LN private COMPAS-LNodeStatus is set to off")
);
}

@Test
void updateLnStatusBasedOnPrivateLNodeStatus_when_compasLNodeStatus_is_off_in_LNode_but_on_in_LN_should_return_error() {
// Given
SCL scl = SclTestMarshaller.getSCLFromFile("/scl-lnodestatus/lnodestatus.scd");
findAnyLn(scl, "IED_NAME_1", "LDEVICE_4", "LLN0", "", "")
.getPrivate().getFirst().getContent().set(0, "on");
findAnyLn(scl, "IED_NAME_1", "LDEVICE_1", "PDIS", "4", "")
.getPrivate().getFirst().getContent().set(0, "on");
// When
List<SclReportItem> sclReportItems = lNodeStatusService.updateLnModStValBasedOnLNodeStatus(scl);
// Then
assertThat(sclReportItems).containsExactlyInAnyOrder(
SclReportItem.error("IED(IED_NAME_1)/LD(LDEVICE_4)/LN[LLN0,,]",
"Cannot set DAI Mod.stVal to off, because LN private COMPAS-LNodeStatus is set to on"),
SclReportItem.error("IED(IED_NAME_1)/LD(LDEVICE_1)/LN[PDIS,4,]",
"Cannot set DAI Mod.stVal to off, because LN private COMPAS-LNodeStatus is set to on")
);
}

@Test
void updateLnStatusBasedOnPrivateLNodeStatus_when_Mod_stVal_enumType_does_not_contains_value_should_return_error() {
// Given
SCL scl = SclTestMarshaller.getSCLFromFile("/scl-lnodestatus/lnodestatus.scd");
scl.getDataTypeTemplates().getEnumType().getFirst().getEnumVal().removeIf(tEnumVal -> tEnumVal.getValue().equals("on"));
// When
List<SclReportItem> sclReportItems = lNodeStatusService.updateLnModStValBasedOnLNodeStatus(scl);
// Then
assertThat(sclReportItems).contains(
SclReportItem.error("IED(IED_NAME_1)/LD(LDEVICE_1)/LN[PDIS,1,]",
"Cannot set DAI Mod.stVal to 'on' because value is not in EnumType [off, test]")
);
}

}
Loading

0 comments on commit f5f34ba

Please sign in to comment.