diff --git a/src/main/java/org/gridsuite/modification/modifications/AbstractBranchModification.java b/src/main/java/org/gridsuite/modification/modifications/AbstractBranchModification.java index 8ccf4514..3a865388 100644 --- a/src/main/java/org/gridsuite/modification/modifications/AbstractBranchModification.java +++ b/src/main/java/org/gridsuite/modification/modifications/AbstractBranchModification.java @@ -6,41 +6,35 @@ */ package org.gridsuite.modification.modifications; -import com.powsybl.commons.PowsyblException; import com.powsybl.commons.report.ReportNode; import com.powsybl.commons.report.TypedValue; import com.powsybl.iidm.network.*; import com.powsybl.iidm.network.extensions.*; -import jakarta.validation.constraints.NotNull; import org.gridsuite.modification.NetworkModificationException; import org.gridsuite.modification.dto.*; -import org.gridsuite.modification.report.NetworkModificationReportResourceBundle; +import org.gridsuite.modification.modifications.olg.OperationalLimitsGroupsModification; import org.gridsuite.modification.utils.ModificationUtils; -import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; import java.util.*; -import java.util.function.Function; import java.util.stream.Collectors; import static org.gridsuite.modification.NetworkModificationException.Type.BRANCH_MODIFICATION_ERROR; -import static org.gridsuite.modification.dto.OperationalLimitsGroupInfos.Applicability.*; -import static org.gridsuite.modification.dto.OperationalLimitsGroupModificationType.DELETE; -import static org.gridsuite.modification.utils.ModificationUtils.insertReportNode; +import static org.gridsuite.modification.utils.ModificationUtils.NO_VALUE; /** * @author Florent MILLOT */ public abstract class AbstractBranchModification extends AbstractModification { - private static final String DURATION = "duration"; + public static final String DURATION = "duration"; public static final String NAME = "name"; public static final String VALUE = "value"; private static final String VALIDITY = "validity"; - private static final String LIMIT_ACCEPTABLE_DURATION = "limitAcceptableDuration"; + public static final String LIMIT_ACCEPTABLE_DURATION = "limitAcceptableDuration"; public static final String OPERATIONAL_LIMITS_GROUP_NAME = "operationalLimitsGroupName"; - private static final String SIDE = "side"; - private static final String APPLICABILITY = "applicability"; + public static final String SIDE = "side"; + public static final String APPLICABILITY = "applicability"; protected final BranchModificationInfos modificationInfos; @@ -54,9 +48,14 @@ protected void modifyBranch(Branch branch, BranchModificationInfos branchModi .withUntypedValue("id", branchModificationInfos.getEquipmentId()) .withSeverity(TypedValue.INFO_SEVERITY) .add(); - if (branchModificationInfos.getEquipmentName() != null) { - insertReportNode(subReportNode, ModificationUtils.getInstance().buildModificationReport(Optional.of(branch.getOptionalName()).orElse(null), branchModificationInfos.getEquipmentName().getValue(), "Name")); - branch.setName(branchModificationInfos.getEquipmentName().getValue()); + if (branchModificationInfos.getEquipmentName() != null && modificationInfos.getEquipmentName().getValue() != null) { + ModificationUtils.getInstance().applyElementaryModifications( + branch::setName, + () -> branch.getOptionalName().orElse(NO_VALUE), + modificationInfos.getEquipmentName(), + subReportNode, + "Name" + ); } modifyBranchVoltageLevelBusOrBusBarSectionAttributesSide1(modificationInfos, branch, subReportNode); @@ -73,14 +72,14 @@ protected void modifyBranch(Branch branch, BranchModificationInfos branchModi boolean modifyOLG = branchModificationInfos.getEnableOLGModification() == null || branchModificationInfos.getEnableOLGModification(); - if (modifyOLG && !CollectionUtils.isEmpty(branchModificationInfos.getOperationalLimitsGroups())) { + if (modifyOLG) { limitsReportNode = subReportNode.newReportNode().withMessageTemplate("network.modification.limits").add(); ReportNode limitSetsReportNode = limitsReportNode.newReportNode().withMessageTemplate("network.modification.limitsSets").add(); - modifyOperationalLimitsGroups( + new OperationalLimitsGroupsModification( branch, - branchModificationInfos.getOperationalLimitsGroupsModificationType(), branchModificationInfos.getOperationalLimitsGroups(), - limitSetsReportNode); + limitSetsReportNode + ).modifyOperationalLimitsGroups(branchModificationInfos.getOperationalLimitsGroupsModificationType()); } applySelectedOLGs(branch, activeOLGReports); @@ -94,185 +93,6 @@ protected void modifyBranch(Branch branch, BranchModificationInfos branchModi updateConnections(branch, branchModificationInfos); } - private void copyOperationalLimitsGroup(CurrentLimitsAdder limitsAdder, OperationalLimitsGroup opLimitGroupToCopy) { - // Copy all limits of the other side - opLimitGroupToCopy.getCurrentLimits().ifPresent(currentLimits -> { - limitsAdder.setPermanentLimit(currentLimits.getPermanentLimit()); - - for (LoadingLimits.TemporaryLimit tempLimit : currentLimits.getTemporaryLimits()) { - addTemporaryLimit(limitsAdder, tempLimit.getName(), tempLimit.getValue(), tempLimit.getAcceptableDuration()); - } - limitsAdder.add(); - }); - } - - private void moveLimitSetToTheOtherSide(Branch branch, List modificationInfos, - OperationalLimitsGroup limitsGroupToCopy, String modifiedLimitSet, - boolean isSide1, List olgReports) { - // if we have only one limit set with the same name but applicability is not good - // we should copy existing limit set on the right side and removed it from the other side - if (modificationInfos.stream().filter(limitSet -> limitSet.getId().equals(modifiedLimitSet)).toList().size() == 1) { - // Copy operational limits group to the other side - OperationalLimitsGroup limitsGroup = isSide1 ? branch.newOperationalLimitsGroup1(limitsGroupToCopy.getId()) - : branch.newOperationalLimitsGroup2(limitsGroupToCopy.getId()); - copyOperationalLimitsGroup(limitsGroup.newCurrentLimits(), limitsGroupToCopy); - - olgReports.add(ReportNode.newRootReportNode().withMessageTemplate("network.modification.applicabilityChanged") - .withUntypedValue(OPERATIONAL_LIMITS_GROUP_NAME, limitsGroupToCopy.getId()) - .withUntypedValue(APPLICABILITY, isSide1 ? SIDE1.toString() : SIDE2.toString()) - .withSeverity(TypedValue.INFO_SEVERITY) - .build()); - // Remove copied operational limits group - if (isSide1) { - branch.removeOperationalLimitsGroup2(modifiedLimitSet); - } else { - branch.removeOperationalLimitsGroup1(modifiedLimitSet); - } - } - } - - private boolean shouldDeletedOtherSide(Branch branch, List modificationInfos, - OperationalLimitsGroupModificationInfos limitsModifInfos) { - boolean hasModificationOnSideOne = !modificationInfos.stream().filter(opLimitModifInfo -> - opLimitModifInfo.getId().equals(limitsModifInfos.getId()) && opLimitModifInfo.getApplicability().equals(SIDE1)) - .toList().isEmpty(); - - boolean hasModificationOnSideTwo = !modificationInfos.stream().filter(opLimitModifInfo -> - opLimitModifInfo.getId().equals(limitsModifInfos.getId()) && opLimitModifInfo.getApplicability().equals(SIDE2)) - .toList().isEmpty(); - - switch (limitsModifInfos.getApplicability()) { - case SIDE1 -> { - return !hasModificationOnSideTwo && branch.getOperationalLimitsGroup2(limitsModifInfos.getId()).isPresent(); - } - case SIDE2 -> { - return !hasModificationOnSideOne && branch.getOperationalLimitsGroup1(limitsModifInfos.getId()).isPresent(); - } - default -> { - return false; - } - } - } - - // If we are changing applicability we may not find operational limits group where we should so check both sides - private void detectApplicabilityChange(Branch branch, List modificationInfos, - OperationalLimitsGroupModificationInfos modifiedLimitSetInfos, List olgReports) { - - OperationalLimitsGroup limitsGroup1 = branch.getOperationalLimitsGroup1(modifiedLimitSetInfos.getId()).orElse(null); - OperationalLimitsGroup limitsGroup2 = branch.getOperationalLimitsGroup2(modifiedLimitSetInfos.getId()).orElse(null); - if (limitsGroup1 == null && modifiedLimitSetInfos.getApplicability().equals(SIDE2) - || limitsGroup2 == null && modifiedLimitSetInfos.getApplicability().equals(SIDE1)) { - return; - } else if (limitsGroup1 != null && limitsGroup2 != null && !modifiedLimitSetInfos.getApplicability().equals(EQUIPMENT)) { - // applicability change from EQUIPMENT to one side - if (shouldDeletedOtherSide(branch, modificationInfos, modifiedLimitSetInfos)) { - if (modifiedLimitSetInfos.getApplicability().equals(SIDE1)) { - branch.removeOperationalLimitsGroup2(modifiedLimitSetInfos.getId()); - logApplicabilityChange(olgReports, limitsGroup1.getId(), SIDE1); - } else if (modifiedLimitSetInfos.getApplicability().equals(SIDE2)) { - branch.removeOperationalLimitsGroup1(modifiedLimitSetInfos.getId()); - logApplicabilityChange(olgReports, limitsGroup2.getId(), SIDE2); - } - } - return; - } - - switch (modifiedLimitSetInfos.getApplicability()) { - case SIDE1 -> moveLimitSetToTheOtherSide(branch, modificationInfos, limitsGroup2, modifiedLimitSetInfos.getId(), true, olgReports); - case SIDE2 -> moveLimitSetToTheOtherSide(branch, modificationInfos, limitsGroup1, modifiedLimitSetInfos.getId(), false, olgReports); - case EQUIPMENT -> { - boolean applicabilityChanged = false; - if (limitsGroup1 == null && limitsGroup2 != null) { - limitsGroup1 = branch.newOperationalLimitsGroup1(limitsGroup2.getId()); - copyOperationalLimitsGroup(limitsGroup1.newCurrentLimits(), limitsGroup2); - applicabilityChanged = true; - } - if (limitsGroup2 == null && limitsGroup1 != null) { - limitsGroup2 = branch.newOperationalLimitsGroup2(limitsGroup1.getId()); - copyOperationalLimitsGroup(limitsGroup2.newCurrentLimits(), limitsGroup1); - applicabilityChanged = true; - } - if (applicabilityChanged) { - logApplicabilityChange(olgReports, limitsGroup1.getId(), EQUIPMENT); - } - } - } - } - - private void logApplicabilityChange(List olgReports, String groupId, OperationalLimitsGroupInfos.Applicability applicability) { - olgReports.add(ReportNode.newRootReportNode().withMessageTemplate("network.modification.applicabilityChanged") - .withUntypedValue(OPERATIONAL_LIMITS_GROUP_NAME, groupId) - .withUntypedValue(APPLICABILITY, applicability.toString()) - .withSeverity(TypedValue.INFO_SEVERITY) - .build()); - } - - private void modifyOperationalLimitsGroups( - Branch branch, - OperationalLimitsGroupsModificationType operationalLimitsGroupsModificationType, - List operationalLimitsInfos, - ReportNode limitSetsReportNode) { - - if (operationalLimitsGroupsModificationType == OperationalLimitsGroupsModificationType.REPLACE) { - // because we are replacing all the limit sets we remove all the limit sets that are not specified in the network modification - // the others may be modified instead of recreated so it is better to not delete them in order to have more precise logs - deleteOlgsUnspecifiedInTheModification(branch, operationalLimitsInfos, branch.getOperationalLimitsGroups1(), SIDE1, limitSetsReportNode); - deleteOlgsUnspecifiedInTheModification(branch, operationalLimitsInfos, branch.getOperationalLimitsGroups2(), SIDE2, limitSetsReportNode); - } - - for (OperationalLimitsGroupModificationInfos opLGModifInfos : operationalLimitsInfos) { - if (opLGModifInfos.getModificationType() == null) { - continue; - } - - ArrayList olgSetReports = new ArrayList<>(); - - OperationalLimitsGroupInfos.Applicability applicability = opLGModifInfos.getApplicability(); - // here the modifications on an applicability EQUIPMENT are separated into two separate applications of both sides - // because iidm has two separated sets of opLGs on the network object (and for better logs) - - if (!opLGModifInfos.getModificationType().equals(DELETE)) { - detectApplicabilityChange(branch, operationalLimitsInfos, opLGModifInfos, olgSetReports); - } - - if (applicability == SIDE1 - || applicability == EQUIPMENT) { - OperationalLimitsGroup operationalLimitsGroup1 = branch.getOperationalLimitsGroup1(opLGModifInfos.getId()).orElse(null); - applyModificationToOperationalLimitsGroup(branch, branch::newOperationalLimitsGroup1, opLGModifInfos, operationalLimitsGroup1, SIDE1, limitSetsReportNode); - } - if (applicability == SIDE2 - || applicability == EQUIPMENT) { - OperationalLimitsGroup operationalLimitsGroup2 = branch.getOperationalLimitsGroup2(opLGModifInfos.getId()).orElse(null); - applyModificationToOperationalLimitsGroup(branch, branch::newOperationalLimitsGroup2, opLGModifInfos, operationalLimitsGroup2, SIDE2, limitSetsReportNode); - } - } - } - - private void deleteOlgsUnspecifiedInTheModification( - Branch branch, - List operationalLimitsInfos, - Collection operationalLimitsGroups, - OperationalLimitsGroupInfos.Applicability applicability, - ReportNode limitSetsReportNode) { - List olgToBeDeleted = new ArrayList<>(); - operationalLimitsGroups.stream().filter( - operationalLimitsGroup -> - operationalLimitsInfos.stream().noneMatch( - operationalLimitsGroupModificationInfos -> - // we don't want to remove the limit sets specified in the network modification (operationalLimitsGroups) : - Objects.equals(operationalLimitsGroupModificationInfos.getId(), operationalLimitsGroup.getId()) - && (operationalLimitsGroupModificationInfos.getApplicability() == applicability - || operationalLimitsGroupModificationInfos.getApplicability() == EQUIPMENT) - ) - ).forEach(operationalLimitsGroup -> olgToBeDeleted.add(operationalLimitsGroup.getId())); - - Iterator i = olgToBeDeleted.iterator(); - while (i.hasNext()) { - String s = i.next(); - removeOlg(branch, s, applicability, limitSetsReportNode); - } - } - private void applySelectedOLGs(Branch branch, List activeOLGReports) { if (modificationInfos.getSelectedOperationalLimitsGroup1() != null) { modifySelectedOperationalLimitsGroup( @@ -462,431 +282,13 @@ private boolean updateConnection(Branch branch, TwoSides side, Boolean connec return done; } - /** - * only apply the modification to a singular operational limits group on a given side (like in iidm) - * therefore applicability is SIDE1 or SIDE2, not EQUIPMENT - */ - protected void applyModificationToOperationalLimitsGroup( - Branch branch, - Function groupFactory, - OperationalLimitsGroupModificationInfos opLGModificationInfos, - OperationalLimitsGroup modifiedOperationalLimitsGroup, - OperationalLimitsGroupInfos.Applicability applicability, - ReportNode limitsSetsReportNode - ) { - switch (opLGModificationInfos.getModificationType()) { - case OperationalLimitsGroupModificationType.MODIFY_OR_ADD: { - if (modifiedOperationalLimitsGroup == null) { - addOpLG(groupFactory, opLGModificationInfos, modifiedOperationalLimitsGroup, applicability, limitsSetsReportNode); - } else { - modifyOLG(opLGModificationInfos, modifiedOperationalLimitsGroup, applicability, limitsSetsReportNode); - } - } break; - case OperationalLimitsGroupModificationType.MODIFY: { - if (modifiedOperationalLimitsGroup == null) { - throw new PowsyblException("Cannot modify operational limit group " + opLGModificationInfos.getId() + " which has not been found in equipment given side"); - } - modifyOLG(opLGModificationInfos, modifiedOperationalLimitsGroup, applicability, limitsSetsReportNode); - } break; - case OperationalLimitsGroupModificationType.ADD: { - addOpLG(groupFactory, opLGModificationInfos, modifiedOperationalLimitsGroup, applicability, limitsSetsReportNode); - } break; - case OperationalLimitsGroupModificationType.REPLACE: { - replaceOpLG(groupFactory, opLGModificationInfos, modifiedOperationalLimitsGroup, applicability, limitsSetsReportNode); - } break; - case DELETE: { - removeOlg(branch, opLGModificationInfos.getId(), applicability, limitsSetsReportNode); - } - } - } - - private void removeOlg( - Branch branch, - String olgId, - OperationalLimitsGroupInfos.Applicability applicability, - ReportNode limitsSetsReportNode) { - if (applicability == SIDE1 && branch.getOperationalLimitsGroup1(olgId).isEmpty() || - applicability == SIDE2 && branch.getOperationalLimitsGroup2(olgId).isEmpty()) { - throw new PowsyblException("Cannot delete operational limit group " + olgId + " which has not been found in equipment on side " + applicability); - } - if (applicability == SIDE1) { - branch.removeOperationalLimitsGroup1(olgId); - } else if (applicability == SIDE2) { - branch.removeOperationalLimitsGroup2(olgId); - } - limitsSetsReportNode.newReportNode() - .withMessageTemplate("network.modification.operationalLimitsGroupDeleted") - .withUntypedValue(OPERATIONAL_LIMITS_GROUP_NAME, olgId) - .withUntypedValue(SIDE, applicability.toString()) - .withSeverity(TypedValue.INFO_SEVERITY) - .add(); - } - - private void replaceOpLG(Function groupFactory, - OperationalLimitsGroupModificationInfos opLGModificationInfos, - OperationalLimitsGroup modifiedOperationalLimitsGroup, - OperationalLimitsGroupInfos.Applicability applicability, - ReportNode limitsSetsReportNode) { - - List limitSetReports = new ArrayList<>(); - if (modifiedOperationalLimitsGroup != null) { - modifiedOperationalLimitsGroup.removeCurrentLimits(); - removeAllProperties(modifiedOperationalLimitsGroup, limitSetReports); - } - - OperationalLimitsGroup newOperationalLimitsGroup = groupFactory.apply(opLGModificationInfos.getId()); - modifyCurrentLimits(opLGModificationInfos, newOperationalLimitsGroup.newCurrentLimits(), null, limitSetReports); - addProperties(newOperationalLimitsGroup, opLGModificationInfos, limitSetReports); - - if (!CollectionUtils.isEmpty(limitSetReports)) { - ReportNode reportNode = limitsSetsReportNode.newReportNode() - .withMessageTemplate("network.modification.operationalLimitsGroupReplaced") - .withUntypedValue(OPERATIONAL_LIMITS_GROUP_NAME, opLGModificationInfos.getId()) - .withUntypedValue(SIDE, applicability.toString()) - .withSeverity(TypedValue.INFO_SEVERITY) - .add(); - ModificationUtils.getInstance().reportModifications(reportNode, limitSetReports); - } - } - - private void addProperties(OperationalLimitsGroup limitsGroup, OperationalLimitsGroupModificationInfos operationalLimitsGroupInfos, List limitSetsReports) { - if (limitsGroup == null || CollectionUtils.isEmpty(operationalLimitsGroupInfos.getLimitsProperties())) { - return; - } - - operationalLimitsGroupInfos.getLimitsProperties().forEach((LimitsPropertyInfos property) -> { - limitSetsReports.add(ReportNode.newRootReportNode() - .withMessageTemplate("network.modification.propertyAdded") - .withUntypedValue(NAME, property.name()) - .withUntypedValue(VALUE, property.value()) - .withSeverity(TypedValue.DETAIL_SEVERITY) - .build()); - limitsGroup.setProperty(property.name(), property.value()); - }); - } - - private void removeAllProperties(OperationalLimitsGroup limitsGroup, List limitSetsReports) { - - if (limitsGroup == null) { - return; - } - - Iterator propertiesIt = limitsGroup.getPropertyNames().iterator(); - while (propertiesIt.hasNext()) { - String propertyName = propertiesIt.next(); - limitsGroup.removeProperty(propertyName); - limitSetsReports.add(ReportNode.newRootReportNode() - .withMessageTemplate("network.modification.propertyDeleted") - .withUntypedValue(NAME, propertyName) - .withSeverity(TypedValue.DETAIL_SEVERITY).build()); - } - } - - private void modifyProperties(OperationalLimitsGroup limitsGroup, - OperationalLimitsGroupModificationInfos operationalLimitsGroupInfos, - List limitSetsReports) { - if (limitsGroup == null || operationalLimitsGroupInfos == null) { - return; - } - - Set currentProperties = limitsGroup.getPropertyNames(); - - List propertiesToModify = new ArrayList<>(); - List propertiesToAdd = new ArrayList<>(); - List propertiesToRemove; - - if (!CollectionUtils.isEmpty(operationalLimitsGroupInfos.getLimitsProperties())) { - for (LimitsPropertyInfos propertyInfos : operationalLimitsGroupInfos.getLimitsProperties()) { - if (currentProperties.contains(propertyInfos.name())) { - propertiesToModify.add(propertyInfos); - } else { - propertiesToAdd.add(propertyInfos); - } - } - - propertiesToRemove = currentProperties.stream().filter( - (String propertyName) -> propertiesToModify.stream().filter(propertyInfos -> - propertyInfos.name().equals(propertyName)).toList().isEmpty()).toList(); - } else { - propertiesToRemove = new ArrayList<>(currentProperties); - } - - propertiesToRemove.forEach((String propertyName) -> { - limitsGroup.removeProperty(propertyName); - limitSetsReports.add(ReportNode.newRootReportNode() - .withMessageTemplate("network.modification.propertyDeleted") - .withUntypedValue(NAME, propertyName) - .withSeverity(TypedValue.DETAIL_SEVERITY).build()); - }); - - propertiesToModify.forEach((LimitsPropertyInfos property) -> { - // Skip changes when value does not change - if (limitsGroup.getProperty(property.name()).equals(property.value())) { - return; - } - limitSetsReports.add(ReportNode.newRootReportNode() - .withMessageTemplate("network.modification.propertyChanged") - .withUntypedValue(NAME, property.name()) - .withUntypedValue("to", property.value()) - .withUntypedValue("from", limitsGroup.getProperty(property.name())) - .withSeverity(TypedValue.DETAIL_SEVERITY).build()); - limitsGroup.setProperty(property.name(), property.value()); - }); - - propertiesToAdd.forEach((LimitsPropertyInfos property) -> { - limitSetsReports.add(ReportNode.newRootReportNode() - .withMessageTemplate("network.modification.propertyAdded") - .withUntypedValue(NAME, property.name()) - .withUntypedValue(VALUE, property.value()) - .withSeverity(TypedValue.DETAIL_SEVERITY) - .build()); - limitsGroup.setProperty(property.name(), property.value()); - }); - } - - private void modifyOLG( - OperationalLimitsGroupModificationInfos operationalLimitsGroupInfos, - OperationalLimitsGroup modifiedOperationalLimitsGroup, - OperationalLimitsGroupInfos.Applicability applicability, - ReportNode limitsSetsReportNode) { - - List limitSetsReports = new ArrayList<>(); - - modifiedOperationalLimitsGroup.getCurrentLimits().ifPresent(currentLimits -> { - modifyCurrentLimits(operationalLimitsGroupInfos, modifiedOperationalLimitsGroup.newCurrentLimits(), currentLimits, limitSetsReports); - modifyProperties(modifiedOperationalLimitsGroup, operationalLimitsGroupInfos, limitSetsReports); - - if (!limitSetsReports.isEmpty()) { - ReportNode limitSetReport = limitsSetsReportNode.newReportNode() - .withMessageTemplate("network.modification.operationalLimitsGroupModified") - .withUntypedValue(OPERATIONAL_LIMITS_GROUP_NAME, operationalLimitsGroupInfos.getId()) - .withUntypedValue(SIDE, applicability.toString()) - .withSeverity(TypedValue.INFO_SEVERITY).add(); - ModificationUtils.getInstance().reportModifications(limitSetReport, limitSetsReports); - } - }); - } - - private void addOpLG(Function groupFactory, OperationalLimitsGroupModificationInfos operationalLimitsGroupInfos, - OperationalLimitsGroup modifiedOperationalLimitsGroup, - OperationalLimitsGroupInfos.Applicability applicability, ReportNode limitsSetsReportNode) { - - List limitSetReports = new ArrayList<>(); - if (modifiedOperationalLimitsGroup != null) { - throw new PowsyblException("Cannot add " + modifiedOperationalLimitsGroup.getId() + " operational limit group, one with the given name already exists"); - } - OperationalLimitsGroup newOperationalLimitsGroup = groupFactory.apply(operationalLimitsGroupInfos.getId()); - modifyCurrentLimits(operationalLimitsGroupInfos, newOperationalLimitsGroup.newCurrentLimits(), - newOperationalLimitsGroup.getCurrentLimits().orElse(null), limitSetReports); - addProperties(newOperationalLimitsGroup, operationalLimitsGroupInfos, limitSetReports); - - if (!CollectionUtils.isEmpty(limitSetReports)) { - ReportNode limitSetNode = limitsSetsReportNode.newReportNode() - .withMessageTemplate("network.modification.operationalLimitsGroupAdded") - .withUntypedValue(OPERATIONAL_LIMITS_GROUP_NAME, operationalLimitsGroupInfos.getId()) - .withUntypedValue(SIDE, applicability.toString()) - .withSeverity(TypedValue.INFO_SEVERITY) - .add(); - ModificationUtils.getInstance().reportModifications(limitSetNode, limitSetReports); - } - } - - protected void modifyCurrentLimits( - OperationalLimitsGroupModificationInfos operationalLimitsGroupModificationInfos, - CurrentLimitsAdder limitsAdder, - CurrentLimits currentLimits, - List limitsReports) { - CurrentLimitsModificationInfos currentLimitsInfos = operationalLimitsGroupModificationInfos.getCurrentLimits(); - boolean hasPermanent = currentLimitsInfos.getPermanentLimit() != null; - if (hasPermanent) { - if (!(currentLimits != null && currentLimits.getPermanentLimit() == currentLimitsInfos.getPermanentLimit())) { - limitsReports.add(ModificationUtils.getInstance().buildModificationReport(currentLimits != null ? currentLimits.getPermanentLimit() : Double.NaN, - currentLimitsInfos.getPermanentLimit(), "PATL")); - } - limitsAdder.setPermanentLimit(currentLimitsInfos.getPermanentLimit()); - } else { - if (currentLimits != null) { - limitsAdder.setPermanentLimit(currentLimits.getPermanentLimit()); - } - } - modifyTemporaryLimits(operationalLimitsGroupModificationInfos, limitsAdder, currentLimits, limitsReports); - limitsAdder.add(); - } - - /** - * is the limit identified by acceptableDuration deleted in temporaryLimitsModification ? - */ - public boolean isThisLimitDeleted(List temporaryLimitsModification, int acceptableDuration) { - return temporaryLimitsModification.stream() - .filter(temporaryLimit -> temporaryLimit.getAcceptableDuration() != null) - .anyMatch(temporaryLimit -> temporaryLimit.getAcceptableDuration() == acceptableDuration && temporaryLimit.getModificationType() == TemporaryLimitModificationType.DELETE); - } - - /** - * This function removes all the temporary limits of the 'currentLimits' concerned and recreates them (except in case of deletion) - */ - protected void modifyTemporaryLimits(@NotNull OperationalLimitsGroupModificationInfos operationalLimitsGroupModificationInfos, - CurrentLimitsAdder limitsAdder, - CurrentLimits currentLimits, - List limitsReports) { - CurrentLimitsModificationInfos currentLimitsInfos = operationalLimitsGroupModificationInfos.getCurrentLimits(); - - // we create a mutable list of temporary limits to be able to remove the limits that are modified in this current modification - // those left at the end of the network modification are those that have not been modified (or deleted) - List unmodifiedTemporaryLimits = new ArrayList<>(); - boolean areLimitsReplaced = TemporaryLimitModificationType.REPLACE.equals(operationalLimitsGroupModificationInfos.getTemporaryLimitsModificationType()); - if (currentLimits != null) { - unmodifiedTemporaryLimits.addAll(currentLimits.getTemporaryLimits()); - } - List temporaryLimitsReports = new ArrayList<>(); - - if (currentLimitsInfos != null && currentLimitsInfos.getTemporaryLimits() != null) { - for (CurrentTemporaryLimitModificationInfos limit : currentLimitsInfos.getTemporaryLimits()) { - applyTemporaryLimitModification( - operationalLimitsGroupModificationInfos, - limitsAdder, - currentLimits, - limit, - unmodifiedTemporaryLimits, - temporaryLimitsReports - ); - } - } - - if (!unmodifiedTemporaryLimits.isEmpty()) { - if (areLimitsReplaced) { - // this needs to be logged only if there are unmodifiedTemporaryLimits left. - // which means that they are going to be removed by the REPLACE mode - temporaryLimitsReports.addFirst(ReportNode.newRootReportNode() - .withResourceBundles(NetworkModificationReportResourceBundle.BASE_NAME) - .withMessageTemplate("network.modification.temporaryLimitsReplaced") - .withSeverity(TypedValue.INFO_SEVERITY) - .build()); - } else { - // we add (back) the temporary limits that have not been modified - for (LoadingLimits.TemporaryLimit limit : unmodifiedTemporaryLimits) { - addTemporaryLimit(limitsAdder, limit.getName(), limit.getValue(), limit.getAcceptableDuration()); - } - } - } - if (!temporaryLimitsReports.isEmpty()) { - temporaryLimitsReports.addFirst(ReportNode.newRootReportNode() - .withResourceBundles(NetworkModificationReportResourceBundle.BASE_NAME) - .withMessageTemplate("network.modification.temporaryLimitsModification") - .withSeverity(TypedValue.INFO_SEVERITY) - .build()); - limitsReports.addAll(temporaryLimitsReports); - } - } - - private static boolean mayCreateALimit(TemporaryLimitModificationType modificationType) { + public static boolean mayCreateLimit(TemporaryLimitModificationType modificationType) { return modificationType == TemporaryLimitModificationType.ADD || modificationType == TemporaryLimitModificationType.REPLACE || modificationType == TemporaryLimitModificationType.MODIFY_OR_ADD; } - /** - * modify a specific limit - * @param operationalLimitsGroupModificationInfos part of the network modification containing the operational limits groups data - * @param limitsAdder adder which receives all the "validated" limits to be added at the end - * @param networkCurrentLimits limits of the branch which is currently modified by the network modification - * @param limit modification to be applied to the limit - * @param unmodifiedTemporaryLimits list of all the unmodified limits that will be added at the end of the network modification - * @param temporaryLimitsReports log report - */ - private void applyTemporaryLimitModification( - OperationalLimitsGroupModificationInfos operationalLimitsGroupModificationInfos, - CurrentLimitsAdder limitsAdder, - CurrentLimits networkCurrentLimits, - CurrentTemporaryLimitModificationInfos limit, - List unmodifiedTemporaryLimits, - List temporaryLimitsReports) { - CurrentLimitsModificationInfos currentLimitsInfos = operationalLimitsGroupModificationInfos.getCurrentLimits(); - int limitAcceptableDuration = limit.getAcceptableDuration() == null ? Integer.MAX_VALUE : limit.getAcceptableDuration(); - double limitValue = limit.getValue() == null ? Double.MAX_VALUE : limit.getValue(); - String limitDurationToReport = limitAcceptableDuration == Integer.MAX_VALUE ? " " : String.valueOf(limitAcceptableDuration); - String limitValueToReport = limitValue == Double.MAX_VALUE ? "no value" : String.valueOf(limitValue); - LoadingLimits.TemporaryLimit limitToModify = null; - if (networkCurrentLimits != null) { - limitToModify = getTemporaryLimitToModify(networkCurrentLimits, limit, currentLimitsInfos, operationalLimitsGroupModificationInfos.getTemporaryLimitsModificationType()); - // this limit is modified by the network modification so we remove it from the list of unmodified temporary limits - unmodifiedTemporaryLimits.removeIf(temporaryLimit -> temporaryLimit.getAcceptableDuration() == limitAcceptableDuration); - } - if (limitToModify == null && mayCreateALimit(limit.getModificationType())) { - createTemporaryLimit(limitsAdder, limit, temporaryLimitsReports, limitDurationToReport, limitValueToReport, limitValue, limitAcceptableDuration); - } else if (limitToModify != null) { - // the limit already exists - if (limit.getModificationType() == TemporaryLimitModificationType.DELETE) { - // the limit has been removed previously - temporaryLimitsReports.add(ReportNode.newRootReportNode() - .withResourceBundles(NetworkModificationReportResourceBundle.BASE_NAME) - .withMessageTemplate("network.modification.temporaryLimitDeleted.name") - .withUntypedValue(NAME, limit.getName()) - .withUntypedValue(DURATION, limitDurationToReport) - .withSeverity(TypedValue.INFO_SEVERITY) - .build()); - } else { - modifyTemporaryLimit(limitsAdder, limit, temporaryLimitsReports, limitToModify, limitValue, limitDurationToReport, limitValueToReport, limitAcceptableDuration); - } - } else if (limit.getModificationType() == TemporaryLimitModificationType.MODIFY || limit.getModificationType() == TemporaryLimitModificationType.MODIFY_OR_ADD) { - // invalid modification - temporaryLimitsReports.add(ReportNode.newRootReportNode() - .withResourceBundles(NetworkModificationReportResourceBundle.BASE_NAME) - .withMessageTemplate("network.modification.temporaryLimitsNoMatch") - .withUntypedValue(LIMIT_ACCEPTABLE_DURATION, limitAcceptableDuration) - .withSeverity(TypedValue.WARN_SEVERITY) - .build()); - } - } - - private static void modifyTemporaryLimit( - CurrentLimitsAdder limitsAdder, - CurrentTemporaryLimitModificationInfos limitModificationInfos, - List temporaryLimitsReports, - LoadingLimits.TemporaryLimit limitToModify, - double limitValue, - String limitDurationToReport, - String limitValueToReport, - int limitAcceptableDuration) { - if (Double.compare(limitToModify.getValue(), limitValue) != 0 && limitModificationInfos.getModificationType() != null) { - temporaryLimitsReports.add(ReportNode.newRootReportNode() - .withResourceBundles(NetworkModificationReportResourceBundle.BASE_NAME) - .withMessageTemplate("network.modification.temporaryLimitModified.name") - .withUntypedValue(NAME, limitModificationInfos.getName()) - .withUntypedValue(DURATION, limitDurationToReport) - .withUntypedValue(VALUE, limitValueToReport) - .withUntypedValue("oldValue", - limitToModify.getValue() == Double.MAX_VALUE ? "no value" - : String.valueOf(limitToModify.getValue())) - .withSeverity(TypedValue.INFO_SEVERITY) - .build()); - addTemporaryLimit(limitsAdder, limitModificationInfos.getName(), limitValue, limitAcceptableDuration); - } else { - // no real modification - addTemporaryLimit(limitsAdder, limitModificationInfos.getName(), limitToModify.getValue(), limitAcceptableDuration); - } - } - - private static void createTemporaryLimit( - CurrentLimitsAdder limitsAdder, - CurrentTemporaryLimitModificationInfos limit, - List temporaryLimitsReports, - String limitDurationToReport, - String limitValueToReport, - double limitValue, - int limitAcceptableDuration) { - temporaryLimitsReports.add(ReportNode.newRootReportNode() - .withResourceBundles(NetworkModificationReportResourceBundle.BASE_NAME) - .withMessageTemplate("network.modification.temporaryLimitAdded.name") - .withUntypedValue(NAME, limit.getName()) - .withUntypedValue(DURATION, limitDurationToReport) - .withUntypedValue(VALUE, limitValueToReport) - .withSeverity(TypedValue.INFO_SEVERITY) - .build()); - addTemporaryLimit(limitsAdder, limit.getName(), limitValue, limitAcceptableDuration); - } - - private static void addTemporaryLimit(CurrentLimitsAdder limitsAdder, String limit, double limitValue, int limitAcceptableDuration) { + public static void addTemporaryLimit(CurrentLimitsAdder limitsAdder, String limit, double limitValue, int limitAcceptableDuration) { limitsAdder .beginTemporaryLimit() .setName(limit) @@ -895,32 +297,6 @@ private static void addTemporaryLimit(CurrentLimitsAdder limitsAdder, String lim .endTemporaryLimit(); } - private LoadingLimits.TemporaryLimit getTemporaryLimitToModify( - CurrentLimits networkCurrentLimits, - CurrentTemporaryLimitModificationInfos limit, - CurrentLimitsModificationInfos currentLimitsInfos, - TemporaryLimitModificationType temporaryLimitsModificationType) { - int limitAcceptableDuration = limit.getAcceptableDuration() == null ? Integer.MAX_VALUE : limit.getAcceptableDuration(); - LoadingLimits.TemporaryLimit limitToModify; - limitToModify = networkCurrentLimits.getTemporaryLimit(limitAcceptableDuration); - if (limitToModify != null && !limitToModify.getName().equals(limit.getName())) { - boolean isThisLimitDeleted = isThisLimitDeleted(currentLimitsInfos.getTemporaryLimits(), limitAcceptableDuration); - if (isThisLimitDeleted) { - limitToModify = null; - } else if (TemporaryLimitModificationType.ADD.equals(limit.getModificationType())) { - throw new PowsyblException("2 temporary limits have the same duration " + limitAcceptableDuration); - } - } - - //Additional check for limit sets tabular modifications - if (TemporaryLimitModificationType.ADD.equals(temporaryLimitsModificationType)) { - networkCurrentLimits.getTemporaryLimits().stream().filter(temporaryLimit -> temporaryLimit.getName().equals(limit.getName())).findFirst().ifPresent(temporaryLimit -> { - throw new PowsyblException("2 temporary limits have the same name " + limit.getName()); - }); - } - return limitToModify; - } - protected boolean characteristicsModified(BranchModificationInfos branchModificationInfos) { return branchModificationInfos.getX() != null && branchModificationInfos.getX().getValue() != null diff --git a/src/main/java/org/gridsuite/modification/modifications/LineCreation.java b/src/main/java/org/gridsuite/modification/modifications/LineCreation.java index d65617f8..b7f8636a 100644 --- a/src/main/java/org/gridsuite/modification/modifications/LineCreation.java +++ b/src/main/java/org/gridsuite/modification/modifications/LineCreation.java @@ -80,16 +80,25 @@ public void apply(Network network, ReportNode subReportNode) { List opLimitsGroupSide1 = ModificationUtils.getOperationalLimitsGroupsOnSide(modificationInfos.getOperationalLimitsGroups(), Applicability.SIDE1); List opLimitsGroupSide2 = ModificationUtils.getOperationalLimitsGroupsOnSide(modificationInfos.getOperationalLimitsGroups(), Applicability.SIDE2); ReportNode reportNode = null; - if (!CollectionUtils.isEmpty(opLimitsGroupSide1)) { + if (!CollectionUtils.isEmpty(modificationInfos.getOperationalLimitsGroups())) { reportNode = addLimitSetReportNode(limitsReporter); - ModificationUtils.getInstance().setCurrentLimitsOnASide(reportNode, opLimitsGroupSide1, line, ONE); - } - if (!CollectionUtils.isEmpty(opLimitsGroupSide2)) { - if (reportNode == null) { - reportNode = addLimitSetReportNode(limitsReporter); + + for (OperationalLimitsGroupInfos olgInfos : modificationInfos.getOperationalLimitsGroups()) { + ReportNode limitSetNode = reportNode.newReportNode() + .withMessageTemplate("network.modification.limitSetAdded") + .withUntypedValue("name", olgInfos.getId()) + .withSeverity(TypedValue.INFO_SEVERITY) + .add(); + + if (olgInfos.getApplicability() == Applicability.SIDE1 || olgInfos.getApplicability() == Applicability.EQUIPMENT) { + ModificationUtils.getInstance().setCurrentLimitsOnASide(limitSetNode, olgInfos, line, ONE); + } + if (olgInfos.getApplicability() == Applicability.SIDE2 || olgInfos.getApplicability() == Applicability.EQUIPMENT) { + ModificationUtils.getInstance().setCurrentLimitsOnASide(limitSetNode, olgInfos, line, TWO); + } } - ModificationUtils.getInstance().setCurrentLimitsOnASide(reportNode, opLimitsGroupSide2, line, TWO); } + List limitSetsOnSideReportNodes = new ArrayList<>(); if (modificationInfos.getSelectedOperationalLimitsGroup1() != null) { if (!ModificationUtils.hasLimitSet(opLimitsGroupSide1, modificationInfos.getSelectedOperationalLimitsGroup1())) { diff --git a/src/main/java/org/gridsuite/modification/modifications/TwoWindingsTransformerCreation.java b/src/main/java/org/gridsuite/modification/modifications/TwoWindingsTransformerCreation.java index 885d3274..e8fc9b8d 100644 --- a/src/main/java/org/gridsuite/modification/modifications/TwoWindingsTransformerCreation.java +++ b/src/main/java/org/gridsuite/modification/modifications/TwoWindingsTransformerCreation.java @@ -20,6 +20,8 @@ import java.util.List; import java.util.Optional; +import static com.powsybl.iidm.network.TwoSides.ONE; +import static com.powsybl.iidm.network.TwoSides.TWO; import static org.gridsuite.modification.NetworkModificationException.Type.*; import static org.gridsuite.modification.dto.OperationalLimitsGroupInfos.Applicability.SIDE1; import static org.gridsuite.modification.dto.OperationalLimitsGroupInfos.Applicability.SIDE2; @@ -239,38 +241,6 @@ private void create2WTInOtherBreaker(Network network, VoltageLevel voltageLevel1 completeTwoWindingsTransformerCreation(network, twoWindingsTransformer, modificationInfos, subReportNode); } - private void setCurrentLimitsForSide(ReportNode reportNode, List operationalLimitsGroups, String selectedGroup, TwoWindingsTransformer transformer, TwoSides side, - List limitSetsOnSideReportNodes) { - if (!CollectionUtils.isEmpty(operationalLimitsGroups)) { - getInstance().setCurrentLimitsOnASide(reportNode, operationalLimitsGroups, transformer, side); - } - if (selectedGroup != null) { - if (!ModificationUtils.hasLimitSet(operationalLimitsGroups, selectedGroup)) { - limitSetsOnSideReportNodes.add(ReportNode.newRootReportNode() - .withMessageTemplate("network.modification.limitSetAbsentOnSide" + side.getNum()) - .withUntypedValue("selectedOperationalLimitsGroup", selectedGroup) - .withSeverity(TypedValue.WARN_SEVERITY) - .build()); - return; - } - - if (side == TwoSides.ONE) { - transformer.setSelectedOperationalLimitsGroup1(selectedGroup); - limitSetsOnSideReportNodes.add(ReportNode.newRootReportNode().withMessageTemplate("network.modification.limitSetSelectedOnSide1") - .withUntypedValue("selectedOperationalLimitsGroup1", modificationInfos.getSelectedOperationalLimitsGroup1()) - .withSeverity(TypedValue.INFO_SEVERITY) - .build()); - } - if (side == TwoSides.TWO) { - transformer.setSelectedOperationalLimitsGroup2(selectedGroup); - limitSetsOnSideReportNodes.add(ReportNode.newRootReportNode().withMessageTemplate("network.modification.limitSetSelectedOnSide2") - .withUntypedValue("selectedOperationalLimitsGroup2", modificationInfos.getSelectedOperationalLimitsGroup2()) - .withSeverity(TypedValue.INFO_SEVERITY) - .build()); - } - } - } - private void completeTwoWindingsTransformerCreation(Network network, TwoWindingsTransformer twoWindingsTransformer, TwoWindingsTransformerCreationInfos modificationInfos, @@ -290,12 +260,61 @@ private void completeTwoWindingsTransformerCreation(Network network, List operationalLimitsGroups2 = ModificationUtils.getOperationalLimitsGroupsOnSide(modificationInfos.getOperationalLimitsGroups(), SIDE2); List limitSetsOnSideReportNodes = new ArrayList<>(); - ReportNode reportNode = limitsReporter.newReportNode() - .withSeverity(TypedValue.INFO_SEVERITY) - .withMessageTemplate("network.modification.LimitSets") - .add(); - setCurrentLimitsForSide(reportNode, operationalLimitsGroups1, modificationInfos.getSelectedOperationalLimitsGroup1(), twoWindingsTransformer, TwoSides.ONE, limitSetsOnSideReportNodes); - setCurrentLimitsForSide(reportNode, operationalLimitsGroups2, modificationInfos.getSelectedOperationalLimitsGroup2(), twoWindingsTransformer, TwoSides.TWO, limitSetsOnSideReportNodes); + if (!CollectionUtils.isEmpty(modificationInfos.getOperationalLimitsGroups())) { + ReportNode reportNode = limitsReporter.newReportNode() + .withSeverity(TypedValue.INFO_SEVERITY) + .withMessageTemplate("network.modification.LimitSets") + .add(); + + for (OperationalLimitsGroupInfos olgInfos : modificationInfos.getOperationalLimitsGroups()) { + ReportNode limitSetNode = reportNode.newReportNode() + .withMessageTemplate("network.modification.limitSetAdded") + .withUntypedValue("name", olgInfos.getId()) + .withSeverity(TypedValue.INFO_SEVERITY) + .add(); + + if (olgInfos.getApplicability() == OperationalLimitsGroupInfos.Applicability.SIDE1 || olgInfos.getApplicability() == OperationalLimitsGroupInfos.Applicability.EQUIPMENT) { + ModificationUtils.getInstance().setCurrentLimitsOnASide(limitSetNode, olgInfos, twoWindingsTransformer, ONE); + } + if (olgInfos.getApplicability() == OperationalLimitsGroupInfos.Applicability.SIDE2 || olgInfos.getApplicability() == OperationalLimitsGroupInfos.Applicability.EQUIPMENT) { + ModificationUtils.getInstance().setCurrentLimitsOnASide(limitSetNode, olgInfos, twoWindingsTransformer, TWO); + } + } + } + + if (modificationInfos.getSelectedOperationalLimitsGroup1() != null) { + if (!ModificationUtils.hasLimitSet(operationalLimitsGroups1, modificationInfos.getSelectedOperationalLimitsGroup1())) { + limitSetsOnSideReportNodes.add(ReportNode.newRootReportNode() + .withMessageTemplate("network.modification.limitSetAbsentOnSide1") + .withUntypedValue("selectedOperationalLimitsGroup", modificationInfos.getSelectedOperationalLimitsGroup1()) + .withSeverity(TypedValue.WARN_SEVERITY) + .build()); + } else { + twoWindingsTransformer.setSelectedOperationalLimitsGroup1(modificationInfos.getSelectedOperationalLimitsGroup1()); + limitSetsOnSideReportNodes.add(ReportNode.newRootReportNode() + .withMessageTemplate("network.modification.limitSetSelectedOnSide1") + .withUntypedValue("selectedOperationalLimitsGroup1", modificationInfos.getSelectedOperationalLimitsGroup1()) + .withSeverity(TypedValue.INFO_SEVERITY) + .build()); + } + } + if (modificationInfos.getSelectedOperationalLimitsGroup2() != null) { + if (!ModificationUtils.hasLimitSet(operationalLimitsGroups2, modificationInfos.getSelectedOperationalLimitsGroup2())) { + limitSetsOnSideReportNodes.add(ReportNode.newRootReportNode() + .withMessageTemplate("network.modification.limitSetAbsentOnSide2") + .withUntypedValue("selectedOperationalLimitsGroup", modificationInfos.getSelectedOperationalLimitsGroup2()) + .withSeverity(TypedValue.WARN_SEVERITY) + .build()); + } else { + twoWindingsTransformer.setSelectedOperationalLimitsGroup2(modificationInfos.getSelectedOperationalLimitsGroup2()); + limitSetsOnSideReportNodes.add(ReportNode.newRootReportNode() + .withMessageTemplate("network.modification.limitSetSelectedOnSide2") + .withUntypedValue("selectedOperationalLimitsGroup2", modificationInfos.getSelectedOperationalLimitsGroup2()) + .withSeverity(TypedValue.INFO_SEVERITY) + .build()); + } + } + if (!limitSetsOnSideReportNodes.isEmpty()) { ModificationUtils.getInstance().reportModifications(limitsReporter, limitSetsOnSideReportNodes, "network.modification.ActiveLimitSets"); diff --git a/src/main/java/org/gridsuite/modification/modifications/olg/OperationalLimitsGroupModification.java b/src/main/java/org/gridsuite/modification/modifications/olg/OperationalLimitsGroupModification.java new file mode 100644 index 00000000..5b70c52d --- /dev/null +++ b/src/main/java/org/gridsuite/modification/modifications/olg/OperationalLimitsGroupModification.java @@ -0,0 +1,583 @@ +/** + * 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.modification.modifications.olg; + +import com.powsybl.commons.PowsyblException; +import com.powsybl.commons.report.ReportNode; +import com.powsybl.commons.report.TypedValue; +import com.powsybl.iidm.network.*; +import org.gridsuite.modification.dto.*; +import org.gridsuite.modification.modifications.AbstractBranchModification; +import org.gridsuite.modification.report.NetworkModificationReportResourceBundle; +import org.gridsuite.modification.utils.ModificationUtils; +import org.springframework.util.CollectionUtils; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +import static org.gridsuite.modification.dto.OperationalLimitsGroupInfos.Applicability.*; +import static org.gridsuite.modification.modifications.AbstractBranchModification.*; + +/** + * handles the modification of a single operational limits group from AbstractBranchModification + * it may affect both sides of an operational limits group + * + * @author Mathieu DEHARBE + */ +public class OperationalLimitsGroupModification { + private final Branch modifiedBranch; // branch modified by the network modification + private final OperationalLimitsGroupModificationInfos olgModifInfos; + private final ReportNode olgsReportNode; + List limitsReportsSide1; + List limitsReportsSide2; + + public OperationalLimitsGroupModification( + Branch modifiedBranch, + OperationalLimitsGroupModificationInfos olgModifInfos, + ReportNode limitSetsReportNode) { + this.modifiedBranch = modifiedBranch; + this.olgModifInfos = olgModifInfos; + olgsReportNode = limitSetsReportNode; + limitsReportsSide1 = new ArrayList<>(); + limitsReportsSide2 = new ArrayList<>(); + } + + private void addToLogsOnSide(ReportNode reportNode, OperationalLimitsGroupInfos.Applicability applicability) { + if (applicability == EQUIPMENT || applicability == SIDE1) { + limitsReportsSide1.add(reportNode); + } + if (applicability == EQUIPMENT || applicability == SIDE2) { + limitsReportsSide2.add(reportNode); + } + } + + private OperationalLimitsGroup modifiedOperationalLimitsGroup1() { + return modifiedBranch.getOperationalLimitsGroup1(olgModifInfos.getId()).orElse(null); + } + + private OperationalLimitsGroup modifiedOperationalLimitsGroup2() { + return modifiedBranch.getOperationalLimitsGroup2(olgModifInfos.getId()).orElse(null); + } + + protected void applyModificationToOperationalLimitsGroup() { + switch (olgModifInfos.getModificationType()) { + case OperationalLimitsGroupModificationType.MODIFY_OR_ADD: + switch (olgModifInfos.getApplicability()) { + case EQUIPMENT : + if (modifiedOperationalLimitsGroup1() == null && modifiedOperationalLimitsGroup2() == null) { + addOlg(EQUIPMENT); + } else if (modifiedOperationalLimitsGroup1() != null && modifiedOperationalLimitsGroup2() != null) { + modifyOLG(EQUIPMENT); + } else { + // one side already existed (modification), the other was empty so this is a creation + if (modifiedOperationalLimitsGroup1() == null) { + addOlg(SIDE1); + } else { + modifyOLG(SIDE1); + } + if (modifiedOperationalLimitsGroup2() == null) { + addOlg(SIDE2); + } else { + modifyOLG(SIDE2); + } + } + break; + case SIDE1 : + if (modifiedOperationalLimitsGroup1() == null) { + addOlg(olgModifInfos.getApplicability()); + } else { + modifyOLG(olgModifInfos.getApplicability()); + } + break; + case SIDE2 : + if (modifiedOperationalLimitsGroup2() == null) { + addOlg(olgModifInfos.getApplicability()); + } else { + modifyOLG(olgModifInfos.getApplicability()); + } + break; + } + break; + case OperationalLimitsGroupModificationType.MODIFY: + if (applicableOnSide1() && modifiedOperationalLimitsGroup1() == null) { + throw new PowsyblException("Cannot modify operational limit group " + olgModifInfos.getId() + " which has not been found in equipment side 1"); + } + if (applicableOnSide2() && modifiedOperationalLimitsGroup2() == null) { + throw new PowsyblException("Cannot modify operational limit group " + olgModifInfos.getId() + " which has not been found in equipment side 2"); + } + modifyOLG(olgModifInfos.getApplicability()); + break; + case OperationalLimitsGroupModificationType.ADD: + addOlg(olgModifInfos.getApplicability()); + break; + case OperationalLimitsGroupModificationType.REPLACE: + replaceOlg(); + break; + case DELETE: + removeOlg(); + } + } + + private boolean applicableOnSide1() { + return olgModifInfos.getApplicability() == SIDE1 || olgModifInfos.getApplicability() == EQUIPMENT; + } + + private boolean applicableOnSide2() { + return olgModifInfos.getApplicability() == SIDE2 || olgModifInfos.getApplicability() == EQUIPMENT; + } + + private void modifyOLG(OperationalLimitsGroupInfos.Applicability applicability) { + if (applicability == SIDE1 || applicability == EQUIPMENT) { + OperationalLimitsGroup modifiedOlg = modifiedOperationalLimitsGroup1(); + if (modifiedOlg != null) { + modifiedOlg.getCurrentLimits().ifPresent(currentLimits -> { + modifyCurrentLimits(modifiedOlg.newCurrentLimits(), currentLimits, SIDE1); + modifyProperties(modifiedOlg, SIDE1); + }); + } + } + if (applicability == SIDE2 || applicability == EQUIPMENT) { + OperationalLimitsGroup modifiedOlg = modifiedOperationalLimitsGroup2(); + if (modifiedOlg != null) { + modifiedOlg.getCurrentLimits().ifPresent(currentLimits -> { + modifyCurrentLimits(modifiedOlg.newCurrentLimits(), currentLimits, SIDE2); + modifyProperties(modifiedOlg, SIDE2); + }); + } + } + + if (!limitsReportsSide1.isEmpty() || !limitsReportsSide2.isEmpty()) { + ReportNode limitSetReport = olgsReportNode.newReportNode() + .withMessageTemplate("network.modification.operationalLimitsGroupModified") + .withUntypedValue(OPERATIONAL_LIMITS_GROUP_NAME, olgModifInfos.getId()) + .withUntypedValue(SIDE, ModificationUtils.applicabilityToString(applicability)) + .withSeverity(TypedValue.INFO_SEVERITY).add(); + createLogNodeForSide(limitSetReport, "network.modification.operationalLimitsGroupModified.detail", SIDE1); + createLogNodeForSide(limitSetReport, "network.modification.operationalLimitsGroupModified.detail", SIDE2); + } + } + + private void createLogNodeForSide(ReportNode limitSetReport, String messageTemplate, OperationalLimitsGroupInfos.Applicability applicability) { + if (applicability == EQUIPMENT) { + throw new IllegalArgumentException("createLogNodeForSide cannot be called on an EQUIPMENT applicability"); + } + List limitsReports = applicability == SIDE1 ? limitsReportsSide1 : limitsReportsSide2; + if (!limitsReports.isEmpty()) { + ReportNode limitSetReportDetail = limitSetReport.newReportNode() + .withMessageTemplate(messageTemplate) + .withUntypedValue(SIDE, ModificationUtils.applicabilityToString(applicability)) + .withSeverity(TypedValue.DETAIL_SEVERITY).add(); + ModificationUtils.getInstance().reportModifications(limitSetReportDetail, limitsReports); + } + } + + private void modifyProperties(OperationalLimitsGroup limitsGroup, OperationalLimitsGroupInfos.Applicability applicability) { + if (limitsGroup == null || olgModifInfos == null) { + return; + } + + Set currentProperties = limitsGroup.getPropertyNames(); + + List propertiesToModify = new ArrayList<>(); + List propertiesToAdd = new ArrayList<>(); + List propertiesToRemove; + + if (!CollectionUtils.isEmpty(olgModifInfos.getLimitsProperties())) { + for (LimitsPropertyInfos propertyInfos : olgModifInfos.getLimitsProperties()) { + if (currentProperties.contains(propertyInfos.name())) { + propertiesToModify.add(propertyInfos); + } else { + propertiesToAdd.add(propertyInfos); + } + } + + propertiesToRemove = currentProperties.stream().filter( + (String propertyName) -> propertiesToModify.stream().filter(propertyInfos -> + propertyInfos.name().equals(propertyName)).toList().isEmpty()).toList(); + } else { + propertiesToRemove = new ArrayList<>(currentProperties); + } + + propertiesToRemove.forEach((String propertyName) -> { + limitsGroup.removeProperty(propertyName); + addToLogsOnSide(ReportNode.newRootReportNode() + .withMessageTemplate("network.modification.propertyDeleted") + .withUntypedValue(NAME, propertyName) + .withSeverity(TypedValue.DETAIL_SEVERITY).build(), + applicability); + }); + + propertiesToModify.forEach((LimitsPropertyInfos property) -> { + // Skip changes when value does not change + if (limitsGroup.getProperty(property.name()).equals(property.value())) { + return; + } + addToLogsOnSide(ReportNode.newRootReportNode() + .withMessageTemplate("network.modification.propertyChanged") + .withUntypedValue(NAME, property.name()) + .withUntypedValue("to", property.value()) + .withUntypedValue("from", limitsGroup.getProperty(property.name())) + .withSeverity(TypedValue.DETAIL_SEVERITY).build(), + applicability); + limitsGroup.setProperty(property.name(), property.value()); + }); + + propertiesToAdd.forEach((LimitsPropertyInfos property) -> { + addToLogsOnSide(ReportNode.newRootReportNode() + .withMessageTemplate("network.modification.propertyAdded") + .withUntypedValue(NAME, property.name()) + .withUntypedValue(VALUE, property.value()) + .withSeverity(TypedValue.DETAIL_SEVERITY) + .build(), + applicability); + limitsGroup.setProperty(property.name(), property.value()); + }); + } + + protected void modifyCurrentLimits( + CurrentLimitsAdder limitsAdder, + CurrentLimits currentLimits, + OperationalLimitsGroupInfos.Applicability applicability) { + CurrentLimitsModificationInfos currentLimitsInfos = olgModifInfos.getCurrentLimits(); + boolean hasPermanent = currentLimitsInfos.getPermanentLimit() != null; + if (hasPermanent) { + if (!(currentLimits != null && currentLimits.getPermanentLimit() == currentLimitsInfos.getPermanentLimit())) { + addToLogsOnSide(ModificationUtils.buildModificationReport( + currentLimits != null ? currentLimits.getPermanentLimit() : Double.NaN, currentLimitsInfos.getPermanentLimit(), + "Permanent limit", + TypedValue.DETAIL_SEVERITY + ), + applicability); + } + limitsAdder.setPermanentLimit(currentLimitsInfos.getPermanentLimit()); + } else { + if (currentLimits != null) { + limitsAdder.setPermanentLimit(currentLimits.getPermanentLimit()); + } + } + modifyTemporaryLimits(limitsAdder, currentLimits, applicability); + limitsAdder.add(); + } + + private void addOlg(OperationalLimitsGroupInfos.Applicability applicability) { + if (applicability == EQUIPMENT || applicability == SIDE1) { + if (modifiedOperationalLimitsGroup1() != null) { + throw new PowsyblException("Cannot add " + modifiedOperationalLimitsGroup1().getId() + " operational limit group, one with the given name already exists"); + } + addOlgOnSide(modifiedBranch.newOperationalLimitsGroup1(olgModifInfos.getId()), SIDE1); + } + if (applicability == EQUIPMENT || applicability == SIDE2) { + if (modifiedOperationalLimitsGroup2() != null) { + throw new PowsyblException("Cannot add " + modifiedOperationalLimitsGroup2().getId() + " operational limit group, one with the given name already exists"); + } + addOlgOnSide(modifiedBranch.newOperationalLimitsGroup2(olgModifInfos.getId()), SIDE2); + } + + if (!limitsReportsSide1.isEmpty() || !limitsReportsSide2.isEmpty()) { + ReportNode limitSetReport = olgsReportNode.newReportNode() + .withMessageTemplate("network.modification.operationalLimitsGroupAdded") + .withUntypedValue(OPERATIONAL_LIMITS_GROUP_NAME, olgModifInfos.getId()) + .withUntypedValue(SIDE, ModificationUtils.applicabilityToString(applicability)) + .withSeverity(TypedValue.INFO_SEVERITY) + .add(); + createLogNodeForSide(limitSetReport, ModificationUtils.OPERATIONAL_LIMITS_GROUP_ADDED_LOG_DETAIL, SIDE1); + createLogNodeForSide(limitSetReport, ModificationUtils.OPERATIONAL_LIMITS_GROUP_ADDED_LOG_DETAIL, SIDE2); + } + } + + private void addOlgOnSide(OperationalLimitsGroup newOperationalLimitsGroup, OperationalLimitsGroupInfos.Applicability applicability) { + modifyCurrentLimits(newOperationalLimitsGroup.newCurrentLimits(), newOperationalLimitsGroup.getCurrentLimits().orElse(null), applicability); + addProperties(newOperationalLimitsGroup, applicability); + } + + private void replaceOlg() { + if (applicableOnSide1()) { + OperationalLimitsGroup modifiedOlg = modifiedOperationalLimitsGroup1(); + if (modifiedOlg != null) { + modifiedOlg.removeCurrentLimits(); + removeAllProperties(modifiedOlg); + } + OperationalLimitsGroup newOperationalLimitsGroup = modifiedBranch.newOperationalLimitsGroup1(olgModifInfos.getId()); + modifyCurrentLimits(newOperationalLimitsGroup.newCurrentLimits(), null, SIDE1); + addProperties(newOperationalLimitsGroup, SIDE1); + } + if (applicableOnSide2()) { + OperationalLimitsGroup modifiedOlg = modifiedOperationalLimitsGroup2(); + if (modifiedOlg != null) { + modifiedOlg.removeCurrentLimits(); + removeAllProperties(modifiedOlg); + } + OperationalLimitsGroup newOperationalLimitsGroup = modifiedBranch.newOperationalLimitsGroup2(olgModifInfos.getId()); + modifyCurrentLimits(newOperationalLimitsGroup.newCurrentLimits(), null, SIDE2); + addProperties(newOperationalLimitsGroup, SIDE2); + } + + if (!limitsReportsSide1.isEmpty() || !limitsReportsSide2.isEmpty()) { + ReportNode limitSetReport = olgsReportNode.newReportNode() + .withMessageTemplate("network.modification.operationalLimitsGroupReplaced") + .withUntypedValue(OPERATIONAL_LIMITS_GROUP_NAME, olgModifInfos.getId()) + .withUntypedValue(SIDE, ModificationUtils.applicabilityToString(olgModifInfos.getApplicability())) + .withSeverity(TypedValue.INFO_SEVERITY) + .add(); + createLogNodeForSide(limitSetReport, ModificationUtils.OPERATIONAL_LIMITS_GROUP_ADDED_LOG_DETAIL, SIDE1); + createLogNodeForSide(limitSetReport, ModificationUtils.OPERATIONAL_LIMITS_GROUP_ADDED_LOG_DETAIL, SIDE2); + } + } + + private void addProperties(OperationalLimitsGroup limitsGroup, OperationalLimitsGroupInfos.Applicability applicability) { + if (limitsGroup == null || CollectionUtils.isEmpty(olgModifInfos.getLimitsProperties())) { + return; + } + + olgModifInfos.getLimitsProperties().forEach((LimitsPropertyInfos property) -> { + addToLogsOnSide(ReportNode.newRootReportNode() + .withMessageTemplate("network.modification.propertyAdded") + .withUntypedValue(NAME, property.name()) + .withUntypedValue(VALUE, property.value()) + .withSeverity(TypedValue.DETAIL_SEVERITY) + .build(), + applicability); + limitsGroup.setProperty(property.name(), property.value()); + }); + } + + public void removeOlg() { + String olgId = olgModifInfos.getId(); + if (applicableOnSide1() && modifiedBranch.getOperationalLimitsGroup1(olgId).isEmpty() || + applicableOnSide2() && modifiedBranch.getOperationalLimitsGroup2(olgId).isEmpty()) { + throw new PowsyblException( + "Cannot delete operational limit group " + olgId + " which has not been found in equipment on " + ModificationUtils.applicabilityToString(olgModifInfos.getApplicability())); + } + if (applicableOnSide1()) { + modifiedBranch.removeOperationalLimitsGroup1(olgId); + } + if (applicableOnSide2()) { + modifiedBranch.removeOperationalLimitsGroup2(olgId); + } + olgsReportNode.newReportNode() + .withMessageTemplate("network.modification.operationalLimitsGroupDeleted") + .withUntypedValue(OPERATIONAL_LIMITS_GROUP_NAME, olgId) + .withUntypedValue(SIDE, ModificationUtils.applicabilityToString(olgModifInfos.getApplicability())) + .withSeverity(TypedValue.INFO_SEVERITY) + .add(); + } + + private void removeAllProperties(OperationalLimitsGroup limitsGroup) { + + if (limitsGroup == null) { + return; + } + + Iterator propertiesIt = limitsGroup.getPropertyNames().iterator(); + while (propertiesIt.hasNext()) { + String propertyName = propertiesIt.next(); + limitsGroup.removeProperty(propertyName); + addToLogsOnSide(ReportNode.newRootReportNode() + .withMessageTemplate("network.modification.propertyDeleted") + .withUntypedValue(NAME, propertyName) + .withSeverity(TypedValue.DETAIL_SEVERITY).build(), + olgModifInfos.getApplicability()); + } + } + + /** + * This function removes all the temporary limits of the 'currentLimits' concerned and recreates them (except in case of deletion) + */ + protected void modifyTemporaryLimits(CurrentLimitsAdder limitsAdder, CurrentLimits currentLimits, OperationalLimitsGroupInfos.Applicability applicability) { + CurrentLimitsModificationInfos currentLimitsInfos = olgModifInfos.getCurrentLimits(); + + // we create a mutable list of temporary limits to be able to remove the limits that are modified in this current modification + // those left at the end of the network modification are those that have not been modified (or deleted) + List unmodifiedTemporaryLimits = new ArrayList<>(); + boolean areLimitsReplaced = TemporaryLimitModificationType.REPLACE.equals(olgModifInfos.getTemporaryLimitsModificationType()); + if (currentLimits != null) { + unmodifiedTemporaryLimits.addAll(currentLimits.getTemporaryLimits()); + } + + if (currentLimitsInfos != null && currentLimitsInfos.getTemporaryLimits() != null) { + for (CurrentTemporaryLimitModificationInfos limit : currentLimitsInfos.getTemporaryLimits()) { + applyTemporaryLimitModification( + limitsAdder, + currentLimits, + limit, + unmodifiedTemporaryLimits, + applicability + ); + } + } + + if (!unmodifiedTemporaryLimits.isEmpty()) { + if (areLimitsReplaced) { + // this needs to be logged only if there are unmodifiedTemporaryLimits left. + // which means that they are going to be removed by the REPLACE mode + addToLogsOnSide(ReportNode.newRootReportNode() + .withResourceBundles(NetworkModificationReportResourceBundle.BASE_NAME) + .withMessageTemplate("network.modification.temporaryLimitsReplaced") + .withSeverity(TypedValue.DETAIL_SEVERITY) + .build(), + applicability); + } else { + // we add (back) the temporary limits that have not been modified + for (LoadingLimits.TemporaryLimit limit : unmodifiedTemporaryLimits) { + addTemporaryLimit(limitsAdder, limit.getName(), limit.getValue(), limit.getAcceptableDuration()); + } + } + } + } + + /** + * modify a specific limit + * @param limitsAdder adder which receives all the "validated" limits to be added at the end + * @param networkCurrentLimits limits of the branch which is currently modified by the network modification + * @param limit modification to be applied to the limit + * @param unmodifiedTemporaryLimits list of all the unmodified limits that will be added at the end of the network modification + */ + private void applyTemporaryLimitModification( + CurrentLimitsAdder limitsAdder, + CurrentLimits networkCurrentLimits, + CurrentTemporaryLimitModificationInfos limit, + List unmodifiedTemporaryLimits, + OperationalLimitsGroupInfos.Applicability applicability) { + CurrentLimitsModificationInfos currentLimitsInfos = olgModifInfos.getCurrentLimits(); + int limitAcceptableDuration = limit.getAcceptableDuration() == null ? Integer.MAX_VALUE : limit.getAcceptableDuration(); + double limitValue = limit.getValue() == null ? Double.MAX_VALUE : limit.getValue(); + String limitDurationToReport = limitAcceptableDuration == Integer.MAX_VALUE ? " " : String.valueOf(limitAcceptableDuration); + String limitValueToReport = limitValue == Double.MAX_VALUE ? "no value" : String.valueOf(limitValue); + LoadingLimits.TemporaryLimit limitToModify = null; + if (networkCurrentLimits != null) { + limitToModify = getTemporaryLimitToModify(networkCurrentLimits, limit, currentLimitsInfos, olgModifInfos.getTemporaryLimitsModificationType()); + // this limit is modified by the network modification so we remove it from the list of unmodified temporary limits + unmodifiedTemporaryLimits.removeIf(temporaryLimit -> temporaryLimit.getAcceptableDuration() == limitAcceptableDuration); + } + if (limitToModify == null && mayCreateLimit(limit.getModificationType())) { + createTemporaryLimit(limitsAdder, limit, limitDurationToReport, limitValueToReport, limitValue, limitAcceptableDuration, applicability); + } else if (limitToModify != null) { + // the limit already exists + if (limit.getModificationType() == TemporaryLimitModificationType.DELETE) { + // the limit has been removed previously + addToLogsOnSide(ReportNode.newRootReportNode() + .withResourceBundles(NetworkModificationReportResourceBundle.BASE_NAME) + .withMessageTemplate("network.modification.temporaryLimitDeleted.name") + .withUntypedValue(NAME, limit.getName()) + .withUntypedValue(DURATION, limitDurationToReport) + .withSeverity(TypedValue.DETAIL_SEVERITY) + .build(), + applicability); + } else { + modifyTemporaryLimit(limitsAdder, limit, limitToModify, limitValue, limitDurationToReport, limitValueToReport, limitAcceptableDuration, applicability); + } + } else if (limit.getModificationType() == TemporaryLimitModificationType.MODIFY || limit.getModificationType() == TemporaryLimitModificationType.MODIFY_OR_ADD) { + // invalid modification + addToLogsOnSide(ReportNode.newRootReportNode() + .withResourceBundles(NetworkModificationReportResourceBundle.BASE_NAME) + .withMessageTemplate("network.modification.temporaryLimitsNoMatch") + .withUntypedValue(LIMIT_ACCEPTABLE_DURATION, limitAcceptableDuration) + .withSeverity(TypedValue.WARN_SEVERITY) + .build(), + applicability); + } + } + + /** + * is the limit identified by acceptableDuration deleted in temporaryLimitsModification ? + */ + public boolean isThisLimitDeleted(List temporaryLimitsModification, int acceptableDuration) { + return temporaryLimitsModification.stream() + .anyMatch(temporaryLimit -> + temporaryLimit.getAcceptableDuration() == acceptableDuration && temporaryLimit.getModificationType() == TemporaryLimitModificationType.DELETE + ); + } + + private LoadingLimits.TemporaryLimit getTemporaryLimitToModify( + CurrentLimits networkCurrentLimits, + CurrentTemporaryLimitModificationInfos limit, + CurrentLimitsModificationInfos currentLimitsInfos, + TemporaryLimitModificationType temporaryLimitsModificationType) { + int limitAcceptableDuration = limit.getAcceptableDuration() == null ? Integer.MAX_VALUE : limit.getAcceptableDuration(); + LoadingLimits.TemporaryLimit limitToModify; + limitToModify = networkCurrentLimits.getTemporaryLimit(limitAcceptableDuration); + if (limitToModify != null && !limitToModify.getName().equals(limit.getName())) { + if (isThisLimitDeleted(currentLimitsInfos.getTemporaryLimits(), limitAcceptableDuration)) { + limitToModify = null; + } else if (TemporaryLimitModificationType.ADD.equals(limit.getModificationType())) { + throw new PowsyblException("2 temporary limits have the same duration " + limitAcceptableDuration); + } + } + + //Additional check for limit sets tabular modifications + if (TemporaryLimitModificationType.ADD.equals(temporaryLimitsModificationType)) { + networkCurrentLimits.getTemporaryLimits().stream().filter(temporaryLimit -> temporaryLimit.getName().equals(limit.getName())).findFirst().ifPresent(temporaryLimit -> { + throw new PowsyblException("2 temporary limits have the same name " + limit.getName()); + }); + } + return limitToModify; + } + + public void modifyTemporaryLimit( + CurrentLimitsAdder limitsAdder, + CurrentTemporaryLimitModificationInfos limitModificationInfos, + LoadingLimits.TemporaryLimit limitToModify, + double limitValue, + String limitDurationToReport, + String limitValueToReport, + int limitAcceptableDuration, + OperationalLimitsGroupInfos.Applicability applicability) { + if (Double.compare(limitToModify.getValue(), limitValue) != 0 && limitModificationInfos.getModificationType() != null) { + // value change + addToLogsOnSide(ReportNode.newRootReportNode() + .withResourceBundles(NetworkModificationReportResourceBundle.BASE_NAME) + .withMessageTemplate("network.modification.temporaryLimitValueModified.name") + .withUntypedValue(AbstractBranchModification.NAME, limitModificationInfos.getName()) + .withUntypedValue(DURATION, limitDurationToReport) + .withUntypedValue(AbstractBranchModification.VALUE, limitValueToReport) + .withUntypedValue("oldValue", + limitToModify.getValue() == Double.MAX_VALUE ? "no value" + : String.valueOf(limitToModify.getValue())) + .withSeverity(TypedValue.DETAIL_SEVERITY) + .build(), + applicability); + addTemporaryLimit(limitsAdder, limitModificationInfos.getName(), limitValue, limitAcceptableDuration); + } else { + addTemporaryLimit(limitsAdder, limitModificationInfos.getName(), limitToModify.getValue(), limitAcceptableDuration); + // log only if there is at least one actual modification + if (Double.compare(limitToModify.getValue(), limitValue) != 0 || + !limitToModify.getName().equals(limitModificationInfos.getName())) { + addToLogsOnSide(ReportNode.newRootReportNode() + .withResourceBundles(NetworkModificationReportResourceBundle.BASE_NAME) + .withMessageTemplate("network.modification.temporaryLimitModified.name") + .withUntypedValue(NAME, limitModificationInfos.getName()) + .withUntypedValue(VALUE, limitToModify.getValue()) + .withUntypedValue(DURATION, limitAcceptableDuration) + .withSeverity(TypedValue.DETAIL_SEVERITY) + .build(), + applicability); + } + } + } + + public void createTemporaryLimit( + CurrentLimitsAdder limitsAdder, + CurrentTemporaryLimitModificationInfos limit, + String limitDurationToReport, + String limitValueToReport, + double limitValue, + int limitAcceptableDuration, + OperationalLimitsGroupInfos.Applicability applicability) { + addToLogsOnSide(ReportNode.newRootReportNode() + .withResourceBundles(NetworkModificationReportResourceBundle.BASE_NAME) + .withMessageTemplate("network.modification.temporaryLimitModified.name") + .withUntypedValue(AbstractBranchModification.NAME, limit.getName()) + .withUntypedValue(DURATION, limitDurationToReport) + .withUntypedValue(AbstractBranchModification.VALUE, limitValueToReport) + .withSeverity(TypedValue.DETAIL_SEVERITY) + .build(), + applicability); + addTemporaryLimit(limitsAdder, limit.getName(), limitValue, limitAcceptableDuration); + } +} diff --git a/src/main/java/org/gridsuite/modification/modifications/olg/OperationalLimitsGroupsModification.java b/src/main/java/org/gridsuite/modification/modifications/olg/OperationalLimitsGroupsModification.java new file mode 100644 index 00000000..f861f72c --- /dev/null +++ b/src/main/java/org/gridsuite/modification/modifications/olg/OperationalLimitsGroupsModification.java @@ -0,0 +1,209 @@ +/** + * 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.modification.modifications.olg; + +import com.powsybl.commons.report.ReportNode; +import com.powsybl.commons.report.TypedValue; +import com.powsybl.iidm.network.*; +import org.gridsuite.modification.dto.*; + +import java.util.*; + +import static org.gridsuite.modification.dto.OperationalLimitsGroupInfos.Applicability.*; +import static org.gridsuite.modification.dto.OperationalLimitsGroupInfos.Applicability.EQUIPMENT; +import static org.gridsuite.modification.dto.OperationalLimitsGroupModificationType.DELETE; +import static org.gridsuite.modification.modifications.AbstractBranchModification.*; + +/** + * handles the modification of a list of operational limits groups from AbstractBranchModification + * + * @author Mathieu DEHARBE + */ +public class OperationalLimitsGroupsModification { + private final Branch modifiedBranch; // branch modified by the network modification + private final List olgModificationInfos; + private final ReportNode olgsReportNode; + + public OperationalLimitsGroupsModification( + Branch branch, + List operationalLimitsInfos, + ReportNode limitSetsReportNode) { + modifiedBranch = branch; + olgModificationInfos = operationalLimitsInfos != null ? operationalLimitsInfos : new ArrayList<>(); + olgsReportNode = limitSetsReportNode; + } + + public void modifyOperationalLimitsGroups(OperationalLimitsGroupsModificationType operationalLimitsGroupsModificationType) { + + if (operationalLimitsGroupsModificationType == OperationalLimitsGroupsModificationType.REPLACE) { + // because we are replacing all the limit sets we remove all the limit sets that are not specified in the network modification + // the others may be modified instead of recreated so it is better to not delete them in order to have more precise logs + deleteOlgsUnspecifiedInTheModification(SIDE1); + deleteOlgsUnspecifiedInTheModification(SIDE2); + } + + for (OperationalLimitsGroupModificationInfos opLGModifInfos : olgModificationInfos) { + if (opLGModifInfos.getModificationType() == null) { + continue; + } + + ArrayList olgSetReports = new ArrayList<>(); + + if (!opLGModifInfos.getModificationType().equals(DELETE)) { + detectApplicabilityChange(opLGModifInfos, olgSetReports); + } + + new OperationalLimitsGroupModification( + modifiedBranch, + opLGModifInfos, + olgsReportNode + ).applyModificationToOperationalLimitsGroup(); + } + } + + private void deleteOlgsUnspecifiedInTheModification(OperationalLimitsGroupInfos.Applicability applicability) { + List olgToBeDeleted = new ArrayList<>(); + Collection operationalLimitsGroups = applicability == SIDE1 ? + modifiedBranch.getOperationalLimitsGroups1() : + modifiedBranch.getOperationalLimitsGroups2(); + operationalLimitsGroups.stream().filter( + operationalLimitsGroup -> + olgModificationInfos.stream().noneMatch( + operationalLimitsGroupModificationInfos -> + // we don't want to remove the limit sets specified in the network modification (operationalLimitsGroups) : + Objects.equals(operationalLimitsGroupModificationInfos.getId(), operationalLimitsGroup.getId()) + && (operationalLimitsGroupModificationInfos.getApplicability() == applicability + || operationalLimitsGroupModificationInfos.getApplicability() == EQUIPMENT) + ) + ).forEach(operationalLimitsGroup -> olgToBeDeleted.add(operationalLimitsGroup.getId())); + + Iterator i = olgToBeDeleted.iterator(); + while (i.hasNext()) { + String s = i.next(); + new OperationalLimitsGroupModification( + modifiedBranch, + OperationalLimitsGroupModificationInfos.builder() + .id(s) + .applicability(applicability) + .build(), + olgsReportNode + ).removeOlg(); + } + } + + private void logApplicabilityChange(List olgReports, String groupId, OperationalLimitsGroupInfos.Applicability applicability) { + olgReports.add(ReportNode.newRootReportNode().withMessageTemplate("network.modification.applicabilityChanged") + .withUntypedValue(OPERATIONAL_LIMITS_GROUP_NAME, groupId) + .withUntypedValue(APPLICABILITY, applicability.toString()) + .withSeverity(TypedValue.INFO_SEVERITY) + .build()); + } + + private boolean shouldDeletedOtherSide(Branch branch, OperationalLimitsGroupModificationInfos limitsModifInfos) { + boolean hasModificationOnSideOne = !olgModificationInfos.stream().filter(opLimitModifInfo -> + opLimitModifInfo.getId().equals(limitsModifInfos.getId()) && opLimitModifInfo.getApplicability().equals(SIDE1)) + .toList().isEmpty(); + + boolean hasModificationOnSideTwo = !olgModificationInfos.stream().filter(opLimitModifInfo -> + opLimitModifInfo.getId().equals(limitsModifInfos.getId()) && opLimitModifInfo.getApplicability().equals(SIDE2)) + .toList().isEmpty(); + + switch (limitsModifInfos.getApplicability()) { + case SIDE1 -> { + return !hasModificationOnSideTwo && branch.getOperationalLimitsGroup2(limitsModifInfos.getId()).isPresent(); + } + case SIDE2 -> { + return !hasModificationOnSideOne && branch.getOperationalLimitsGroup1(limitsModifInfos.getId()).isPresent(); + } + default -> { + return false; + } + } + } + + // If we are changing applicability we may not find operational limits group where we should so check both sides + private void detectApplicabilityChange(OperationalLimitsGroupModificationInfos modifiedLimitSetInfos, List olgReports) { + + OperationalLimitsGroup limitsGroup1 = modifiedBranch.getOperationalLimitsGroup1(modifiedLimitSetInfos.getId()).orElse(null); + OperationalLimitsGroup limitsGroup2 = modifiedBranch.getOperationalLimitsGroup2(modifiedLimitSetInfos.getId()).orElse(null); + if (limitsGroup1 == null && modifiedLimitSetInfos.getApplicability().equals(SIDE2) + || limitsGroup2 == null && modifiedLimitSetInfos.getApplicability().equals(SIDE1)) { + return; + } else if (limitsGroup1 != null && limitsGroup2 != null && !modifiedLimitSetInfos.getApplicability().equals(EQUIPMENT)) { + // applicability change from EQUIPMENT to one side + if (shouldDeletedOtherSide(modifiedBranch, modifiedLimitSetInfos)) { + if (modifiedLimitSetInfos.getApplicability().equals(SIDE1)) { + modifiedBranch.removeOperationalLimitsGroup2(modifiedLimitSetInfos.getId()); + logApplicabilityChange(olgReports, limitsGroup1.getId(), SIDE1); + } else if (modifiedLimitSetInfos.getApplicability().equals(SIDE2)) { + modifiedBranch.removeOperationalLimitsGroup1(modifiedLimitSetInfos.getId()); + logApplicabilityChange(olgReports, limitsGroup2.getId(), SIDE2); + } + } + return; + } + + switch (modifiedLimitSetInfos.getApplicability()) { + case SIDE1 -> moveLimitSetToTheOtherSide(modifiedBranch, limitsGroup2, modifiedLimitSetInfos.getId(), true, olgReports); + case SIDE2 -> moveLimitSetToTheOtherSide(modifiedBranch, limitsGroup1, modifiedLimitSetInfos.getId(), false, olgReports); + case EQUIPMENT -> { + boolean applicabilityChanged = false; + if (limitsGroup1 == null && limitsGroup2 != null) { + limitsGroup1 = modifiedBranch.newOperationalLimitsGroup1(limitsGroup2.getId()); + copyOperationalLimitsGroup(limitsGroup1.newCurrentLimits(), limitsGroup2); + applicabilityChanged = true; + } + if (limitsGroup2 == null && limitsGroup1 != null) { + limitsGroup2 = modifiedBranch.newOperationalLimitsGroup2(limitsGroup1.getId()); + copyOperationalLimitsGroup(limitsGroup2.newCurrentLimits(), limitsGroup1); + applicabilityChanged = true; + } + if (applicabilityChanged) { + logApplicabilityChange(olgReports, limitsGroup1.getId(), EQUIPMENT); + } + } + } + } + + private void moveLimitSetToTheOtherSide(Branch branch, + OperationalLimitsGroup limitsGroupToCopy, String modifiedLimitSet, + boolean isSide1, + List olgReports) { + // if we have only one limit set with the same name but applicability is not good + // we should copy existing limit set on the right side and removed it from the other side + if (olgModificationInfos.stream().filter(limitSet -> limitSet.getId().equals(modifiedLimitSet)).toList().size() == 1) { + // Copy operational limits group to the other side + OperationalLimitsGroup limitsGroup = isSide1 ? branch.newOperationalLimitsGroup1(limitsGroupToCopy.getId()) + : branch.newOperationalLimitsGroup2(limitsGroupToCopy.getId()); + copyOperationalLimitsGroup(limitsGroup.newCurrentLimits(), limitsGroupToCopy); + + olgReports.add(ReportNode.newRootReportNode().withMessageTemplate("network.modification.applicabilityChanged") + .withUntypedValue(OPERATIONAL_LIMITS_GROUP_NAME, limitsGroupToCopy.getId()) + .withUntypedValue(APPLICABILITY, isSide1 ? SIDE1.toString() : SIDE2.toString()) + .withSeverity(TypedValue.INFO_SEVERITY) + .build()); + // Remove copied operational limits group + if (isSide1) { + branch.removeOperationalLimitsGroup2(modifiedLimitSet); + } else { + branch.removeOperationalLimitsGroup1(modifiedLimitSet); + } + } + } + + private void copyOperationalLimitsGroup(CurrentLimitsAdder limitsAdder, OperationalLimitsGroup opLimitGroupToCopy) { + // Copy all limits of the other side + opLimitGroupToCopy.getCurrentLimits().ifPresent(currentLimits -> { + limitsAdder.setPermanentLimit(currentLimits.getPermanentLimit()); + + for (LoadingLimits.TemporaryLimit tempLimit : currentLimits.getTemporaryLimits()) { + addTemporaryLimit(limitsAdder, tempLimit.getName(), tempLimit.getValue(), tempLimit.getAcceptableDuration()); + } + limitsAdder.add(); + }); + } +} diff --git a/src/main/java/org/gridsuite/modification/utils/ModificationUtils.java b/src/main/java/org/gridsuite/modification/utils/ModificationUtils.java index 7ee7c8bb..f1442aee 100644 --- a/src/main/java/org/gridsuite/modification/utils/ModificationUtils.java +++ b/src/main/java/org/gridsuite/modification/utils/ModificationUtils.java @@ -16,6 +16,7 @@ import org.gridsuite.modification.IFilterService; import org.gridsuite.modification.NetworkModificationException; import org.gridsuite.modification.dto.*; +import org.gridsuite.modification.modifications.AbstractBranchModification; import org.gridsuite.modification.modifications.BusbarSectionFinderTraverser; import org.gridsuite.modification.report.NetworkModificationReportResourceBundle; import org.springframework.util.CollectionUtils; @@ -34,6 +35,8 @@ import static com.powsybl.iidm.network.TwoSides.ONE; import static org.gridsuite.modification.NetworkModificationException.Type.*; +import static org.gridsuite.modification.dto.OperationalLimitsGroupInfos.Applicability.SIDE1; +import static org.gridsuite.modification.dto.OperationalLimitsGroupInfos.Applicability.SIDE2; import static org.gridsuite.modification.modifications.AbstractBranchModification.*; /** @@ -52,6 +55,7 @@ public final class ModificationUtils { public static final String NO_VALUE = "No value"; public static final String LIMITS = "network.modification.limits"; public static final String REACTIVE_LIMITS = "network.modification.ReactiveLimits"; + public static final String OPERATIONAL_LIMITS_GROUP_ADDED_LOG_DETAIL = "network.modification.operationalLimitsGroupAdded.detail"; private static final String SETPOINTS = "network.modification.Setpoints"; private static final String MIN_REACTIVE_POWER_FIELDNAME = "Minimum reactive power"; private static final String MAX_REACTIVE_POWER_FIELDNAME = "Maximum reactive power"; @@ -69,6 +73,14 @@ public final class ModificationUtils { public static final String CONNECT = "connect"; public static final String DISCONNECT = "disconnect"; + public static String applicabilityToString(OperationalLimitsGroupInfos.Applicability applicability) { + return switch (applicability) { + case EQUIPMENT -> "sides 1 & 2"; + case SIDE1 -> "side 1"; + case SIDE2 -> "side 2"; + }; + } + public enum FeederSide { INJECTION_SINGLE_SIDE, BRANCH_SIDE_ONE, @@ -1081,70 +1093,76 @@ public Identifiable getEquipmentByIdentifiableType(Network network, Identifia } /** - * @param reportNode Limit sets report node - * @param opLimitGroups added current limits + * @param reportNode Limit set report node + * @param opLimitsGroup added current limits * @param branch branch to which limits are going to be added * @param side which side of the branch receives the limits */ - public void setCurrentLimitsOnASide(ReportNode reportNode, List opLimitGroups, Branch branch, TwoSides side) { + public void setCurrentLimitsOnASide(ReportNode reportNode, OperationalLimitsGroupInfos opLimitsGroup, Branch branch, TwoSides side) { - if (CollectionUtils.isEmpty(opLimitGroups)) { + boolean hasPermanent = opLimitsGroup.getCurrentLimits().getPermanentLimit() != null; + boolean hasTemporary = !CollectionUtils.isEmpty(opLimitsGroup.getCurrentLimits().getTemporaryLimits()); + boolean hasLimits = hasPermanent || hasTemporary; + + if (!hasPermanent && !hasLimits || opLimitsGroup.getId() == null) { return; } - - for (OperationalLimitsGroupInfos opLimitsGroup : opLimitGroups) { - boolean hasPermanent = opLimitsGroup.getCurrentLimits().getPermanentLimit() != null; - boolean hasTemporary = !CollectionUtils.isEmpty(opLimitsGroup.getCurrentLimits().getTemporaryLimits()); - boolean hasLimits = hasPermanent || hasTemporary; - - if (!hasLimits || opLimitsGroup.getId() == null) { - continue; - } - - OperationalLimitsGroup opGroup = side == ONE - ? branch.newOperationalLimitsGroup1(opLimitsGroup.getId()) - : branch.newOperationalLimitsGroup2(opLimitsGroup.getId()); - - ReportNode limitSetNode = reportNode.newReportNode() - .withMessageTemplate("network.modification.limitSetAdded") - .withUntypedValue("name", opLimitsGroup.getId()) - .withSeverity(TypedValue.INFO_SEVERITY) - .add(); - - CurrentLimitsAdder limitsAdder = opGroup.newCurrentLimits(); - if (hasPermanent) { - limitsAdder.setPermanentLimit(opLimitsGroup.getCurrentLimits().getPermanentLimit()); - } - if (hasTemporary) { - opLimitsGroup.getCurrentLimits().getTemporaryLimits().forEach(limit -> { - double value = limit.getValue() != null ? limit.getValue() : Double.MAX_VALUE; - int duration = limit.getAcceptableDuration() != null ? limit.getAcceptableDuration() : Integer.MAX_VALUE; - - limitsAdder.beginTemporaryLimit() - .setName(limit.getName()) - .setValue(value) - .setAcceptableDuration(duration) - .endTemporaryLimit(); - }); - } - limitsAdder.add(); - - //add properties - List detailsOnLimitSet = new ArrayList<>(); - if (!CollectionUtils.isEmpty(opLimitsGroup.getLimitsProperties()) && - checkPropertiesUnicity(opLimitsGroup.getLimitsProperties(), detailsOnLimitSet)) { - opLimitsGroup.getLimitsProperties().forEach(property -> { - detailsOnLimitSet.add( - ReportNode.newRootReportNode().withSeverity(TypedValue.DETAIL_SEVERITY) - .withMessageTemplate("network.modification.propertyAdded") - .withUntypedValue(NAME, property.name()) - .withUntypedValue(VALUE, property.value()).build()); - opGroup.setProperty(property.name(), property.value()); - }); - if (!detailsOnLimitSet.isEmpty()) { - ModificationUtils.getInstance().reportModifications(limitSetNode, detailsOnLimitSet); - } - } + ReportNode limitSetReportDetail = reportNode.newReportNode() + .withMessageTemplate(OPERATIONAL_LIMITS_GROUP_ADDED_LOG_DETAIL) + .withUntypedValue(SIDE, applicabilityToString(side == ONE ? SIDE1 : SIDE2)) + .withSeverity(TypedValue.DETAIL_SEVERITY).add(); + + OperationalLimitsGroup opGroup = side == ONE + ? branch.newOperationalLimitsGroup1(opLimitsGroup.getId()) + : branch.newOperationalLimitsGroup2(opLimitsGroup.getId()); + List detailsOnLimitSet = new ArrayList<>(); + + CurrentLimitsAdder limitsAdder = opGroup.newCurrentLimits(); + if (hasPermanent) { + limitsAdder.setPermanentLimit(opLimitsGroup.getCurrentLimits().getPermanentLimit()); + detailsOnLimitSet.add(ReportNode.newRootReportNode() + .withResourceBundles(NetworkModificationReportResourceBundle.BASE_NAME) + .withMessageTemplate("network.modification.permanentLimit") + .withUntypedValue(VALUE, opLimitsGroup.getCurrentLimits().getPermanentLimit()) + .withSeverity(TypedValue.DETAIL_SEVERITY) + .build()); + } + if (hasTemporary) { + opLimitsGroup.getCurrentLimits().getTemporaryLimits().forEach(limit -> { + double value = limit.getValue() != null ? limit.getValue() : Double.MAX_VALUE; + int duration = limit.getAcceptableDuration() != null ? limit.getAcceptableDuration() : Integer.MAX_VALUE; + detailsOnLimitSet.add(ReportNode.newRootReportNode() + .withResourceBundles(NetworkModificationReportResourceBundle.BASE_NAME) + .withMessageTemplate("network.modification.temporaryLimitModified.name") + .withUntypedValue(AbstractBranchModification.NAME, limit.getName()) + .withUntypedValue(DURATION, duration) + .withUntypedValue(AbstractBranchModification.VALUE, value) + .withSeverity(TypedValue.DETAIL_SEVERITY) + .build()); + + limitsAdder.beginTemporaryLimit() + .setName(limit.getName()) + .setValue(value) + .setAcceptableDuration(duration) + .endTemporaryLimit(); + }); + } + limitsAdder.add(); + + //add properties + if (!CollectionUtils.isEmpty(opLimitsGroup.getLimitsProperties()) && + checkPropertiesUnicity(opLimitsGroup.getLimitsProperties(), detailsOnLimitSet)) { + opLimitsGroup.getLimitsProperties().forEach(property -> { + detailsOnLimitSet.add( + ReportNode.newRootReportNode().withSeverity(TypedValue.DETAIL_SEVERITY) + .withMessageTemplate("network.modification.propertyAdded") + .withUntypedValue(NAME, property.name()) + .withUntypedValue(VALUE, property.value()).build()); + opGroup.setProperty(property.name(), property.value()); + }); + } + if (!detailsOnLimitSet.isEmpty()) { + ModificationUtils.getInstance().reportModifications(limitSetReportDetail, detailsOnLimitSet); } } diff --git a/src/main/resources/org/gridsuite/modification/reports.properties b/src/main/resources/org/gridsuite/modification/reports.properties index 972c25f6..fb15a042 100644 --- a/src/main/resources/org/gridsuite/modification/reports.properties +++ b/src/main/resources/org/gridsuite/modification/reports.properties @@ -175,11 +175,13 @@ network.modification.invalidFilters = ${errorType}: There is no valid equipment network.modification.lcc.creation = Lcc creation ${lccId} network.modification.lccCharacteristics = Characteristics network.modification.lccCreated = New lcc with id=${id} created -network.modification.limitSetAdded = Limit set ${name} added +network.modification.limitSetAdded = ${name} added network.modification.operationalLimitsGroupDeleted = Limit set ${operationalLimitsGroupName} has been deleted on ${side} network.modification.operationalLimitsGroupAdded = Limit set ${operationalLimitsGroupName} added on ${side} +network.modification.operationalLimitsGroupAdded.detail = Added to operational limits group on ${side} network.modification.operationalLimitsGroupReplaced = Limit set ${operationalLimitsGroupName} has been replaced on ${side} network.modification.operationalLimitsGroupModified = Limit set ${operationalLimitsGroupName} has been modified on ${side} +network.modification.operationalLimitsGroupModified.detail = Modified in operational limits group on ${side} network.modification.limitSetSelectedOnSide1 = limit set selected on side 1 : ${selectedOperationalLimitsGroup1} network.modification.limitSetSelectedOnSide2 = limit set selected on side 2 : ${selectedOperationalLimitsGroup2} network.modification.noLimitSetSelectedOnSide1 = No limit set selected on side 1 @@ -296,10 +298,10 @@ network.modification.tabularCreation = Tabular creation (${creationType}) network.modification.tabularModification = Tabular modification (${modificationType}) network.modification.tapChangerStepsModification = Taps were replaced by new ones below network.modification.tapsModification = Taps -network.modification.temporaryLimitAdded.name = ${name} (${duration}) added with ${value} +network.modification.permanentLimit = Permanent limit value: ${value} network.modification.temporaryLimitDeleted.name = ${name} (${duration}) deleted -network.modification.temporaryLimitModified.name = ${name} (${duration}) : ${oldValue} -> ${value} -network.modification.temporaryLimitsModification = Temporary current limits : +network.modification.temporaryLimitValueModified.name = Temporary limit ${name} (duration: ${duration}) : ${oldValue} -> ${value} +network.modification.temporaryLimitModified.name = Temporary limit ${name}; value: ${value}; acceptable duration: ${duration} network.modification.temporaryLimitsReplaced = Previous temporary limits were removed network.modification.temporaryLimitsNoMatch = No existing temporary limit found with acceptableDuration = ${limitAcceptableDuration} matching is based on acceptableDuration if that helps network.modification.terminal1Disconnected = Equipment with id=${id} disconnected on side 1 diff --git a/src/test/java/org/gridsuite/modification/modifications/LineModificationTest.java b/src/test/java/org/gridsuite/modification/modifications/LineModificationTest.java index c890a802..109f7d65 100644 --- a/src/test/java/org/gridsuite/modification/modifications/LineModificationTest.java +++ b/src/test/java/org/gridsuite/modification/modifications/LineModificationTest.java @@ -8,6 +8,7 @@ import com.fasterxml.jackson.core.type.TypeReference; import com.powsybl.commons.PowsyblException; +import com.powsybl.commons.report.ReportNode; import com.powsybl.iidm.network.*; import com.powsybl.iidm.network.extensions.ConnectablePosition; import com.powsybl.iidm.network.extensions.Measurement; @@ -16,6 +17,7 @@ import org.apache.commons.collections4.CollectionUtils; import org.gridsuite.modification.NetworkModificationException; import org.gridsuite.modification.dto.*; +import org.gridsuite.modification.report.NetworkModificationReportResourceBundle; import org.gridsuite.modification.utils.NetworkCreation; import org.junit.jupiter.api.Test; @@ -27,6 +29,7 @@ import static org.gridsuite.modification.dto.OperationalLimitsGroupModificationType.DELETE; import static org.gridsuite.modification.dto.OperationalLimitsGroupModificationType.MODIFY_OR_ADD; import static org.gridsuite.modification.dto.OperationalLimitsGroupModificationType.REPLACE; +import static org.gridsuite.modification.utils.TestUtils.assertLogMessageWithoutRank; import static org.junit.jupiter.api.Assertions.*; /** @@ -337,6 +340,24 @@ void testConnectWhenNoSwitchesOpened() { assertEquals("BRANCH_MODIFICATION_ERROR : Could not connect equipment 'line1' on side 1 & 2", exception.getMessage()); } + @Test + void testRename() { + Line line = getNetwork().getLine("line1"); + line.setName(null); + + LineModificationInfos modificationInfos = LineModificationInfos.builder() + .equipmentName(AttributeModification.toAttributeModification("newName", OperationType.SET)) + .equipmentId("line1") + .build(); + ReportNode reportNode = modificationInfos.createSubReportNode(ReportNode.newRootReportNode() + .withResourceBundles(NetworkModificationReportResourceBundle.BASE_NAME) + .withMessageTemplate("test").build()); + modificationInfos.toModification().apply(getNetwork(), reportNode); + + assertEquals("newName", line.getOptionalName().orElseThrow()); + assertLogMessageWithoutRank("Name : No value → newName", "network.modification.fieldModification", reportNode); + } + @Test void testDelete() { Line line = getNetwork().getLine("line1"); @@ -407,7 +428,7 @@ void testDelete() { Network network = getNetwork(); AbstractModification modification = lineModificationInfos4.toModification(); String errorMessage = assertThrows(PowsyblException.class, () -> modification.apply(network)).getMessage(); - assertEquals("Cannot delete operational limit group doesNotExist which has not been found in equipment on side SIDE2", errorMessage); + assertEquals("Cannot delete operational limit group doesNotExist which has not been found in equipment on side 2", errorMessage); } private void changeLineConnectionState(Line existingEquipment, boolean expectedState) { diff --git a/src/test/java/org/gridsuite/modification/modifications/tabularmodifications/LimitSetModificationsTest.java b/src/test/java/org/gridsuite/modification/modifications/tabularmodifications/LimitSetModificationsTest.java index 87090902..dade1fc8 100644 --- a/src/test/java/org/gridsuite/modification/modifications/tabularmodifications/LimitSetModificationsTest.java +++ b/src/test/java/org/gridsuite/modification/modifications/tabularmodifications/LimitSetModificationsTest.java @@ -239,13 +239,13 @@ protected void assertAfterNetworkModificationApplication() { private void assertAfterNetworkModificationApplication(ReportNode reportNode) { assertAfterNetworkModificationApplication(); - assertLogMessageWithoutRank("Limit set DEFAULT has been modified on SIDE1", "network.modification.operationalLimitsGroupModified", reportNode); + assertLogMessageWithoutRank("Limit set DEFAULT has been modified on side 1", "network.modification.operationalLimitsGroupModified", reportNode); assertLogMessageWithoutRank("Previous temporary limits were removed", "network.modification.temporaryLimitsReplaced", reportNode); assertLogMessageWithoutRank("Cannot add DEFAULT operational limit group, one with the given name already exists", "network.modification.tabular.modification.exception", reportNode); assertLogMessageWithoutRank("No existing temporary limit found with acceptableDuration = 3 matching is based on acceptableDuration if that helps", "network.modification.temporaryLimitsNoMatch", reportNode); assertLogMessageWithoutRank("limit set selected on side 2 : group0", "network.modification.limitSetSelectedOnSide2", reportNode); - assertLogMessageWithoutRank("Limit set group0 has been replaced on SIDE2", "network.modification.operationalLimitsGroupReplaced", reportNode); - assertLogMessageWithoutRank("Limit set DEFAULT added on SIDE1", "network.modification.operationalLimitsGroupAdded", reportNode); + assertLogMessageWithoutRank("Limit set group0 has been replaced on side 2", "network.modification.operationalLimitsGroupReplaced", reportNode); + assertLogMessageWithoutRank("Limit set DEFAULT added on side 1", "network.modification.operationalLimitsGroupAdded", reportNode); }