Commit 3ef9dc5c authored by Figue Orange's avatar Figue Orange
Browse files

add venueType wildcard and no duplicates validators + refactor selection algo (tests not ok)

parent 7770258e
......@@ -30,7 +30,7 @@ clea:
- '1,1,1,3,1,3.0,2.0'
- '*,*,*,3,1,3.0,2.0'
- '3,*,*,3,1,3.0,2.0'
- '*,*,*,1,1,3.0,2.0'
- '2,1,1,1,1,3.0,2.0'
batch:
duration-unit-in-seconds: 3600
cluster:
......
......@@ -8,54 +8,81 @@ import java.util.stream.Collectors;
@Slf4j
public abstract class ScoringConfiguration {
abstract public List<? extends ScoringConfigurationItem> getScorings();
public abstract List<? extends ScoringConfigurationItem> getScorings();
public ScoringConfigurationItem getConfigurationFor(int venueType, int venueCategory1, int venueCategory2) {
log.debug("Fetching rules candidates for venueType : {}, venueCategory1 : {}, venuCategory2: {}", venueType, venueCategory1, venueCategory2);
log.info("Fetching rules candidates for venueType : {}, venueCategory1 : {}, venuCategory2: {}", venueType, venueCategory1, venueCategory2);
List<ScoringConfigurationItem> compatibleRules = this.getScorings().stream()
.filter(scoring -> scoring.isCompatibleWith(venueType, venueCategory1, venueCategory2))
.collect(Collectors.toList());
log.debug("Found {} compatibles rules", compatibleRules.size());
ScoringConfigurationItem selectedRule = null;
log.info("Found {} compatibles rules", compatibleRules.size());
//Only one match found
if (compatibleRules.size() == 1) {
log.info("Found suitable rule {}", compatibleRules.get(0));
return compatibleRules.get(0);
}
//All are wildcarded, then rule to apply is the default one
if (allAreWilcarded(venueType, venueCategory1, venueCategory2)) {
getConfigurationFor(
ScoringConfigurationItem.wildcardValue,
ScoringConfigurationItem.wildcardValue,
ScoringConfigurationItem.wildcardValue);
}
ScoringConfigurationItem bestRuleCandidate = compatibleRules.get(0);
for (ScoringConfigurationItem rule : compatibleRules) {
if (rule.isFullMatch()) {
log.info("Found matching rule {}", rule);
selectedRule = rule;
break;
} else if (rule.getVenueType() != ScoringConfigurationItem.wildcardValue &&
(
rule.getVenueCategory1() == ScoringConfigurationItem.wildcardValue ||
rule.getVenueCategory2() == ScoringConfigurationItem.wildcardValue
)
) {
if (rule.getVenueCategory1() != ScoringConfigurationItem.wildcardValue) {
log.info("Found suitable rule {}", rule);
selectedRule = rule;
break;
}
} else if (rule.getVenueType() == ScoringConfigurationItem.wildcardValue &&
(
rule.getVenueCategory1() == ScoringConfigurationItem.wildcardValue ||
rule.getVenueCategory2() == ScoringConfigurationItem.wildcardValue
)
) {
if (rule.getVenueCategory1() != ScoringConfigurationItem.wildcardValue) {
log.info("Found suitable rule {}", rule);
selectedRule = rule;
break;
}
return rule;
} else if (bothCategoryAreWildcard(bestRuleCandidate) && firstCategoryIsNotWildcarded(rule)) {
bestRuleCandidate = rule;
} else if (eitherOneCategoryIsWildcard(bestRuleCandidate) && firstCategoryIsNotWildcarded(rule)) {
bestRuleCandidate = rule;
} else if (firstCategoryIsNotWildcarded(bestRuleCandidate) && secondCategoryIsNotWildcarded(bestRuleCandidate)) {
return bestRuleCandidate;
}
}
log.info("Retrieving best rule {}", bestRuleCandidate);
return bestRuleCandidate;
}
private boolean allAreWilcarded(int venueType, int venueCategory1, int venueCategory2) {
int count = 0;
if (venueType == ScoringConfigurationItem.wildcardValue) {
count++;
}
if (venueCategory1 == ScoringConfigurationItem.wildcardValue) {
count++;
}
if (venueCategory2 == ScoringConfigurationItem.wildcardValue) {
count++;
}
return count == 3;
}
return selectedRule;
private boolean eitherOneCategoryIsWildcard(ScoringConfigurationItem rule) {
return isWildcarded(rule.getVenueCategory1()) || isWildcarded(rule.getVenueCategory2());
}
private boolean bothCategoryAreWildcard(ScoringConfigurationItem rule) {
return isWildcarded(rule.getVenueCategory1()) && isWildcarded(rule.getVenueCategory2());
}
private boolean isWildcarded(int venueItem) {
return venueItem == ScoringConfigurationItem.wildcardValue;
}
private boolean firstCategoryIsNotWildcarded(ScoringConfigurationItem rule) {
return isWildcarded(rule.getVenueCategory1());
}
private boolean secondCategoryIsNotWildcarded(ScoringConfigurationItem rule) {
return isWildcarded(rule.getVenueCategory2());
}
}
package fr.gouv.clea.clea.scoring.configuration;
import fr.gouv.clea.clea.scoring.configuration.validators.NoDuplicates;
import fr.gouv.clea.clea.scoring.configuration.validators.ValidateWildcards;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.ToString;
import lombok.experimental.SuperBuilder;
import javax.validation.Valid;
import javax.validation.constraints.Min;
import java.util.Objects;
// @Valid
@SuperBuilder
@AllArgsConstructor
@ToString
@Getter
@Valid
@ValidateWildcards
@NoDuplicates
public class ScoringConfigurationItem {
public static int wildcardValue = -1;
@Min(value = -1)
private int venueType;
@Min(value = -1)
private int venueCategory1;
@Min(value = -1)
private int venueCategory2;
/**
* @return the scoring configuration priority. The greater it is, the most priority it has.
* It allows to select a scoring configuration between many that are compatible for a
* tuple(venueType, venueCategory1, venueCategory2).
*/
public int getPriority() {
int priority = 100;
if (venueType == wildcardValue) {
// venueType is the most important part for configuration selection
priority -= 51;
}
if (venueCategory1 == wildcardValue) {
// venueCategory1 has priority over venueCategory2
priority -= 25;
}
if (venueCategory2 == wildcardValue) {
priority -= 20;
}
return priority;
}
public boolean isCompatibleWith(int venueType, int venueCategory1, int venueCategory2) {
return this.hasVenueTypeCompatibleWith(venueType)
&& this.hasVenueCategory1CompatibleWith(venueCategory1)
......@@ -78,4 +68,16 @@ public class ScoringConfigurationItem {
return this.getWildCardCount() == 0;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ScoringConfigurationItem that = (ScoringConfigurationItem) o;
return getVenueType() == that.getVenueType() && getVenueCategory1() == that.getVenueCategory1() && getVenueCategory2() == that.getVenueCategory2();
}
@Override
public int hashCode() {
return Objects.hash(getVenueType(), getVenueCategory1(), getVenueCategory2());
}
}
package fr.gouv.clea.clea.scoring.configuration.validators;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.TYPE;
@Constraint(validatedBy = {NoDuplicatesValidator.class})
@Target({TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface NoDuplicates {
String message() default
"Found at least one duplicate in scoring configuration";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
package fr.gouv.clea.clea.scoring.configuration.validators;
import fr.gouv.clea.clea.scoring.configuration.ScoringConfigurationItem;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.ArrayList;
import java.util.List;
public class NoDuplicatesValidator implements ConstraintValidator<NoDuplicates, Object> {
private List<ScoringConfigurationItem> list;
@Override
public void initialize(NoDuplicates noDuplicates) {
this.list = new ArrayList<>();
}
@Override
public boolean isValid(Object value, ConstraintValidatorContext context) {
ScoringConfigurationItem scoringConfigurationItem = (ScoringConfigurationItem) value;
if (list.contains(scoringConfigurationItem)) {
return false;
} else {
list.add(scoringConfigurationItem);
return true;
}
}
}
package fr.gouv.clea.clea.scoring.configuration.validators;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.TYPE;
@Constraint(validatedBy = {VenueTypeValidator.class})
@Target({TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ValidateWildcards {
String message() default
"VenueType can not be wildcarded unless all fields are too";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
package fr.gouv.clea.clea.scoring.configuration.validators;
import fr.gouv.clea.clea.scoring.configuration.ScoringConfigurationItem;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public class VenueTypeValidator implements ConstraintValidator<ValidateWildcards, Object> {
@Override
public boolean isValid(Object value, ConstraintValidatorContext context) {
ScoringConfigurationItem scoringConfigurationItem = (ScoringConfigurationItem) value;
if (allFieldsAreWildcards(scoringConfigurationItem)) {
return true;
} else {
return venueTypeIsNotWildcarded(scoringConfigurationItem);
}
}
private boolean venueTypeIsNotWildcarded(ScoringConfigurationItem scoringConfigurationItem) {
return scoringConfigurationItem.getVenueType() != ScoringConfigurationItem.wildcardValue ||
(scoringConfigurationItem.getVenueCategory1() != ScoringConfigurationItem.wildcardValue &&
scoringConfigurationItem.getVenueCategory2() != ScoringConfigurationItem.wildcardValue);
}
private boolean allFieldsAreWildcards(ScoringConfigurationItem scoringConfigurationItem) {
return scoringConfigurationItem.getVenueType() == ScoringConfigurationItem.wildcardValue &&
scoringConfigurationItem.getVenueCategory1() == ScoringConfigurationItem.wildcardValue &&
scoringConfigurationItem.getVenueCategory2() == ScoringConfigurationItem.wildcardValue;
}
}
......@@ -26,7 +26,7 @@ class ExposureTimeConfigurationTest {
@Test
void testExposureTimeConfigurationHasExpectedSize() {
assertThat(configuration.getScorings()).hasSize(6);
assertThat(configuration.getScorings()).hasSize(4);
}
@Test
......
......@@ -26,12 +26,12 @@ public class RiskConfigurationTest {
@Test
void testRiskConfigurationHasExpectedSize() {
assertThat(configuration.getScorings()).hasSize(6);
assertThat(configuration.getScorings()).hasSize(7);
}
@Test
void testExposureTimeConfigurationHasExpectedData() {
RiskRule scoring = (RiskRule) configuration.getScorings().get(5);
RiskRule scoring = (RiskRule) configuration.getScorings().get(3);
assertThat(scoring.getVenueType()).isEqualTo(3);
assertThat(scoring.getVenueCategory1()).isEqualTo(ScoringConfigurationItem.wildcardValue);
......
......@@ -4,6 +4,7 @@ import fr.gouv.clea.clea.scoring.configuration.exposure.ExposureTimeConfiguratio
import fr.gouv.clea.clea.scoring.configuration.exposure.ExposureTimeConfigurationConverter;
import fr.gouv.clea.clea.scoring.configuration.risk.RiskConfiguration;
import fr.gouv.clea.clea.scoring.configuration.risk.RiskConfigurationConverter;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
......@@ -20,7 +21,8 @@ import static org.assertj.core.api.Assertions.assertThat;
@EnableConfigurationProperties(value = {RiskConfiguration.class, ExposureTimeConfiguration.class})
@ContextConfiguration(classes = {RiskConfigurationConverter.class, ExposureTimeConfigurationConverter.class})
@ActiveProfiles("test")
public class ScoringConfigurationItemTest {
@Slf4j
class ScoringConfigurationItemTest {
@Autowired
private RiskConfiguration riskConfiguration;
......@@ -29,18 +31,46 @@ public class ScoringConfigurationItemTest {
private ExposureTimeConfiguration exposureTimeConfiguration;
@Test
void should_return_the_most_specified_rule() {
void should_return_the_full_wildcard_rule() {
assertThat(riskConfiguration.getConfigurationFor(2, 1, 1))
.isEqualTo(riskConfiguration.getScorings().get(0));
}
@Test
void should_return_rule_one_one_one() {
assertThat(riskConfiguration.getConfigurationFor(1, 1, 1))
.isEqualTo(riskConfiguration.getScorings().get(1));
}
@Test
void should_return_the_full_wildcard_rule() {
assertThat(riskConfiguration.getConfigurationFor(2, 1, 1))
.isEqualTo(riskConfiguration.getScorings().get(0));
void should_return_rule_one_two_three() {
assertThat(riskConfiguration.getConfigurationFor(1, 2, 3))
.isEqualTo(riskConfiguration.getScorings().get(2));
}
@Test
void should_return_the_rule_three_wildcard_wildcard() {
assertThat(riskConfiguration.getConfigurationFor(3, 2, 1))
.isEqualTo(riskConfiguration.getScorings().get(3));
}
@Test
void should_return_the_rule_three_one_wildcard() {
assertThat(riskConfiguration.getConfigurationFor(3, 1, 2))
.isEqualTo(riskConfiguration.getScorings().get(4));
}
//TODO: add more tests according to rules set
@Test
void should_return_the_rule_three_wildcard_two() {
assertThat(riskConfiguration.getConfigurationFor(3, 2, 2))
.isEqualTo(riskConfiguration.getScorings().get(5));
}
@Test
void should_return_the_rule_three_one_two() {
assertThat(riskConfiguration.getConfigurationFor(3, 1, 2))
.isEqualTo(riskConfiguration.getScorings().get(6));
}
}
......@@ -4,12 +4,10 @@ clea:
enabled: "true"
rules:
# venueType, venueCat1, venueCat2, exposureTimeBackward, exposureTimeForward, exposureTimeStaffBackward, exposureTimeStaffForward
- '*,*,*,3,11,22,31'
- '1,1,1,3,13,23,33'
- '*,*,*,2,12,22,32'
- '3,*,*,1,11,21,31'
- '1,2,3,2,12,22,32'
- '*,2,*,2,12,22,32'
- '*,*,3,2,12,22,32'
risk:
enabled: "true"
rules:
......@@ -17,6 +15,7 @@ clea:
- '*,*,*,3,1,3.0,2.0'
- '1,1,1,3,1,3.0,2.0'
- '1,2,3,3,1,3.0,2.0'
- '*,2,*,3,1,3.0,2.0'
- '*,*,3,3,1,3.0,2.0'
- '3,*,*,3,1,3.0,2.0'
\ No newline at end of file
- '3,*,*,3,1,3.0,2.0'
- '3,1,*,3,1,3.0,2.0'
- '3,*,2,3,1,3.0,2.0'
- '3,1,2,3,1,3.0,2.0'
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment