From ae12c84f1ab9fc337e7d0363cd203eca56ad7549 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 28 Jun 2024 12:55:13 +0000 Subject: [PATCH 01/43] Update dependency maven to v3.9.8 --- .mvn/wrapper/maven-wrapper.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties index 23c7e599..f95f1ee8 100644 --- a/.mvn/wrapper/maven-wrapper.properties +++ b/.mvn/wrapper/maven-wrapper.properties @@ -16,4 +16,4 @@ # under the License. wrapperVersion=3.3.2 distributionType=only-script -distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.7/apache-maven-3.9.7-bin.zip +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.8/apache-maven-3.9.8-bin.zip From e125cf449e0a70a539d90001ef45ba02a07c114c Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 28 Jun 2024 13:26:50 +0000 Subject: [PATCH 02/43] Update docker/build-push-action action to v6 --- .github/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index bfdc32c3..c1c9dd7a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -57,7 +57,7 @@ jobs: uses: docker/setup-buildx-action@v3 - name: Build and Export to Docker - uses: docker/build-push-action@v5 + uses: docker/build-push-action@v6 with: context: . tags: flare:latest @@ -222,7 +222,7 @@ jobs: type=semver,pattern={{major}}.{{minor}} - name: Build and push - uses: docker/build-push-action@v5 + uses: docker/build-push-action@v6 with: context: . platforms: linux/amd64,linux/arm64 From d01a114b80d8c767004965617e364ced4469b711 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 28 Jun 2024 13:37:26 +0000 Subject: [PATCH 03/43] Update keycloak/keycloak Docker tag to v25 --- .../integration-test/oauth/docker-compose.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/integration-test/oauth/docker-compose.yml b/.github/integration-test/oauth/docker-compose.yml index dfd9d34e..eae28a4a 100644 --- a/.github/integration-test/oauth/docker-compose.yml +++ b/.github/integration-test/oauth/docker-compose.yml @@ -29,19 +29,19 @@ services: generate-cert: condition: service_completed_successfully keycloak: - image: "keycloak/keycloak:24.0.5" + image: "keycloak/keycloak:25.0.1" command: ["start", "--import-realm"] healthcheck: - test: ["CMD-SHELL", "exec 3<>/dev/tcp/127.0.0.1/8080;echo -e \"GET /health/ready HTTP/1.1\r\nhost: localhost\r\nConnection: close\r\n\r\n\" >&3;grep \"HTTP/1.1 200 OK\" <&3"] + test: ["CMD-SHELL", "exec 3<>/dev/tcp/127.0.0.1/9000;echo -e \"GET /health/ready HTTP/1.1\r\nhost: localhost\r\nConnection: close\r\n\r\n\" >&3;grep \"HTTP/1.1 200 OK\" <&3"] interval: "5s" timeout: "5s" - retries: "3" + retries: 3 start_period: "30s" networks: test-oauth: environment: - KC_HOSTNAME_URL: "https://secure-keycloak:8443" - KC_HOSTNAME_ADMIN_URL: "https://secure-keycloak:8443" + KC_HOSTNAME: "https://secure-keycloak:8443" + KC_HOSTNAME_ADMIN: "https://secure-keycloak:8443" KC_HTTP_RELATIVE_PATH: "/" KC_PROXY_HEADERS: "xforwarded" KC_HTTP_ENABLED: "true" @@ -55,7 +55,7 @@ services: test: ["CMD-SHELL", "curl --fail -s http://localhost:8080"] interval: "5s" timeout: "5s" - retries: "3" + retries: 3 start_period: "5s" networks: test-oauth: @@ -77,7 +77,7 @@ services: test: ["CMD-SHELL", "curl --fail -s http://localhost:8080/health"] interval: "5s" timeout: "5s" - retries: "3" + retries: 3 start_period: "60s" networks: test-oauth: @@ -106,7 +106,7 @@ services: test: ["CMD-SHELL", "curl --fail -s http://localhost:8080/cache/stats"] interval: "5s" timeout: "5s" - retries: "3" + retries: 3 start_period: "60s" networks: test-oauth: From f3de3739b8f9afbf64425ec421573a74cacb82bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julian=20Gr=C3=BCndner?= Date: Tue, 9 Jul 2024 09:34:44 +0200 Subject: [PATCH 04/43] Implement cohort extraction endpoint --- .../flare/rest/QueryController.java | 25 +++++++++- .../flare/service/StructuredQueryService.java | 21 ++++++-- .../flare/rest/QueryControllerTest.java | 50 +++++++++++++++++-- .../service/StructuredQueryServiceIT.java | 12 ++--- .../service/StructuredQueryServiceTest.java | 28 +++++------ 5 files changed, 106 insertions(+), 30 deletions(-) diff --git a/src/main/java/de/medizininformatikinitiative/flare/rest/QueryController.java b/src/main/java/de/medizininformatikinitiative/flare/rest/QueryController.java index dd42b672..eb0304ec 100644 --- a/src/main/java/de/medizininformatikinitiative/flare/rest/QueryController.java +++ b/src/main/java/de/medizininformatikinitiative/flare/rest/QueryController.java @@ -39,14 +39,15 @@ public QueryController(StructuredQueryService queryService) { @Bean public RouterFunction queryRouter() { return route(POST("query/execute").and(accept(MEDIA_TYPE_SQ)), this::execute) - .andRoute(POST("query/translate").and(accept(MEDIA_TYPE_SQ)), this::translate); + .andRoute(POST("query/translate").and(accept(MEDIA_TYPE_SQ)), this::translate) + .andRoute(POST("query/execute-cohort").and(accept(MEDIA_TYPE_SQ)), this::executeCohort); } public Mono execute(ServerRequest request) { var startNanoTime = System.nanoTime(); logger.debug("Execute query"); return request.bodyToMono(StructuredQuery.class) - .flatMap(queryService::execute) + .flatMap(queryService::executeCohortSize) .flatMap(count -> { logger.debug("Finished query returning {} patients in {} seconds.", count, "%.1f".formatted(Util.durationSecondsSince(startNanoTime))); @@ -62,6 +63,26 @@ public Mono execute(ServerRequest request) { }); } + public Mono executeCohort(ServerRequest request) { + var startNanoTime = System.nanoTime(); + logger.debug("Execute query"); + return request.bodyToMono(StructuredQuery.class) + .flatMap(queryService::executeCohort) + .flatMap(population -> { + logger.debug("Finished query returning {} patients in {} seconds.", population.size(), + "%.1f".formatted(Util.durationSecondsSince(startNanoTime))); + return ok().bodyValue(population); + }) + .onErrorResume(MappingException.class, e -> { + logger.warn("Mapping error: {}", e.getMessage()); + return badRequest().bodyValue(new Error(e.getMessage())); + }) + .onErrorResume(WebClientRequestException.class, e -> { + logger.error("Service not available because of downstream web client errors: {}", e.getMessage()); + return status(503).bodyValue(new Error(e.getMessage())); + }); + } + public Mono translate(ServerRequest request) { logger.debug("Translate query"); return request.bodyToMono(StructuredQuery.class) diff --git a/src/main/java/de/medizininformatikinitiative/flare/service/StructuredQueryService.java b/src/main/java/de/medizininformatikinitiative/flare/service/StructuredQueryService.java index f5499c15..27d7e787 100644 --- a/src/main/java/de/medizininformatikinitiative/flare/service/StructuredQueryService.java +++ b/src/main/java/de/medizininformatikinitiative/flare/service/StructuredQueryService.java @@ -34,13 +34,14 @@ public StructuredQueryService(@Qualifier("memCachingFhirQueryService") FhirQuery this.translator = requireNonNull(translator); } + /** - * Executes {@code query} and returns the number of Patients qualifying its criteria. + * Executes {@code query} and returns the Population of Patient IDs. * * @param query the query to execute - * @return the number of Patients qualifying the criteria + * @return the Patient IDs qualifying the criteria */ - public Mono execute(StructuredQuery query) { + public Mono executeCohort(StructuredQuery query) { var includedPatients = query.inclusionCriteria().executeAndIntersection(this::executeUnionGroup) .defaultIfEmpty(Population.of()); var excludedPatients = query.exclusionCriteria().map(c -> c.map(CriterionGroup::wrapCriteria) @@ -48,8 +49,18 @@ public Mono execute(StructuredQuery query) { .defaultIfEmpty(Population.of())) .orElse(Mono.just(Population.of())); return includedPatients - .flatMap(i -> excludedPatients.map(i::difference)) - .map(Set::size); + .flatMap(i -> excludedPatients.map(i::difference)); + } + + + /** + * Executes {@code query} and returns the number of Patients qualifying its criteria. + * + * @param query the query to execute + * @return the number of Patients qualifying the criteria + */ + public Mono executeCohortSize(StructuredQuery query) { + return this.executeCohort(query).map(Set::size); } private Mono executeUnionGroup(CriterionGroup group) { diff --git a/src/test/java/de/medizininformatikinitiative/flare/rest/QueryControllerTest.java b/src/test/java/de/medizininformatikinitiative/flare/rest/QueryControllerTest.java index 8ca25d53..0dbaa876 100644 --- a/src/test/java/de/medizininformatikinitiative/flare/rest/QueryControllerTest.java +++ b/src/test/java/de/medizininformatikinitiative/flare/rest/QueryControllerTest.java @@ -1,6 +1,7 @@ package de.medizininformatikinitiative.flare.rest; import de.medizininformatikinitiative.flare.Either; +import de.medizininformatikinitiative.flare.model.Population; import de.medizininformatikinitiative.flare.model.fhir.Query; import de.medizininformatikinitiative.flare.model.mapping.MappingNotFoundException; import de.medizininformatikinitiative.flare.model.sq.*; @@ -15,13 +16,19 @@ import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.http.MediaType; import org.springframework.test.web.reactive.server.WebTestClient; +import org.testcontainers.shaded.com.fasterxml.jackson.core.JsonProcessingException; +import org.testcontainers.shaded.com.fasterxml.jackson.databind.ObjectMapper; import reactor.core.publisher.Mono; +import java.util.Arrays; + import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) class QueryControllerTest { + static final String PATIENT_ID = "patient-id-211701"; + static final String PATIENT_ID_1 = "patient-id-1-131300"; static final MediaType MEDIA_TYPE_SQ = MediaType.valueOf("application/sq+json"); static final TermCode FEVER = TermCode.of("http://snomed.info/sct", "386661006", "Fever (finding)"); static final StructuredQuery STRUCTURED_QUERY = StructuredQuery.of(CriterionGroup.of(CriterionGroup.of( @@ -42,8 +49,8 @@ void setUp() { } @Test - void execute() { - when(queryService.execute(STRUCTURED_QUERY)).thenReturn(Mono.just(1)); + void executeCohortSize() { + when(queryService.executeCohortSize(STRUCTURED_QUERY)).thenReturn(Mono.just(1)); client.post() .uri("/query/execute") @@ -75,9 +82,46 @@ void execute() { .expectBody().json("1"); } + + @Test + void executeCohort() throws JsonProcessingException { + when(queryService.executeCohort(STRUCTURED_QUERY)).thenReturn(Mono.just(Population.of(PATIENT_ID,PATIENT_ID_1))); + + ObjectMapper objectMapper = new ObjectMapper(); + + client.post() + .uri("/query/execute-cohort") + .contentType(MEDIA_TYPE_SQ) + .bodyValue(""" + { + "inclusionCriteria": [ + [ + { + "context": { + "system": "context-system", + "code": "context-code", + "display": "context-display" + }, + "termCodes": [ + { + "system": "http://snomed.info/sct", + "code": "386661006", + "display": "Fever (finding)" + } + ] + } + ] + ] + } + """) + .exchange() + .expectStatus().isOk() + .expectBody().json(objectMapper.writeValueAsString(Arrays.asList(PATIENT_ID, PATIENT_ID_1))); + } + @Test void execute_error() { - when(queryService.execute(STRUCTURED_QUERY)).thenReturn(Mono.error(new MappingNotFoundException(ContextualTermCode.of(TestUtil.CONTEXT, FEVER)))); + when(queryService.executeCohortSize(STRUCTURED_QUERY)).thenReturn(Mono.error(new MappingNotFoundException(ContextualTermCode.of(TestUtil.CONTEXT, FEVER)))); client.post() .uri("/query/execute") diff --git a/src/test/java/de/medizininformatikinitiative/flare/service/StructuredQueryServiceIT.java b/src/test/java/de/medizininformatikinitiative/flare/service/StructuredQueryServiceIT.java index 8410931a..84b4cc49 100644 --- a/src/test/java/de/medizininformatikinitiative/flare/service/StructuredQueryServiceIT.java +++ b/src/test/java/de/medizininformatikinitiative/flare/service/StructuredQueryServiceIT.java @@ -131,7 +131,7 @@ void setUp() throws URISyntaxException, IOException { void execute_Criterion() { var query = StructuredQuery.of(CriterionGroup.of(CriterionGroup.of(Criterion.of(ContextualConcept.of(DIAGNOSIS, Concept.of(I08)))))); - var result = service.execute(query).block(); + var result = service.executeCohortSize(query).block(); assertThat(result).isOne(); } @@ -140,7 +140,7 @@ void execute_Criterion() { void execute_genderTestCase() throws URISyntaxException, IOException { var query = parseSq(Files.readString(resourcePathFlareApplication("testCases").resolve("returningOther").resolve("2-gender.json"))); - var result = service.execute(query).block(); + var result = service.executeCohortSize(query).block(); assertThat(result).isEqualTo(172); } @@ -149,7 +149,7 @@ void execute_genderTestCase() throws URISyntaxException, IOException { void execute_consentTestCase() throws URISyntaxException, IOException { var query = parseSq(Files.readString(resourcePathFlareApplication("testCases").resolve("returningOther").resolve("consent.json"))); - var result = service.execute(query).block(); + var result = service.executeCohortSize(query).block(); assertThat(result).isOne(); } @@ -165,7 +165,7 @@ void execute_specimenTestCase() throws IOException, URISyntaxException { var query = parseSq(slurpStructuredQueryService("referencedCriteria/sq-test-specimen-diag.json")); - var result = service.execute(query).block(); + var result = service.executeCohortSize(query).block(); assertThat(result).isOne(); } @@ -173,7 +173,7 @@ void execute_specimenTestCase() throws IOException, URISyntaxException { @ParameterizedTest @MethodSource("getTestQueriesReturningOnePatient") void execute_casesReturningOne(StructuredQuery query) { - var result = service.execute(query).block(); + var result = service.executeCohortSize(query).block(); assertThat(result).isOne(); } @@ -221,7 +221,7 @@ void execute_BloodPressureTestCase() throws Exception { } """); - var result = service_BloodPressure.execute(query).block(); + var result = service_BloodPressure.executeCohortSize(query).block(); assertThat(result).isOne(); } diff --git a/src/test/java/de/medizininformatikinitiative/flare/service/StructuredQueryServiceTest.java b/src/test/java/de/medizininformatikinitiative/flare/service/StructuredQueryServiceTest.java index 74b960bb..f5f9c611 100644 --- a/src/test/java/de/medizininformatikinitiative/flare/service/StructuredQueryServiceTest.java +++ b/src/test/java/de/medizininformatikinitiative/flare/service/StructuredQueryServiceTest.java @@ -63,7 +63,7 @@ void setUp() { @Test void execute() { - var result = service.execute(query); + var result = service.executeCohortSize(query); StepVerifier.create(result).expectError(MappingNotFoundException.class).verify(); } @@ -85,7 +85,7 @@ class SingleExpansion { void execute() { var query = query(incl(PATIENT)); - var result = service.execute(query).block(); + var result = service.executeCohortSize(query).block(); assertThat(result).isOne(); } @@ -110,7 +110,7 @@ class MultipleExpansion { void execute_MultiplePatients() { var query = query(inclExpand(PATIENT_1, PATIENT_2)); - var result = service.execute(query).block(); + var result = service.executeCohortSize(query).block(); assertThat(result).isEqualTo(2); } @@ -120,7 +120,7 @@ void execute_MultiplePatients() { void execute_SamePatient() { var query = query(inclExpand(PATIENT, PATIENT)); - var result = service.execute(query).block(); + var result = service.executeCohortSize(query).block(); assertThat(result).isOne(); } @@ -146,7 +146,7 @@ class ConjunctionInclusion { void execute_MultiplePatients() { var query = query(inclAnd(PATIENT_1, PATIENT_2)); - var result = service.execute(query).block(); + var result = service.executeCohortSize(query).block(); assertThat(result).isZero(); } @@ -156,7 +156,7 @@ void execute_MultiplePatients() { void execute_SamePatient() { var query = query(inclAnd(PATIENT, PATIENT)); - var result = service.execute(query).block(); + var result = service.executeCohortSize(query).block(); assertThat(result).isOne(); } @@ -181,7 +181,7 @@ class DisjunctionInclusion { void execute_MultiplePatients() { var query = query(inclOr(PATIENT_1, PATIENT_2)); - var result = service.execute(query).block(); + var result = service.executeCohortSize(query).block(); assertThat(result).isEqualTo(2); } @@ -191,7 +191,7 @@ void execute_MultiplePatients() { void execute_SamePatient() { var query = query(inclOr(PATIENT, PATIENT)); - var result = service.execute(query).block(); + var result = service.executeCohortSize(query).block(); assertThat(result).isOne(); } @@ -216,7 +216,7 @@ class SingleInclusionAndExclusion { void execute_PatientNotExcluded() { var query = query(incl(PATIENT), excl(PATIENT_1)); - var result = service.execute(query).block(); + var result = service.executeCohortSize(query).block(); assertThat(result).isOne(); } @@ -226,7 +226,7 @@ void execute_PatientNotExcluded() { void execute_PatientExcluded() { var query = query(incl(PATIENT), excl(PATIENT)); - var result = service.execute(query).block(); + var result = service.executeCohortSize(query).block(); assertThat(result).isZero(); } @@ -252,7 +252,7 @@ class SingleInclusionAndDisjunctionExclusion { void execute_PatientNotExcluded() { var query = query(incl(PATIENT), exclOr(PATIENT_1, PATIENT_2)); - var result = service.execute(query).block(); + var result = service.executeCohortSize(query).block(); assertThat(result).isOne(); } @@ -262,7 +262,7 @@ void execute_PatientNotExcluded() { void execute_PatientExcluded() { var query = query(incl(PATIENT), exclOr(PATIENT, PATIENT_1)); - var result = service.execute(query).block(); + var result = service.executeCohortSize(query).block(); assertThat(result).isZero(); } @@ -289,7 +289,7 @@ class SingleInclusionAndConjunctionExclusion { void execute_PatientNotExcluded() { var query = query(incl(PATIENT), exclAnd(PATIENT, PATIENT_1)); - var result = service.execute(query).block(); + var result = service.executeCohortSize(query).block(); assertThat(result).isOne(); } @@ -299,7 +299,7 @@ void execute_PatientNotExcluded() { void execute_PatientExcluded() { var query = query(incl(PATIENT), exclAnd(PATIENT, PATIENT)); - var result = service.execute(query).block(); + var result = service.executeCohortSize(query).block(); assertThat(result).isZero(); } From a543223c1556959eb94b6647c755f16cfc781a03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julian=20Gr=C3=BCndner?= Date: Wed, 10 Jul 2024 10:49:51 +0200 Subject: [PATCH 05/43] Refactor to only one execute function in query service --- .../flare/rest/QueryController.java | 13 +++---- .../flare/service/StructuredQueryService.java | 12 +------ .../flare/rest/QueryControllerTest.java | 13 +++---- .../service/StructuredQueryServiceIT.java | 18 +++++----- .../service/StructuredQueryServiceTest.java | 34 ++++++++++--------- 5 files changed, 43 insertions(+), 47 deletions(-) diff --git a/src/main/java/de/medizininformatikinitiative/flare/rest/QueryController.java b/src/main/java/de/medizininformatikinitiative/flare/rest/QueryController.java index eb0304ec..283e932b 100644 --- a/src/main/java/de/medizininformatikinitiative/flare/rest/QueryController.java +++ b/src/main/java/de/medizininformatikinitiative/flare/rest/QueryController.java @@ -17,6 +17,7 @@ import reactor.core.publisher.Mono; import java.util.Objects; +import java.util.Set; import static org.springframework.web.reactive.function.server.RequestPredicates.POST; import static org.springframework.web.reactive.function.server.RequestPredicates.accept; @@ -45,11 +46,11 @@ public RouterFunction queryRouter() { public Mono execute(ServerRequest request) { var startNanoTime = System.nanoTime(); - logger.debug("Execute query"); + logger.debug("Execute feasibility query"); return request.bodyToMono(StructuredQuery.class) - .flatMap(queryService::executeCohortSize) + .flatMap(queryService::execute).map(Set::size) .flatMap(count -> { - logger.debug("Finished query returning {} patients in {} seconds.", count, + logger.debug("Finished feasibility query returning cohort size {} in {} seconds.", count, "%.1f".formatted(Util.durationSecondsSince(startNanoTime))); return ok().bodyValue(count); }) @@ -65,11 +66,11 @@ public Mono execute(ServerRequest request) { public Mono executeCohort(ServerRequest request) { var startNanoTime = System.nanoTime(); - logger.debug("Execute query"); + logger.debug("Execute cohort query"); return request.bodyToMono(StructuredQuery.class) - .flatMap(queryService::executeCohort) + .flatMap(queryService::execute) .flatMap(population -> { - logger.debug("Finished query returning {} patients in {} seconds.", population.size(), + logger.debug("Finished cohort query returning cohort of {} patient IDs in {} seconds.", population.size(), "%.1f".formatted(Util.durationSecondsSince(startNanoTime))); return ok().bodyValue(population); }) diff --git a/src/main/java/de/medizininformatikinitiative/flare/service/StructuredQueryService.java b/src/main/java/de/medizininformatikinitiative/flare/service/StructuredQueryService.java index 27d7e787..237af6a1 100644 --- a/src/main/java/de/medizininformatikinitiative/flare/service/StructuredQueryService.java +++ b/src/main/java/de/medizininformatikinitiative/flare/service/StructuredQueryService.java @@ -41,7 +41,7 @@ public StructuredQueryService(@Qualifier("memCachingFhirQueryService") FhirQuery * @param query the query to execute * @return the Patient IDs qualifying the criteria */ - public Mono executeCohort(StructuredQuery query) { + public Mono execute(StructuredQuery query) { var includedPatients = query.inclusionCriteria().executeAndIntersection(this::executeUnionGroup) .defaultIfEmpty(Population.of()); var excludedPatients = query.exclusionCriteria().map(c -> c.map(CriterionGroup::wrapCriteria) @@ -53,16 +53,6 @@ public Mono executeCohort(StructuredQuery query) { } - /** - * Executes {@code query} and returns the number of Patients qualifying its criteria. - * - * @param query the query to execute - * @return the number of Patients qualifying the criteria - */ - public Mono executeCohortSize(StructuredQuery query) { - return this.executeCohort(query).map(Set::size); - } - private Mono executeUnionGroup(CriterionGroup group) { return group.executeAndUnion(this::executeSingle); } diff --git a/src/test/java/de/medizininformatikinitiative/flare/rest/QueryControllerTest.java b/src/test/java/de/medizininformatikinitiative/flare/rest/QueryControllerTest.java index 0dbaa876..e9fc0412 100644 --- a/src/test/java/de/medizininformatikinitiative/flare/rest/QueryControllerTest.java +++ b/src/test/java/de/medizininformatikinitiative/flare/rest/QueryControllerTest.java @@ -21,6 +21,7 @@ import reactor.core.publisher.Mono; import java.util.Arrays; +import java.util.List; import static org.mockito.Mockito.when; @@ -49,8 +50,8 @@ void setUp() { } @Test - void executeCohortSize() { - when(queryService.executeCohortSize(STRUCTURED_QUERY)).thenReturn(Mono.just(1)); + void executeCohort() { + when(queryService.execute(STRUCTURED_QUERY)).thenReturn(Mono.just(Population.of(PATIENT_ID))); client.post() .uri("/query/execute") @@ -84,8 +85,8 @@ void executeCohortSize() { @Test - void executeCohort() throws JsonProcessingException { - when(queryService.executeCohort(STRUCTURED_QUERY)).thenReturn(Mono.just(Population.of(PATIENT_ID,PATIENT_ID_1))); + void execute() throws JsonProcessingException { + when(queryService.execute(STRUCTURED_QUERY)).thenReturn(Mono.just(Population.of(PATIENT_ID,PATIENT_ID_1))); ObjectMapper objectMapper = new ObjectMapper(); @@ -116,12 +117,12 @@ void executeCohort() throws JsonProcessingException { """) .exchange() .expectStatus().isOk() - .expectBody().json(objectMapper.writeValueAsString(Arrays.asList(PATIENT_ID, PATIENT_ID_1))); + .expectBody().json(objectMapper.writeValueAsString(List.of(PATIENT_ID, PATIENT_ID_1))); } @Test void execute_error() { - when(queryService.executeCohortSize(STRUCTURED_QUERY)).thenReturn(Mono.error(new MappingNotFoundException(ContextualTermCode.of(TestUtil.CONTEXT, FEVER)))); + when(queryService.execute(STRUCTURED_QUERY)).thenReturn(Mono.error(new MappingNotFoundException(ContextualTermCode.of(TestUtil.CONTEXT, FEVER)))); client.post() .uri("/query/execute") diff --git a/src/test/java/de/medizininformatikinitiative/flare/service/StructuredQueryServiceIT.java b/src/test/java/de/medizininformatikinitiative/flare/service/StructuredQueryServiceIT.java index 84b4cc49..9ebe17d1 100644 --- a/src/test/java/de/medizininformatikinitiative/flare/service/StructuredQueryServiceIT.java +++ b/src/test/java/de/medizininformatikinitiative/flare/service/StructuredQueryServiceIT.java @@ -96,6 +96,10 @@ private static Path resourcePathFlareApplication(String name) throws URISyntaxEx return Paths.get(Objects.requireNonNull(FlareApplication.class.getResource(name)).toURI()); } + private int getExecutionResult(StructuredQuery query){ + return service.execute(query).block().size(); + } + public static List getTestQueriesReturningOnePatient() throws URISyntaxException, IOException { Path directoryPath = Paths.get(resourcePathFlareApplication("testCases").resolve("returningOnePatient").toString()); @@ -131,7 +135,7 @@ void setUp() throws URISyntaxException, IOException { void execute_Criterion() { var query = StructuredQuery.of(CriterionGroup.of(CriterionGroup.of(Criterion.of(ContextualConcept.of(DIAGNOSIS, Concept.of(I08)))))); - var result = service.executeCohortSize(query).block(); + var result = getExecutionResult(query); assertThat(result).isOne(); } @@ -139,8 +143,7 @@ void execute_Criterion() { @Test void execute_genderTestCase() throws URISyntaxException, IOException { var query = parseSq(Files.readString(resourcePathFlareApplication("testCases").resolve("returningOther").resolve("2-gender.json"))); - - var result = service.executeCohortSize(query).block(); + var result = getExecutionResult(query); assertThat(result).isEqualTo(172); } @@ -148,8 +151,7 @@ void execute_genderTestCase() throws URISyntaxException, IOException { @Test void execute_consentTestCase() throws URISyntaxException, IOException { var query = parseSq(Files.readString(resourcePathFlareApplication("testCases").resolve("returningOther").resolve("consent.json"))); - - var result = service.executeCohortSize(query).block(); + var result = getExecutionResult(query); assertThat(result).isOne(); } @@ -165,7 +167,7 @@ void execute_specimenTestCase() throws IOException, URISyntaxException { var query = parseSq(slurpStructuredQueryService("referencedCriteria/sq-test-specimen-diag.json")); - var result = service.executeCohortSize(query).block(); + var result = getExecutionResult(query); assertThat(result).isOne(); } @@ -173,7 +175,7 @@ void execute_specimenTestCase() throws IOException, URISyntaxException { @ParameterizedTest @MethodSource("getTestQueriesReturningOnePatient") void execute_casesReturningOne(StructuredQuery query) { - var result = service.executeCohortSize(query).block(); + var result = getExecutionResult(query); assertThat(result).isOne(); } @@ -221,7 +223,7 @@ void execute_BloodPressureTestCase() throws Exception { } """); - var result = service_BloodPressure.executeCohortSize(query).block(); + var result = service_BloodPressure.execute(query).block().size(); assertThat(result).isOne(); } diff --git a/src/test/java/de/medizininformatikinitiative/flare/service/StructuredQueryServiceTest.java b/src/test/java/de/medizininformatikinitiative/flare/service/StructuredQueryServiceTest.java index f5f9c611..1d06f3ce 100644 --- a/src/test/java/de/medizininformatikinitiative/flare/service/StructuredQueryServiceTest.java +++ b/src/test/java/de/medizininformatikinitiative/flare/service/StructuredQueryServiceTest.java @@ -47,6 +47,10 @@ class StructuredQueryServiceTest { @InjectMocks private StructuredQueryService service; + private int getExecutionResult(StructuredQuery query){ + return service.execute(query).block().size(); + } + @Nested class SingleInclusion { @@ -63,7 +67,7 @@ void setUp() { @Test void execute() { - var result = service.executeCohortSize(query); + var result = service.execute(query); StepVerifier.create(result).expectError(MappingNotFoundException.class).verify(); } @@ -85,7 +89,7 @@ class SingleExpansion { void execute() { var query = query(incl(PATIENT)); - var result = service.executeCohortSize(query).block(); + var result = getExecutionResult(query); assertThat(result).isOne(); } @@ -110,7 +114,7 @@ class MultipleExpansion { void execute_MultiplePatients() { var query = query(inclExpand(PATIENT_1, PATIENT_2)); - var result = service.executeCohortSize(query).block(); + var result = getExecutionResult(query); assertThat(result).isEqualTo(2); } @@ -120,7 +124,7 @@ void execute_MultiplePatients() { void execute_SamePatient() { var query = query(inclExpand(PATIENT, PATIENT)); - var result = service.executeCohortSize(query).block(); + var result = getExecutionResult(query); assertThat(result).isOne(); } @@ -146,7 +150,7 @@ class ConjunctionInclusion { void execute_MultiplePatients() { var query = query(inclAnd(PATIENT_1, PATIENT_2)); - var result = service.executeCohortSize(query).block(); + var result = getExecutionResult(query); assertThat(result).isZero(); } @@ -156,7 +160,7 @@ void execute_MultiplePatients() { void execute_SamePatient() { var query = query(inclAnd(PATIENT, PATIENT)); - var result = service.executeCohortSize(query).block(); + var result = getExecutionResult(query); assertThat(result).isOne(); } @@ -181,7 +185,7 @@ class DisjunctionInclusion { void execute_MultiplePatients() { var query = query(inclOr(PATIENT_1, PATIENT_2)); - var result = service.executeCohortSize(query).block(); + var result = getExecutionResult(query); assertThat(result).isEqualTo(2); } @@ -191,7 +195,7 @@ void execute_MultiplePatients() { void execute_SamePatient() { var query = query(inclOr(PATIENT, PATIENT)); - var result = service.executeCohortSize(query).block(); + var result = getExecutionResult(query); assertThat(result).isOne(); } @@ -215,9 +219,7 @@ class SingleInclusionAndExclusion { @DisplayName("execute: PATIENT is not excluded → returns 1") void execute_PatientNotExcluded() { var query = query(incl(PATIENT), excl(PATIENT_1)); - - var result = service.executeCohortSize(query).block(); - + var result = getExecutionResult(query); assertThat(result).isOne(); } @@ -226,7 +228,7 @@ void execute_PatientNotExcluded() { void execute_PatientExcluded() { var query = query(incl(PATIENT), excl(PATIENT)); - var result = service.executeCohortSize(query).block(); + var result = getExecutionResult(query); assertThat(result).isZero(); } @@ -252,7 +254,7 @@ class SingleInclusionAndDisjunctionExclusion { void execute_PatientNotExcluded() { var query = query(incl(PATIENT), exclOr(PATIENT_1, PATIENT_2)); - var result = service.executeCohortSize(query).block(); + var result = getExecutionResult(query); assertThat(result).isOne(); } @@ -262,7 +264,7 @@ void execute_PatientNotExcluded() { void execute_PatientExcluded() { var query = query(incl(PATIENT), exclOr(PATIENT, PATIENT_1)); - var result = service.executeCohortSize(query).block(); + var result = service.execute(query).block().size(); assertThat(result).isZero(); } @@ -289,7 +291,7 @@ class SingleInclusionAndConjunctionExclusion { void execute_PatientNotExcluded() { var query = query(incl(PATIENT), exclAnd(PATIENT, PATIENT_1)); - var result = service.executeCohortSize(query).block(); + var result = service.execute(query).block().size(); assertThat(result).isOne(); } @@ -299,7 +301,7 @@ void execute_PatientNotExcluded() { void execute_PatientExcluded() { var query = query(incl(PATIENT), exclAnd(PATIENT, PATIENT)); - var result = service.executeCohortSize(query).block(); + var result = service.execute(query).block().size(); assertThat(result).isZero(); } From f7b1717d04083a7fe8b9811bb155d2e1d976b974 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julian=20Gr=C3=BCndner?= Date: Wed, 10 Jul 2024 10:58:02 +0200 Subject: [PATCH 06/43] replace remaining .size() calls --- .../flare/service/StructuredQueryServiceTest.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/test/java/de/medizininformatikinitiative/flare/service/StructuredQueryServiceTest.java b/src/test/java/de/medizininformatikinitiative/flare/service/StructuredQueryServiceTest.java index 1d06f3ce..7b19274b 100644 --- a/src/test/java/de/medizininformatikinitiative/flare/service/StructuredQueryServiceTest.java +++ b/src/test/java/de/medizininformatikinitiative/flare/service/StructuredQueryServiceTest.java @@ -264,7 +264,7 @@ void execute_PatientNotExcluded() { void execute_PatientExcluded() { var query = query(incl(PATIENT), exclOr(PATIENT, PATIENT_1)); - var result = service.execute(query).block().size(); + var result = getExecutionResult(query); assertThat(result).isZero(); } @@ -291,7 +291,7 @@ class SingleInclusionAndConjunctionExclusion { void execute_PatientNotExcluded() { var query = query(incl(PATIENT), exclAnd(PATIENT, PATIENT_1)); - var result = service.execute(query).block().size(); + var result = getExecutionResult(query); assertThat(result).isOne(); } @@ -301,7 +301,7 @@ void execute_PatientNotExcluded() { void execute_PatientExcluded() { var query = query(incl(PATIENT), exclAnd(PATIENT, PATIENT)); - var result = service.execute(query).block().size(); + var result = getExecutionResult(query); assertThat(result).isZero(); } From 27517af09b1d9982fe07018a51cd9c767611408a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julian=20Gr=C3=BCndner?= Date: Wed, 10 Jul 2024 16:47:32 +0200 Subject: [PATCH 07/43] Refactor tests, Change tets to test for specific patient IDs instead of the size of the Population --- .../flare/rest/QueryControllerTest.java | 4 +- .../service/StructuredQueryServiceIT.java | 22 ++-- .../service/StructuredQueryServiceTest.java | 113 +++++++++--------- .../flare/GeneratedBundle.json | 20 ++-- .../specimen-diag-testbundle.json | 12 +- 5 files changed, 89 insertions(+), 82 deletions(-) diff --git a/src/test/java/de/medizininformatikinitiative/flare/rest/QueryControllerTest.java b/src/test/java/de/medizininformatikinitiative/flare/rest/QueryControllerTest.java index e9fc0412..6ccedb75 100644 --- a/src/test/java/de/medizininformatikinitiative/flare/rest/QueryControllerTest.java +++ b/src/test/java/de/medizininformatikinitiative/flare/rest/QueryControllerTest.java @@ -50,7 +50,7 @@ void setUp() { } @Test - void executeCohort() { + void execute() { when(queryService.execute(STRUCTURED_QUERY)).thenReturn(Mono.just(Population.of(PATIENT_ID))); client.post() @@ -85,7 +85,7 @@ void executeCohort() { @Test - void execute() throws JsonProcessingException { + void executeCohort() throws JsonProcessingException { when(queryService.execute(STRUCTURED_QUERY)).thenReturn(Mono.just(Population.of(PATIENT_ID,PATIENT_ID_1))); ObjectMapper objectMapper = new ObjectMapper(); diff --git a/src/test/java/de/medizininformatikinitiative/flare/service/StructuredQueryServiceIT.java b/src/test/java/de/medizininformatikinitiative/flare/service/StructuredQueryServiceIT.java index 9ebe17d1..d7d83385 100644 --- a/src/test/java/de/medizininformatikinitiative/flare/service/StructuredQueryServiceIT.java +++ b/src/test/java/de/medizininformatikinitiative/flare/service/StructuredQueryServiceIT.java @@ -4,6 +4,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import de.medizininformatikinitiative.flare.FlareApplication; import de.medizininformatikinitiative.flare.Util; +import de.medizininformatikinitiative.flare.model.Population; import de.medizininformatikinitiative.flare.model.mapping.Mapping; import de.medizininformatikinitiative.flare.model.mapping.MappingContext; import de.medizininformatikinitiative.flare.model.mapping.TermCodeNode; @@ -26,6 +27,7 @@ import org.testcontainers.images.PullPolicy; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; +import reactor.test.StepVerifier; import java.io.IOException; import java.net.URISyntaxException; @@ -135,9 +137,8 @@ void setUp() throws URISyntaxException, IOException { void execute_Criterion() { var query = StructuredQuery.of(CriterionGroup.of(CriterionGroup.of(Criterion.of(ContextualConcept.of(DIAGNOSIS, Concept.of(I08)))))); - var result = getExecutionResult(query); - - assertThat(result).isOne(); + var result = service.execute(query); + StepVerifier.create(result).expectNext(Population.of("id-pat-diag-I08.0")).verifyComplete(); } @Test @@ -151,9 +152,10 @@ void execute_genderTestCase() throws URISyntaxException, IOException { @Test void execute_consentTestCase() throws URISyntaxException, IOException { var query = parseSq(Files.readString(resourcePathFlareApplication("testCases").resolve("returningOther").resolve("consent.json"))); - var result = getExecutionResult(query); - assertThat(result).isOne(); + var result = service.execute(query); + + StepVerifier.create(result).expectNext(Population.of("id-pat-consent-test")).verifyComplete(); } @Test @@ -167,9 +169,10 @@ void execute_specimenTestCase() throws IOException, URISyntaxException { var query = parseSq(slurpStructuredQueryService("referencedCriteria/sq-test-specimen-diag.json")); - var result = getExecutionResult(query); + var result = service.execute(query); + + StepVerifier.create(result).expectNext(Population.of("id-pat-diab-test-1")).verifyComplete(); - assertThat(result).isOne(); } @ParameterizedTest @@ -223,9 +226,10 @@ void execute_BloodPressureTestCase() throws Exception { } """); - var result = service_BloodPressure.execute(query).block().size(); + var result = service_BloodPressure.execute(query); + + StepVerifier.create(result).expectNext(Population.of("id-pat-bloodpressure-test")).verifyComplete(); - assertThat(result).isOne(); } @Configuration diff --git a/src/test/java/de/medizininformatikinitiative/flare/service/StructuredQueryServiceTest.java b/src/test/java/de/medizininformatikinitiative/flare/service/StructuredQueryServiceTest.java index 7b19274b..cfca0fb7 100644 --- a/src/test/java/de/medizininformatikinitiative/flare/service/StructuredQueryServiceTest.java +++ b/src/test/java/de/medizininformatikinitiative/flare/service/StructuredQueryServiceTest.java @@ -34,9 +34,10 @@ class StructuredQueryServiceTest { static final String BFARM = "http://fhir.de/CodeSystem/bfarm/icd-10-gm"; static final Criterion CONCEPT_CRITERION = Criterion.of(cc(1)); - static final Population PATIENT = Population.of("patient-id-140857"); - static final Population PATIENT_1 = Population.of("patient-id-144725"); - static final Population PATIENT_2 = Population.of("patient-id-144727"); + static final Population EMPTY_POP = Population.of(); + static final Population PATIENT_POP = Population.of("patient-id-140857"); + static final Population PATIENT_1_POP = Population.of("patient-id-144725"); + static final Population PATIENT_2_POP = Population.of("patient-id-144727"); @Mock private FhirQueryService fhirQueryService; @@ -47,10 +48,6 @@ class StructuredQueryServiceTest { @InjectMocks private StructuredQueryService service; - private int getExecutionResult(StructuredQuery query){ - return service.execute(query).block().size(); - } - @Nested class SingleInclusion { @@ -87,11 +84,12 @@ class SingleExpansion { @Test @DisplayName("execute: returns 1") void execute() { - var query = query(incl(PATIENT)); + var query = query(incl(PATIENT_POP)); + + var result = service.execute(query); - var result = getExecutionResult(query); + StepVerifier.create(result).expectNext(PATIENT_POP).verifyComplete(); - assertThat(result).isOne(); } @Test @@ -112,21 +110,23 @@ class MultipleExpansion { @Test @DisplayName("execute: multiple patients → returns 2") void execute_MultiplePatients() { - var query = query(inclExpand(PATIENT_1, PATIENT_2)); + var query = query(inclExpand(PATIENT_1_POP, PATIENT_2_POP)); - var result = getExecutionResult(query); + var result = service.execute(query); - assertThat(result).isEqualTo(2); + StepVerifier.create(result).expectNext(PATIENT_1_POP.union(PATIENT_2_POP)).verifyComplete(); } @Test @DisplayName("execute: same patient → returns 1") void execute_SamePatient() { - var query = query(inclExpand(PATIENT, PATIENT)); + var query = query(inclExpand(PATIENT_POP, PATIENT_POP)); + + var result = service.execute(query); + + StepVerifier.create(result).expectNext(PATIENT_POP).verifyComplete(); - var result = getExecutionResult(query); - assertThat(result).isOne(); } @Test @@ -146,23 +146,23 @@ void translate() { class ConjunctionInclusion { @Test - @DisplayName("execute: multiple patients → returns 0") + @DisplayName("execute: multiple patients → returns empty population") void execute_MultiplePatients() { - var query = query(inclAnd(PATIENT_1, PATIENT_2)); + var query = query(inclAnd(PATIENT_1_POP, PATIENT_2_POP)); - var result = getExecutionResult(query); + var result = service.execute(query); - assertThat(result).isZero(); + StepVerifier.create(result).expectNext(EMPTY_POP).verifyComplete(); } @Test - @DisplayName("execute: same patient → returns 1") + @DisplayName("execute: same patient → returns the Patient once") void execute_SamePatient() { - var query = query(inclAnd(PATIENT, PATIENT)); + var query = query(inclAnd(PATIENT_POP, PATIENT_POP)); - var result = getExecutionResult(query); + var result = service.execute(query); - assertThat(result).isOne(); + StepVerifier.create(result).expectNext(PATIENT_POP).verifyComplete(); } @Test @@ -181,23 +181,23 @@ void translate() { class DisjunctionInclusion { @Test - @DisplayName("execute: multiple patients → returns 2") + @DisplayName("execute: multiple patients → returns the two patients") void execute_MultiplePatients() { - var query = query(inclOr(PATIENT_1, PATIENT_2)); + var query = query(inclOr(PATIENT_1_POP, PATIENT_2_POP)); - var result = getExecutionResult(query); + var result = service.execute(query); - assertThat(result).isEqualTo(2); + StepVerifier.create(result).expectNext(PATIENT_1_POP.union(PATIENT_2_POP)).verifyComplete(); } @Test @DisplayName("execute: same patient → returns 1") void execute_SamePatient() { - var query = query(inclOr(PATIENT, PATIENT)); + var query = query(inclOr(PATIENT_POP, PATIENT_POP)); - var result = getExecutionResult(query); + var result = service.execute(query); - assertThat(result).isOne(); + StepVerifier.create(result).expectNext(PATIENT_POP).verifyComplete(); } @Test @@ -216,21 +216,23 @@ void translate() { class SingleInclusionAndExclusion { @Test - @DisplayName("execute: PATIENT is not excluded → returns 1") + @DisplayName("execute: PATIENT is not excluded → returns patient") void execute_PatientNotExcluded() { - var query = query(incl(PATIENT), excl(PATIENT_1)); - var result = getExecutionResult(query); - assertThat(result).isOne(); + var query = query(incl(PATIENT_POP), excl(PATIENT_1_POP)); + + var result = service.execute(query); + + StepVerifier.create(result).expectNext(PATIENT_POP).verifyComplete(); } @Test - @DisplayName("execute: PATIENT is excluded → returns 0") + @DisplayName("execute: PATIENT is excluded → returns empty Population") void execute_PatientExcluded() { - var query = query(incl(PATIENT), excl(PATIENT)); + var query = query(incl(PATIENT_POP), excl(PATIENT_POP)); - var result = getExecutionResult(query); + var result = service.execute(query); - assertThat(result).isZero(); + StepVerifier.create(result).expectNext(EMPTY_POP).verifyComplete(); } @Test @@ -250,23 +252,24 @@ void translate() { class SingleInclusionAndDisjunctionExclusion { @Test - @DisplayName("execute: PATIENT is not excluded → returns 1") + @DisplayName("execute: PATIENT is not excluded → returns PATIENT") void execute_PatientNotExcluded() { - var query = query(incl(PATIENT), exclOr(PATIENT_1, PATIENT_2)); + var query = query(incl(PATIENT_POP), exclOr(PATIENT_1_POP, PATIENT_2_POP)); + + var result = service.execute(query); - var result = getExecutionResult(query); + StepVerifier.create(result).expectNext(PATIENT_POP).verifyComplete(); - assertThat(result).isOne(); } @Test - @DisplayName("execute: PATIENT is excluded → returns 0") + @DisplayName("execute: PATIENT is excluded → returns empty Population") void execute_PatientExcluded() { - var query = query(incl(PATIENT), exclOr(PATIENT, PATIENT_1)); + var query = query(incl(PATIENT_POP), exclOr(PATIENT_POP, PATIENT_1_POP)); - var result = getExecutionResult(query); + var result = service.execute(query); - assertThat(result).isZero(); + StepVerifier.create(result).expectNext(EMPTY_POP).verifyComplete(); } @Test @@ -287,23 +290,23 @@ void translate() { class SingleInclusionAndConjunctionExclusion { @Test - @DisplayName("execute: PATIENT is not excluded → returns 1") + @DisplayName("execute: PATIENT is not excluded → returns PATIENT") void execute_PatientNotExcluded() { - var query = query(incl(PATIENT), exclAnd(PATIENT, PATIENT_1)); + var query = query(incl(PATIENT_POP), exclAnd(PATIENT_POP, PATIENT_1_POP)); - var result = getExecutionResult(query); + var result = service.execute(query); - assertThat(result).isOne(); + StepVerifier.create(result).expectNext(PATIENT_POP).verifyComplete(); } @Test - @DisplayName("execute: PATIENT is excluded → returns 0") + @DisplayName("execute: PATIENT is excluded → returns empty Population") void execute_PatientExcluded() { - var query = query(incl(PATIENT), exclAnd(PATIENT, PATIENT)); + var query = query(incl(PATIENT_POP), exclAnd(PATIENT_POP, PATIENT_POP)); - var result = getExecutionResult(query); + var result = service.execute(query); - assertThat(result).isZero(); + StepVerifier.create(result).expectNext(EMPTY_POP).verifyComplete(); } @Test diff --git a/src/test/resources/de/medizininformatikinitiative/flare/GeneratedBundle.json b/src/test/resources/de/medizininformatikinitiative/flare/GeneratedBundle.json index fac19a2d..be6e47b3 100644 --- a/src/test/resources/de/medizininformatikinitiative/flare/GeneratedBundle.json +++ b/src/test/resources/de/medizininformatikinitiative/flare/GeneratedBundle.json @@ -47,7 +47,7 @@ ] }, "subject": { - "reference": "Patient/062d64e5-e4e5-4c89-8fa7-a47ae3b2b95c", + "reference": "Patient/id-pat-bloodpressure-test", "display": "Alexia Wachtel" }, "effectiveDateTime": "2010-08-05T07:16:58+02:00", @@ -176,7 +176,7 @@ }, { "resource": { "resourceType": "Patient", - "id": "7ca6f273-82f1-4fd5-9b0d-e9e4f957fe42", + "id": "id-pat-diag-I08.0", "meta": { "profile": [ "https://www.medizininformatik-initiative.de/fhir/core/modul-person/StructureDefinition/Patient" ] }, @@ -211,7 +211,7 @@ }, "request": { "method": "PUT", - "url": "Patient/7ca6f273-82f1-4fd5-9b0d-e9e4f957fe42" + "url": "Patient/id-pat-diag-I08.0" } }, { "resource": { @@ -228,7 +228,7 @@ } ] }, "subject": { - "reference": "Patient/7ca6f273-82f1-4fd5-9b0d-e9e4f957fe42", + "reference": "Patient/id-pat-diag-I08.0", "display": "Baran Stolle" }, "recordedDate": "2012-12-11T00:26:30+01:00" @@ -255,7 +255,7 @@ }, { "resource": { "resourceType": "Patient", - "id": "8da7a09c-c2b4-4b8f-9144-0ecf407f2ed1", + "id": "id-pat-consent-test", "meta": { "profile": [ "https://www.medizininformatik-initiative.de/fhir/core/modul-person/StructureDefinition/Patient" ] }, @@ -290,7 +290,7 @@ }, "request": { "method": "PUT", - "url": "Patient/8da7a09c-c2b4-4b8f-9144-0ecf407f2ed1" + "url": "Patient/id-pat-consent-test" } }, { "resource": { @@ -313,7 +313,7 @@ } ] } ], "patient": { - "reference": "Patient/8da7a09c-c2b4-4b8f-9144-0ecf407f2ed1", + "reference": "Patient/id-pat-consent-test", "display": "Ina Bielert" }, "dateTime": "2003-07-09T00:45:44+02:00", @@ -663,7 +663,7 @@ }, { "resource": { "resourceType": "Patient", - "id": "062d64e5-e4e5-4c89-8fa7-a47ae3b2b95c", + "id": "id-pat-bloodpressure-test", "meta": { "profile": [ "https://www.medizininformatik-initiative.de/fhir/core/modul-person/StructureDefinition/Patient" ] }, @@ -698,7 +698,7 @@ }, "request": { "method": "PUT", - "url": "Patient/062d64e5-e4e5-4c89-8fa7-a47ae3b2b95c" + "url": "Patient/id-pat-bloodpressure-test" } }, { "resource": { @@ -750,7 +750,7 @@ } ] }, "subject": { - "reference": "Patient/062d64e5-e4e5-4c89-8fa7-a47ae3b2b95c", + "reference": "Patient/id-pat-bloodpressure-test", "display": "Alexia Wachtel" }, "effectiveDateTime": "2010-08-05T07:16:58+02:00", diff --git a/src/test/resources/de/medizininformatikinitiative/flare/service/referencedCriteria/specimen-diag-testbundle.json b/src/test/resources/de/medizininformatikinitiative/flare/service/referencedCriteria/specimen-diag-testbundle.json index 0ce4d36a..864eaaa0 100644 --- a/src/test/resources/de/medizininformatikinitiative/flare/service/referencedCriteria/specimen-diag-testbundle.json +++ b/src/test/resources/de/medizininformatikinitiative/flare/service/referencedCriteria/specimen-diag-testbundle.json @@ -3,10 +3,10 @@ "type": "transaction", "entry": [ { - "fullUrl": "Patient/SPEC-DIAB-TEST-P-1", + "fullUrl": "Patient/id-pat-diab-test-1", "resource": { "resourceType": "Patient", - "id": "SPEC-DIAB-TEST-P-1", + "id": "id-pat-diab-test-1", "meta": { "profile": [ "https://www.medizininformatik-initiative.de/fhir/core/modul-person/StructureDefinition/Patient" @@ -24,7 +24,7 @@ ] }, "system": "https://test/pid", - "value": "SPEC-DIAB-TEST-P-1" + "value": "id-pat-diab-test-1" } ], "gender": "male", @@ -32,7 +32,7 @@ }, "request": { "method": "PUT", - "url": "Patient/SPEC-DIAB-TEST-P-1" + "url": "Patient/id-pat-diab-test-1" } }, { @@ -61,7 +61,7 @@ "text": "Idiopathische Hypotonie" }, "subject": { - "reference": "Patient/SPEC-DIAB-TEST-P-1" + "reference": "Patient/id-pat-diab-test-1" }, "recordedDate": "2021-01-01T00:00:00+01:00" }, @@ -99,7 +99,7 @@ "text": "Serum Specimen" }, "subject": { - "reference": "Patient/SPEC-DIAB-TEST-P-1" + "reference": "Patient/id-pat-diab-test-1" } }, "request": { From 371ef1f61e00d1e533884c8ce4f8a2e4acda5217 Mon Sep 17 00:00:00 2001 From: Bastian Schaffer Date: Mon, 2 Sep 2024 15:16:58 +0200 Subject: [PATCH 08/43] Update Mapping Tree Fixes: #191 --- .../synthea-test-mapping/mapping.zip | Bin 1994 -> 1768 bytes .gitignore | 1 + pom.xml | 5 +- .../flare/Util.java | 13 +- .../flare/model/mapping/MappingContext.java | 8 +- .../flare/model/mapping/MappingTreeBase.java | 22 ++ .../model/mapping/MappingTreeModuleEntry.java | 16 + .../model/mapping/MappingTreeModuleRoot.java | 34 +++ .../flare/model/mapping/TermCodeNode.java | 99 ------ .../flare/service/StructuredQueryService.java | 2 - .../model/mapping/MappingContextTest.java | 34 ++- .../model/mapping/MappingTreeBaseTest.java | 289 ++++++++++++++++++ .../flare/model/mapping/TermCodeNodeTest.java | 192 ------------ .../flare/rest/QueryControllerTest.java | 3 +- .../service/StructuredQueryServiceIT.java | 9 +- .../service/StructuredQueryServiceTest.java | 1 - .../tree-bloodPressure.json | 27 +- .../testCases/returningOnePatient/1-age.json | 33 +- 18 files changed, 452 insertions(+), 336 deletions(-) create mode 100644 src/main/java/de/medizininformatikinitiative/flare/model/mapping/MappingTreeBase.java create mode 100644 src/main/java/de/medizininformatikinitiative/flare/model/mapping/MappingTreeModuleEntry.java create mode 100644 src/main/java/de/medizininformatikinitiative/flare/model/mapping/MappingTreeModuleRoot.java delete mode 100644 src/main/java/de/medizininformatikinitiative/flare/model/mapping/TermCodeNode.java create mode 100644 src/test/java/de/medizininformatikinitiative/flare/model/mapping/MappingTreeBaseTest.java delete mode 100644 src/test/java/de/medizininformatikinitiative/flare/model/mapping/TermCodeNodeTest.java diff --git a/.github/integration-test/synthea-test-mapping/mapping.zip b/.github/integration-test/synthea-test-mapping/mapping.zip index fdeb51490e3acd21181010748448d1adb690bacb..415497d9ffa57ba640ed76d0756ee0c5d3a97f3c 100644 GIT binary patch literal 1768 zcmWIWW@Zs#00FaFrAROXN^meJFytl{6lCV5>xYK$GO*7)a5ZfQ5T^ifX$3a}BMV3+ zmr+lmgX^C z2bW7<6kSvJ_ZjD((<%2}9WwCwd*Sn@XxUj`qNGOO}wzG{_d`WOAHEfcg0vf*dh?*{QO^k@Ao{t zPrD>S=k!Ocd;eBDU{__m$PeZGC$?V>H>_G#@quSW(o@aQEle@lSJv8>8^tlUUDG%> zE5Yu9X2MzNXNTuVWt8QAuVYrreBr8S#93`LGcuOhc*5kDCx2c%c;UQE_w@}kQs#+& zPkG3(XW9Xk-p4oI`#<1L;5qTG>G8%z1*tEOD*Vyudc5F&=*IJF7&m3<_v~7drOQ>G z`epC2%_<%?`Y)oFF46Uxu54R$qLwlgXYkI?VpodH z?M>b5Esper7iDViIFi_Q#^K?nkS8UcrLOxWcf{Lm&hGvg#p4Fcveb>C^A4Ro_vZGBB|j|xSp66BX<^ya zu)pc@?>A1pn;q@jGj?3*To~4$`tCKmxy)jlAIY1oZ@t^Akk$MC``4G!)9rsQ;9s11 z`&(?K!LppF`m6Jk`U=ftR~}Jy+dRAZe#(2N7vZO$-jlpNCGUk>e9_-WMiR-B40^79 zxvnVrOlMJ~T**>{wwD*DvM@|y^%Y@X5LtD0$^xdxGWMd+B2pE@7g(IX#LE=oG{dwu@9 z_WnmlvJXa=U&zgTb4|Ba_)^h~+02e@;O8@`> literal 1994 zcmWIWW@h1H00HZ4+i)-gN^mmBFy!Zz_G8xO(<7($dnvlv7?*!D%?RFa&-G`b`KNck6CI}q+f?xRd`>!M zqRn_ctMu{twLViC7H*CDU^U}=iB!ekwwngYVz2Jb-h02nQ>ZGi!{<<+&$DwO2Xs?X zs*1`=48B~RcQiY~C+PfQ*~l*}ao!1&0=)jdW7)@Wtl^CwtDN@deL8z3TIRb=VY|D& ztM;AFgwo=~5Z24mGrxzsUC*}ZZNBljXp_gWSGUw2+q zHC{03+BJP`aw<=tcao&to#!V`SK8RWs^8Sf#(C7uBCGdi`qW&HlM}cWHE0`sw40$ZgVL2J^w&ra zslKQ!TVm9MJ&G0o1;2Z0sB<${osDZsUE1rB|0DR1^K`g_Q%>^?pWBR)VDe_CD=!33h(I5McV}6D5%TONvrc5$S)O`TrCU#z_Bn^Jg0f9Jv2E zJi?~*mQxQi+plX&XGN{Nt(E%9`zkxT%my~IPwpWhU+d>9o^78JuvU~``YgB5zRmC7 z9iRLD$Yl-&^^=hky-v+7F37*nSdle9Y|%=cbfu+Xn6-gV!ZtGI&9PnG*jV$wL)n>0(aAT8BtSx8+`+?V<0 z(ratiyzTJ|+z^=Uese{4%-q`HlWc zS38~>@3Ze4Z{1nZr8a9>!ne=weigkr>b%6XSMiAL1u3n?nJtFjekFeCjXHne=A@5P zKIRl0eSVbj*oyhuuayjEHHB6ehFVTEJpY$(L#YQ!E?-=G!>>f2=A_~sQf0Tb$iYcjYDN0III@^)Vd@!3cC%xh$dPTc7O%Ux)#%og2dq~+BeQil zoNp&2x*Ketw5&&G#j;n*!A=Xs{o)Un9LzjI7Cpw>b0Ft9LC53cmO2Tce?A zt>KGfCa2&2IXcPl{{o8xVI0SV!%w`C*dS0T(I4n8vj0evN+-iAk^Qr8$7U|-WwKwl z?s@)^QfDzIsnrToJ&O)+=IdVn>Tfs0UV}sTB+6A~G=8J;66@k%4OMhGxkb z!I{%rS+=tVs{Yi^y!G69aqWZG*UnEr{4MsD&f7KS%eNKE*Y%gnm{iIA1*TUj+{Z)h4Wv+5|0$A{&QUmmnMGjBFgDW&s+DwN?SS5?8$fF&0>vF)V59 z!)7d&S_WtjmKp})d|de##U5s)(11A~W)D(s2HJxqCkJ@5vH=~+zygG2K!@9bc>psv B3Pk__ diff --git a/.gitignore b/.gitignore index 505763e4..62a060e8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .mvn +.idea/ cache target /ontology/mapping.zip diff --git a/pom.xml b/pom.xml index 16f36097..c78ce822 100644 --- a/pom.xml +++ b/pom.xml @@ -21,7 +21,7 @@ 17 1.19.8 - 2.2.0 + 3.0.0-test.1 @@ -206,9 +206,10 @@ wget - https://github.com/medizininformatik-initiative/fhir-ontology-generator/raw/v${ontology.version}/example/mii_core_data_set/ontology/mapping.zip + https://github.com/medizininformatik-initiative/fhir-ontology-generator/raw/v${ontology.version}/example/fdpg-ontology/mapping.zip mapping.zip ontology/ + true diff --git a/src/main/java/de/medizininformatikinitiative/flare/Util.java b/src/main/java/de/medizininformatikinitiative/flare/Util.java index 6ca035c1..a1551424 100644 --- a/src/main/java/de/medizininformatikinitiative/flare/Util.java +++ b/src/main/java/de/medizininformatikinitiative/flare/Util.java @@ -3,7 +3,8 @@ import com.fasterxml.jackson.databind.ObjectMapper; import de.medizininformatikinitiative.flare.model.mapping.Mapping; import de.medizininformatikinitiative.flare.model.mapping.MappingContext; -import de.medizininformatikinitiative.flare.model.mapping.TermCodeNode; +import de.medizininformatikinitiative.flare.model.mapping.MappingTreeBase; +import de.medizininformatikinitiative.flare.model.mapping.MappingTreeModuleRoot; import de.medizininformatikinitiative.flare.model.sq.ContextualTermCode; import java.io.BufferedInputStream; @@ -79,11 +80,11 @@ static double durationSecondsSince(long startNanoTime) { static MappingContext flareMappingContext(Clock clock) throws Exception { var mapper = new ObjectMapper(); String ontologyZipFile = "ontology/mapping.zip"; - String mappingFile = "ontology/mapping/mapping_fhir.json"; - String conceptTreeFile = "ontology/mapping/mapping_tree.json"; + String mappingFile = "mapping/fhir/mapping_fhir.json"; + String conceptTreeFile = "mapping/mapping_tree.json"; Map mappings = null; - TermCodeNode conceptTree = null; + MappingTreeBase conceptTree = null; try (FileInputStream fis = new FileInputStream(ontologyZipFile); BufferedInputStream bis = new BufferedInputStream(fis); @@ -94,8 +95,8 @@ static MappingContext flareMappingContext(Clock clock) throws Exception { mappings = Arrays.stream(mapper.readValue(readZipEntryContent(zis), Mapping[].class)) .collect(Collectors.toMap(Mapping::key, identity())); } else if (ze.getName().equals(conceptTreeFile)) { - String treeString = readZipEntryContent(zis); - conceptTree = mapper.readValue(treeString, TermCodeNode.class); + conceptTree = new MappingTreeBase( + Arrays.stream(mapper.readValue(readZipEntryContent(zis), MappingTreeModuleRoot[].class)).toList()); } } } diff --git a/src/main/java/de/medizininformatikinitiative/flare/model/mapping/MappingContext.java b/src/main/java/de/medizininformatikinitiative/flare/model/mapping/MappingContext.java index 7da06eb1..a9299429 100644 --- a/src/main/java/de/medizininformatikinitiative/flare/model/mapping/MappingContext.java +++ b/src/main/java/de/medizininformatikinitiative/flare/model/mapping/MappingContext.java @@ -21,10 +21,10 @@ public class MappingContext { private final Map mappings; - private final TermCodeNode conceptTree; + private final MappingTreeBase conceptTree; private final Clock clock; - private MappingContext(Map mappings, TermCodeNode conceptTree, Clock clock) { + private MappingContext(Map mappings, MappingTreeBase conceptTree, Clock clock) { this.mappings = Map.copyOf(mappings); this.conceptTree = requireNonNull(conceptTree); this.clock = requireNonNull(clock); @@ -37,7 +37,7 @@ private MappingContext(Map mappings, TermCodeNode c * @param conceptTree a tree of concepts to expand (can be null) * @return the mapping context */ - public static MappingContext of(Map mappings, TermCodeNode conceptTree) { + public static MappingContext of(Map mappings, MappingTreeBase conceptTree) { return new MappingContext(mappings, conceptTree, Clock.systemDefaultZone()); } @@ -49,7 +49,7 @@ public static MappingContext of(Map mappings, TermC * @param clock a clock that is used for time-related calculations * @return the mapping context */ - public static MappingContext of(Map mappings, TermCodeNode conceptTree, Clock clock) { + public static MappingContext of(Map mappings, MappingTreeBase conceptTree, Clock clock) { return new MappingContext(mappings, conceptTree, clock); } diff --git a/src/main/java/de/medizininformatikinitiative/flare/model/mapping/MappingTreeBase.java b/src/main/java/de/medizininformatikinitiative/flare/model/mapping/MappingTreeBase.java new file mode 100644 index 00000000..a96ac4d6 --- /dev/null +++ b/src/main/java/de/medizininformatikinitiative/flare/model/mapping/MappingTreeBase.java @@ -0,0 +1,22 @@ +package de.medizininformatikinitiative.flare.model.mapping; + +import de.medizininformatikinitiative.flare.model.sq.ContextualTermCode; + +import java.util.List; +import java.util.stream.Stream; + +public record MappingTreeBase(List moduleRoots) { + + public Stream expand(ContextualTermCode termCode) { + var key = termCode.termCode().code(); + + return moduleRoots.stream().flatMap(moduleRoot -> + isModuleMatching(termCode, moduleRoot) ? moduleRoot.expand(key) : Stream.empty()); + } + + private boolean isModuleMatching(ContextualTermCode termCode, MappingTreeModuleRoot moduleRoot) { + return termCode.context().equals(moduleRoot.context()) && + moduleRoot.system().equals(termCode.termCode().system()) && + moduleRoot.entries().containsKey(termCode.termCode().code()); + } +} diff --git a/src/main/java/de/medizininformatikinitiative/flare/model/mapping/MappingTreeModuleEntry.java b/src/main/java/de/medizininformatikinitiative/flare/model/mapping/MappingTreeModuleEntry.java new file mode 100644 index 00000000..465a9d14 --- /dev/null +++ b/src/main/java/de/medizininformatikinitiative/flare/model/mapping/MappingTreeModuleEntry.java @@ -0,0 +1,16 @@ +package de.medizininformatikinitiative.flare.model.mapping; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.List; + +@JsonIgnoreProperties(ignoreUnknown = true) +record MappingTreeModuleEntry(String key, List children) { + @JsonCreator + static MappingTreeModuleEntry fromJson(@JsonProperty("key") String key, + @JsonProperty("children") List children) { + return new MappingTreeModuleEntry(key, children); + } +} diff --git a/src/main/java/de/medizininformatikinitiative/flare/model/mapping/MappingTreeModuleRoot.java b/src/main/java/de/medizininformatikinitiative/flare/model/mapping/MappingTreeModuleRoot.java new file mode 100644 index 00000000..68a6c6aa --- /dev/null +++ b/src/main/java/de/medizininformatikinitiative/flare/model/mapping/MappingTreeModuleRoot.java @@ -0,0 +1,34 @@ +package de.medizininformatikinitiative.flare.model.mapping; + + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import de.medizininformatikinitiative.flare.model.sq.ContextualTermCode; +import de.medizininformatikinitiative.flare.model.sq.TermCode; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static java.util.function.Function.identity; + +@JsonIgnoreProperties(ignoreUnknown = true) +public record MappingTreeModuleRoot(TermCode context, String system, Map entries) { + @JsonCreator + static MappingTreeModuleRoot fromJson(@JsonProperty("context") TermCode context, + @JsonProperty("system") String system, + @JsonProperty("entries") List entries) { + return new MappingTreeModuleRoot( + context, + system, + entries.stream().collect(Collectors.toMap(MappingTreeModuleEntry::key, identity()))); + } + + public Stream expand(String key) { + var newTermCode = new ContextualTermCode(context, new TermCode(system, key, "")); + + return Stream.concat(Stream.of(newTermCode), entries.get(key).children().stream().flatMap(this::expand)); + } +} diff --git a/src/main/java/de/medizininformatikinitiative/flare/model/mapping/TermCodeNode.java b/src/main/java/de/medizininformatikinitiative/flare/model/mapping/TermCodeNode.java deleted file mode 100644 index bd8bfd3a..00000000 --- a/src/main/java/de/medizininformatikinitiative/flare/model/mapping/TermCodeNode.java +++ /dev/null @@ -1,99 +0,0 @@ -package de.medizininformatikinitiative.flare.model.mapping; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonProperty; -import de.medizininformatikinitiative.flare.model.sq.ContextualTermCode; -import de.medizininformatikinitiative.flare.model.sq.TermCode; - -import java.util.List; -import java.util.stream.Stream; - -import static java.util.Objects.requireNonNull; - -/** - * @author Alexander Kiel - */ -@JsonIgnoreProperties(ignoreUnknown = true) -public sealed interface TermCodeNode permits TermCodeNode.Abstract, TermCodeNode.Normal { - - static TermCodeNode createAbstract(ContextualTermCode termCode, TermCodeNode... children) { - return new TermCodeNode.Abstract(termCode, children == null ? List.of() : List.of(children)); - } - - static TermCodeNode createNormal(ContextualTermCode termCode, TermCodeNode... children) { - return new TermCodeNode.Normal(termCode, children == null ? List.of() : List.of(children)); - } - - @JsonCreator - static TermCodeNode fromJson(@JsonProperty("context") TermCode context, - @JsonProperty("termCode") TermCode termCode, - @JsonProperty("abstract") boolean abstractJson, - @JsonProperty("children") TermCodeNode... children) { - var contextualTermCode = ContextualTermCode.of(requireNonNull(context, "missing JSON property: context"), - requireNonNull(termCode, "missing JSON property: termCode")); - return abstractJson - ? new Abstract(contextualTermCode, children == null ? List.of() : List.of(children)) - : new Normal(contextualTermCode, children == null ? List.of() : List.of(children)); - } - - Stream expand(ContextualTermCode termCode); - - Stream leafConcepts(); - - ContextualTermCode contextualTermCode(); - - List children(); - - record Abstract(ContextualTermCode contextualTermCode, List children) implements TermCodeNode { - - public Abstract { - requireNonNull(contextualTermCode); - children = List.copyOf(children); - } - - @Override - public Stream expand(ContextualTermCode termCode) { - if (requireNonNull(termCode).equals(this.contextualTermCode)) { - return leafConcepts(); - } else if (children.isEmpty()) { - return Stream.of(); - } else { - return children.stream().flatMap(n -> n.expand(termCode)); - } - } - - @Override - public Stream leafConcepts() { - return children.isEmpty() ? Stream.of() : children.stream().flatMap(TermCodeNode::leafConcepts); - } - } - - record Normal(ContextualTermCode contextualTermCode, List children) implements TermCodeNode { - - public Normal { - requireNonNull(contextualTermCode); - children = List.copyOf(children); - } - - @Override - public Stream expand(ContextualTermCode termCode) { - if (requireNonNull(termCode).equals(this.contextualTermCode)) { - return leafConcepts(); - } else if (children.isEmpty()) { - return Stream.of(); - } else { - return children.stream().flatMap(n -> n.expand(termCode)); - } - } - - @Override - public Stream leafConcepts() { - if (children.isEmpty()) { - return Stream.of(contextualTermCode); - } else { - return Stream.concat(Stream.of(contextualTermCode), children.stream().flatMap(TermCodeNode::leafConcepts)); - } - } - } -} diff --git a/src/main/java/de/medizininformatikinitiative/flare/service/StructuredQueryService.java b/src/main/java/de/medizininformatikinitiative/flare/service/StructuredQueryService.java index 237af6a1..035c6235 100644 --- a/src/main/java/de/medizininformatikinitiative/flare/service/StructuredQueryService.java +++ b/src/main/java/de/medizininformatikinitiative/flare/service/StructuredQueryService.java @@ -15,8 +15,6 @@ import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; -import java.util.Set; - import static de.medizininformatikinitiative.flare.model.translate.Operator.Name.UNION; import static java.util.Objects.requireNonNull; diff --git a/src/test/java/de/medizininformatikinitiative/flare/model/mapping/MappingContextTest.java b/src/test/java/de/medizininformatikinitiative/flare/model/mapping/MappingContextTest.java index c858f8c3..745111c7 100644 --- a/src/test/java/de/medizininformatikinitiative/flare/model/mapping/MappingContextTest.java +++ b/src/test/java/de/medizininformatikinitiative/flare/model/mapping/MappingContextTest.java @@ -5,6 +5,7 @@ import de.medizininformatikinitiative.flare.model.sq.TermCode; import org.junit.jupiter.api.Test; +import java.util.List; import java.util.Map; import static de.medizininformatikinitiative.flare.Assertions.assertThat; @@ -13,36 +14,45 @@ class MappingContextTest { static final TermCode CONTEXT = TermCode.of("context", "context", "context"); + static final String SYSTEM = "sys"; static final ContextualTermCode ROOT = ContextualTermCode.of(CONTEXT, TermCode.of("root", "root", "root")); - static final ContextualTermCode C1 = ContextualTermCode.of(CONTEXT, TermCode.of("foo", "c1", "c1")); - static final ContextualTermCode C11 = ContextualTermCode.of(CONTEXT, TermCode.of("foo", "c11", "c11")); - static final ContextualTermCode C12 = ContextualTermCode.of(CONTEXT, TermCode.of("foo", "c12", "c12")); + static final String C1 = "c1"; + static final String C2 = "c2"; + + private ContextualTermCode contextualTermCodeOf(String code) { + return new ContextualTermCode(MappingContextTest.CONTEXT, new TermCode(MappingContextTest.SYSTEM, code, "display")); + } @Test void expandConcept_ConceptNotExpandable() { - var context = MappingContext.of(Map.of(), TermCodeNode.createNormal(ROOT)); + var context = MappingContext.of(Map.of(), new MappingTreeBase(List.of(new MappingTreeModuleRoot(CONTEXT, SYSTEM, Map.of())))); - var result = context.expandConcept(ContextualConcept.of(C1)); + var result = context.expandConcept(ContextualConcept.of(contextualTermCodeOf(C1))); assertThat(result).isLeftInstanceOf(ContextualConceptNotExpandableException.class); } @Test void expandConcept_OneConcept() { - var context = MappingContext.of(Map.of(), TermCodeNode.createNormal(ROOT, TermCodeNode.createNormal(C1))); + var context = MappingContext.of(Map.of(), new MappingTreeBase(List.of( + new MappingTreeModuleRoot(CONTEXT, SYSTEM, Map.of(C1, new MappingTreeModuleEntry(C1, List.of())))))); - var result = context.expandConcept(ContextualConcept.of(C1)); + var result = context.expandConcept(ContextualConcept.of(contextualTermCodeOf(C1))); - assertThat(result).isRightSatisfying(r -> assertThat(r).containsExactly(C1)); + assertThat(result).isRightSatisfying(r -> assertThat(r).containsExactly(contextualTermCodeOf(C1))); } @Test void expandConcept_TwoConcepts() { - var context = MappingContext.of(Map.of(), TermCodeNode.createAbstract(C1, TermCodeNode.createNormal(C11), - TermCodeNode.createNormal(C12))); + var context = MappingContext.of(Map.of(), new MappingTreeBase(List.of( + new MappingTreeModuleRoot(CONTEXT, SYSTEM, Map.of(C1, new MappingTreeModuleEntry(C1, List.of(C2)), + C2, new MappingTreeModuleEntry(C2, List.of())))))); - var result = context.expandConcept(ContextualConcept.of(C1)); + var result = context.expandConcept(ContextualConcept.of(contextualTermCodeOf(C1))); - assertThat(result).isRightSatisfying(r -> assertThat(r).containsExactly(C11, C12)); + assertThat(result).isRightSatisfying(r -> { + assertThat(r.get(0)).isEqualTo(contextualTermCodeOf(C1)); + assertThat(r.get(1)).isEqualTo(contextualTermCodeOf(C2)); + }); } } diff --git a/src/test/java/de/medizininformatikinitiative/flare/model/mapping/MappingTreeBaseTest.java b/src/test/java/de/medizininformatikinitiative/flare/model/mapping/MappingTreeBaseTest.java new file mode 100644 index 00000000..b47c0b2d --- /dev/null +++ b/src/test/java/de/medizininformatikinitiative/flare/model/mapping/MappingTreeBaseTest.java @@ -0,0 +1,289 @@ +package de.medizininformatikinitiative.flare.model.mapping; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import de.medizininformatikinitiative.flare.model.sq.ContextualTermCode; +import de.medizininformatikinitiative.flare.model.sq.TermCode; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class MappingTreeBaseTest { + + static final TermCode CONTEXT_1 = TermCode.of("context1", "context1", "context1"); + static final TermCode CONTEXT_2 = TermCode.of("context2", "context2", "context2"); + static final String SYSTEM_1 = "sys1"; + static final String SYSTEM_2 = "sys2"; + static final String C1 = "c1"; + static final String C2 = "c2"; + static final String C3 = "c3"; + static final String C4 = "c4"; + static final String C5 = "c5"; + + + private ContextualTermCode contextualTermCodeOf(TermCode context, String system, String code) { + return new ContextualTermCode(context, new TermCode(system, code, "display")); + } + + @Test + void expand_empty() { + var base = new MappingTreeBase(List.of(new MappingTreeModuleRoot(CONTEXT_1, SYSTEM_1, Map.of()))); + + var result = base.expand(contextualTermCodeOf(CONTEXT_1, SYSTEM_1, C1)).toList(); + + assertThat(result).isEmpty(); + } + + @Test + void expand_noMatch_differentCode() { + var base = new MappingTreeBase(List.of(new MappingTreeModuleRoot(CONTEXT_1, SYSTEM_1, + Map.of(C1, new MappingTreeModuleEntry(C1, List.of()))))); + + var result = base.expand(contextualTermCodeOf(CONTEXT_1, SYSTEM_1, C2)).toList(); + + assertThat(result).isEmpty(); + } + + @Test + void expand_noMatch_differentContext() { + var base = new MappingTreeBase(List.of(new MappingTreeModuleRoot(CONTEXT_1, SYSTEM_1, + Map.of(C1, new MappingTreeModuleEntry(C1, List.of()))))); + + var result = base.expand( + contextualTermCodeOf(new TermCode("", "different-context", ""), SYSTEM_1, C2)) + .toList(); + + assertThat(result).isEmpty(); + } + + @Test + void expand_noMatch_differentSystem() { + var base = new MappingTreeBase(List.of(new MappingTreeModuleRoot(CONTEXT_1, SYSTEM_1, + Map.of(C1, new MappingTreeModuleEntry(C1, List.of()))))); + + var result = base.expand(contextualTermCodeOf(CONTEXT_1, "system2", C2)).toList(); + + assertThat(result).isEmpty(); + } + + @Test + void expand_oneModule_twoEntries_withoutChildren() { + var base = new MappingTreeBase(List.of(new MappingTreeModuleRoot(CONTEXT_1, SYSTEM_1, + Map.of(C1, new MappingTreeModuleEntry(C1, List.of()), + C2, new MappingTreeModuleEntry(C2, List.of()))))); + + var result = base.expand(contextualTermCodeOf(CONTEXT_1, SYSTEM_1, C1)).toList(); + + assertThat(result).containsExactlyInAnyOrder(contextualTermCodeOf(CONTEXT_1, SYSTEM_1, C1)); + } + + @Test + void expand_twoModules_sameContext_differentSystem_withoutChildren() { + var base = new MappingTreeBase(List.of( + new MappingTreeModuleRoot(CONTEXT_1, SYSTEM_1, + Map.of(C1, new MappingTreeModuleEntry(C1, List.of()), + C2, new MappingTreeModuleEntry(C2, List.of()))), + new MappingTreeModuleRoot(CONTEXT_1, SYSTEM_2, + Map.of(C3, new MappingTreeModuleEntry(C3, List.of()), + C4, new MappingTreeModuleEntry(C4, List.of()))))); + + var result = base.expand(contextualTermCodeOf(CONTEXT_1, SYSTEM_1, C2)).toList(); + + assertThat(result).containsExactlyInAnyOrder(contextualTermCodeOf(CONTEXT_1, SYSTEM_1, C2)); + } + + @Test + void expand_twoModules_sameSystem_differentContext_withoutChildren() { + var base = new MappingTreeBase(List.of( + new MappingTreeModuleRoot(CONTEXT_1, SYSTEM_1, + Map.of(C1, new MappingTreeModuleEntry(C1, List.of()), + C2, new MappingTreeModuleEntry(C2, List.of()))), + new MappingTreeModuleRoot(CONTEXT_2, SYSTEM_1, + Map.of(C3, new MappingTreeModuleEntry(C3, List.of()), + C4, new MappingTreeModuleEntry(C4, List.of()))))); + + var result = base.expand(contextualTermCodeOf(CONTEXT_1, SYSTEM_1, C2)).toList(); + + assertThat(result).containsExactlyInAnyOrder(contextualTermCodeOf(CONTEXT_1, SYSTEM_1, C2)); + } + + @Test + void expand_oneChild_withNoReference() { + var base = new MappingTreeBase(List.of( + new MappingTreeModuleRoot(CONTEXT_1, SYSTEM_1, + Map.of(C1, new MappingTreeModuleEntry(C1, List.of(C2)))))); + + assertThatThrownBy(() -> base.expand(contextualTermCodeOf(CONTEXT_1, SYSTEM_1, C1)).toList()) + .isInstanceOf(NullPointerException.class); + } + + @Test + void expand_oneChild_onFirstLayer() { + var base = new MappingTreeBase(List.of( + new MappingTreeModuleRoot(CONTEXT_1, SYSTEM_1, + Map.of(C1, new MappingTreeModuleEntry(C1, List.of(C2)), + C2, new MappingTreeModuleEntry(C2, List.of()))))); + + var result = base.expand(contextualTermCodeOf(CONTEXT_1, SYSTEM_1, C1)).toList(); + + assertThat(result).containsExactlyInAnyOrder( + contextualTermCodeOf(CONTEXT_1, SYSTEM_1, C1), + contextualTermCodeOf(CONTEXT_1, SYSTEM_1, C2)); + } + + @Test + void expand_twoChildren_onFirstLayer() { + var base = new MappingTreeBase(List.of( + new MappingTreeModuleRoot(CONTEXT_1, SYSTEM_1, + Map.of(C1, new MappingTreeModuleEntry(C1, List.of(C2, C3)), + C2, new MappingTreeModuleEntry(C2, List.of()), + C3, new MappingTreeModuleEntry(C3, List.of()))))); + + var result = base.expand(contextualTermCodeOf(CONTEXT_1, SYSTEM_1, C1)).toList(); + + assertThat(result).containsExactlyInAnyOrder( + contextualTermCodeOf(CONTEXT_1, SYSTEM_1, C1), + contextualTermCodeOf(CONTEXT_1, SYSTEM_1, C2), + contextualTermCodeOf(CONTEXT_1, SYSTEM_1, C3)); + } + + @Test + void expand_twoChildren_onFirstAndSecondLayer() { + var base = new MappingTreeBase(List.of( + new MappingTreeModuleRoot(CONTEXT_1, SYSTEM_1, + Map.of(C1, new MappingTreeModuleEntry(C1, List.of(C2, C3)), + C2, new MappingTreeModuleEntry(C2, List.of(C4)), + C3, new MappingTreeModuleEntry(C3, List.of(C5)), + C4, new MappingTreeModuleEntry(C4, List.of()), + C5, new MappingTreeModuleEntry(C5, List.of()))))); + + var result = base.expand(contextualTermCodeOf(CONTEXT_1, SYSTEM_1, C1)).toList(); + + assertThat(result).containsExactlyInAnyOrder( + contextualTermCodeOf(CONTEXT_1, SYSTEM_1, C1), + contextualTermCodeOf(CONTEXT_1, SYSTEM_1, C2), + contextualTermCodeOf(CONTEXT_1, SYSTEM_1, C3), + contextualTermCodeOf(CONTEXT_1, SYSTEM_1, C4), + contextualTermCodeOf(CONTEXT_1, SYSTEM_1, C5)); + } + + @Test + void expand_oneChild_onThreeLayers() { + var base = new MappingTreeBase(List.of( + new MappingTreeModuleRoot(CONTEXT_1, SYSTEM_1, + Map.of(C1, new MappingTreeModuleEntry(C1, List.of(C2)), + C2, new MappingTreeModuleEntry(C2, List.of(C3)), + C3, new MappingTreeModuleEntry(C3, List.of(C4, C5)), + C4, new MappingTreeModuleEntry(C4, List.of()), + C5, new MappingTreeModuleEntry(C5, List.of()))))); + + var result = base.expand(contextualTermCodeOf(CONTEXT_1, SYSTEM_1, C1)).toList(); + + assertThat(result).containsExactlyInAnyOrder( + contextualTermCodeOf(CONTEXT_1, SYSTEM_1, C1), + contextualTermCodeOf(CONTEXT_1, SYSTEM_1, C2), + contextualTermCodeOf(CONTEXT_1, SYSTEM_1, C3), + contextualTermCodeOf(CONTEXT_1, SYSTEM_1, C4), + contextualTermCodeOf(CONTEXT_1, SYSTEM_1, C5)); + } + + @Test + void fromJson() throws Exception { + var base = parse(""" + [ + { + "entries": [ + { + "key": "C1", + "parents": [], + "children": [] + } + ], + "context": { + "system": "sys1", + "code": "code1", + "display": "display" + }, + "system": "module-system" + } + ] + """); + + assertThat(base.moduleRoots().get(0).context()) + .isEqualTo(new TermCode("sys1", "code1", "display")); + assertThat(base.moduleRoots().get(0).system()).isEqualTo("module-system"); + assertThat(base.moduleRoots().get(0).entries().get("C1")).isNotNull(); + } + + @Test + void fromJson_AdditionalPropertyIsIgnored() throws Exception { + var base = parse(""" + [ + { + "foo-133831": "bar-133841", + "entries": [ + { + "key": "C1", + "parents": [], + "children": [] + } + ], + "context": { + "system": "sys1", + "code": "code1", + "display": "display" + }, + "system": "module-system" + } + ] + """); + + assertThat(base.moduleRoots().get(0).context()) + .isEqualTo(new TermCode("sys1", "code1", "display")); + assertThat(base.moduleRoots().get(0).system()).isEqualTo("module-system"); + assertThat(base.moduleRoots().get(0).entries().get("C1")).isNotNull(); + } + + @Test + void fromJson_withChildren() throws Exception { + var base = parse(""" + [ + { + "entries": [ + { + "key": "C1", + "parents": [], + "children": ["C2"] + }, + { + "key": "C2", + "parents": [], + "children": [] + } + ], + "context": { + "system": "sys1", + "code": "code1", + "display": "display" + }, + "system": "module-system" + } + ] + """); + + assertThat(base.moduleRoots().get(0).context()) + .isEqualTo(new TermCode("sys1", "code1", "display")); + assertThat(base.moduleRoots().get(0).system()).isEqualTo("module-system"); + assertThat(base.moduleRoots().get(0).entries().get("C1").children()).containsExactly("C2"); + assertThat(base.moduleRoots().get(0).entries().get("C2")).isNotNull(); + } + + static MappingTreeBase parse(String s) throws JsonProcessingException { + return new MappingTreeBase(Arrays.stream(new ObjectMapper().readValue(s, MappingTreeModuleRoot[].class)).toList()); + } +} diff --git a/src/test/java/de/medizininformatikinitiative/flare/model/mapping/TermCodeNodeTest.java b/src/test/java/de/medizininformatikinitiative/flare/model/mapping/TermCodeNodeTest.java deleted file mode 100644 index 389eab9e..00000000 --- a/src/test/java/de/medizininformatikinitiative/flare/model/mapping/TermCodeNodeTest.java +++ /dev/null @@ -1,192 +0,0 @@ -package de.medizininformatikinitiative.flare.model.mapping; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import de.medizininformatikinitiative.flare.model.sq.ContextualTermCode; -import de.medizininformatikinitiative.flare.model.sq.TermCode; -import org.junit.jupiter.api.Test; - -import static org.assertj.core.api.Assertions.assertThat; - -class TermCodeNodeTest { - - static final TermCode CONTEXT = TermCode.of("context", "context", "context"); - static final ContextualTermCode ROOT = ContextualTermCode.of(CONTEXT, TermCode.of("foo", "root", "root")); - static final ContextualTermCode C1 = ContextualTermCode.of(CONTEXT, TermCode.of("foo", "c1", "c1")); - static final ContextualTermCode C2 = ContextualTermCode.of(CONTEXT, TermCode.of("foo", "c2", "c2")); - static final ContextualTermCode C11 = ContextualTermCode.of(CONTEXT, TermCode.of("foo", "c11", "c11")); - static final ContextualTermCode C12 = ContextualTermCode.of(CONTEXT, TermCode.of("foo", "c12", "c12")); - static final ContextualTermCode C111 = ContextualTermCode.of(CONTEXT, TermCode.of("foo", "c111", "c111")); - static final ContextualTermCode C112 = ContextualTermCode.of(CONTEXT, TermCode.of("foo", "c112", "c112")); - - @Test - void noChildren() { - var node = TermCodeNode.createNormal(ROOT); - - assertThat(node.children()).isEmpty(); - } - - @Test - void expand_notFound() { - var node = TermCodeNode.createNormal(ROOT); - - var result = node.expand(C1).toList(); - - assertThat(result).isEmpty(); - } - - @Test - void expand_itSelf_asLeaf() { - var node = TermCodeNode.createNormal(ROOT); - - var result = node.expand(ROOT).toList(); - - assertThat(result).containsExactly(ROOT); - } - - @Test - void expand_itSelf_withChildren_abstract() { - var node = TermCodeNode.createAbstract(ROOT, TermCodeNode.createNormal(C1), TermCodeNode.createNormal(C2)); - - var result = node.expand(ROOT).toList(); - - assertThat(result).containsExactly(C1, C2); - } - - @Test - void expand_itSelf_withChildren_normal() { - var node = TermCodeNode.createNormal(ROOT, TermCodeNode.createNormal(C1), TermCodeNode.createNormal(C2)); - - var result = node.expand(ROOT).toList(); - - assertThat(result).containsExactly(ROOT, C1, C2); - } - - @Test - void expand_child_withChildren() { - var c1 = TermCodeNode.createNormal(C1, TermCodeNode.createNormal(C11), TermCodeNode.createNormal(C12)); - var node = TermCodeNode.createNormal(ROOT, c1, TermCodeNode.createNormal(C2)); - - var result = node.expand(C1).toList(); - - assertThat(result).containsExactly(C1, C11, C12); - } - - @Test - void expand_child_deep() { - var c11 = TermCodeNode.createNormal(C11, TermCodeNode.createNormal(C111), TermCodeNode.createNormal(C112)); - var c1 = TermCodeNode.createNormal(C1, c11, TermCodeNode.createNormal(C12)); - var node = TermCodeNode.createNormal(ROOT, c1, TermCodeNode.createNormal(C2)); - - var result = node.expand(C1).toList(); - - assertThat(result).containsOnly(C1, C11, C12, C111, C112); - } - - @Test - void fromJson() throws Exception { - var conceptNode = parse(""" - { - "context": { - "system": "context-system-142748", - "code": "context-code-142803", - "display": "context-display-142810" - }, - "termCode": { - "system": "system-143705", - "code": "code-143708", - "display": "display-143716" - }, - "children": [] - } - """); - - assertThat(conceptNode.contextualTermCode()) - .isEqualTo(ContextualTermCode.of( - TermCode.of("context-system-142748", "context-code-142803", "context-display-142810"), - TermCode.of("system-143705", "code-143708", "display-143716"))); - } - - @Test - void fromJson_AdditionalPropertyIsIgnored() throws Exception { - var conceptNode = parse(""" - { - "foo-152133": "bar-152136", - "context": { - "system": "context-system-142748", - "code": "context-code-142803", - "display": "context-display-142810" - }, - "termCode": { - "system": "system-143705", - "code": "code-143708", - "display": "display-143716" - }, - "children": [] - } - """); - - assertThat(conceptNode.contextualTermCode().context().system()).isEqualTo("context-system-142748"); - } - - @Test - void fromJson_WithChildren() throws Exception { - var conceptNode = parse(""" - { - "context": { - "system": "parent-context-system", - "code": "parent-context-code", - "display": "parent-context-display" - }, - "termCode": { - "system": "parent-system", - "code": "parent-code", - "display": "parent-display" - }, - "children": [ - { - "context": { - "system": "child-1-context-system", - "code": "child-1-context-code", - "display": "child-1-context-display" - }, - "termCode": { - "system": "child-1-system", - "code": "child-1-code", - "display": "child-1-display" - } - }, - { - "context": { - "system": "child-2-context-system", - "code": "child-2-context-code", - "display": "child-2-context-display" - }, - "termCode": { - "system": "child-2-system", - "code": "child-2-code", - "display": "child-2-display" - } - } - ] - } - """); - - assertThat(conceptNode.contextualTermCode()) - .isEqualTo(ContextualTermCode.of( - TermCode.of("parent-context-system", "parent-context-code", "parent-context-display"), - TermCode.of("parent-system", "parent-code", "parent-display"))); - assertThat(conceptNode.children().get(0).contextualTermCode()) - .isEqualTo(ContextualTermCode.of( - TermCode.of("child-1-context-system", "child-1-context-code", "child-1-context-display"), - TermCode.of("child-1-system", "child-1-code", "child-1-display"))); - assertThat(conceptNode.children().get(1).contextualTermCode()) - .isEqualTo(ContextualTermCode.of( - TermCode.of("child-2-context-system", "child-2-context-code", "child-2-context-display"), - TermCode.of("child-2-system", "child-2-code", "child-2-display"))); - } - - static TermCodeNode parse(String s) throws JsonProcessingException { - return new ObjectMapper().readValue(s, TermCodeNode.class); - } -} diff --git a/src/test/java/de/medizininformatikinitiative/flare/rest/QueryControllerTest.java b/src/test/java/de/medizininformatikinitiative/flare/rest/QueryControllerTest.java index 6ccedb75..1268ea7c 100644 --- a/src/test/java/de/medizininformatikinitiative/flare/rest/QueryControllerTest.java +++ b/src/test/java/de/medizininformatikinitiative/flare/rest/QueryControllerTest.java @@ -20,7 +20,6 @@ import org.testcontainers.shaded.com.fasterxml.jackson.databind.ObjectMapper; import reactor.core.publisher.Mono; -import java.util.Arrays; import java.util.List; import static org.mockito.Mockito.when; @@ -86,7 +85,7 @@ void execute() { @Test void executeCohort() throws JsonProcessingException { - when(queryService.execute(STRUCTURED_QUERY)).thenReturn(Mono.just(Population.of(PATIENT_ID,PATIENT_ID_1))); + when(queryService.execute(STRUCTURED_QUERY)).thenReturn(Mono.just(Population.of(PATIENT_ID, PATIENT_ID_1))); ObjectMapper objectMapper = new ObjectMapper(); diff --git a/src/test/java/de/medizininformatikinitiative/flare/service/StructuredQueryServiceIT.java b/src/test/java/de/medizininformatikinitiative/flare/service/StructuredQueryServiceIT.java index d7d83385..5347e3e9 100644 --- a/src/test/java/de/medizininformatikinitiative/flare/service/StructuredQueryServiceIT.java +++ b/src/test/java/de/medizininformatikinitiative/flare/service/StructuredQueryServiceIT.java @@ -7,7 +7,8 @@ import de.medizininformatikinitiative.flare.model.Population; import de.medizininformatikinitiative.flare.model.mapping.Mapping; import de.medizininformatikinitiative.flare.model.mapping.MappingContext; -import de.medizininformatikinitiative.flare.model.mapping.TermCodeNode; +import de.medizininformatikinitiative.flare.model.mapping.MappingTreeBase; +import de.medizininformatikinitiative.flare.model.mapping.MappingTreeModuleRoot; import de.medizininformatikinitiative.flare.model.sq.*; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -43,7 +44,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import static java.util.function.Function.identity; +import static java.util.function.UnaryOperator.identity; import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.http.MediaType.APPLICATION_JSON; @@ -98,7 +99,7 @@ private static Path resourcePathFlareApplication(String name) throws URISyntaxEx return Paths.get(Objects.requireNonNull(FlareApplication.class.getResource(name)).toURI()); } - private int getExecutionResult(StructuredQuery query){ + private int getExecutionResult(StructuredQuery query) { return service.execute(query).block().size(); } @@ -255,7 +256,7 @@ public MappingContext mappingContext_BloodPressure() throws Exception { var mapper = new ObjectMapper(); var mappings = Arrays.stream(mapper.readValue(slurpStructuredQueryService("compositeSearchParams/mapping-bloodPressure.json"), Mapping[].class)) .collect(Collectors.toMap(Mapping::key, identity())); - var conceptTree = mapper.readValue(slurpStructuredQueryService("compositeSearchParams/tree-bloodPressure.json"), TermCodeNode.class); + var conceptTree = new MappingTreeBase(Arrays.stream(mapper.readValue(slurpStructuredQueryService("compositeSearchParams/tree-bloodPressure.json"), MappingTreeModuleRoot[].class)).toList()); return MappingContext.of(mappings, conceptTree, CLOCK_2000); } diff --git a/src/test/java/de/medizininformatikinitiative/flare/service/StructuredQueryServiceTest.java b/src/test/java/de/medizininformatikinitiative/flare/service/StructuredQueryServiceTest.java index cfca0fb7..e3fa3e45 100644 --- a/src/test/java/de/medizininformatikinitiative/flare/service/StructuredQueryServiceTest.java +++ b/src/test/java/de/medizininformatikinitiative/flare/service/StructuredQueryServiceTest.java @@ -25,7 +25,6 @@ import static de.medizininformatikinitiative.flare.Assertions.assertThat; import static de.medizininformatikinitiative.flare.model.fhir.QueryParams.conceptValue; import static de.medizininformatikinitiative.flare.model.sq.TestUtil.*; -import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.when; @SuppressWarnings("SameParameterValue") diff --git a/src/test/resources/de/medizininformatikinitiative/flare/service/compositeSearchParams/tree-bloodPressure.json b/src/test/resources/de/medizininformatikinitiative/flare/service/compositeSearchParams/tree-bloodPressure.json index f377bcdc..284cda92 100644 --- a/src/test/resources/de/medizininformatikinitiative/flare/service/compositeSearchParams/tree-bloodPressure.json +++ b/src/test/resources/de/medizininformatikinitiative/flare/service/compositeSearchParams/tree-bloodPressure.json @@ -1,13 +1,18 @@ -{ - "context": { - "code": "Laboruntersuchung", - "display": "Laboruntersuchung", - "system": "fdpg.mii.cds", - "version": "1.0.0" - }, - "termCode": { - "code": "85354-9", - "display": "Blood pressure panel with all children optional", +[ + { + "entries": [ + { + "key": "85354-9", + "parents": [], + "children": [] + } + ], + "context": { + "code": "Laboruntersuchung", + "display": "Laboruntersuchung", + "system": "fdpg.mii.cds", + "version": "1.0.0" + }, "system": "http://loinc.org" } -} +] diff --git a/src/test/resources/de/medizininformatikinitiative/flare/testCases/returningOnePatient/1-age.json b/src/test/resources/de/medizininformatikinitiative/flare/testCases/returningOnePatient/1-age.json index 6f0a3e20..a00889b2 100644 --- a/src/test/resources/de/medizininformatikinitiative/flare/testCases/returningOnePatient/1-age.json +++ b/src/test/resources/de/medizininformatikinitiative/flare/testCases/returningOnePatient/1-age.json @@ -1 +1,32 @@ -{"inclusionCriteria": [[{"attributeFilters": [], "termCodes": [{"code": "424144002", "display": "Current chronological age (observable entity)", "system": "http://snomed.info/sct"}], "valueFilter": {"comparator": "eq", "type": "quantity-comparator", "unit": {"code": "a", "display": "a"}, "value": 9}, "context": {"code": "Patient", "display": "Patient", "system": "fdpg.mii.cds", "version": "1.0.0"}}]], "version": "http://to_be_decided.com/draft-1/schema#"} +{ + "inclusionCriteria": [ + [ + { + "attributeFilters": [], + "termCodes": [ + { + "code": "424144002", + "display": "Current chronological age (observable entity)", + "system": "http://snomed.info/sct" + } + ], + "valueFilter": { + "comparator": "eq", + "type": "quantity-comparator", + "unit": { + "code": "a", + "display": "a" + }, + "value": 9 + }, + "context": { + "code": "Patient", + "display": "Patient", + "system": "fdpg.mii.cds", + "version": "1.0.0" + } + } + ] + ], + "version": "http://to_be_decided.com/draft-1/schema#" +} From 8c295af057ac1eea7ddad0bf786046001aadf95b Mon Sep 17 00:00:00 2001 From: Alexander Kiel Date: Tue, 3 Sep 2024 13:31:34 +0200 Subject: [PATCH 09/43] Minor Improvements of Update Mapping Tree * moved isModuleMatching method into MappingTreeModuleRoot class * improve assert in MappingContextTest.expandConcept_TwoConcepts * made some method static * updated Blaze to v0.29 * use StepVerifier instead of blocking on Mono * reverted formatting on 1-age.json --- .../basic-auth/docker-compose.yml | 2 +- .../no-auth/docker-compose.yml | 2 +- .../integration-test/oauth/docker-compose.yml | 2 +- docker-compose.yml | 2 +- .../flare/model/mapping/MappingTreeBase.java | 8 +---- .../model/mapping/MappingTreeModuleRoot.java | 6 ++++ .../model/mapping/MappingContextTest.java | 9 ++--- .../model/mapping/MappingTreeBaseTest.java | 3 +- .../flare/service/DataStoreIT.java | 2 +- .../service/StructuredQueryServiceIT.java | 22 +++++-------- .../testCases/returningOnePatient/1-age.json | 33 +------------------ 11 files changed, 25 insertions(+), 66 deletions(-) diff --git a/.github/integration-test/basic-auth/docker-compose.yml b/.github/integration-test/basic-auth/docker-compose.yml index 2a9b7eaf..85b441f1 100644 --- a/.github/integration-test/basic-auth/docker-compose.yml +++ b/.github/integration-test/basic-auth/docker-compose.yml @@ -1,6 +1,6 @@ services: data-store: - image: "samply/blaze:0.28" + image: "samply/blaze:0.29" healthcheck: test: ["CMD-SHELL", "curl --fail -s http://localhost:8080/health"] interval: "5s" diff --git a/.github/integration-test/no-auth/docker-compose.yml b/.github/integration-test/no-auth/docker-compose.yml index 0ed3af12..d6495841 100644 --- a/.github/integration-test/no-auth/docker-compose.yml +++ b/.github/integration-test/no-auth/docker-compose.yml @@ -1,6 +1,6 @@ services: data-store: - image: "samply/blaze:0.28" + image: "samply/blaze:0.29" healthcheck: test: ["CMD-SHELL", "curl --fail -s http://localhost:8080/health"] interval: "5s" diff --git a/.github/integration-test/oauth/docker-compose.yml b/.github/integration-test/oauth/docker-compose.yml index eae28a4a..0fe92965 100644 --- a/.github/integration-test/oauth/docker-compose.yml +++ b/.github/integration-test/oauth/docker-compose.yml @@ -72,7 +72,7 @@ services: keycloak: condition: service_healthy data-store: - image: "samply/blaze:0.28" + image: "samply/blaze:0.29" healthcheck: test: ["CMD-SHELL", "curl --fail -s http://localhost:8080/health"] interval: "5s" diff --git a/docker-compose.yml b/docker-compose.yml index 32dbf096..90ebf5f3 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,6 +1,6 @@ services: data-store: - image: "samply/blaze:0.28" + image: "samply/blaze:0.29" environment: BASE_URL: "http://localhost:8082" JAVA_TOOL_OPTIONS: "-Xmx1g" diff --git a/src/main/java/de/medizininformatikinitiative/flare/model/mapping/MappingTreeBase.java b/src/main/java/de/medizininformatikinitiative/flare/model/mapping/MappingTreeBase.java index a96ac4d6..bc4ecd7d 100644 --- a/src/main/java/de/medizininformatikinitiative/flare/model/mapping/MappingTreeBase.java +++ b/src/main/java/de/medizininformatikinitiative/flare/model/mapping/MappingTreeBase.java @@ -11,12 +11,6 @@ public Stream expand(ContextualTermCode termCode) { var key = termCode.termCode().code(); return moduleRoots.stream().flatMap(moduleRoot -> - isModuleMatching(termCode, moduleRoot) ? moduleRoot.expand(key) : Stream.empty()); - } - - private boolean isModuleMatching(ContextualTermCode termCode, MappingTreeModuleRoot moduleRoot) { - return termCode.context().equals(moduleRoot.context()) && - moduleRoot.system().equals(termCode.termCode().system()) && - moduleRoot.entries().containsKey(termCode.termCode().code()); + moduleRoot.isModuleMatching(termCode) ? moduleRoot.expand(key) : Stream.empty()); } } diff --git a/src/main/java/de/medizininformatikinitiative/flare/model/mapping/MappingTreeModuleRoot.java b/src/main/java/de/medizininformatikinitiative/flare/model/mapping/MappingTreeModuleRoot.java index 68a6c6aa..5ce27256 100644 --- a/src/main/java/de/medizininformatikinitiative/flare/model/mapping/MappingTreeModuleRoot.java +++ b/src/main/java/de/medizininformatikinitiative/flare/model/mapping/MappingTreeModuleRoot.java @@ -31,4 +31,10 @@ public Stream expand(String key) { return Stream.concat(Stream.of(newTermCode), entries.get(key).children().stream().flatMap(this::expand)); } + + boolean isModuleMatching(ContextualTermCode contextualTermCode) { + return context.equals(contextualTermCode.context()) && + system.equals(contextualTermCode.termCode().system()) && + entries.containsKey(contextualTermCode.termCode().code()); + } } diff --git a/src/test/java/de/medizininformatikinitiative/flare/model/mapping/MappingContextTest.java b/src/test/java/de/medizininformatikinitiative/flare/model/mapping/MappingContextTest.java index 745111c7..304aceee 100644 --- a/src/test/java/de/medizininformatikinitiative/flare/model/mapping/MappingContextTest.java +++ b/src/test/java/de/medizininformatikinitiative/flare/model/mapping/MappingContextTest.java @@ -15,11 +15,10 @@ class MappingContextTest { static final TermCode CONTEXT = TermCode.of("context", "context", "context"); static final String SYSTEM = "sys"; - static final ContextualTermCode ROOT = ContextualTermCode.of(CONTEXT, TermCode.of("root", "root", "root")); static final String C1 = "c1"; static final String C2 = "c2"; - private ContextualTermCode contextualTermCodeOf(String code) { + private static ContextualTermCode contextualTermCodeOf(String code) { return new ContextualTermCode(MappingContextTest.CONTEXT, new TermCode(MappingContextTest.SYSTEM, code, "display")); } @@ -50,9 +49,7 @@ void expandConcept_TwoConcepts() { var result = context.expandConcept(ContextualConcept.of(contextualTermCodeOf(C1))); - assertThat(result).isRightSatisfying(r -> { - assertThat(r.get(0)).isEqualTo(contextualTermCodeOf(C1)); - assertThat(r.get(1)).isEqualTo(contextualTermCodeOf(C2)); - }); + assertThat(result).isRightSatisfying(r -> + assertThat(r).containsExactly(contextualTermCodeOf(C1), contextualTermCodeOf(C2))); } } diff --git a/src/test/java/de/medizininformatikinitiative/flare/model/mapping/MappingTreeBaseTest.java b/src/test/java/de/medizininformatikinitiative/flare/model/mapping/MappingTreeBaseTest.java index b47c0b2d..0c58c3ca 100644 --- a/src/test/java/de/medizininformatikinitiative/flare/model/mapping/MappingTreeBaseTest.java +++ b/src/test/java/de/medizininformatikinitiative/flare/model/mapping/MappingTreeBaseTest.java @@ -25,8 +25,7 @@ class MappingTreeBaseTest { static final String C4 = "c4"; static final String C5 = "c5"; - - private ContextualTermCode contextualTermCodeOf(TermCode context, String system, String code) { + private static ContextualTermCode contextualTermCodeOf(TermCode context, String system, String code) { return new ContextualTermCode(context, new TermCode(system, code, "display")); } diff --git a/src/test/java/de/medizininformatikinitiative/flare/service/DataStoreIT.java b/src/test/java/de/medizininformatikinitiative/flare/service/DataStoreIT.java index ed58cd70..98f2e1aa 100644 --- a/src/test/java/de/medizininformatikinitiative/flare/service/DataStoreIT.java +++ b/src/test/java/de/medizininformatikinitiative/flare/service/DataStoreIT.java @@ -35,7 +35,7 @@ class DataStoreIT { @Container @SuppressWarnings("resource") - private final GenericContainer blaze = new GenericContainer<>("samply/blaze:0.28") + private final GenericContainer blaze = new GenericContainer<>("samply/blaze:0.29") .withImagePullPolicy(PullPolicy.alwaysPull()) .withEnv("LOG_LEVEL", "debug") .withExposedPorts(8080) diff --git a/src/test/java/de/medizininformatikinitiative/flare/service/StructuredQueryServiceIT.java b/src/test/java/de/medizininformatikinitiative/flare/service/StructuredQueryServiceIT.java index 5347e3e9..c5b0ead9 100644 --- a/src/test/java/de/medizininformatikinitiative/flare/service/StructuredQueryServiceIT.java +++ b/src/test/java/de/medizininformatikinitiative/flare/service/StructuredQueryServiceIT.java @@ -44,8 +44,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import static java.util.function.UnaryOperator.identity; -import static org.assertj.core.api.Assertions.assertThat; +import static java.util.function.Function.identity; import static org.springframework.http.MediaType.APPLICATION_JSON; @Testcontainers @@ -54,16 +53,13 @@ class StructuredQueryServiceIT { private static final Clock CLOCK_2000 = Clock.fixed(LocalDate.of(2000, 1, 1).atStartOfDay().toInstant(ZoneOffset.UTC), ZoneOffset.UTC); private static final TermCode I08 = TermCode.of("http://fhir.de/CodeSystem/bfarm/icd-10-gm", "I08", ""); - private static final TermCode COVID = TermCode.of("http://loinc.org", "94500-6", ""); - private static final TermCode INVALID = TermCode.of("http://loinc.org", "LA15841-2", "Invalid"); private static final TermCode DIAGNOSIS = TermCode.of("fdpg.mii.cds", "Diagnose", "Diagnose"); - private static final TermCode OBSERVATION = TermCode.of("fdpg.mii.cds", "Laboruntersuchung", "Laboruntersuchung"); private static final Logger logger = LoggerFactory.getLogger(StructuredQueryServiceIT.class); @Container @SuppressWarnings("resource") - private static final GenericContainer blaze = new GenericContainer<>("samply/blaze:0.28") + private static final GenericContainer blaze = new GenericContainer<>("samply/blaze:0.29") .withImagePullPolicy(PullPolicy.alwaysPull()) .withEnv("LOG_LEVEL", "debug") .withEnv("DB_SEARCH_PARAM_BUNDLE", "/app/custom-search-parameters.json") @@ -99,10 +95,6 @@ private static Path resourcePathFlareApplication(String name) throws URISyntaxEx return Paths.get(Objects.requireNonNull(FlareApplication.class.getResource(name)).toURI()); } - private int getExecutionResult(StructuredQuery query) { - return service.execute(query).block().size(); - } - public static List getTestQueriesReturningOnePatient() throws URISyntaxException, IOException { Path directoryPath = Paths.get(resourcePathFlareApplication("testCases").resolve("returningOnePatient").toString()); @@ -139,15 +131,17 @@ void execute_Criterion() { var query = StructuredQuery.of(CriterionGroup.of(CriterionGroup.of(Criterion.of(ContextualConcept.of(DIAGNOSIS, Concept.of(I08)))))); var result = service.execute(query); + StepVerifier.create(result).expectNext(Population.of("id-pat-diag-I08.0")).verifyComplete(); } @Test void execute_genderTestCase() throws URISyntaxException, IOException { var query = parseSq(Files.readString(resourcePathFlareApplication("testCases").resolve("returningOther").resolve("2-gender.json"))); - var result = getExecutionResult(query); - assertThat(result).isEqualTo(172); + var result = service.execute(query); + + StepVerifier.create(result).expectNextMatches(p -> p.size() == 172).verifyComplete(); } @Test @@ -179,9 +173,9 @@ void execute_specimenTestCase() throws IOException, URISyntaxException { @ParameterizedTest @MethodSource("getTestQueriesReturningOnePatient") void execute_casesReturningOne(StructuredQuery query) { - var result = getExecutionResult(query); + var result = service.execute(query); - assertThat(result).isOne(); + StepVerifier.create(result).expectNextMatches(p -> p.size() == 1).verifyComplete(); } @Test diff --git a/src/test/resources/de/medizininformatikinitiative/flare/testCases/returningOnePatient/1-age.json b/src/test/resources/de/medizininformatikinitiative/flare/testCases/returningOnePatient/1-age.json index a00889b2..6f0a3e20 100644 --- a/src/test/resources/de/medizininformatikinitiative/flare/testCases/returningOnePatient/1-age.json +++ b/src/test/resources/de/medizininformatikinitiative/flare/testCases/returningOnePatient/1-age.json @@ -1,32 +1 @@ -{ - "inclusionCriteria": [ - [ - { - "attributeFilters": [], - "termCodes": [ - { - "code": "424144002", - "display": "Current chronological age (observable entity)", - "system": "http://snomed.info/sct" - } - ], - "valueFilter": { - "comparator": "eq", - "type": "quantity-comparator", - "unit": { - "code": "a", - "display": "a" - }, - "value": 9 - }, - "context": { - "code": "Patient", - "display": "Patient", - "system": "fdpg.mii.cds", - "version": "1.0.0" - } - } - ] - ], - "version": "http://to_be_decided.com/draft-1/schema#" -} +{"inclusionCriteria": [[{"attributeFilters": [], "termCodes": [{"code": "424144002", "display": "Current chronological age (observable entity)", "system": "http://snomed.info/sct"}], "valueFilter": {"comparator": "eq", "type": "quantity-comparator", "unit": {"code": "a", "display": "a"}, "value": 9}, "context": {"code": "Patient", "display": "Patient", "system": "fdpg.mii.cds", "version": "1.0.0"}}]], "version": "http://to_be_decided.com/draft-1/schema#"} From 2227ef2201376e822b4308969fc7eca7ba636786 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 3 Sep 2024 11:56:29 +0000 Subject: [PATCH 10/43] Update testcontainers-java monorepo to v1.20.1 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index c78ce822..cbba99f1 100644 --- a/pom.xml +++ b/pom.xml @@ -20,7 +20,7 @@ 17 - 1.19.8 + 1.20.1 3.0.0-test.1 From 4e496041043d9adf1b745db08f7fe97428c415ef Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 3 Sep 2024 13:00:50 +0000 Subject: [PATCH 11/43] Update keycloak/keycloak Docker tag to v25.0.4 --- .github/integration-test/oauth/docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/integration-test/oauth/docker-compose.yml b/.github/integration-test/oauth/docker-compose.yml index 0fe92965..dbbe5db4 100644 --- a/.github/integration-test/oauth/docker-compose.yml +++ b/.github/integration-test/oauth/docker-compose.yml @@ -29,7 +29,7 @@ services: generate-cert: condition: service_completed_successfully keycloak: - image: "keycloak/keycloak:25.0.1" + image: "keycloak/keycloak:25.0.4" command: ["start", "--import-realm"] healthcheck: test: ["CMD-SHELL", "exec 3<>/dev/tcp/127.0.0.1/9000;echo -e \"GET /health/ready HTTP/1.1\r\nhost: localhost\r\nConnection: close\r\n\r\n\" >&3;grep \"HTTP/1.1 200 OK\" <&3"] From b0418e8a25948a3cb136821cc80353902505a1f4 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 3 Sep 2024 13:16:19 +0000 Subject: [PATCH 12/43] Update dependency org.rocksdb:rocksdbjni to v9.5.2 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index cbba99f1..048243ef 100644 --- a/pom.xml +++ b/pom.xml @@ -61,7 +61,7 @@ org.rocksdb rocksdbjni - 9.2.1 + 9.5.2 From fe7d5877de8d2956c33ee039da30701016e78966 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 3 Sep 2024 13:28:21 +0000 Subject: [PATCH 13/43] Update dependency org.springframework.boot:spring-boot-starter-parent to v3.3.3 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 048243ef..feac6a9a 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ org.springframework.boot spring-boot-starter-parent - 3.3.1 + 3.3.3 From 4aa255fdab901e751f15e33378eb72cea69c67c4 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 3 Sep 2024 13:41:28 +0000 Subject: [PATCH 14/43] Update dependency maven to v3.9.9 --- .mvn/wrapper/maven-wrapper.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties index f95f1ee8..d58dfb70 100644 --- a/.mvn/wrapper/maven-wrapper.properties +++ b/.mvn/wrapper/maven-wrapper.properties @@ -16,4 +16,4 @@ # under the License. wrapperVersion=3.3.2 distributionType=only-script -distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.8/apache-maven-3.9.8-bin.zip +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip From 6b93f2031edd048435d0a73d7f85adf62bb70a32 Mon Sep 17 00:00:00 2001 From: Bastian Schaffer Date: Tue, 3 Sep 2024 15:13:39 +0200 Subject: [PATCH 15/43] Change Docker Compose Fixes: #184 Also updated README --- README.md | 46 ++++++++++++++++++++++++---------------------- docker-compose.yml | 36 +++++++++++++++++++++++++++--------- 2 files changed, 51 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index 43d3dfa2..825fdfa3 100644 --- a/README.md +++ b/README.md @@ -34,28 +34,30 @@ docker run -p 8080:8080 ghcr.io/medizininformatik-initiative/flare:2.3 ## Environment Variables -| Name | Default | Description | -|:----------------------------------|:--------------------------------------|:-------------------------------------------------------------------------------------------------| -| FLARE_FHIR_SERVER | http://localhost:8082/fhir | The base URL of the FHIR server to use. | -| FLARE_FHIR_USER | | The username to use for HTTP Basic Authentication. | -| FLARE_FHIR_PASSWORD | | The password to use for HTTP Basic Authentication. | -| FLARE_FHIR_OAUTH_ISSUER_URI | | The issuer URI of the OpenID Connect provider. | -| FLARE_FHIR_OAUTH_CLIENT_ID | | The client ID to use for authentication with OpenID Connect provider. | -| FLARE_FHIR_OAUTH_CLIENT_SECRET | | The client secret to use for authentication with OpenID Connect provider. | -| FLARE_FHIR_MAX_CONNECTIONS | 4 | The maximum number of connections Flare opens towards the FHIR server. | -| FLARE_FHIR_MAX_QUEUE_SIZE | 500 | The maximum number FHIR server requests Flare queues before returning an error. | -| FLARE_FHIR_PAGE_COUNT | 1000 | The number of resources per page to request from the FHIR server. | -| FLARE_CACHE_MEM_SIZE_MB | 1024 | The size of the in-memory cache in mebibytes. | -| FLARE_CACHE_MEM_EXPIRE | PT48H | The duration after which in-memory cache entries should expire in [ISO 8601 durations][1]. | -| FLARE_CACHE_MEM_REFRESH | PT24H | The duration after which in-memory cache entries should be refreshed in [ISO 8601 durations][1]. | -| FLARE_CACHE_DISK_PATH | cache | The name of the directory in which the on-disk cache should be written. | -| FLARE_CACHE_DISK_EXPIRE | P7D | The duration after which on-disk cache entries should expire in [ISO 8601 durations][1]. | -| FLARE_CACHE_DISK_THREADS | 4 | The number of threads the disk cache should use for reading and writing entries. | -| FLARE_MAPPING_MAPPING_FILE | ontology/codex-term-code-mapping.json | The mappings to use. | -| FLARE_MAPPING_CONCEPT_TREE_FILE | ontology/codex-code-tree.json | The code tree to use. | -| SERVER_PORT | 8080 | The port at which Flare provides its REST API. | -| JAVA_TOOL_OPTIONS | -Xmx4g | JVM options \(Docker only\) | -| LOG_LEVEL | info | one of trace, debug, info, warn or error | +| Name | Default | Depr ¹ | Description | +|:--------------------------------|:--------------------------------------|--------|:-------------------------------------------------------------------------------------------------| +| FLARE_FHIR_SERVER | http://localhost:8082/fhir | | The base URL of the FHIR server to use. | +| FLARE_FHIR_USER | | | The username to use for HTTP Basic Authentication. | +| FLARE_FHIR_PASSWORD | | | The password to use for HTTP Basic Authentication. | +| FLARE_FHIR_OAUTH_ISSUER_URI | | | The issuer URI of the OpenID Connect provider. | +| FLARE_FHIR_OAUTH_CLIENT_ID | | | The client ID to use for authentication with OpenID Connect provider. | +| FLARE_FHIR_OAUTH_CLIENT_SECRET | | | The client secret to use for authentication with OpenID Connect provider. | +| FLARE_FHIR_MAX_CONNECTIONS | 4 | | The maximum number of connections Flare opens towards the FHIR server. | +| FLARE_FHIR_MAX_QUEUE_SIZE | 500 | | The maximum number FHIR server requests Flare queues before returning an error. | +| FLARE_FHIR_PAGE_COUNT | 1000 | | The number of resources per page to request from the FHIR server. | +| FLARE_CACHE_MEM_SIZE_MB | 1024 | | The size of the in-memory cache in mebibytes. | +| FLARE_CACHE_MEM_EXPIRE | PT48H | | The duration after which in-memory cache entries should expire in [ISO 8601 durations][1]. | +| FLARE_CACHE_MEM_REFRESH | PT24H | | The duration after which in-memory cache entries should be refreshed in [ISO 8601 durations][1]. | +| FLARE_CACHE_DISK_PATH | cache | | The name of the directory in which the on-disk cache should be written. | +| FLARE_CACHE_DISK_EXPIRE | P7D | | The duration after which on-disk cache entries should expire in [ISO 8601 durations][1]. | +| FLARE_CACHE_DISK_THREADS | 4 | | The number of threads the disk cache should use for reading and writing entries. | +| FLARE_MAPPING_MAPPING_FILE | ontology/codex-term-code-mapping.json | v2.4.0 | The mappings to use. | +| FLARE_MAPPING_CONCEPT_TREE_FILE | ontology/codex-code-tree.json | v2.4.0 | The code tree to use. | +| SERVER_PORT | 8080 | | The port at which Flare provides its REST API. | +| JAVA_TOOL_OPTIONS | -Xmx4g | | JVM options \(Docker only\) | +| LOG_LEVEL | info | | one of trace, debug, info, warn or error | + +¹ Deprecated since ## Default Configuration diff --git a/docker-compose.yml b/docker-compose.yml index 90ebf5f3..e48e9082 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,13 +1,31 @@ services: - data-store: - image: "samply/blaze:0.29" - environment: - BASE_URL: "http://localhost:8082" - JAVA_TOOL_OPTIONS: "-Xmx1g" - LOG_LEVEL: "debug" + flare: + image: ghcr.io/medizininformatik-initiative/flare:2.3.0 ports: - - "8082:8080" + - 8080:8080 + environment: + FLARE_FHIR_SERVER: ${FLARE_FHIR_SERVER_URL:-http://fhir-server:8080/fhir/} + FLARE_FHIR_USER: ${FLARE_FHIR_USER:-} + FLARE_FHIR_PASSWORD: ${FLARE_FHIR_PW:-} + FLARE_FHIR_OAUTH_ISSUER_URI: ${FLARE_FHIR_OAUTH_ISSUER_URI} + FLARE_FHIR_OAUTH_CLIENT_ID: ${FLARE_FHIR_OAUTH_CLIENT_ID} + FLARE_FHIR_OAUTH_CLIENT_SECRET: ${FLARE_FHIR_OAUTH_CLIENT_SECRET} + FLARE_FHIR_MAX_CONNECTIONS: ${FLARE_FHIR_MAX_CONNECTIONS:-32} + FLARE_FHIR_MAX_QUEUE_SIZE: ${FLARE_FHIR_MAX_QUEUE_SIZE:-500} + FLARE_FHIR_PAGE_COUNT: ${FLARE_FHIR_PAGE_COUNT:-500} + FLARE_CACHE_MEM_SIZE_MB: ${FLARE_CACHE_MEM_SIZE_MB:-1024} + FLARE_CACHE_MEM_EXPIRE: ${FLARE_CACHE_MEM_EXPIRE:-PT48H} + FLARE_CACHE_MEM_REFRESH: ${FLARE_CACHE_MEM_REFRESH:-PT24H} + FLARE_CACHE_DISK_PATH: ${FLARE_CACHE_DISK_PATH:-cache} + FLARE_CACHE_DISK_EXPIRE: ${FLARE_CACHE_DISK_EXPIRE:-P7D} + FLARE_CACHE_DISK_THREADS: ${FLARE_CACHE_DISK_THREADS:-4} + JAVA_TOOL_OPTIONS: ${FLARE_JAVA_TOOL_OPTIONS:--Xmx4g} + LOG_LEVEL: ${FLARE_LOG_LEVEL:-info} volumes: - - "data-store-data:/app/data" + - type: volume + source: flare-cache + target: /app/cache + volumes: - data-store-data: + flare-cache: + name: "flare-cache" From 1f2e856371af55879f32c5ec5baa1230bd2aa1c1 Mon Sep 17 00:00:00 2001 From: Bastian Schaffer Date: Tue, 3 Sep 2024 16:33:01 +0200 Subject: [PATCH 16/43] Remove Old Test Environment --- README.md | 18 +++--------------- docker-compose.yml | 23 +---------------------- import-test-data.sh | 7 ------- 3 files changed, 4 insertions(+), 44 deletions(-) delete mode 100755 import-test-data.sh diff --git a/README.md b/README.md index 825fdfa3..2ee62c63 100644 --- a/README.md +++ b/README.md @@ -6,20 +6,6 @@ The goal of this project is to provide a service that allows for execution of feasibility queries on a FHIR-server. -## Setting up the test-environment - -Set up FHIR test server - -```sh -docker compose up -``` - -Load example data into FHIR server - -```sh -import-test-data.sh -``` - ## Build ```sh @@ -29,9 +15,11 @@ mvn clean install ## Run ```sh -docker run -p 8080:8080 ghcr.io/medizininformatik-initiative/flare:2.3 +docker-compose up ``` +In order for flare to work, a FHIR server such as [blaze](https://github.com/samply/blaze) must be running. + ## Environment Variables | Name | Default | Depr ¹ | Description | diff --git a/docker-compose.yml b/docker-compose.yml index e48e9082..36fc942b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,29 +3,8 @@ services: image: ghcr.io/medizininformatik-initiative/flare:2.3.0 ports: - 8080:8080 - environment: - FLARE_FHIR_SERVER: ${FLARE_FHIR_SERVER_URL:-http://fhir-server:8080/fhir/} - FLARE_FHIR_USER: ${FLARE_FHIR_USER:-} - FLARE_FHIR_PASSWORD: ${FLARE_FHIR_PW:-} - FLARE_FHIR_OAUTH_ISSUER_URI: ${FLARE_FHIR_OAUTH_ISSUER_URI} - FLARE_FHIR_OAUTH_CLIENT_ID: ${FLARE_FHIR_OAUTH_CLIENT_ID} - FLARE_FHIR_OAUTH_CLIENT_SECRET: ${FLARE_FHIR_OAUTH_CLIENT_SECRET} - FLARE_FHIR_MAX_CONNECTIONS: ${FLARE_FHIR_MAX_CONNECTIONS:-32} - FLARE_FHIR_MAX_QUEUE_SIZE: ${FLARE_FHIR_MAX_QUEUE_SIZE:-500} - FLARE_FHIR_PAGE_COUNT: ${FLARE_FHIR_PAGE_COUNT:-500} - FLARE_CACHE_MEM_SIZE_MB: ${FLARE_CACHE_MEM_SIZE_MB:-1024} - FLARE_CACHE_MEM_EXPIRE: ${FLARE_CACHE_MEM_EXPIRE:-PT48H} - FLARE_CACHE_MEM_REFRESH: ${FLARE_CACHE_MEM_REFRESH:-PT24H} - FLARE_CACHE_DISK_PATH: ${FLARE_CACHE_DISK_PATH:-cache} - FLARE_CACHE_DISK_EXPIRE: ${FLARE_CACHE_DISK_EXPIRE:-P7D} - FLARE_CACHE_DISK_THREADS: ${FLARE_CACHE_DISK_THREADS:-4} - JAVA_TOOL_OPTIONS: ${FLARE_JAVA_TOOL_OPTIONS:--Xmx4g} - LOG_LEVEL: ${FLARE_LOG_LEVEL:-info} volumes: - - type: volume - source: flare-cache - target: /app/cache + - flare-cache:/app/cache volumes: flare-cache: - name: "flare-cache" diff --git a/import-test-data.sh b/import-test-data.sh deleted file mode 100755 index d601ebb7..00000000 --- a/import-test-data.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/bash -e - -curl -sH 'Content-Type: application/fhir+json' \ - -d @src/test/resources/de/medizininformatikinitiative/flare/GeneratedBundle.json \ - -o /dev/null \ - -w 'Result: %{response_code}\n' \ - http://localhost:8082/fhir From 362b84076f76ddd0379eef14d0449192305ed2a8 Mon Sep 17 00:00:00 2001 From: Bastian Schaffer Date: Tue, 3 Sep 2024 17:05:16 +0200 Subject: [PATCH 17/43] Update README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2ee62c63..82b4e83f 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ mvn clean install docker-compose up ``` -In order for flare to work, a FHIR server such as [blaze](https://github.com/samply/blaze) must be running. +In order for flare to work, a FHIR server such as [Blaze](https://github.com/samply/blaze) must be running on port 8082 by default. ## Environment Variables From ac8f04d3cf8da324f03f16d4b27a813331a2519c Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 3 Sep 2024 15:16:58 +0000 Subject: [PATCH 18/43] Update dependency org.yaml:snakeyaml to v2.3 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index feac6a9a..00a6c27c 100644 --- a/pom.xml +++ b/pom.xml @@ -44,7 +44,7 @@ org.yaml snakeyaml - 2.2 + 2.3 From 7bb4d529bf3b112e6c25ee7e1c5d2ed697d618e8 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 4 Sep 2024 08:25:12 +0000 Subject: [PATCH 19/43] Update nginx Docker tag to v1.27.1 --- .github/integration-test/basic-auth/docker-compose.yml | 2 +- .github/integration-test/oauth/docker-compose.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/integration-test/basic-auth/docker-compose.yml b/.github/integration-test/basic-auth/docker-compose.yml index 85b441f1..0b332f26 100644 --- a/.github/integration-test/basic-auth/docker-compose.yml +++ b/.github/integration-test/basic-auth/docker-compose.yml @@ -37,7 +37,7 @@ services: depends_on: - data-store proxy: - image: "nginx:1.27.0" + image: "nginx:1.27.1" volumes: - "./nginx.conf:/etc/nginx/nginx.conf" - "./proxy.htpasswd:/etc/auth/.htpasswd" diff --git a/.github/integration-test/oauth/docker-compose.yml b/.github/integration-test/oauth/docker-compose.yml index dbbe5db4..99b609b0 100644 --- a/.github/integration-test/oauth/docker-compose.yml +++ b/.github/integration-test/oauth/docker-compose.yml @@ -50,7 +50,7 @@ services: volumes: - "./realm-test.json:/opt/keycloak/data/import/realm-test.json" proxy: - image: "nginx:1.27.0" + image: "nginx:1.27.1" healthcheck: test: ["CMD-SHELL", "curl --fail -s http://localhost:8080"] interval: "5s" From 68dc945303f698465e38053fb7a4b0dc872c55a6 Mon Sep 17 00:00:00 2001 From: Alexander Kiel Date: Wed, 4 Sep 2024 14:44:23 +0200 Subject: [PATCH 20/43] Release v2.4.0-alpha.1 --- CHANGELOG.md | 8 ++++++++ pom.xml | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9e5b2dec..8f6908e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## v2.4.0-alpha.1 + +### Enhancements + +* Update mapping_tree creation ([#191](https://github.com/medizininformatik-initiative/flare/issues/191)) +* Add cohort extraction endpoint ([#178](https://github.com/medizininformatik-initiative/flare/issues/178)) +* docker-compose.yml should be for flare instead of blaze ([#184](https://github.com/medizininformatik-initiative/flare/issues/184)) + ## v2.3.0 ### Enhancements diff --git a/pom.xml b/pom.xml index 00a6c27c..3624df61 100644 --- a/pom.xml +++ b/pom.xml @@ -13,7 +13,7 @@ de.medizininformatik-initiative flare - 2.3.0 + 2.4.0-alpha.1 Flare Flare From 933903a11bbd1f214cd3c6eb02154f7b9cc1dfb4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Sep 2024 08:19:03 +0000 Subject: [PATCH 21/43] Bump org.springframework.boot:spring-boot-starter-parent Bumps [org.springframework.boot:spring-boot-starter-parent](https://github.com/spring-projects/spring-boot) from 3.3.3 to 3.3.4. - [Release notes](https://github.com/spring-projects/spring-boot/releases) - [Commits](https://github.com/spring-projects/spring-boot/compare/v3.3.3...v3.3.4) --- updated-dependencies: - dependency-name: org.springframework.boot:spring-boot-starter-parent dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 3624df61..a7f5a5c1 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ org.springframework.boot spring-boot-starter-parent - 3.3.3 + 3.3.4 From 78c5e6c2992db0bafb3cd3b401e00590c0f237ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julian=20Gr=C3=BCndner?= Date: Wed, 2 Oct 2024 17:39:30 +0200 Subject: [PATCH 22/43] bump ontology version --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index a7f5a5c1..39c8a36a 100644 --- a/pom.xml +++ b/pom.xml @@ -21,7 +21,7 @@ 17 1.20.1 - 3.0.0-test.1 + 3.0.0-test.9 From afcf51fc99a12a3e243e17e5f713f0b09134d312 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julian=20Gr=C3=BCndner?= Date: Mon, 21 Oct 2024 00:10:23 +0200 Subject: [PATCH 23/43] add trivy fix and update ontology version to v3.0.0-alpha --- .github/workflows/build.yml | 3 +++ pom.xml | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c1c9dd7a..039b1d16 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -94,6 +94,9 @@ jobs: output: trivy-results.sarif severity: 'CRITICAL,HIGH' timeout: '15m0s' + env: + TRIVY_DB_REPOSITORY: public.ecr.aws/aquasecurity/trivy-db:2 + TRIVY_JAVA_DB_REPOSITORY: public.ecr.aws/aquasecurity/trivy-java-db:1 - name: Upload Trivy Scan Results to GitHub Security Tab uses: github/codeql-action/upload-sarif@v3 diff --git a/pom.xml b/pom.xml index 39c8a36a..acddcdea 100644 --- a/pom.xml +++ b/pom.xml @@ -21,7 +21,7 @@ 17 1.20.1 - 3.0.0-test.9 + 3.0.0-alpha From 6be61cfb9c3555f9ea842085ec8a9552f30a6043 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julian=20Gr=C3=BCndner?= Date: Mon, 21 Oct 2024 00:30:29 +0200 Subject: [PATCH 24/43] Release v2.4.0-alpha.2 --- CHANGELOG.md | 4 ++++ docker-compose.yml | 2 +- pom.xml | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f6908e5..2a27b3f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## v2.4.0-alpha.2 + +* Updated ontology to v3.0.0-alpha + ## v2.4.0-alpha.1 ### Enhancements diff --git a/docker-compose.yml b/docker-compose.yml index 36fc942b..30622073 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,6 +1,6 @@ services: flare: - image: ghcr.io/medizininformatik-initiative/flare:2.3.0 + image: ghcr.io/medizininformatik-initiative/flare:2.3.0-alpha.2 ports: - 8080:8080 volumes: diff --git a/pom.xml b/pom.xml index acddcdea..dab95319 100644 --- a/pom.xml +++ b/pom.xml @@ -13,7 +13,7 @@ de.medizininformatik-initiative flare - 2.4.0-alpha.1 + 2.4.0-alpha.2 Flare Flare From 7cf4169b78e7c4c555396dc37d60cb186c1b2626 Mon Sep 17 00:00:00 2001 From: Alexander Kiel Date: Wed, 6 Nov 2024 15:12:05 +0100 Subject: [PATCH 25/43] Refactor StepVerifier Verify Calls --- .../medizininformatikinitiative/flare/service/DataStore.java | 1 + src/test/java/de/medizininformatikinitiative/MonosTest.java | 2 +- .../flare/service/DataStoreTest.java | 4 ++-- .../flare/service/StructuredQueryServiceTest.java | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/main/java/de/medizininformatikinitiative/flare/service/DataStore.java b/src/main/java/de/medizininformatikinitiative/flare/service/DataStore.java index 5f6405da..653ec1a7 100644 --- a/src/main/java/de/medizininformatikinitiative/flare/service/DataStore.java +++ b/src/main/java/de/medizininformatikinitiative/flare/service/DataStore.java @@ -76,6 +76,7 @@ private static boolean shouldRetry(HttpStatusCode code) { } private Mono fetchPage(String url) { + logger.trace("fetch page {}", url); return client.get() .uri(url) diff --git a/src/test/java/de/medizininformatikinitiative/MonosTest.java b/src/test/java/de/medizininformatikinitiative/MonosTest.java index e57da28e..faeec000 100644 --- a/src/test/java/de/medizininformatikinitiative/MonosTest.java +++ b/src/test/java/de/medizininformatikinitiative/MonosTest.java @@ -20,7 +20,7 @@ class OfEither { void ofLeft() { var result = Monos.ofEither(Either.left(new Exception(MESSAGE))); - StepVerifier.create(result).expectErrorMessage(MESSAGE).verify(); + StepVerifier.create(result).verifyErrorMessage(MESSAGE); } @Test diff --git a/src/test/java/de/medizininformatikinitiative/flare/service/DataStoreTest.java b/src/test/java/de/medizininformatikinitiative/flare/service/DataStoreTest.java index 839a6e27..ea4e43fb 100644 --- a/src/test/java/de/medizininformatikinitiative/flare/service/DataStoreTest.java +++ b/src/test/java/de/medizininformatikinitiative/flare/service/DataStoreTest.java @@ -66,7 +66,7 @@ void execute_retry_fails() { var result = dataStore.execute(Query.ofType("Observation")); - StepVerifier.create(result).expectErrorMessage("Retries exhausted: 3/3").verify(); + StepVerifier.create(result).verifyErrorMessage("Retries exhausted: 3/3"); } @Test @@ -76,6 +76,6 @@ void execute_retry_400() { var result = dataStore.execute(Query.ofType("Observation")); - StepVerifier.create(result).expectError(WebClientResponseException.BadRequest.class).verify(); + StepVerifier.create(result).verifyError(WebClientResponseException.BadRequest.class); } } diff --git a/src/test/java/de/medizininformatikinitiative/flare/service/StructuredQueryServiceTest.java b/src/test/java/de/medizininformatikinitiative/flare/service/StructuredQueryServiceTest.java index e3fa3e45..5ce6a4cc 100644 --- a/src/test/java/de/medizininformatikinitiative/flare/service/StructuredQueryServiceTest.java +++ b/src/test/java/de/medizininformatikinitiative/flare/service/StructuredQueryServiceTest.java @@ -65,7 +65,7 @@ void setUp() { void execute() { var result = service.execute(query); - StepVerifier.create(result).expectError(MappingNotFoundException.class).verify(); + StepVerifier.create(result).verifyError(MappingNotFoundException.class); } @Test From 66db740f42adf50b4de7ed721024fbccac9ad85d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julian=20Gr=C3=BCndner?= Date: Thu, 14 Nov 2024 17:26:36 +0100 Subject: [PATCH 26/43] Add toggle to enable cohort endpoint --- README.md | 1 + docs/api.md | 62 +++++++++++++++++++ .../flare/rest/QueryController.java | 15 +++-- src/main/resources/application.yml | 2 + .../flare/rest/QueryControllerTest.java | 40 +++++++++++- 5 files changed, 114 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 82b4e83f..271c460f 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,7 @@ In order for flare to work, a FHIR server such as [Blaze](https://github.com/sam | FLARE_CACHE_DISK_THREADS | 4 | | The number of threads the disk cache should use for reading and writing entries. | | FLARE_MAPPING_MAPPING_FILE | ontology/codex-term-code-mapping.json | v2.4.0 | The mappings to use. | | FLARE_MAPPING_CONCEPT_TREE_FILE | ontology/codex-code-tree.json | v2.4.0 | The code tree to use. | +| FLARE_COHORT_ENABLED | false | | Toggle - enables the cohort extraction endpoint, which returns the actual patient ids | | SERVER_PORT | 8080 | | The port at which Flare provides its REST API. | | JAVA_TOOL_OPTIONS | -Xmx4g | | JVM options \(Docker only\) | | LOG_LEVEL | info | | one of trace, debug, info, warn or error | diff --git a/docs/api.md b/docs/api.md index f57245a0..912df69c 100644 --- a/docs/api.md +++ b/docs/api.md @@ -45,6 +45,68 @@ The result of that query could be: 31910 ``` +## Execute Cohort (Cohort Extraction) + +> [!CAUTION] +> This feature is disabled by default. Be aware that it returns the actual patient IDs (techinal IDs of the FHIR server) for the patients. +> To enable set the env var FLARE_COHORT_ENABLED=true + +The ExecuteCohort-Endpoint returns a list of patient ids for patients fitting the criteria of the structured query that is passed in the body of a POST-Request. Also, the POST-Request must contain `application/sq+json` as Content-Type. It can be called like this: + +```sh +curl -s http://localhost:8080/query/execute-cohort -H "Content-Type: application/sq+json" -d '' +``` + +An example `` is: +```json +{ + "version": "https://medizininformatik-initiative.de/fdpg/StructuredQuery/v3/schema", + "display": "", + "inclusionCriteria": [ + [ + { + "termCodes": [ + { + "code": "263495000", + "system": "http://snomed.info/sct", + "display": "Geschlecht" + } + ], + "valueFilter": { + "selectedConcepts": [ + { + "code": "female", + "system": "http://hl7.org/fhir/administrative-gender", + "display": "Female" + } + ], + "type": "concept" + } + } + ] + ] +} +``` + +The result of that query could be: +```json +[ + "VHF02060", + "VHF02061", + "VHF02062", + "VHF02066", + "VHF02067", + "VHF02069", + "VHF02072", + "VHF02073", + "VHF02075", + "VHF02076", + "VHF02082", + "VHF02083" +] +``` + + ## Translate Flare also provides a Translate-Endpoint, which returns the separate FHIR Search queries wrapped in a json format that shows each set operation that is necessary to fit the original inclusion/ exclusion structure. diff --git a/src/main/java/de/medizininformatikinitiative/flare/rest/QueryController.java b/src/main/java/de/medizininformatikinitiative/flare/rest/QueryController.java index 283e932b..538926ef 100644 --- a/src/main/java/de/medizininformatikinitiative/flare/rest/QueryController.java +++ b/src/main/java/de/medizininformatikinitiative/flare/rest/QueryController.java @@ -7,6 +7,7 @@ import de.medizininformatikinitiative.flare.service.StructuredQueryService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.http.MediaType; import org.springframework.stereotype.Component; @@ -38,10 +39,16 @@ public QueryController(StructuredQueryService queryService) { } @Bean - public RouterFunction queryRouter() { - return route(POST("query/execute").and(accept(MEDIA_TYPE_SQ)), this::execute) - .andRoute(POST("query/translate").and(accept(MEDIA_TYPE_SQ)), this::translate) - .andRoute(POST("query/execute-cohort").and(accept(MEDIA_TYPE_SQ)), this::executeCohort); + public RouterFunction queryRouter(@Value("${flare.cohort.enabled}") boolean cohortEnabled) { + + var route = route(POST("query/execute").and(accept(MEDIA_TYPE_SQ)), this::execute) + .andRoute(POST("query/translate").and(accept(MEDIA_TYPE_SQ)), this::translate); + + if (cohortEnabled){ + route = route.andRoute(POST("query/execute-cohort").and(accept(MEDIA_TYPE_SQ)), this::executeCohort); + } + + return route; } public Mono execute(ServerRequest request) { diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index dbf1b040..326d80da 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -1,4 +1,6 @@ flare: + cohort: + enabled: false fhir: server: 'http://localhost:8082/fhir' user: '' diff --git a/src/test/java/de/medizininformatikinitiative/flare/rest/QueryControllerTest.java b/src/test/java/de/medizininformatikinitiative/flare/rest/QueryControllerTest.java index 1268ea7c..8ba19d52 100644 --- a/src/test/java/de/medizininformatikinitiative/flare/rest/QueryControllerTest.java +++ b/src/test/java/de/medizininformatikinitiative/flare/rest/QueryControllerTest.java @@ -15,6 +15,9 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.http.MediaType; +import org.springframework.test.context.event.annotation.BeforeTestExecution; +import org.springframework.test.context.event.annotation.BeforeTestMethod; +import org.springframework.test.context.transaction.BeforeTransaction; import org.springframework.test.web.reactive.server.WebTestClient; import org.testcontainers.shaded.com.fasterxml.jackson.core.JsonProcessingException; import org.testcontainers.shaded.com.fasterxml.jackson.databind.ObjectMapper; @@ -45,7 +48,7 @@ class QueryControllerTest { @BeforeEach void setUp() { - client = WebTestClient.bindToRouterFunction(controller.queryRouter()).build(); + client = WebTestClient.bindToRouterFunction(controller.queryRouter(true)).build(); } @Test @@ -82,7 +85,6 @@ void execute() { .expectBody().json("1"); } - @Test void executeCohort() throws JsonProcessingException { when(queryService.execute(STRUCTURED_QUERY)).thenReturn(Mono.just(Population.of(PATIENT_ID, PATIENT_ID_1))); @@ -119,6 +121,40 @@ void executeCohort() throws JsonProcessingException { .expectBody().json(objectMapper.writeValueAsString(List.of(PATIENT_ID, PATIENT_ID_1))); } + @Test + void executeCohortDisabled() { + + client = WebTestClient.bindToRouterFunction(controller.queryRouter(false)).build(); + + client.post() + .uri("/query/execute-cohort") + .contentType(MEDIA_TYPE_SQ) + .bodyValue(""" + { + "inclusionCriteria": [ + [ + { + "context": { + "system": "context-system", + "code": "context-code", + "display": "context-display" + }, + "termCodes": [ + { + "system": "http://snomed.info/sct", + "code": "386661006", + "display": "Fever (finding)" + } + ] + } + ] + ] + } + """) + .exchange() + .expectStatus().isNotFound(); + } + @Test void execute_error() { when(queryService.execute(STRUCTURED_QUERY)).thenReturn(Mono.error(new MappingNotFoundException(ContextualTermCode.of(TestUtil.CONTEXT, FEVER)))); From 0135c2e600a698d94a0e0f1324e1dcfb9f7c99c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julian=20Gr=C3=BCndner?= Date: Thu, 14 Nov 2024 19:46:29 +0100 Subject: [PATCH 27/43] Add integration test for cohort endpoint --- .../no-auth-cohort-enabled/docker-compose.yml | 40 +++++++++++++++++++ .../no-auth-cohort-enabled/load-data.sh | 5 +++ .../scripts/execute-cohort-query-enabled.sh | 39 ++++++++++++++++++ .../execute-cohort-query-not-enabled.sh | 12 ++++++ .github/scripts/util.sh | 28 +++++++++++++ .github/workflows/build.yml | 21 +++++++--- README.md | 2 +- .../flare/rest/QueryController.java | 2 +- src/main/resources/application.yml | 2 +- 9 files changed, 142 insertions(+), 9 deletions(-) create mode 100644 .github/integration-test/no-auth-cohort-enabled/docker-compose.yml create mode 100755 .github/integration-test/no-auth-cohort-enabled/load-data.sh create mode 100755 .github/scripts/execute-cohort-query-enabled.sh create mode 100755 .github/scripts/execute-cohort-query-not-enabled.sh diff --git a/.github/integration-test/no-auth-cohort-enabled/docker-compose.yml b/.github/integration-test/no-auth-cohort-enabled/docker-compose.yml new file mode 100644 index 00000000..d8e9cd14 --- /dev/null +++ b/.github/integration-test/no-auth-cohort-enabled/docker-compose.yml @@ -0,0 +1,40 @@ +services: + data-store: + image: "samply/blaze:0.29" + healthcheck: + test: ["CMD-SHELL", "curl --fail -s http://localhost:8080/health"] + interval: "5s" + timeout: "5s" + retries: "3" + start_period: "60s" + environment: + BASE_URL: "http://data-store:8080" + JAVA_TOOL_OPTIONS: "-Xmx1g" + LOG_LEVEL: "debug" + ports: + - "8082:8080" + volumes: + - "data-store-data:/app/data" + flare: + image: "flare:latest" + healthcheck: + test: ["CMD-SHELL", "curl --fail -s http://localhost:8080/cache/stats"] + interval: "5s" + timeout: "5s" + retries: "3" + start_period: "60s" + environment: + JAVA_TOOL_OPTIONS: "-Xmx1g" + FLARE_FHIR_SERVER: "http://data-store:8080/fhir" + LOG_LEVEL: "debug" + FLARE_ENABLE_COHORT_ENDPOINT: true + ports: + - "8080:8080" + volumes: + - "flare-cache:/app/cache" + - "../synthea-test-mapping:/app/ontology" + depends_on: + - data-store +volumes: + data-store-data: + flare-cache: diff --git a/.github/integration-test/no-auth-cohort-enabled/load-data.sh b/.github/integration-test/no-auth-cohort-enabled/load-data.sh new file mode 100755 index 00000000..57e469aa --- /dev/null +++ b/.github/integration-test/no-auth-cohort-enabled/load-data.sh @@ -0,0 +1,5 @@ +#!/bin/bash -e + +SCRIPT_DIR="$(dirname "$(readlink -f "$0")")" + +blazectl --no-progress --server http://localhost:8082/fhir upload "$SCRIPT_DIR/../../test-data/synthea" diff --git a/.github/scripts/execute-cohort-query-enabled.sh b/.github/scripts/execute-cohort-query-enabled.sh new file mode 100755 index 00000000..9e02c145 --- /dev/null +++ b/.github/scripts/execute-cohort-query-enabled.sh @@ -0,0 +1,39 @@ +#!/bin/bash -e + +SCRIPT_DIR="$(dirname "$(readlink -f "$0")")" +. "util.sh" + + +BASE="http://localhost:8080" +curl_response=$(curl -s "$BASE/query/execute-cohort" -H "Content-Type: application/sq+json" -d @"../integration-test/query-$1.json") + +ACTUAL_PAT_ID_ARRAY=() + +while IFS= read -r line; do + ACTUAL_PAT_ID_ARRAY+=("$line") +done < <(echo "$curl_response" | jq -r '.[]') + + +EXPECTED_PAT_ID_ARRAY=( + "DEZLXBBZCMJRHIWP" "DEZLXBBZGCL4O6LJ" "DEZLXBC7JY7I7VTM" "DEZLXBCWHPIDEXQP" + "DEZLXBD2ZFD7ICE5" "DEZLXBD6YMHCAI5B" "DEZLXBDDTY5KODRB" "DEZLXBDGKPNNPOLS" + "DEZLXBDLW2IYJNEV" "DEZLXBDQSTBEEKD3" "DEZLXBDVCCO6KA5K" "DEZLXBDYX5VLFMDF" + "DEZLXBEGC5T2PBAM" "DEZLXBEJDWUZP7A5" "DEZLXBEKK3HVHOCL" "DEZLXBESEJYLN2RV" + "DEZLXBEZ5C5V77WP" "DEZLXBF4MNTUBRUT" "DEZLXBFBLJUS3IS6" "DEZLXBFE2ON4CWLO" + "DEZLXBG3MKK6PUVX" "DEZLXBG7NVKBU3JG" "DEZLXBGKPFVZ7AI3" "DEZLXBGKU74A44Z7" + "DEZLXBGQ44BQHYDL" "DEZLXBGQDKOZHFU6" "DEZLXBGTXJS2VHEG" "DEZLXBGVQRWEBUGV" + "DEZLXBH6JTYKLN37" "DEZLXBHP4DGCTTRN" "DEZLXBHQG2CBAKXS" "DEZLXBI4I24ZCM3H" + "DEZLXBI5A2TSPB5S" "DEZLXBIIP6ZUTPIA" "DEZLXBIMZPVN64B3" "DEZLXBIT7CK3VRVH" + "DEZLXBIUGA4R4KTC" "DEZLXBJJWNJTPMYC" "DEZLXBK3LRLPGP2U" "DEZLXBK4R55CSR2X" + "DEZLXBKAF4GKMAQD" "DEZLXBKFWGK4TDK5" "DEZLXBKIQNZAHQJV" "DEZLXBL7YHECLK7W" + "DEZLXBLD72E7CEJT" "DEZLXBLFX7YBP6HI" "DEZLXBLNYKQLJS5T" "DEZLXBLQGE5EKJGE" + "DEZLXBM7F2I6MGPG" "DEZLXBMJLDY5OXHS" "DEZLXBMOJAE635MI" "DEZLXBMQR4FTZKEP" + "DEZLXBN5O5IJB2OM" "DEZLXBN7PFFZDAYM" "DEZLXBNCGIA6Y2P7" "DEZLXBNIU42GXBTO" + "DEZLXBNM37HOP6GW" "DEZLXBNU37U7ZIFR" "DEZLXBNY4SPRN7MD" "DEZLXBOS3OMTQZ42" + "DEZLXBPJTYSY4PUU" "DEZLXBPJZEVQ6JYY" "DEZLXBPNIOO7JG4O" "DEZLXBPSXO5T4ZIO" + "DEZLXBQI5WPVLRR4" "DEZLXBQWR5KHBB44" "DEZLXBR4IC6RUSTD" "DEZLXBRJ4TA7BJYV" + "DEZLXBS5QKVCQCTT" + ) + + +test_array "cohort test" ACTUAL_PAT_ID_ARRAY EXPECTED_PAT_ID_ARRAY diff --git a/.github/scripts/execute-cohort-query-not-enabled.sh b/.github/scripts/execute-cohort-query-not-enabled.sh new file mode 100755 index 00000000..5b1d8aac --- /dev/null +++ b/.github/scripts/execute-cohort-query-not-enabled.sh @@ -0,0 +1,12 @@ +#!/bin/bash -e + +SCRIPT_DIR="$(dirname "$(readlink -f "$0")")" +. "util.sh" + + +BASE="http://localhost:8080" +curl_response=$(curl -s "$BASE/query/execute-cohort" -H "Content-Type: application/sq+json" -d @"../integration-test/query-$1.json") + +status_code=$(curl -s -o /dev/null -w "%{http_code}" "$BASE/query/execute-cohort" -H "Content-Type: application/sq+json" -d @"../integration-test/query-$1.json") + +test "cohort endpoint status code" $status_code 404 diff --git a/.github/scripts/util.sh b/.github/scripts/util.sh index 93519cd2..5bc3ad8c 100644 --- a/.github/scripts/util.sh +++ b/.github/scripts/util.sh @@ -8,3 +8,31 @@ test() { exit 1 fi } + +test_array() { + local name="$1" + local array1_name="$2" + local array2_name="$3" + + # Get the length of both arrays + local len1=$(eval "echo \${#$array1_name[@]}") + local len2=$(eval "echo \${#$array2_name[@]}") + + # Check if the arrays have the same length + if [ "$len1" -ne "$len2" ]; then + echo "Fail: the $name arrays have different lengths ($len1 vs $len2)" + exit 1 + fi + + # Compare each element + for i in $(seq 0 $((len1 - 1))); do + local val1=$(eval "echo \${$array1_name[$i]}") + local val2=$(eval "echo \${$array2_name[$i]}") + if [ "$val1" != "$val2" ]; then + echo "Fail: the $name arrays differ at index $i: $val1 != $val2" + exit 1 + fi + done + + echo "OK: the $name arrays are equal" +} diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 039b1d16..aaf28c74 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -3,16 +3,16 @@ name: Build on: push: branches: - - main - - develop + - main + - develop tags: - - 'v*.*.*' + - 'v*.*.*' pull_request: branches: - - main - - develop + - main + - develop schedule: - - cron: '0 1 * * *' + - cron: '0 1 * * *' jobs: build: @@ -134,6 +134,7 @@ jobs: - no-auth - basic-auth - oauth + - no-auth-cohort-enabled runs-on: ubuntu-22.04 steps: @@ -161,6 +162,14 @@ jobs: - name: Query for Viral sinusitis (disorder) run: .github/scripts/execute-query.sh 444814009 69 + - name: Query for Viral sinusitis (disorder) cohort not enabled + if: ${{ matrix.test != 'no-auth-cohort-enabled' }} + run: .github/scripts/execute-cohort-query-not-enabled.sh 444814009 + + - name: Query for Viral sinusitis (disorder) cohort enabled + if: ${{ matrix.test == 'no-auth-cohort-enabled' }} + run: .github/scripts/execute-cohort-query-enabled.sh 444814009 + push-image: needs: - build diff --git a/README.md b/README.md index 271c460f..09db85eb 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ In order for flare to work, a FHIR server such as [Blaze](https://github.com/sam | FLARE_CACHE_DISK_THREADS | 4 | | The number of threads the disk cache should use for reading and writing entries. | | FLARE_MAPPING_MAPPING_FILE | ontology/codex-term-code-mapping.json | v2.4.0 | The mappings to use. | | FLARE_MAPPING_CONCEPT_TREE_FILE | ontology/codex-code-tree.json | v2.4.0 | The code tree to use. | -| FLARE_COHORT_ENABLED | false | | Toggle - enables the cohort extraction endpoint, which returns the actual patient ids | +| FLARE_ENABLE_COHORT_ENDPOINT | false | | Set to `true` to enable the cohort extraction endpoint, which returns the actual patient IDs. | | SERVER_PORT | 8080 | | The port at which Flare provides its REST API. | | JAVA_TOOL_OPTIONS | -Xmx4g | | JVM options \(Docker only\) | | LOG_LEVEL | info | | one of trace, debug, info, warn or error | diff --git a/src/main/java/de/medizininformatikinitiative/flare/rest/QueryController.java b/src/main/java/de/medizininformatikinitiative/flare/rest/QueryController.java index 538926ef..e2d26cb6 100644 --- a/src/main/java/de/medizininformatikinitiative/flare/rest/QueryController.java +++ b/src/main/java/de/medizininformatikinitiative/flare/rest/QueryController.java @@ -44,7 +44,7 @@ public RouterFunction queryRouter(@Value("${flare.cohort.enabled var route = route(POST("query/execute").and(accept(MEDIA_TYPE_SQ)), this::execute) .andRoute(POST("query/translate").and(accept(MEDIA_TYPE_SQ)), this::translate); - if (cohortEnabled){ + if (cohortEnabled) { route = route.andRoute(POST("query/execute-cohort").and(accept(MEDIA_TYPE_SQ)), this::executeCohort); } diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 326d80da..bc7e4e57 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -1,6 +1,6 @@ flare: cohort: - enabled: false + enabled: ${FLARE_ENABLE_COHORT_ENDPOINT:false} fhir: server: 'http://localhost:8082/fhir' user: '' From f09edc21189acd2ec8027403d41d292b4897162f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julian=20Gr=C3=BCndner?= Date: Thu, 14 Nov 2024 19:59:48 +0100 Subject: [PATCH 28/43] fix script dirs for new test scripts --- .github/scripts/execute-cohort-query-enabled.sh | 9 +++------ .github/scripts/execute-cohort-query-not-enabled.sh | 10 ++++------ 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/.github/scripts/execute-cohort-query-enabled.sh b/.github/scripts/execute-cohort-query-enabled.sh index 9e02c145..fbc8a547 100755 --- a/.github/scripts/execute-cohort-query-enabled.sh +++ b/.github/scripts/execute-cohort-query-enabled.sh @@ -1,18 +1,16 @@ #!/bin/bash -e SCRIPT_DIR="$(dirname "$(readlink -f "$0")")" -. "util.sh" - +. "$SCRIPT_DIR/util.sh" BASE="http://localhost:8080" -curl_response=$(curl -s "$BASE/query/execute-cohort" -H "Content-Type: application/sq+json" -d @"../integration-test/query-$1.json") +CURL_RESPONSE=$(curl -s "$BASE/query/execute-cohort" -H "Content-Type: application/sq+json" -d @"$SCRIPT_DIR/../integration-test/query-$1.json") ACTUAL_PAT_ID_ARRAY=() while IFS= read -r line; do ACTUAL_PAT_ID_ARRAY+=("$line") -done < <(echo "$curl_response" | jq -r '.[]') - +done < <(echo "$CURL_RESPONSE" | jq -r '.[]') EXPECTED_PAT_ID_ARRAY=( "DEZLXBBZCMJRHIWP" "DEZLXBBZGCL4O6LJ" "DEZLXBC7JY7I7VTM" "DEZLXBCWHPIDEXQP" @@ -35,5 +33,4 @@ EXPECTED_PAT_ID_ARRAY=( "DEZLXBS5QKVCQCTT" ) - test_array "cohort test" ACTUAL_PAT_ID_ARRAY EXPECTED_PAT_ID_ARRAY diff --git a/.github/scripts/execute-cohort-query-not-enabled.sh b/.github/scripts/execute-cohort-query-not-enabled.sh index 5b1d8aac..438e277a 100755 --- a/.github/scripts/execute-cohort-query-not-enabled.sh +++ b/.github/scripts/execute-cohort-query-not-enabled.sh @@ -1,12 +1,10 @@ #!/bin/bash -e SCRIPT_DIR="$(dirname "$(readlink -f "$0")")" -. "util.sh" - +. "$SCRIPT_DIR/util.sh" BASE="http://localhost:8080" -curl_response=$(curl -s "$BASE/query/execute-cohort" -H "Content-Type: application/sq+json" -d @"../integration-test/query-$1.json") - -status_code=$(curl -s -o /dev/null -w "%{http_code}" "$BASE/query/execute-cohort" -H "Content-Type: application/sq+json" -d @"../integration-test/query-$1.json") +STATUS_CODE=$(curl -s -o /dev/null -w "%{http_code}" "$BASE/query/execute-cohort" -H "Content-Type: application/sq+json" -d @"$SCRIPT_DIR/../integration-test/query-$1.json") +EXPECTED_STATUS_CODE=404 -test "cohort endpoint status code" $status_code 404 +test "cohort endpoint status code" $STATUS_CODE $EXPECTED_STATUS_CODE From 00a98d7ce6776170a410382e8c475a765bc2bec4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julian=20Gr=C3=BCndner?= Date: Thu, 14 Nov 2024 20:46:16 +0100 Subject: [PATCH 29/43] change test to check response array and array length with strings of size id length --- .../scripts/execute-cohort-query-enabled.sh | 26 ++-------------- .github/scripts/util.sh | 31 ++++++++++--------- .github/workflows/build.yml | 2 +- 3 files changed, 20 insertions(+), 39 deletions(-) diff --git a/.github/scripts/execute-cohort-query-enabled.sh b/.github/scripts/execute-cohort-query-enabled.sh index fbc8a547..d7689677 100755 --- a/.github/scripts/execute-cohort-query-enabled.sh +++ b/.github/scripts/execute-cohort-query-enabled.sh @@ -4,33 +4,13 @@ SCRIPT_DIR="$(dirname "$(readlink -f "$0")")" . "$SCRIPT_DIR/util.sh" BASE="http://localhost:8080" +EXPECTED_ARRAY_LENGTH=$2 +EXPECTED_ID_LENGTH=$3 CURL_RESPONSE=$(curl -s "$BASE/query/execute-cohort" -H "Content-Type: application/sq+json" -d @"$SCRIPT_DIR/../integration-test/query-$1.json") - ACTUAL_PAT_ID_ARRAY=() while IFS= read -r line; do ACTUAL_PAT_ID_ARRAY+=("$line") done < <(echo "$CURL_RESPONSE" | jq -r '.[]') -EXPECTED_PAT_ID_ARRAY=( - "DEZLXBBZCMJRHIWP" "DEZLXBBZGCL4O6LJ" "DEZLXBC7JY7I7VTM" "DEZLXBCWHPIDEXQP" - "DEZLXBD2ZFD7ICE5" "DEZLXBD6YMHCAI5B" "DEZLXBDDTY5KODRB" "DEZLXBDGKPNNPOLS" - "DEZLXBDLW2IYJNEV" "DEZLXBDQSTBEEKD3" "DEZLXBDVCCO6KA5K" "DEZLXBDYX5VLFMDF" - "DEZLXBEGC5T2PBAM" "DEZLXBEJDWUZP7A5" "DEZLXBEKK3HVHOCL" "DEZLXBESEJYLN2RV" - "DEZLXBEZ5C5V77WP" "DEZLXBF4MNTUBRUT" "DEZLXBFBLJUS3IS6" "DEZLXBFE2ON4CWLO" - "DEZLXBG3MKK6PUVX" "DEZLXBG7NVKBU3JG" "DEZLXBGKPFVZ7AI3" "DEZLXBGKU74A44Z7" - "DEZLXBGQ44BQHYDL" "DEZLXBGQDKOZHFU6" "DEZLXBGTXJS2VHEG" "DEZLXBGVQRWEBUGV" - "DEZLXBH6JTYKLN37" "DEZLXBHP4DGCTTRN" "DEZLXBHQG2CBAKXS" "DEZLXBI4I24ZCM3H" - "DEZLXBI5A2TSPB5S" "DEZLXBIIP6ZUTPIA" "DEZLXBIMZPVN64B3" "DEZLXBIT7CK3VRVH" - "DEZLXBIUGA4R4KTC" "DEZLXBJJWNJTPMYC" "DEZLXBK3LRLPGP2U" "DEZLXBK4R55CSR2X" - "DEZLXBKAF4GKMAQD" "DEZLXBKFWGK4TDK5" "DEZLXBKIQNZAHQJV" "DEZLXBL7YHECLK7W" - "DEZLXBLD72E7CEJT" "DEZLXBLFX7YBP6HI" "DEZLXBLNYKQLJS5T" "DEZLXBLQGE5EKJGE" - "DEZLXBM7F2I6MGPG" "DEZLXBMJLDY5OXHS" "DEZLXBMOJAE635MI" "DEZLXBMQR4FTZKEP" - "DEZLXBN5O5IJB2OM" "DEZLXBN7PFFZDAYM" "DEZLXBNCGIA6Y2P7" "DEZLXBNIU42GXBTO" - "DEZLXBNM37HOP6GW" "DEZLXBNU37U7ZIFR" "DEZLXBNY4SPRN7MD" "DEZLXBOS3OMTQZ42" - "DEZLXBPJTYSY4PUU" "DEZLXBPJZEVQ6JYY" "DEZLXBPNIOO7JG4O" "DEZLXBPSXO5T4ZIO" - "DEZLXBQI5WPVLRR4" "DEZLXBQWR5KHBB44" "DEZLXBR4IC6RUSTD" "DEZLXBRJ4TA7BJYV" - "DEZLXBS5QKVCQCTT" - ) - -test_array "cohort test" ACTUAL_PAT_ID_ARRAY EXPECTED_PAT_ID_ARRAY +test_array "cohort test" ACTUAL_PAT_ID_ARRAY "$EXPECTED_ID_LENGTH" "$EXPECTED_ARRAY_LENGTH" diff --git a/.github/scripts/util.sh b/.github/scripts/util.sh index 5bc3ad8c..91aa0381 100644 --- a/.github/scripts/util.sh +++ b/.github/scripts/util.sh @@ -11,28 +11,29 @@ test() { test_array() { local name="$1" - local array1_name="$2" - local array2_name="$3" + local array_name="$2" + local expected_length_id="$3" + local expected_array_length="$4" - # Get the length of both arrays - local len1=$(eval "echo \${#$array1_name[@]}") - local len2=$(eval "echo \${#$array2_name[@]}") + # Get the length of the array (number of elements) + local len=$(eval "echo \${#$array_name[@]}") - # Check if the arrays have the same length - if [ "$len1" -ne "$len2" ]; then - echo "Fail: the $name arrays have different lengths ($len1 vs $len2)" + # Check if the array has the expected number of elements + if [ "$len" -ne "$expected_array_length" ]; then + echo "Fail: the $name array has an unexpected number of elements ($len vs $expected_array_length)" exit 1 fi - # Compare each element - for i in $(seq 0 $((len1 - 1))); do - local val1=$(eval "echo \${$array1_name[$i]}") - local val2=$(eval "echo \${$array2_name[$i]}") - if [ "$val1" != "$val2" ]; then - echo "Fail: the $name arrays differ at index $i: $val1 != $val2" + # Compare the length of each string in the array + for i in $(seq 0 $((len - 1))); do + local val=$(eval "echo \${$array_name[$i]}") + + # Check if the string has the same expected length + if [ ${#val} -ne "$expected_length_id" ]; then + echo "Fail: the string at index $i in the $name array does not have the expected length of $expected_length_id: ${#val} != $expected_length_id" exit 1 fi done - echo "OK: the $name arrays are equal" + echo "OK: all strings in the $name array have the expected length of $expected_length_id, and the array has the expected number of elements ($expected_array_length)" } diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index aaf28c74..eebe2356 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -168,7 +168,7 @@ jobs: - name: Query for Viral sinusitis (disorder) cohort enabled if: ${{ matrix.test == 'no-auth-cohort-enabled' }} - run: .github/scripts/execute-cohort-query-enabled.sh 444814009 + run: .github/scripts/execute-cohort-query-enabled.sh 444814009 69 16 push-image: needs: From f3c3a2a84eff285591eac85b533865d52e585d12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julian=20Gr=C3=BCndner?= Date: Thu, 14 Nov 2024 21:26:53 +0100 Subject: [PATCH 30/43] simplify execution and only check enxpoint not enabled no auth --- .../scripts/execute-cohort-query-enabled.sh | 9 +++--- .../execute-cohort-query-not-enabled.sh | 2 +- .github/scripts/util.sh | 29 ------------------- .github/workflows/build.yml | 2 +- 4 files changed, 6 insertions(+), 36 deletions(-) diff --git a/.github/scripts/execute-cohort-query-enabled.sh b/.github/scripts/execute-cohort-query-enabled.sh index d7689677..f3b731b9 100755 --- a/.github/scripts/execute-cohort-query-enabled.sh +++ b/.github/scripts/execute-cohort-query-enabled.sh @@ -7,10 +7,9 @@ BASE="http://localhost:8080" EXPECTED_ARRAY_LENGTH=$2 EXPECTED_ID_LENGTH=$3 CURL_RESPONSE=$(curl -s "$BASE/query/execute-cohort" -H "Content-Type: application/sq+json" -d @"$SCRIPT_DIR/../integration-test/query-$1.json") -ACTUAL_PAT_ID_ARRAY=() -while IFS= read -r line; do - ACTUAL_PAT_ID_ARRAY+=("$line") -done < <(echo "$CURL_RESPONSE" | jq -r '.[]') +ACTUAL_PAT_ID_ARRAY_LENGTH=$(echo "$CURL_RESPONSE" | jq '. | length') +ACTUAL_PAT_ID_ARRAY_ID_LENGTH=$(echo "$CURL_RESPONSE" | jq '.[] | length' | uniq) -test_array "cohort test" ACTUAL_PAT_ID_ARRAY "$EXPECTED_ID_LENGTH" "$EXPECTED_ARRAY_LENGTH" +test "cohort length" $ACTUAL_PAT_ID_ARRAY_LENGTH $EXPECTED_ARRAY_LENGTH +test "cohort id length" $ACTUAL_PAT_ID_ARRAY_ID_LENGTH $EXPECTED_ID_LENGTH diff --git a/.github/scripts/execute-cohort-query-not-enabled.sh b/.github/scripts/execute-cohort-query-not-enabled.sh index 438e277a..158eb0f3 100755 --- a/.github/scripts/execute-cohort-query-not-enabled.sh +++ b/.github/scripts/execute-cohort-query-not-enabled.sh @@ -7,4 +7,4 @@ BASE="http://localhost:8080" STATUS_CODE=$(curl -s -o /dev/null -w "%{http_code}" "$BASE/query/execute-cohort" -H "Content-Type: application/sq+json" -d @"$SCRIPT_DIR/../integration-test/query-$1.json") EXPECTED_STATUS_CODE=404 -test "cohort endpoint status code" $STATUS_CODE $EXPECTED_STATUS_CODE +test "cohort endpoint status code" "$STATUS_CODE" $EXPECTED_STATUS_CODE diff --git a/.github/scripts/util.sh b/.github/scripts/util.sh index 91aa0381..93519cd2 100644 --- a/.github/scripts/util.sh +++ b/.github/scripts/util.sh @@ -8,32 +8,3 @@ test() { exit 1 fi } - -test_array() { - local name="$1" - local array_name="$2" - local expected_length_id="$3" - local expected_array_length="$4" - - # Get the length of the array (number of elements) - local len=$(eval "echo \${#$array_name[@]}") - - # Check if the array has the expected number of elements - if [ "$len" -ne "$expected_array_length" ]; then - echo "Fail: the $name array has an unexpected number of elements ($len vs $expected_array_length)" - exit 1 - fi - - # Compare the length of each string in the array - for i in $(seq 0 $((len - 1))); do - local val=$(eval "echo \${$array_name[$i]}") - - # Check if the string has the same expected length - if [ ${#val} -ne "$expected_length_id" ]; then - echo "Fail: the string at index $i in the $name array does not have the expected length of $expected_length_id: ${#val} != $expected_length_id" - exit 1 - fi - done - - echo "OK: all strings in the $name array have the expected length of $expected_length_id, and the array has the expected number of elements ($expected_array_length)" -} diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index eebe2356..8f18c202 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -163,7 +163,7 @@ jobs: run: .github/scripts/execute-query.sh 444814009 69 - name: Query for Viral sinusitis (disorder) cohort not enabled - if: ${{ matrix.test != 'no-auth-cohort-enabled' }} + if: ${{ matrix.test == 'no-auth' }} run: .github/scripts/execute-cohort-query-not-enabled.sh 444814009 - name: Query for Viral sinusitis (disorder) cohort enabled From edff3e0d5ff4c3e0763484b515929c1acf5b40a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julian=20Gr=C3=BCndner?= Date: Thu, 14 Nov 2024 21:28:19 +0100 Subject: [PATCH 31/43] fix api.md --- docs/api.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api.md b/docs/api.md index 912df69c..b931654c 100644 --- a/docs/api.md +++ b/docs/api.md @@ -49,7 +49,7 @@ The result of that query could be: > [!CAUTION] > This feature is disabled by default. Be aware that it returns the actual patient IDs (techinal IDs of the FHIR server) for the patients. -> To enable set the env var FLARE_COHORT_ENABLED=true +> To enable set the env var FLARE_ENABLE_COHORT_ENDPOINT=true The ExecuteCohort-Endpoint returns a list of patient ids for patients fitting the criteria of the structured query that is passed in the body of a POST-Request. Also, the POST-Request must contain `application/sq+json` as Content-Type. It can be called like this: From d83c005d72033351b18faf671fcbe19268465d63 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 14 Nov 2024 20:53:03 +0000 Subject: [PATCH 32/43] Update samply/blaze Docker tag to v0.30 --- .github/integration-test/basic-auth/docker-compose.yml | 2 +- .../integration-test/no-auth-cohort-enabled/docker-compose.yml | 2 +- .github/integration-test/no-auth/docker-compose.yml | 2 +- .github/integration-test/oauth/docker-compose.yml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/integration-test/basic-auth/docker-compose.yml b/.github/integration-test/basic-auth/docker-compose.yml index 0b332f26..9bd9b730 100644 --- a/.github/integration-test/basic-auth/docker-compose.yml +++ b/.github/integration-test/basic-auth/docker-compose.yml @@ -1,6 +1,6 @@ services: data-store: - image: "samply/blaze:0.29" + image: "samply/blaze:0.30" healthcheck: test: ["CMD-SHELL", "curl --fail -s http://localhost:8080/health"] interval: "5s" diff --git a/.github/integration-test/no-auth-cohort-enabled/docker-compose.yml b/.github/integration-test/no-auth-cohort-enabled/docker-compose.yml index d8e9cd14..95236ab7 100644 --- a/.github/integration-test/no-auth-cohort-enabled/docker-compose.yml +++ b/.github/integration-test/no-auth-cohort-enabled/docker-compose.yml @@ -1,6 +1,6 @@ services: data-store: - image: "samply/blaze:0.29" + image: "samply/blaze:0.30" healthcheck: test: ["CMD-SHELL", "curl --fail -s http://localhost:8080/health"] interval: "5s" diff --git a/.github/integration-test/no-auth/docker-compose.yml b/.github/integration-test/no-auth/docker-compose.yml index d6495841..273c19e3 100644 --- a/.github/integration-test/no-auth/docker-compose.yml +++ b/.github/integration-test/no-auth/docker-compose.yml @@ -1,6 +1,6 @@ services: data-store: - image: "samply/blaze:0.29" + image: "samply/blaze:0.30" healthcheck: test: ["CMD-SHELL", "curl --fail -s http://localhost:8080/health"] interval: "5s" diff --git a/.github/integration-test/oauth/docker-compose.yml b/.github/integration-test/oauth/docker-compose.yml index 99b609b0..37fbbc09 100644 --- a/.github/integration-test/oauth/docker-compose.yml +++ b/.github/integration-test/oauth/docker-compose.yml @@ -72,7 +72,7 @@ services: keycloak: condition: service_healthy data-store: - image: "samply/blaze:0.29" + image: "samply/blaze:0.30" healthcheck: test: ["CMD-SHELL", "curl --fail -s http://localhost:8080/health"] interval: "5s" From 8b4b5fc337498b48908f4d0814031d41d8926c75 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 15 Nov 2024 06:27:55 +0000 Subject: [PATCH 33/43] Update dependency org.springframework.boot:spring-boot-starter-parent to v3.3.5 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index dab95319..3c8f0266 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ org.springframework.boot spring-boot-starter-parent - 3.3.4 + 3.3.5 From 4905be91c80fb7971c0bf8ef469e8200fed4c5cb Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 15 Nov 2024 06:42:15 +0000 Subject: [PATCH 34/43] Update eclipse-temurin Docker tag to v21.0.5_11-jre --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 4e71852c..2f8cc3d5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM eclipse-temurin:21.0.3_9-jre +FROM eclipse-temurin:21.0.5_11-jre RUN apt-get update && apt-get upgrade -y && \ apt-get purge wget libbinutils libctf0 libctf-nobfd0 libncurses6 -y && \ From e7826415ed86e2cdb3e7acb33db2042c7c8feac2 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 15 Nov 2024 07:22:49 +0000 Subject: [PATCH 35/43] Update nginx Docker tag to v1.27.2 --- .github/integration-test/basic-auth/docker-compose.yml | 2 +- .github/integration-test/oauth/docker-compose.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/integration-test/basic-auth/docker-compose.yml b/.github/integration-test/basic-auth/docker-compose.yml index 9bd9b730..b212f4a0 100644 --- a/.github/integration-test/basic-auth/docker-compose.yml +++ b/.github/integration-test/basic-auth/docker-compose.yml @@ -37,7 +37,7 @@ services: depends_on: - data-store proxy: - image: "nginx:1.27.1" + image: "nginx:1.27.2" volumes: - "./nginx.conf:/etc/nginx/nginx.conf" - "./proxy.htpasswd:/etc/auth/.htpasswd" diff --git a/.github/integration-test/oauth/docker-compose.yml b/.github/integration-test/oauth/docker-compose.yml index 37fbbc09..24dfad06 100644 --- a/.github/integration-test/oauth/docker-compose.yml +++ b/.github/integration-test/oauth/docker-compose.yml @@ -50,7 +50,7 @@ services: volumes: - "./realm-test.json:/opt/keycloak/data/import/realm-test.json" proxy: - image: "nginx:1.27.1" + image: "nginx:1.27.2" healthcheck: test: ["CMD-SHELL", "curl --fail -s http://localhost:8080"] interval: "5s" From b409391f171323249db64b6f803f0a450097a6c4 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 15 Nov 2024 09:58:40 +0000 Subject: [PATCH 36/43] Update testcontainers-java monorepo to v1.20.3 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 3c8f0266..e85da458 100644 --- a/pom.xml +++ b/pom.xml @@ -20,7 +20,7 @@ 17 - 1.20.1 + 1.20.3 3.0.0-alpha From f2df77f2fdd9d90accb32ffbd9a91ed34ff354b3 Mon Sep 17 00:00:00 2001 From: Alexander Kiel Date: Fri, 15 Nov 2024 11:19:29 +0100 Subject: [PATCH 37/43] Release v2.4.0-alpha.3 --- CHANGELOG.md | 9 +++++++++ README.md | 6 ++++++ docker-compose.yml | 2 +- pom.xml | 4 ++-- 4 files changed, 18 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a27b3f6..a07cea60 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,16 @@ # Changelog +## v2.4.0-alpha.3 + +### Enhancements + +* Add Feature Toggle for the Cohort Endpoint ([#217](https://github.com/medizininformatik-initiative/flare/pull/217)) +* Updated ontology to v3.0.0-alpha.1 + ## v2.4.0-alpha.2 +### Enhancements + * Updated ontology to v3.0.0-alpha ## v2.4.0-alpha.1 diff --git a/README.md b/README.md index 09db85eb..b560243e 100644 --- a/README.md +++ b/README.md @@ -79,6 +79,12 @@ have the .pem extension. * [REST API](docs/api.md) +## FHIR Endpoint Requirements + +The FHIR endpoint used by FLARE (see `FLARE_FHIR_SERVER`) has to support the following parts of the FHIR specification: + +* the search result parameters `_count` and `_elements` should be supported for optimal performance + ## License Copyright ??? diff --git a/docker-compose.yml b/docker-compose.yml index 30622073..580ee74d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,6 +1,6 @@ services: flare: - image: ghcr.io/medizininformatik-initiative/flare:2.3.0-alpha.2 + image: ghcr.io/medizininformatik-initiative/flare:2.4.0-alpha.3 ports: - 8080:8080 volumes: diff --git a/pom.xml b/pom.xml index e85da458..a4d26cd7 100644 --- a/pom.xml +++ b/pom.xml @@ -13,7 +13,7 @@ de.medizininformatik-initiative flare - 2.4.0-alpha.2 + 2.4.0-alpha.3 Flare Flare @@ -21,7 +21,7 @@ 17 1.20.3 - 3.0.0-alpha + 3.0.0-alpha.1 From ab20e62fa7edcb2810aa28701ddc07398ca5c84e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 15 Nov 2024 10:49:47 +0000 Subject: [PATCH 38/43] Update keycloak/keycloak Docker tag to v25.0.6 --- .github/integration-test/oauth/docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/integration-test/oauth/docker-compose.yml b/.github/integration-test/oauth/docker-compose.yml index 24dfad06..a655ecb3 100644 --- a/.github/integration-test/oauth/docker-compose.yml +++ b/.github/integration-test/oauth/docker-compose.yml @@ -29,7 +29,7 @@ services: generate-cert: condition: service_completed_successfully keycloak: - image: "keycloak/keycloak:25.0.4" + image: "keycloak/keycloak:25.0.6" command: ["start", "--import-realm"] healthcheck: test: ["CMD-SHELL", "exec 3<>/dev/tcp/127.0.0.1/9000;echo -e \"GET /health/ready HTTP/1.1\r\nhost: localhost\r\nConnection: close\r\n\r\n\" >&3;grep \"HTTP/1.1 200 OK\" <&3"] From d6846784c89085e120ef5a881a1f827440de3c2d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 15 Nov 2024 14:38:00 +0000 Subject: [PATCH 39/43] Update dependency com.googlecode.maven-download-plugin:download-maven-plugin to v1.11.3 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index a4d26cd7..d6d44744 100644 --- a/pom.xml +++ b/pom.xml @@ -197,7 +197,7 @@ com.googlecode.maven-download-plugin download-maven-plugin - 1.9.0 + 1.11.3 download-ontology From aeee1d2426f95819427701c67ff5ea7d97bc48a6 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 15 Nov 2024 14:55:28 +0000 Subject: [PATCH 40/43] Update dependency org.rocksdb:rocksdbjni to v9.7.3 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index d6d44744..43cbea61 100644 --- a/pom.xml +++ b/pom.xml @@ -61,7 +61,7 @@ org.rocksdb rocksdbjni - 9.5.2 + 9.7.3 From 22dd1182ea0c80913f246433d751969e1b066ac4 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 15 Nov 2024 15:16:17 +0000 Subject: [PATCH 41/43] Update dependency ubuntu to v24 --- .github/workflows/build.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8f18c202..eb60b19e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -16,7 +16,7 @@ on: jobs: build: - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v4 @@ -71,7 +71,7 @@ jobs: image-scan: needs: build - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - name: Download Flare Image @@ -105,7 +105,7 @@ jobs: integration-test-default-config: needs: build - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - name: Check out Git repository @@ -135,7 +135,7 @@ jobs: - basic-auth - oauth - no-auth-cohort-enabled - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - name: Check out Git repository @@ -176,7 +176,7 @@ jobs: - integration-test-default-config - integration-test - image-scan - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 if: ${{ ! startsWith(github.head_ref, 'dependabot/')}} steps: From 94fef0ab5d6d47a40585c975d912ab599672b4da Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 19 Nov 2024 12:12:00 +0000 Subject: [PATCH 42/43] Update dependency com.googlecode.maven-download-plugin:download-maven-plugin to v1.12.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 43cbea61..d8d273e9 100644 --- a/pom.xml +++ b/pom.xml @@ -197,7 +197,7 @@ com.googlecode.maven-download-plugin download-maven-plugin - 1.11.3 + 1.12.0 download-ontology From fe4b78a524a998dc92f2eaa89a5b3726cad4cff7 Mon Sep 17 00:00:00 2001 From: Alexander Kiel Date: Wed, 20 Nov 2024 20:42:44 +0100 Subject: [PATCH 43/43] Release v2.4.0 --- CHANGELOG.md | 12 ++++++++++++ docker-compose.yml | 2 +- pom.xml | 4 ++-- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a07cea60..5f72d077 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Changelog +## v2.4.0 + +### Enhancements + +* Updated ontology to v3.0.0 +* Add Feature Toggle for the Cohort Endpoint ([#217](https://github.com/medizininformatik-initiative/flare/pull/217)) +* Update mapping_tree creation ([#191](https://github.com/medizininformatik-initiative/flare/issues/191)) +* Add cohort extraction endpoint ([#178](https://github.com/medizininformatik-initiative/flare/issues/178)) +* docker-compose.yml should be for flare instead of blaze ([#184](https://github.com/medizininformatik-initiative/flare/issues/184)) + +The full changelog can be found [here](https://github.com/medizininformatik-initiative/flare/milestone/15?closed=1). + ## v2.4.0-alpha.3 ### Enhancements diff --git a/docker-compose.yml b/docker-compose.yml index 580ee74d..ca1dfe08 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,6 +1,6 @@ services: flare: - image: ghcr.io/medizininformatik-initiative/flare:2.4.0-alpha.3 + image: ghcr.io/medizininformatik-initiative/flare:2.4.0 ports: - 8080:8080 volumes: diff --git a/pom.xml b/pom.xml index d8d273e9..7eac7274 100644 --- a/pom.xml +++ b/pom.xml @@ -13,7 +13,7 @@ de.medizininformatik-initiative flare - 2.4.0-alpha.3 + 2.4.0 Flare Flare @@ -21,7 +21,7 @@ 17 1.20.3 - 3.0.0-alpha.1 + 3.0.0