Skip to content

Commit

Permalink
Add expert filter feature (#71)
Browse files Browse the repository at this point in the history
* Add expert filter feature

Signed-off-by: BOUHOURS Antoine <[email protected]>
  • Loading branch information
antoinebhs authored Nov 2, 2023
1 parent 6c3581e commit a80cb59
Show file tree
Hide file tree
Showing 23 changed files with 1,205 additions and 25 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
/**
* Copyright (c) 2023, 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.filter.server;

import com.powsybl.commons.PowsyblException;
import org.gridsuite.filter.server.dto.*;
import org.gridsuite.filter.server.dto.expertrule.*;
import org.gridsuite.filter.server.entities.*;
import org.gridsuite.filter.server.repositories.ExpertFilterRepository;
import org.gridsuite.filter.server.utils.EquipmentType;
import org.gridsuite.filter.server.utils.FilterType;

import java.util.Collections;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;

/**
* @author Antoine Bouhours <antoine.bouhours at rte-france.com>
*/
public class ExpertFilterRepositoryProxy extends AbstractFilterRepositoryProxy<ExpertFilterEntity, ExpertFilterRepository> {
private final ExpertFilterRepository expertFilterRepository;

public ExpertFilterRepositoryProxy(ExpertFilterRepository expertFilterRepository) {
this.expertFilterRepository = expertFilterRepository;
}

@Override
ExpertFilterRepository getRepository() {
return expertFilterRepository;
}

@Override
AbstractFilter toDto(ExpertFilterEntity filterEntity) {
return ExpertFilter.builder()
.id(filterEntity.getId())
.modificationDate(filterEntity.getModificationDate())
.equipmentType(filterEntity.getEquipmentType())
.rules(entityToDto(filterEntity.getRules()))
.build();
}

public static AbstractExpertRule entityToDto(ExpertRuleEntity filterEntity) {
switch (filterEntity.getDataType()) {
case COMBINATOR -> {
return CombinatorExpertRule.builder()
.combinator(filterEntity.getCombinator())
.rules(entitiesToDto(filterEntity.getRules()))
.build();
}
case BOOLEAN -> {
return BooleanExpertRule.builder()
.field(filterEntity.getField())
.operator(filterEntity.getOperator())
.value(Boolean.parseBoolean(filterEntity.getValue()))
.build();
}
case NUMBER -> {
return NumberExpertRule.builder()
.field(filterEntity.getField())
.operator(filterEntity.getOperator())
.value(Double.valueOf(filterEntity.getValue()))
.build();
}
case STRING -> {
return StringExpertRule.builder()
.field(filterEntity.getField())
.operator(filterEntity.getOperator())
.value(filterEntity.getValue())
.build();
}
case ENUM -> {
return EnumExpertRule.builder()
.field(filterEntity.getField())
.operator(filterEntity.getOperator())
.value(filterEntity.getValue())
.build();
}
default -> throw new PowsyblException("Unknown rule data type: " + filterEntity.getDataType() + ", supported data types are: COMBINATOR, BOOLEAN, NUMBER, STRING, ENUM");
}
}

private static List<AbstractExpertRule> entitiesToDto(List<ExpertRuleEntity> entities) {
if (entities == null) {
return Collections.emptyList();
}

return entities.stream()
.map(ExpertFilterRepositoryProxy::entityToDto)
.collect(Collectors.toList());
}

@Override
ExpertFilterEntity fromDto(AbstractFilter dto) {
if (dto instanceof ExpertFilter filter) {
var expertFilterEntityBuilder = ExpertFilterEntity.builder()
.modificationDate(filter.getModificationDate())
.equipmentType(filter.getEquipmentType())
.rules(dtoToEntity(filter.getRules()));
buildAbstractFilter(expertFilterEntityBuilder, filter);
return expertFilterEntityBuilder.build();
}
throw new PowsyblException(WRONG_FILTER_TYPE);
}

public static ExpertRuleEntity dtoToEntity(AbstractExpertRule filter) {
var expertFilterEntityBuilder = ExpertRuleEntity.builder()
.id(UUID.randomUUID())
.combinator(filter.getCombinator())
.operator(filter.getOperator())
.dataType(filter.getDataType())
.field(filter.getField())
.value(filter.getStringValue());
expertFilterEntityBuilder.rules(dtoToEntities(filter.getRules(), expertFilterEntityBuilder.build()));
return expertFilterEntityBuilder.build();
}

private static List<ExpertRuleEntity> dtoToEntities(List<AbstractExpertRule> ruleFromDto, ExpertRuleEntity parentRuleEntity) {
if (ruleFromDto == null) {
return Collections.emptyList();
}

return ruleFromDto.stream()
.map(rule -> {
var expertRuleEntityBuilder = ExpertRuleEntity.builder()
.id(UUID.randomUUID())
.combinator(rule.getCombinator())
.operator(rule.getOperator())
.dataType(rule.getDataType())
.field(rule.getField())
.value(rule.getStringValue())
.parentRule(parentRuleEntity);

List<ExpertRuleEntity> rules = dtoToEntities(rule.getRules(), expertRuleEntityBuilder.build());
expertRuleEntityBuilder.rules(rules);

return expertRuleEntityBuilder.build();
})
.collect(Collectors.toList());
}

@Override
FilterType getFilterType() {
return FilterType.EXPERT;
}

@Override
public EquipmentType getEquipmentType() {
throw new UnsupportedOperationException("A filter id must be provided to get equipment type !!");
}

@Override
public EquipmentType getEquipmentType(UUID id) {
return expertFilterRepository.findById(id)
.map(ExpertFilterEntity::getEquipmentType)
.orElseThrow(() -> new PowsyblException("Identifier list filter " + id + " not found"));
}

@Override
public AbstractEquipmentFilterForm buildEquipmentFormFilter(AbstractFilterEntity entity) {
return null;
}
}
17 changes: 8 additions & 9 deletions src/main/java/org/gridsuite/filter/server/FilterService.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,7 @@
import org.springframework.util.CollectionUtils;
import org.springframework.web.server.ResponseStatusException;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

Expand Down Expand Up @@ -71,6 +65,7 @@ public FilterService(FiltersToGroovyScript filtersToScript,
final VoltageLevelFilterRepository voltageLevelFilterRepository,
final SubstationFilterRepository substationFilterRepository,
final IdentifierListFilterRepository identifierListFilterRepository,
final ExpertFilterRepository expertFilterRepository,
NetworkStoreService networkStoreService,
NotificationService notificationService) {
this.filtersToScript = filtersToScript;
Expand All @@ -95,6 +90,7 @@ public FilterService(FiltersToGroovyScript filtersToScript,

filterRepositories.put(FilterType.IDENTIFIER_LIST.name(), new IdentifierListFilterRepositoryProxy(identifierListFilterRepository));

filterRepositories.put(FilterType.EXPERT.name(), new ExpertFilterRepositoryProxy(expertFilterRepository));
this.networkStoreService = networkStoreService;
this.notificationService = notificationService;
}
Expand Down Expand Up @@ -297,6 +293,9 @@ private <I extends Injection<I>> Stream<Injection<I>> getInjectionList(Stream<In
} else if (filter instanceof IdentifierListFilter) {
List<String> equipmentIds = getIdentifierListFilterEquipmentIds((IdentifierListFilter) filter);
return stream.filter(injection -> equipmentIds.contains(injection.getId()));
} else if (filter instanceof ExpertFilter expertFilter) {
var rule = expertFilter.getRules();
return stream.filter(rule::evaluateRule);
} else {
return Stream.empty();
}
Expand All @@ -309,7 +308,7 @@ private List<Identifiable<?>> getGeneratorList(Network network, AbstractFilter f
return getInjectionList(network.getGeneratorStream().map(injection -> injection), filter)
.filter(injection -> filterByEnergySource((Generator) injection, generatorFilter.getEnergySource()))
.collect(Collectors.toList());
} else if (filter instanceof IdentifierListFilter) {
} else if (filter instanceof IdentifierListFilter || filter instanceof ExpertFilter) {
return getInjectionList(network.getGeneratorStream().map(generator -> generator), filter).collect(Collectors.toList());
} else {
return List.of();
Expand Down Expand Up @@ -634,7 +633,7 @@ private List<Identifiable<?>> getIdentifiables(AbstractFilter filter, Network ne
}

private List<Identifiable<?>> toIdentifiableFilter(AbstractFilter filter, UUID networkUuid, String variantId) {
if (filter.getType() == FilterType.CRITERIA || filter.getType() == FilterType.IDENTIFIER_LIST) {
if (filter.getType() == FilterType.CRITERIA || filter.getType() == FilterType.IDENTIFIER_LIST || filter.getType() == FilterType.EXPERT) {
Network network = networkStoreService.getNetwork(networkUuid);

if (network == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@
@JsonSubTypes({//Below, we define the names and the binding classes.
@JsonSubTypes.Type(value = ScriptFilter.class, name = "SCRIPT"),
@JsonSubTypes.Type(value = CriteriaFilter.class, name = "CRITERIA"),
@JsonSubTypes.Type(value = IdentifierListFilter.class, name = "IDENTIFIER_LIST")
@JsonSubTypes.Type(value = IdentifierListFilter.class, name = "IDENTIFIER_LIST"),
@JsonSubTypes.Type(value = ExpertFilter.class, name = "EXPERT")
})
@Getter
@Setter
Expand Down
43 changes: 43 additions & 0 deletions src/main/java/org/gridsuite/filter/server/dto/ExpertFilter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/**
* Copyright (c) 2023, 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.filter.server.dto;

import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
import org.gridsuite.filter.server.dto.expertrule.AbstractExpertRule;
import org.gridsuite.filter.server.utils.EquipmentType;
import org.gridsuite.filter.server.utils.FilterType;

import java.util.Date;
import java.util.UUID;

/**
* @author Antoine Bouhours <antoine.bouhours at rte-france.com>
*/
@Getter
@Schema(description = "Expert Filters", allOf = AbstractFilter.class)
@SuperBuilder
@NoArgsConstructor
public class ExpertFilter extends AbstractFilter {

@Schema(description = "Rules")
private AbstractExpertRule rules;

public ExpertFilter(UUID id, Date modificationDate, EquipmentType equipmentType, AbstractExpertRule rules) {
super(id, modificationDate, equipmentType);
this.rules = rules;
}

@JsonProperty(access = JsonProperty.Access.READ_ONLY)
@Override
public FilterType getType() {
return FilterType.EXPERT;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/**
* Copyright (c) 2023, 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.filter.server.dto.expertrule;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.powsybl.iidm.network.Identifiable;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
import org.gridsuite.filter.server.utils.CombinatorType;
import org.gridsuite.filter.server.utils.DataType;
import org.gridsuite.filter.server.utils.FieldType;
import org.gridsuite.filter.server.utils.OperatorType;

import java.util.List;

/**
* @author Antoine Bouhours <antoine.bouhours at rte-france.com>
*/
@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
property = "dataType",
include = JsonTypeInfo.As.EXISTING_PROPERTY)
@JsonSubTypes({
@JsonSubTypes.Type(value = StringExpertRule.class, name = "STRING"),
@JsonSubTypes.Type(value = BooleanExpertRule.class, name = "BOOLEAN"),
@JsonSubTypes.Type(value = EnumExpertRule.class, name = "ENUM"),
@JsonSubTypes.Type(value = NumberExpertRule.class, name = "NUMBER"),
@JsonSubTypes.Type(value = CombinatorExpertRule.class, name = "COMBINATOR")
})
@JsonInclude(JsonInclude.Include.NON_NULL)
@NoArgsConstructor
@AllArgsConstructor
@Getter
@SuperBuilder
public abstract class AbstractExpertRule {

@Schema(description = "Combinator")
private CombinatorType combinator;

@Schema(description = "Field")
private FieldType field;

@Schema(description = "Operator")
private OperatorType operator;

@Schema(description = "Rules")
private List<AbstractExpertRule> rules;

public abstract boolean evaluateRule(Identifiable<?> identifiable);

public abstract DataType getDataType();

@JsonIgnore
public abstract String getStringValue();
}
Loading

0 comments on commit a80cb59

Please sign in to comment.