Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

121-unknown-data-type-name #122

Merged
merged 5 commits into from
Dec 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

<groupId>de.medizininformatikinitiative</groupId>
<artifactId>torch</artifactId>
<version>1.0.0-alpha.2</version>
<version>1.0.0-SNAPSHOT</version>

<properties>
<maven.compiler.source>21</maven.compiler.source>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ private static ElementDefinition resolveSlicePath(ElementDefinition slice, Eleme
if (Objects.equals(path, "$this")) {
return slice;
}
return snapshot.getElementByPath(slice.getId() + "." + slice.getPath());
return snapshot.getElementById(slice.getId() + "." + path);
}

/**
Expand All @@ -64,22 +64,17 @@ private static Boolean resolvePattern(Base base, ElementDefinition slice,
StructureDefinition.StructureDefinitionSnapshotComponent snapshot) {
ElementDefinition elementContainingInfo = resolveSlicePath(slice, discriminator, snapshot);

// Resolve the base element along the path specified by the discriminator.
if (elementContainingInfo == null) {
return false;
}
Base resolvedBase = resolveElementPath(base, discriminator);

// If the resolved base element is null, the pattern cannot be resolved.
if (resolvedBase == null) {
return false;
}
// If the resolved base element is null, the pattern cannot be resolved.
if (elementContainingInfo == null) {
return false;
}

// Check if the element containing the info has a fixed value or pattern.
if (elementContainingInfo.hasFixedOrPattern()) {

// Get the fixed value or pattern from the element definition.
Type fixedOrPatternValue = elementContainingInfo.getFixedOrPattern();
return compareBaseToFixedOrPattern(resolvedBase, fixedOrPatternValue);

Expand All @@ -90,68 +85,47 @@ private static Boolean resolvePattern(Base base, ElementDefinition slice,
}

private static boolean compareBaseToFixedOrPattern(Base resolvedBase, Base fixedOrPatternValue) {
// Handle null inputs
if (resolvedBase == null || fixedOrPatternValue == null) {
logger.trace("One or both inputs are null: resolvedBase={}, fixedOrPatternValue={}", resolvedBase, fixedOrPatternValue);
return false;
}

// Check if FHIR types match
if (!Objects.equals(resolvedBase.fhirType(), fixedOrPatternValue.fhirType())) {
logger.trace("Incompatible Data types when comparing {} {}", resolvedBase.fhirType(), fixedOrPatternValue.fhirType());
return false;
}

// Handle primitive types
if (fixedOrPatternValue.isPrimitive()) {
logger.trace("Handling Primitive Types {}", fixedOrPatternValue.getIdBase());
return resolvedBase.equalsDeep(fixedOrPatternValue);
} else {
logger.trace("Handling Complex Types {}", fixedOrPatternValue.fhirType());

// Collect children from fixedOrPatternValue
List<Property> fixedChildren = fixedOrPatternValue.children().stream()
.filter(Property::hasValues)
.toList();

// Collect children from resolvedBase
List<Property> resolvedChildren = resolvedBase.children().stream()
.filter(Property::hasValues)
.toList();

// Check if the number of children matches
if (fixedChildren.size() > resolvedChildren.size()) {
logger.trace("Mismatch in number of children: fixedOrPatternValue has {} children, resolvedBase has {} children",
fixedChildren.size(), resolvedChildren.size());
return false;
}

// Iterate through each child in fixedOrPatternValue
for (Property fixedChild : fixedChildren) {
String childName = fixedChild.getName();
Property resolvedChild = resolvedBase.getChildByName(childName);

logger.trace("Handling Child {} {}", childName, resolvedChild);

// If the resolved base doesn't have this child, return false
if (resolvedChild == null || !resolvedChild.hasValues()) {
logger.trace("Missing or isEmpty child '{}' in resolvedBase", childName);
return false;
}

// Compare the first value of each child
Base resolvedChildValue = resolvedChild.getValues().getFirst();
Base fixedChildValue = fixedChild.getValues().getFirst();

// Recursive comparison
boolean childComparison = compareBaseToFixedOrPattern(resolvedChildValue, fixedChildValue);
if (!childComparison) {
logger.trace("Mismatch found in child '{}'", childName);
return false;
}
}

// All children matched
return true;
}
}
Expand Down Expand Up @@ -222,7 +196,7 @@ private static Boolean resolveType(Base base, ElementDefinition slice, ElementDe
}

// Proceed with the type comparison
return base.fhirType().equalsIgnoreCase(elementContainingInfo.getType().getFirst().getCode());
return elementContainingInfo.getType().stream().anyMatch(x -> base.fhirType().equalsIgnoreCase(x.getCode()));
}


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

import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.r4.model.Extension;
import org.hl7.fhir.r4.model.Factory;
import org.hl7.fhir.r4.model.Narrative;
import org.hl7.fhir.r4.model.Reference;
import org.hl7.fhir.r4.model.Type;
import org.hl7.fhir.r4.model.*;

public class HapiFactory {

Expand All @@ -17,12 +13,17 @@ public class HapiFactory {
*/
public static Type create(String name) throws FHIRException {
switch (name) {
case "*":
//Any type allowed
return new StringType();
case "Reference(Organization)":
return new Reference();
case "Extension":
return new Extension();
case "Narrative":
return new Narrative();
case "Identifier":
return new Identifier();
default:
// For standard types not considered complex, delegate to the standard create method
return FACTORY.create(name);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
package de.medizininformatikinitiative.torch.util;

import de.medizininformatikinitiative.torch.CdsStructureDefinitionHandler;
import org.hl7.fhir.r4.model.Base;
import org.hl7.fhir.r4.model.DomainResource;
import org.hl7.fhir.r4.model.Element;
import org.hl7.fhir.r4.model.ElementDefinition;
import org.hl7.fhir.r4.model.StructureDefinition;
import org.hl7.fhir.r4.model.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand Down Expand Up @@ -75,7 +71,6 @@ public Base redact(Base base, String elementID, int recursion, StructureDefiniti
throw new NoSuchElementException("Definiton unknown for" + base.fhirType() + "in Element ID " + elementID + "in StructureDefinition " + structureDefinition.getUrl());

} else if (definition.hasSlicing()) {

ElementDefinition slicedElement = slicing.checkSlicing(base, elementID, structureDefinition);

if (slicedElement != null) {
Expand All @@ -89,13 +84,14 @@ public Base redact(Base base, String elementID, int recursion, StructureDefiniti

} else {
base.children().forEach(child -> {
String type = child.getTypeCode();
Element element = HapiFactory.create(type);
if (child.hasValues()) {
element.addExtension(createAbsentReasonExtension("masked"));
}
base.setProperty(child.getName(), element);
child.getValues().forEach(value -> {
base.removeChild(child.getName(), value);
});
});
if (definition.getMin() > 0) {
base.setProperty("extension", createAbsentReasonExtension("masked"));
}

return base;
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
package de.medizininformatikinitiative.torch.util;

import de.medizininformatikinitiative.torch.exceptions.PatientIdNotFoundException;
import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.Consent;
import org.hl7.fhir.r4.model.DomainResource;
import org.hl7.fhir.r4.model.Patient;
import org.hl7.fhir.r4.model.Resource;
import org.hl7.fhir.r4.model.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
* Resource Utils to extract References and IDs from Resources
Expand Down Expand Up @@ -88,4 +87,31 @@ public static String getPatientIdFromBundle(Bundle bundle) throws PatientIdNotFo
}


public static List<ElementDefinition> getElementsByPath(String path, StructureDefinition.StructureDefinitionSnapshotComponent snapshot) {
if (path == null) {
return Collections.emptyList();
}
List<ElementDefinition> matchingElements = new ArrayList<>();
for (ElementDefinition ed : snapshot.getElement()) {
if (path.equals(ed.getPath()) || (path + "[x]").equals(ed.getPath())) {
matchingElements.add(ed);
}
}
return List.copyOf(matchingElements);
}

public static List<ElementDefinition> getElementById(String id, StructureDefinition.StructureDefinitionSnapshotComponent snapshot) {
if (id == null) {
return Collections.emptyList();
}
List<ElementDefinition> matchingElements = new ArrayList<>();
for (ElementDefinition ed : snapshot.getElement()) {
if (id.equals(ed.getId())) {
matchingElements.add(ed);
}
}
return List.copyOf(matchingElements);
}


}
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
package de.medizininformatikinitiative.torch.util;

import ca.uhn.fhir.context.FhirContext;
import org.hl7.fhir.r4.model.Base;
import org.hl7.fhir.r4.model.Element;
import org.hl7.fhir.r4.model.ElementDefinition;
import org.hl7.fhir.r4.model.StructureDefinition;
import org.hl7.fhir.r4.model.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand All @@ -20,15 +17,12 @@
public class Slicing {

private static final Logger logger = LoggerFactory.getLogger(Slicing.class);
private final FhirContext ctx;


/**
* Constructor for Slicing
*/
public Slicing(FhirContext ctx) {

this.ctx = ctx;
}

/**
Expand All @@ -40,44 +34,51 @@ public Slicing(FhirContext ctx) {
* @return Returns null if no slicing is found and an elementdefinition for the slice otherwise
*/
public ElementDefinition checkSlicing(Base base, String elementID, StructureDefinition structureDefinition) {

StructureDefinition.StructureDefinitionSnapshotComponent snapshot = structureDefinition.getSnapshot();
String fhirPath = "StructureDefinition.snapshot.element.where(path = '" + elementID + "')";
ElementDefinition slicedElement = snapshot.getElementById(elementID);

ElementDefinition slicedElement = snapshot.getElementByPath(elementID);
if (elementID.contains(":")) {
slicedElement = snapshot.getElementById(elementID);
}

AtomicReference<ElementDefinition> returnElement = new AtomicReference<>(null);

if (slicedElement == null) {
logger.warn("slicedElement null {} {}", elementID, structureDefinition.getUrl());
return null;
}
if (!slicedElement.hasSlicing()) {
logger.warn("Element has no slicing {} {}", elementID, structureDefinition.getUrl());
return null;
}


List<ElementDefinition.ElementDefinitionSlicingDiscriminatorComponent> slicingDiscriminator = slicedElement.getSlicing().getDiscriminator();

List<ElementDefinition> ElementDefinition = ctx.newFhirPath().evaluate(structureDefinition, fhirPath, ElementDefinition.class);
ElementDefinition.forEach(element -> {
logger.trace("Slice to be handled {}", element.getIdElement());
List<ElementDefinition> elementDefinitions = ResourceUtils.getElementsByPath(slicedElement.getPath(), structureDefinition.getSnapshot());
elementDefinitions.forEach(element -> {

boolean foundSlice = true;
if (element.hasSliceName()) {
//iterate over every discriminator and test if base holds for it
for (ElementDefinition.ElementDefinitionSlicingDiscriminatorComponent discriminator : slicingDiscriminator) {
if ("url".equals(discriminator.getPath()) && "VALUE".equals(discriminator.getType().toString())) {

if ("Extension".equals(element.getType().getFirst().getWorkingCode())) {
UriType baseTypeUrl = (UriType) base.getNamedProperty("url").getValues().getFirst();
List<CanonicalType> profiles = element.getType().stream()
.flatMap(type -> type.getProfile().stream())
.toList();
// Check if any profile matches the base type URL
boolean anyMatchBaseUrl = profiles.stream()
.anyMatch(profile -> profile.getValue().equals(baseTypeUrl.getValue()));
if (anyMatchBaseUrl) {
continue;
} else {
foundSlice = false;
break;
}
}

}
if (!resolveDiscriminator(base, element, discriminator, snapshot)) {
logger.trace("Check failed {}", element.getIdElement());
foundSlice = false;
break; // Stop iterating if condition check fails
}
}
if (foundSlice) {
//logger.error("Check passed {}", element.getIdElement());
returnElement.set(element);
}

Expand All @@ -97,7 +98,6 @@ public ElementDefinition checkSlicing(Base base, String elementID, StructureDefi
*/
public List<String> generateConditionsForFHIRPath(String elementID, StructureDefinition.StructureDefinitionSnapshotComponent snapshot) {
List<String> conditions = new ArrayList<>();
logger.info("Generating Slicing Conditions for ElementID: {}", elementID);
// Find the sliced element using the element ID
ElementDefinition slicedElement = snapshot.getElementById(elementID);
if (slicedElement == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,7 @@
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.boot.test.web.server.LocalServerPort;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.*;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.web.client.HttpStatusCodeException;
import org.springframework.web.reactive.function.client.WebClient;
Expand All @@ -46,11 +42,7 @@
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Scanner;
import java.util.*;

import static org.junit.jupiter.api.Assertions.assertEquals;

Expand Down Expand Up @@ -218,7 +210,7 @@ public void testFhirSearchConditionObservation() throws IOException, PatientIdNo

@Test
public void testAllFields() throws IOException, PatientIdNotFoundException {
executeTest(List.of(RESOURCE_PATH_PREFIX + "DataStoreIT/expectedOutput/observation_all_fields.json"), List.of(RESOURCE_PATH_PREFIX + "CRTDL/CRTDL_all_fields.json"));
executeTest(List.of(RESOURCE_PATH_PREFIX + "DataStoreIT/expectedOutput/all_fields_patient_3.json"), List.of(RESOURCE_PATH_PREFIX + "CRTDL/CRTDL_all_fields.json"));
}


Expand Down Expand Up @@ -252,7 +244,11 @@ private void processFile(String filePath, PatientBatch patients, Map<String, Bun

StepVerifier.create(collectedResourcesMono).expectNextMatches(combinedResourcesByPatientId -> {
Map<String, Bundle> bundles = bundleCreator.createBundles(combinedResourcesByPatientId);
fhirTestHelper.validate(bundles, expectedResources);
try {
fhirTestHelper.validate(bundles, expectedResources);
} catch (PatientIdNotFoundException e) {
throw new RuntimeException(e);
}
return true;
}).expectComplete().verify();
}
Expand Down
Loading
Loading