Skip to content

Commit

Permalink
100-refactor-and-change-filter-expansion (#102)
Browse files Browse the repository at this point in the history
reworked Query Building, added more tests, restructured crtdl processing

---------

Signed-off-by: Lucas Triefenbach <[email protected]>
Co-authored-by: Julian Gründner <[email protected]>
Co-authored-by: Bastian Schaffer <[email protected]>
  • Loading branch information
3 people authored Nov 4, 2024
1 parent f60f8e5 commit d8f4211
Show file tree
Hide file tree
Showing 95 changed files with 39,707 additions and 2,618 deletions.
48 changes: 48 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,28 @@
<ontology-tag>v3.0.0-alpha</ontology-tag>
</properties>



<dependencies>

<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>mockwebserver</artifactId>
<scope>test</scope>
</dependency>

<!-- https://mvnrepository.com/artifact/ca.uhn.hapi.fhir/hapi-fhir-base -->
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
Expand All @@ -39,6 +60,11 @@
<version>${Hapi.version}</version>
</dependency>

<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>

<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-structures-r4</artifactId>
Expand Down Expand Up @@ -97,6 +123,8 @@
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>


<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
Expand Down Expand Up @@ -152,13 +180,33 @@
<artifactId>spring-security-oauth2-client</artifactId>
</dependency>

<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</dependency>

<!-- https://mvnrepository.com/artifact/org.codehaus.mojo/exec-maven-plugin -->
<dependency>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.1.0</version>
</dependency>

<!-- Mockito Core -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>

<scope>test</scope>
</dependency>

<!-- Mockito JUnit Jupiter Extension -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<scope>test</scope>
</dependency>

</dependencies>

<profiles>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@

@Component

/**
* Bundle creator for collecting Resources by patient into bundles for export
*/

public class BundleCreator {
private static final Logger logger = LoggerFactory.getLogger(BundleCreator.class);
Expand All @@ -23,7 +26,7 @@ public class BundleCreator {
@Autowired
FhirContext context;

org.hl7.fhir.r4.model.Bundle.HTTPVerb method=Bundle.HTTPVerb.PUT;
org.hl7.fhir.r4.model.Bundle.HTTPVerb method = Bundle.HTTPVerb.PUT;

public BundleCreator() {

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package de.medizininformatikinitiative.torch;

import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import de.medizininformatikinitiative.torch.util.ResourceReader;
import org.hl7.fhir.r4.model.CanonicalType;
import org.hl7.fhir.r4.model.StructureDefinition;
Expand All @@ -12,14 +10,20 @@
import java.util.HashMap;
import java.util.List;

/**
* Structure for loading and serving the CDS structrue definitions
*/

@Component
public class CdsStructureDefinitionHandler {

public FhirContext ctx;

private HashMap<String, StructureDefinition> definitionsMap = new HashMap<>();
protected ResourceReader resourceReader;

public CdsStructureDefinitionHandler(String fileDirectory) {
public CdsStructureDefinitionHandler(String fileDirectory, ResourceReader resourceReader) {
try {
this.resourceReader = resourceReader;
processDirectory(fileDirectory);
} catch (IOException e) {
throw new RuntimeException(e);
Expand All @@ -34,7 +38,7 @@ public CdsStructureDefinitionHandler(String fileDirectory) {
* @throws IOException
*/
public void readStructureDefinition(String filePath) throws IOException {
StructureDefinition structureDefinition = (StructureDefinition) ResourceReader.readResource(filePath);
StructureDefinition structureDefinition = (StructureDefinition) resourceReader.readResource(filePath);
definitionsMap.put(structureDefinition.getUrl(), structureDefinition);
}

Expand Down Expand Up @@ -66,15 +70,12 @@ public StructureDefinition getDefinition(List<CanonicalType> urls) {
}
return null;
}

public StructureDefinition.StructureDefinitionSnapshotComponent getSnapshot(String url) {
return (definitionsMap.get(url)).getSnapshot();

}

public RuntimeResourceDefinition getStandardDefinition(String url) {
return ctx.getResourceDefinition(String.valueOf(definitionsMap.get(url).getResourceType()));

}

/**
* Reads all JSON files in a directory and stores their StructureDefinitions in the definitionsMap
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import de.medizininformatikinitiative.torch.exceptions.PatientIdNotFoundException;
import de.medizininformatikinitiative.torch.util.*;
import de.medizininformatikinitiative.torch.model.fhir.Query;
import de.medizininformatikinitiative.torch.model.fhir.QueryParams;
import de.medizininformatikinitiative.torch.service.DataStore;
import de.medizininformatikinitiative.torch.util.*;
import org.hl7.fhir.r4.model.*;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
Expand All @@ -20,6 +21,9 @@
import java.io.IOException;
import java.util.*;

import static de.medizininformatikinitiative.torch.model.fhir.QueryParams.EMPTY;
import static de.medizininformatikinitiative.torch.model.fhir.QueryParams.stringValue;

/**
* The {@code ConsentHandler} class is responsible for managing and verifying patient consents
* within the Torch application.
Expand Down Expand Up @@ -49,21 +53,21 @@ public class ConsentHandler {
/**
* Constructs a new {@code ConsentHandler} with the specified dependencies.
*
* @param dataStore The {@link DataStore} service for Server Calls.
* @param mapper The {@link ConsentCodeMapper} for mapping consent codes.
* @param profilePath The file system path to the consent profile mapping configuration.
* @param dataStore The {@link DataStore} service for Server Calls.
* @param mapper The {@link ConsentCodeMapper} for mapping consent codes.
* @param profilePath The file system path to the consent profile mapping configuration.
* @param cdsStructureDefinitionHandler The {@link CdsStructureDefinitionHandler} for handling structure definitions.
* @param objectMapper
* @throws IOException If an error occurs while reading the mapping profile file.
*/
@Autowired
public ConsentHandler(DataStore dataStore, ConsentCodeMapper mapper, String profilePath, CdsStructureDefinitionHandler cdsStructureDefinitionHandler) throws IOException {
public ConsentHandler(DataStore dataStore, ConsentCodeMapper mapper, String profilePath, CdsStructureDefinitionHandler cdsStructureDefinitionHandler, FhirContext ctx, ObjectMapper objectMapper) throws IOException {
this.dataStore = dataStore;
this.mapper = mapper;
this.ctx = ResourceReader.ctx;
this.fhirPathBuilder = new FhirPathBuilder(cdsStructureDefinitionHandler);
this.ctx = ctx;
this.fhirPathBuilder = new FhirPathBuilder(new Slicing(cdsStructureDefinitionHandler, ctx));
this.cdsStructureDefinitionHandler = cdsStructureDefinitionHandler;
this.consentProcessor = new ConsentProcessor(ctx);
ObjectMapper objectMapper = new ObjectMapper();
mappingProfiletoDateField = objectMapper.readTree(new File(profilePath).getAbsoluteFile());
}

Expand All @@ -77,8 +81,8 @@ public ConsentHandler(DataStore dataStore, ConsentCodeMapper mapper, String prof
* @param consentInfo A map containing consent information structured by patient ID and consent codes.
* @return {@code true} if the resource complies with the consents; {@code false} otherwise.
*/
public Boolean checkConsent(@NotNull DomainResource resource, Map<String, Map<String, List<Period>>> consentInfo) {
logger.trace("Checking Consent for {}", resource.getResourceType());
public Boolean checkConsent(DomainResource resource, Map<String, Map<String, List<Period>>> consentInfo) {
logger.trace("Checking Consent for {} {}", resource.getResourceType(), resource.getId());
Iterator<CanonicalType> profileIterator = resource.getMeta().getProfile().iterator();
JsonNode fieldValue = null;
StructureDefinition.StructureDefinitionSnapshotComponent snapshot = null;
Expand All @@ -100,7 +104,7 @@ public Boolean checkConsent(@NotNull DomainResource resource, Map<String, Map<St
}

if (fieldValue == null) {
logger.warn("No matching profile found for resource of type: {}", resource.getResourceType());
logger.warn("No matching profile found for resource {} of type: {}", resource.getId(), resource.getResourceType());
return false;
}

Expand Down Expand Up @@ -133,7 +137,7 @@ public Boolean checkConsent(@NotNull DomainResource resource, Map<String, Map<St
try {
patientID = ResourceUtils.getPatientId(resource);
} catch (PatientIdNotFoundException e) {
logger.warn("Resource does not Contain any Patient Reference {}", resource.getIdElement());
logger.warn("Resource {} does not Contain any Patient Reference ", resource.getIdElement());
return false;
}

Expand Down Expand Up @@ -163,7 +167,8 @@ public Boolean checkConsent(@NotNull DomainResource resource, Map<String, Map<St
return true;
}
}
logger.debug("No valid consent period found for any value.");
logger.warn("No valid consent period found for any value.");
logger.debug("No valid consent period found for any value in Resource {}", resource.getIdElement());
return false; // No matching consent period found
}
}
Expand All @@ -178,19 +183,22 @@ public Boolean checkConsent(@NotNull DomainResource resource, Map<String, Map<St
* @param batch A list of patient IDs to process in this batch.
* @return A {@link Flux} emitting maps containing consent information structured by patient ID and consent codes.
*/
public Flux<Map<String, Map<String, List<Period>>>> buildingConsentInfo(String key, @NotNull List<String> batch) {
public Flux<Map<String, Map<String, List<Period>>>> buildingConsentInfo(String key, List<String> batch) {
Objects.requireNonNull(batch, "Patient batch cannot be null");
// Retrieve the relevant codes for the given key
Set<String> codes = mapper.getRelevantCodes(key);

logger.trace("Starting to build consent info for key: {} with batch size: {}", key, batch.size());

QueryParams consentParams = EMPTY.appendParam("_profile", stringValue("https://www.medizininformatik-initiative.de/fhir/modul-consent/StructureDefinition/mii-pr-consent-einwilligung"));
consentParams = consentParams.appendParam(BatchUtils.queryElements("Consent"), QueryParams.stringValue(String.join(",", batch)));
// Fetch resources using a bounded elastic scheduler for offloading blocking HTTP I/O
return dataStore.getResources("Consent", FhirSearchBuilder.getConsent(batch))
Query query = new Query("Consent", consentParams);
return dataStore.getResources(query)
.subscribeOn(Schedulers.boundedElastic()) // Offload the HTTP requests
.doOnSubscribe(subscription -> logger.debug("Fetching resources for batch: {}", batch))
.doOnNext(resource -> logger.trace("Resource fetched for ConsentBuild: {}", resource.getIdElement().getIdPart()))
.onErrorResume(e -> {
logger.error("Error fetching resources for parameters: {}", FhirSearchBuilder.getConsent(batch), e);
logger.error("Error fetching resources for parameters: {}", query, e);
return Flux.empty();
})

Expand Down Expand Up @@ -244,12 +252,13 @@ public Flux<Map<String, Map<String, List<Period>>>> buildingConsentInfo(String k
* @return A {@link Flux} emitting updated maps of consent information.
*/
public Flux<Map<String, Map<String, List<Period>>>> updateConsentPeriodsByPatientEncounters(
Flux<Map<String, Map<String, List<Period>>>> consentInfoFlux, @NotNull List<String> batch) {

logger.info("Starting to update consent info with batch size: {}", batch.size());

Flux<Map<String, Map<String, List<Period>>>> consentInfoFlux, List<String> batch) {
Objects.requireNonNull(batch, "Patient batch cannot be null");
logger.debug("Starting to update consent info with batch size: {}", batch.size());
QueryParams encounterParams = EMPTY.appendParam("_profile", stringValue("https://www.medizininformatik-initiative.de/fhir/core/modul-fall/StructureDefinition/KontaktGesundheitseinrichtung"));
encounterParams = encounterParams.appendParam(BatchUtils.queryElements("Encounter"), QueryParams.stringValue(String.join(",", batch)));
// Step 1: Fetch all encounters for the batch of patients
Flux<Encounter> allEncountersFlux = dataStore.getResources("Encounter", FhirSearchBuilder.getEncounter(batch))
Flux<Encounter> allEncountersFlux = dataStore.getResources(new Query("Encounter", encounterParams))
.subscribeOn(Schedulers.boundedElastic())
.cast(Encounter.class)
.doOnSubscribe(subscription -> logger.debug("Fetching encounters for batch: {}", batch))
Expand Down Expand Up @@ -289,7 +298,7 @@ public Flux<Map<String, Map<String, List<Period>>>> updateConsentPeriodsByPatien
List<Encounter> patientEncounters = (List<Encounter>) encountersByPatientMap.get(patientId);

if (patientEncounters == null || patientEncounters.isEmpty()) {
logger.info("No encounters found for patient {}", patientId);
logger.warn("No encounters found for patient {}", patientId);
// No encounters for this patient, return the consent info as is
return Mono.just(patientConsentInfo);
}
Expand All @@ -312,8 +321,8 @@ public Flux<Map<String, Map<String, List<Period>>>> updateConsentPeriodsByPatien
* @param encounters A list of {@link Encounter} resources associated with the patient.
*/
private void updateConsentPeriodsByPatientEncounters(
Map<String, List<Period>> patientConsentInfo, @NotNull List<Encounter> encounters) {

Map<String, List<Period>> patientConsentInfo, List<Encounter> encounters) {
Objects.requireNonNull(encounters, "Encounters list cannot be null");
for (Encounter encounter : encounters) {
Period encounterPeriod = encounter.getPeriod();

Expand Down
Loading

0 comments on commit d8f4211

Please sign in to comment.