-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(#447): RSR-1116 - update LN Mod.stVal based on COMPAS-LNodeStatus
- Loading branch information
Showing
8 changed files
with
787 additions
and
226 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
100 changes: 100 additions & 0 deletions
100
sct-commons/src/main/java/org/lfenergy/compas/sct/commons/LNodeStatusService.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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))); | ||
|
||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
188 changes: 188 additions & 0 deletions
188
sct-commons/src/test/java/org/lfenergy/compas/sct/commons/LNodeStatusServiceTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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]") | ||
); | ||
} | ||
|
||
} |
Oops, something went wrong.