diff --git a/pom.xml b/pom.xml index da9a155b..71513e90 100644 --- a/pom.xml +++ b/pom.xml @@ -213,7 +213,7 @@ de.medizininformatik-initiative sq2cql - 0.5.0 + 0.6.0 diff --git a/src/main/java/de/numcodex/feasibility_gui_backend/query/api/status/ValidationIssue.java b/src/main/java/de/numcodex/feasibility_gui_backend/query/api/status/ValidationIssue.java index f42212a1..6692323c 100644 --- a/src/main/java/de/numcodex/feasibility_gui_backend/query/api/status/ValidationIssue.java +++ b/src/main/java/de/numcodex/feasibility_gui_backend/query/api/status/ValidationIssue.java @@ -7,7 +7,8 @@ @Slf4j @JsonSerialize(using = ValidationIssueSerializer.class) public enum ValidationIssue { - TERMCODE_CONTEXT_COMBINATION_INVALID(20001, "The combination of context and termcode(s) is not found."); + TERMCODE_CONTEXT_COMBINATION_INVALID(20001, "The combination of context and termcode(s) is not found."), + TIMERESTRICTION_INVALID(20002, "The TimeRestriction is invalid. 'beforeDate' must not be before 'afterDate'"); private static final ValidationIssue[] VALUES; diff --git a/src/main/java/de/numcodex/feasibility_gui_backend/terminology/validation/StructuredQueryValidation.java b/src/main/java/de/numcodex/feasibility_gui_backend/terminology/validation/StructuredQueryValidation.java index a1cf677b..d688580b 100644 --- a/src/main/java/de/numcodex/feasibility_gui_backend/terminology/validation/StructuredQueryValidation.java +++ b/src/main/java/de/numcodex/feasibility_gui_backend/terminology/validation/StructuredQueryValidation.java @@ -5,8 +5,11 @@ import de.numcodex.feasibility_gui_backend.common.api.TermCode; import de.numcodex.feasibility_gui_backend.query.api.MutableStructuredQuery; import de.numcodex.feasibility_gui_backend.query.api.StructuredQuery; +import de.numcodex.feasibility_gui_backend.query.api.TimeRestriction; import de.numcodex.feasibility_gui_backend.query.api.status.ValidationIssue; import de.numcodex.feasibility_gui_backend.terminology.TerminologyService; + +import java.time.LocalDate; import java.util.List; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; @@ -86,6 +89,10 @@ private void annotateCriteria(List criteria, boolean skipValid criterion.setValidationIssues(List.of(ValidationIssue.TERMCODE_CONTEXT_COMBINATION_INVALID)); continue; } + if (isTimeRestrictionInvalid(criterion.getTimeRestriction())) { + criterion.setValidationIssues(List.of(ValidationIssue.TIMERESTRICTION_INVALID)); + continue; + } for (TermCode termCode : criterion.getTermCodes()) { if (terminologyService.isExistingTermCode(termCode.system(), termCode.code())) { log.trace("termcode ok: {} - {}", termCode.system(), termCode.code()); @@ -103,6 +110,11 @@ private boolean containsInvalidCriteria(List criteria) { if (criterion.context() == null) { return true; } + if (isTimeRestrictionInvalid(criterion.timeRestriction())) { + log.debug("TimeRestriction invalid. 'beforeDate' ({}) must be after 'afterDate' ({}) but is not", + criterion.timeRestriction().beforeDate(), criterion.timeRestriction().afterDate()); + return true; + } for (TermCode termCode : criterion.termCodes()) { if (terminologyService.isExistingTermCode(termCode.system(), termCode.code())) { log.trace("termcode ok: {} - {}", termCode.system(), termCode.code()); @@ -115,4 +127,12 @@ private boolean containsInvalidCriteria(List criteria) { } return false; } + + private boolean isTimeRestrictionInvalid(TimeRestriction timeRestriction) { + // If no timeRestriction is set, it is not invalid + if (timeRestriction == null) { + return false; + } + return LocalDate.parse(timeRestriction.beforeDate()).isBefore(LocalDate.parse(timeRestriction.afterDate())); + } } diff --git a/src/test/java/de/numcodex/feasibility_gui_backend/terminology/validation/StructuredQueryValidationTest.java b/src/test/java/de/numcodex/feasibility_gui_backend/terminology/validation/StructuredQueryValidationTest.java index aaddc12a..e4ab71b5 100644 --- a/src/test/java/de/numcodex/feasibility_gui_backend/terminology/validation/StructuredQueryValidationTest.java +++ b/src/test/java/de/numcodex/feasibility_gui_backend/terminology/validation/StructuredQueryValidationTest.java @@ -3,6 +3,7 @@ import de.numcodex.feasibility_gui_backend.common.api.Criterion; import de.numcodex.feasibility_gui_backend.common.api.TermCode; import de.numcodex.feasibility_gui_backend.query.api.StructuredQuery; +import de.numcodex.feasibility_gui_backend.query.api.TimeRestriction; import de.numcodex.feasibility_gui_backend.terminology.TerminologyService; import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.BeforeEach; @@ -62,6 +63,13 @@ void testIsValid_falseOnMissingContext() { assertFalse(isValid); } + @Test + void testIsValid_falseOnInvalidTimeRestriction() { + var isValid = structuredQueryValidation.isValid(createStructuredQueryWithInvalidTimeRestriction()); + + assertFalse(isValid); + } + @ParameterizedTest @CsvSource({"true,true", "true,false", "false,true", "false,false"}) void testAnnotateStructuredQuery_emptyIssuesOnValidCriteriaOrSkippedValidation(String withExclusionCriteriaString, String skipValidationString) { @@ -106,6 +114,18 @@ void testAnnotateStructuredQuery_nonEmptyIssuesOnMissingContext(boolean skipVali } } + @ParameterizedTest + @ValueSource(booleans = {true, false}) + void testAnnotateStructuredQuery_nonEmptyIssuesOnInvalidTimeRestriction(boolean skipValidation) { + var annotatedStructuredQuery = structuredQueryValidation.annotateStructuredQuery(createStructuredQueryWithInvalidTimeRestriction(), skipValidation); + + if (skipValidation) { + assertTrue(annotatedStructuredQuery.inclusionCriteria().get(0).get(0).validationIssues().isEmpty()); + } else { + assertFalse(annotatedStructuredQuery.inclusionCriteria().get(0).get(0).validationIssues().isEmpty()); + } + } + @NotNull private static StructuredQuery createValidStructuredQuery(boolean withExclusionCriteria) { var context = TermCode.builder() @@ -150,4 +170,35 @@ private static StructuredQuery createStructuredQueryWithoutContext() { .display("foo") .build(); } + + @NotNull + private static StructuredQuery createStructuredQueryWithInvalidTimeRestriction() { + var context = TermCode.builder() + .code("Laboruntersuchung") + .system("fdpg.mii.cds") + .display("Laboruntersuchung") + .version("1.0.0") + .build(); + var termCode = TermCode.builder() + .code("19113-0") + .system("http://loinc.org") + .display("IgE") + .build(); + var timeRestriction = TimeRestriction.builder() + .afterDate("1998-05-09") + .beforeDate("1991-06-15") + .build(); + var criterion = Criterion.builder() + .termCodes(List.of(termCode)) + .context(context) + .attributeFilters(List.of()) + .timeRestriction(timeRestriction) + .build(); + return StructuredQuery.builder() + .version(URI.create("http://to_be_decided.com/draft-2/schema#")) + .inclusionCriteria(List.of(List.of(criterion))) + .exclusionCriteria(List.of()) + .display("foo") + .build(); + } }