Skip to content

Commit

Permalink
Merge pull request #1371 from CDCgov/story/1299-1-remove-accession-nu…
Browse files Browse the repository at this point in the history
…mber

CA Transform: Remove Accession Number
  • Loading branch information
jbiskie authored Oct 2, 2024
2 parents 69a9335 + 209e5a8 commit 93d46ac
Show file tree
Hide file tree
Showing 6 changed files with 360 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
import gov.hhs.cdc.trustedintermediary.wrappers.Logger;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.Coding;
import org.hl7.fhir.r4.model.Observation;
Expand Down Expand Up @@ -43,7 +42,8 @@ public void transform(FhirResource<?> resource, Map<String, Object> args) {
}

var coding = codingList.get(0);
if (!hasLocalCodeInAlternateCoding(coding)) {
if (!HapiHelper.hasDefinedCoding(
coding, HapiHelper.EXTENSION_ALT_CODING, HapiHelper.LOCAL_CODE)) {
continue;
}

Expand All @@ -60,24 +60,6 @@ public void transform(FhirResource<?> resource, Map<String, Object> args) {
}
}

private Boolean hasLocalCodeInAlternateCoding(Coding coding) {
if (!HapiHelper.hasCodingExtensionWithUrl(coding, HapiHelper.EXTENSION_CWE_CODING)) {
return false;
}

if (!HapiHelper.hasCodingSystem(coding)) {
return false;
}

var cwe =
HapiHelper.getCodingExtensionByUrl(coding, HapiHelper.EXTENSION_CWE_CODING)
.getValue()
.toString();
var codingSystem = HapiHelper.getCodingSystem(coding);

return Objects.equals(cwe, "alt-coding") && HapiHelper.LOCAL_CODE_URL.equals(codingSystem);
}

private void logUnmappedLocalCode(Bundle bundle, Coding coding) {
var msh41Identifier = HapiHelper.getMSH4_1Identifier(bundle);
var msh41Value = msh41Identifier != null ? msh41Identifier.getValue() : null;
Expand All @@ -103,6 +85,12 @@ private Coding getMappedCoding(IdentifierCode identifierCode) {
return mappedCoding;
}

/**
* Initializes the local-to-LOINC/PLT hash map, customized for CDPH and UCSD. Currently, the
* mapping is hardcoded for simplicity. If expanded to support additional entities, the
* implementation may be updated to allow dynamic configuration via
* transformation_definitions.json or a database-driven mapping.
*/
private void initMap() {
this.codingMap = new HashMap<>();
// ALD
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package gov.hhs.cdc.trustedintermediary.etor.ruleengine.transformation.custom;

import gov.hhs.cdc.trustedintermediary.etor.ruleengine.FhirResource;
import gov.hhs.cdc.trustedintermediary.etor.ruleengine.transformation.CustomFhirTransformation;
import gov.hhs.cdc.trustedintermediary.external.hapi.HapiHelper;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.Observation;
import org.hl7.fhir.r4.model.Resource;

public class RemoveObservationByCode implements CustomFhirTransformation {
public static final String CODE_NAME = "code";
public static final String CODING_SYSTEM_NAME = "codingSystemExtension";
public static final String CODING_NAME = "codingExtension";

@Override
public void transform(FhirResource<?> resource, Map<String, Object> args) {
var bundle = (Bundle) resource.getUnderlyingResource();
Set<Resource> resourcesToRemove = new HashSet<>();

for (Bundle.BundleEntryComponent entry : bundle.getEntry()) {
Resource resourceEntry = entry.getResource();

if (!(resourceEntry instanceof Observation observation)) {
continue;
}

if (HapiHelper.hasMatchingCoding(
observation,
args.get(CODE_NAME).toString(),
args.get(CODING_NAME).toString(),
args.get(CODING_SYSTEM_NAME).toString())) {
resourcesToRemove.add(resourceEntry);
}
}

bundle.getEntry().removeIf(entry -> resourcesToRemove.contains(entry.getResource()));
}
}
19 changes: 19 additions & 0 deletions etor/src/main/resources/transformation_definitions.json
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,25 @@
"args": {}
}
]
},
{
"name": "ucsdOruRemoveAccessionNumberObservation",
"description": "Remove Observations for UCSD ORUs when their OBX-3.4 value is '99717-5' and OBX-3.6 is 'L'",
"message": "",
"conditions": [
"Bundle.entry.resource.ofType(MessageHeader).destination.receiver.resolve().identifier.where(extension.value = 'HD.1').value in ('R797' | 'R508')",
"Bundle.entry.resource.ofType(MessageHeader).event.code = 'R01'"
],
"rules": [
{
"name": "RemoveObservationByCode",
"args": {
"code": "99717-5",
"codingSystemExtension": "L",
"codingExtension": "alt-coding"
}
}
]
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
package gov.hhs.cdc.trustedintermediary.etor.ruleengine.transformation.custom

import gov.hhs.cdc.trustedintermediary.ExamplesHelper
import gov.hhs.cdc.trustedintermediary.context.TestApplicationContext
import gov.hhs.cdc.trustedintermediary.external.hapi.HapiFhirHelper
import gov.hhs.cdc.trustedintermediary.external.hapi.HapiFhirResource
import gov.hhs.cdc.trustedintermediary.external.hapi.HapiHelper
import org.hl7.fhir.r4.model.Bundle
import org.hl7.fhir.r4.model.Coding
import org.hl7.fhir.r4.model.Observation
import org.hl7.fhir.r4.model.StringType
import spock.lang.Specification

class RemoveObservationByCodeTest extends Specification {
def transformClass

def setup() {
TestApplicationContext.reset()
TestApplicationContext.init()
TestApplicationContext.injectRegisteredImplementations()

transformClass = new RemoveObservationByCode()
}

def "When an observation has the desired coding, it should be removed"() {
given:
def bundle = HapiFhirHelper.createMessageBundle(messageTypeCode: 'ORU_R01')
def observation = new Observation()
addCodingToObservation(observation, code, codingSystemExt, codingExt)
bundle.addEntry(new Bundle.BundleEntryComponent().setResource(observation))

def args = getArgs(code, codingSystemExt, codingExt)

expect:
HapiHelper.resourcesInBundle(bundle, Observation.class).count() == 1

when:
transformClass.transform(new HapiFhirResource(bundle), args)

then:
HapiHelper.resourcesInBundle(bundle, Observation.class).count() == 0

where:
code | codingSystemExt | codingExt
"99717-5" | "L" | "alt-coding"
"my_code" | "MY_SYS" | "coding"
}

def "When an observation with >1 coding has the desired coding, it should be removed"() {
given:
final String MATCHING_CODE = "99717-5"
final String MATCHING_CODING_SYSTEM_EXT = "L"
final String MATCHING_CODING_EXT = "alt-coding"

def bundle = HapiFhirHelper.createMessageBundle(messageTypeCode: 'ORU_R01')
def observation = new Observation()

addCodingToObservation(observation, "ANOTHER_CODE", "ANOTHER_SYSTEM", "coding")
addCodingToObservation(observation, MATCHING_CODE, MATCHING_CODING_SYSTEM_EXT, MATCHING_CODING_EXT)
bundle.addEntry(new Bundle.BundleEntryComponent().setResource(observation))

def args = getArgs(MATCHING_CODE, MATCHING_CODING_SYSTEM_EXT, MATCHING_CODING_EXT)

expect:
HapiHelper.resourcesInBundle(bundle, Observation.class).count() == 1

when:
transformClass.transform(new HapiFhirResource(bundle), args)

then:
HapiHelper.resourcesInBundle(bundle, Observation.class).count() == 0
}

def "When an observation has coding that's only a partial match, it should NOT be removed"() {
given:
final String MATCHING_CODE = "99717-5"
final String MATCHING_CODING_SYSTEM_EXT = "L"
final String MATCHING_CODING_EXT = "alt-coding"

def bundle = HapiFhirHelper.createMessageBundle(messageTypeCode: 'ORU_R01')
def observation = new Observation()
addCodingToObservation(observation, code, codingSystemExt, codingExt)
bundle.addEntry(new Bundle.BundleEntryComponent().setResource(observation))

def args = getArgs(MATCHING_CODE, MATCHING_CODING_SYSTEM_EXT, MATCHING_CODING_EXT)

expect:
HapiHelper.resourcesInBundle(bundle, Observation.class).count() == 1

when:
transformClass.transform(new HapiFhirResource(bundle), args)

then:
HapiHelper.resourcesInBundle(bundle, Observation.class).count() == 1

where:
code | codingSystemExt | codingExt
"11111-1" | "L" | "alt-coding"
"99717-5" | "DIFFERENT_SYS" | "alt-coding"
"99717-5" | "L" | "coding"
}

def "When an observation has no identifier OBX-3, it should NOT be removed"() {
given:
def bundle = HapiFhirHelper.createMessageBundle(messageTypeCode: 'ORU_R01')

// Add an observation with an observation value and a status, but no observation identifier
def observation = new Observation()
observation.status = Observation.ObservationStatus.FINAL
def valueCoding = new Coding()
valueCoding.code = "123456"
observation.valueCodeableConcept.coding.add(valueCoding)
bundle.addEntry(new Bundle.BundleEntryComponent().setResource(observation))

def args = getArgs("55555-5", "LN", "coding")

expect:
HapiHelper.resourcesInBundle(bundle, Observation.class).count() == 1

when:
transformClass.transform(new HapiFhirResource(bundle), args)

then:
HapiHelper.resourcesInBundle(bundle, Observation.class).count() == 1
}

def "When there is >1 matching observation, all matching observations should be removed"() {
given:
final String MATCHING_CODE = "99717-5"
final String MATCHING_CODING_SYSTEM_EXT = "L"
final String MATCHING_CODING_EXT = "alt-coding"

def bundle = HapiFhirHelper.createMessageBundle(messageTypeCode: 'ORU_R01')

def observation1 = new Observation()
def observation2 = new Observation()

addCodingToObservation(observation1, MATCHING_CODE, MATCHING_CODING_SYSTEM_EXT, MATCHING_CODING_EXT)
addCodingToObservation(observation2, MATCHING_CODE, MATCHING_CODING_SYSTEM_EXT, MATCHING_CODING_EXT)

bundle.addEntry(new Bundle.BundleEntryComponent().setResource(observation1))
bundle.addEntry(new Bundle.BundleEntryComponent().setResource(observation2))

def args = getArgs(MATCHING_CODE, MATCHING_CODING_SYSTEM_EXT, MATCHING_CODING_EXT)

expect:
HapiHelper.resourcesInBundle(bundle, Observation.class).count() == 2

when:
transformClass.transform(new HapiFhirResource(bundle), args)

then:
HapiHelper.resourcesInBundle(bundle, Observation.class).count() == 0
}

def "When message has multiple observations with only 1 matching, only 1 is removed"() {
given:
final String MATCHING_CODE = "99717-5"
final String MATCHING_CODING_SYSTEM_EXT = "L"
final String MATCHING_CODING_EXT = "alt-coding"

final String FHIR_ORU_PATH = "../CA/020_CA_ORU_R01_CDPH_OBX_to_LOINC_1_hl7_translation.fhir"
def fhirResource = ExamplesHelper.getExampleFhirResource(FHIR_ORU_PATH)
def bundle = fhirResource.getUnderlyingResource() as Bundle

def args = getArgs(MATCHING_CODE, MATCHING_CODING_SYSTEM_EXT, MATCHING_CODING_EXT)

expect:
HapiHelper.resourcesInBundle(bundle, Observation.class).count() == 114

when:
transformClass.transform(new HapiFhirResource(bundle), args)

then:
HapiHelper.resourcesInBundle(bundle, Observation.class).count() == 113
}

void addCodingToObservation(Observation observation, String code, String codingSystemExtension, String codingExtension) {
def coding = new Coding()

coding.code = code
coding.addExtension(HapiHelper.EXTENSION_CODING_SYSTEM, new StringType(codingSystemExtension))
coding.addExtension(HapiHelper.EXTENSION_CWE_CODING, new StringType(codingExtension))
observation.code.addCoding(coding)
}

Map<String, String> getArgs(String code, String codingSystem, String coding) {
return [
"code" : code,
"codingSystemExtension" : codingSystem,
codingExtension : coding]
}
}
Loading

0 comments on commit 93d46ac

Please sign in to comment.