Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

dynamic rounding of results for comparisons #151

Merged
merged 12 commits into from
Nov 26, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,13 @@
* @param type the type of filter (contains, startsWith...)
* @param value the value of the filter
* @param column the column / field on which the filter will be applied
* @param tolerance precision/tolerance used for the comparisons (simulates the rounding of the database values) Only useful for numbers.
* @author Kevin Le Saulnier <kevin.lesaulnier at rte-france.com>
*/
public record ResourceFilterDTO(DataType dataType, Type type, Object value, String column) {
public record ResourceFilterDTO(DataType dataType, Type type, Object value, String column, Double tolerance) {
public ResourceFilterDTO(DataType dataType, Type type, Object value, String column) {
this(dataType, type, value, column, null);
}

public enum DataType {
@JsonProperty("text")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,17 @@ public static <X> Specification<X> startsWith(String field, String value) {
return (root, cq, cb) -> cb.like(cb.upper(getColumnPath(root, field)), EscapeCharacter.DEFAULT.escape(value).toUpperCase() + "%", EscapeCharacter.DEFAULT.getEscapeCharacter());
}

/**
* Returns a specification where the field value is not equal within the given tolerance.
*/
public static <X> Specification<X> notEqual(String field, Double value, Double tolerance) {
return (root, cq, cb) -> {
Expression<Double> doubleExpression = getColumnPath(root, field).as(Double.class);
/**
* in order to be equal to doubleExpression, value has to fit :
* value - tolerance <= doubleExpression <= value + tolerance
* therefore in order to be different at least one of the opposite comparison needs to be true :
*/
return cb.or(
cb.greaterThan(doubleExpression, value + tolerance),
cb.lessThan(doubleExpression, value - tolerance)
Expand Down Expand Up @@ -137,13 +145,27 @@ private static <X> Specification<X> appendTextFilterToSpecification(Specificatio

@NotNull
private static <X> Specification<X> appendNumberFilterToSpecification(Specification<X> specification, ResourceFilterDTO resourceFilter) {
final double tolerance = 0.00001; // tolerance for comparison
String value = resourceFilter.value().toString();
return createNumberPredicate(specification, resourceFilter, value, tolerance);
return createNumberPredicate(specification, resourceFilter, value);
}

private static <X> Specification<X> createNumberPredicate(Specification<X> specification, ResourceFilterDTO resourceFilter, String value, double tolerance) {
Double valueDouble = Double.valueOf(value);
private static <X> Specification<X> createNumberPredicate(Specification<X> specification, ResourceFilterDTO resourceFilter, String filterValue) {
double tolerance;
if (resourceFilter.tolerance() != null) {
tolerance = resourceFilter.tolerance();
} else {
// the reference for the comparison is the number of digits after the decimal point in filterValue
// extra digits are ignored, but the user may add '0's after the decimal point in order to get a better precision
String[] splitValue = filterValue.split("\\.");
int numberOfDecimalAfterDot = 0;
if (splitValue.length > 1) {
numberOfDecimalAfterDot = splitValue[1].length();
}
// tolerance is multiplied by 0.5 to simulate the fact that the database value is rounded (in the front, from the user viewpoint)
// more than 13 decimal after dot will likely cause rounding errors due to double precision
tolerance = Math.pow(10, -numberOfDecimalAfterDot) * 0.5;
}
Double valueDouble = Double.valueOf(filterValue);
return switch (resourceFilter.type()) {
case NOT_EQUAL -> specification.and(notEqual(resourceFilter.column(), valueDouble, tolerance));
case LESS_THAN_OR_EQUAL ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,10 @@ void findFilteredPrecontingencyLimitViolationResultsTest(List<ResourceFilterDTO>
List<PreContingencyLimitViolationResultDTO> preContingencyLimitViolation = securityAnalysisResultService.findNResult(resultEntity.getId(), filters, sort);

// assert subject ids to check parent filters
assertThat(preContingencyLimitViolation).extracting(SubjectLimitViolationEntity.Fields.subjectId).containsExactlyElementsOf(expectedResult.stream().map(PreContingencyLimitViolationResultDTO::getSubjectId).toList());
assertThat(preContingencyLimitViolation).extracting(SubjectLimitViolationEntity.Fields.subjectId)
.containsExactlyElementsOf(
expectedResult.stream().map(PreContingencyLimitViolationResultDTO::getSubjectId).toList()
);
assertSelectCount(expectedSelectCount);
}

Expand Down Expand Up @@ -110,9 +113,11 @@ private static Stream<Arguments> provideChildFilter() {

private static Stream<Arguments> provideChildFilterWithTolerance() {
return Stream.of(
Arguments.of(List.of(new ResourceFilterDTO(ResourceFilterDTO.DataType.NUMBER, ResourceFilterDTO.Type.NOT_EQUAL, "11.02425", AbstractLimitViolationEntity.Fields.value),
Arguments.of(List.of(
new ResourceFilterDTO(ResourceFilterDTO.DataType.NUMBER, ResourceFilterDTO.Type.NOT_EQUAL, "11.02425", AbstractLimitViolationEntity.Fields.value),
new ResourceFilterDTO(ResourceFilterDTO.DataType.NUMBER, ResourceFilterDTO.Type.LESS_THAN_OR_EQUAL, "10.51243", AbstractLimitViolationEntity.Fields.limit),
new ResourceFilterDTO(ResourceFilterDTO.DataType.NUMBER, ResourceFilterDTO.Type.GREATER_THAN_OR_EQUAL, "1.00001", AbstractLimitViolationEntity.Fields.limitReduction)), Sort.by(Sort.Direction.ASC, "limit"),
new ResourceFilterDTO(ResourceFilterDTO.DataType.NUMBER, ResourceFilterDTO.Type.GREATER_THAN_OR_EQUAL, "0.999999", AbstractLimitViolationEntity.Fields.limitReduction)
), Sort.by(Sort.Direction.ASC, "limit"),
getResultPreContingencyWithNestedFilter(p -> p.getLimitViolation().getLimit() <= 10.51243)
.stream().sorted(Comparator.comparing(x -> x.getLimitViolation().getLimit())).toList(), 2)
);
Expand Down
Loading