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

CA observation transforms from local code to LOINC or PLT #1306

Merged
merged 62 commits into from
Sep 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
b3c2d0b
Create 020_CA_ORU_R01_CDPH_OBX_to_LOINC_0_initial_message.hl7
luis-pabon-tf Sep 10, 2024
f3daa87
Update 020_CA_ORU_R01_CDPH_OBX_to_LOINC_0_initial_message.hl7
luis-pabon-tf Sep 11, 2024
7d5d1ef
Adding translated example file #20
jbiskie Sep 12, 2024
40c2656
Addin skeleton for transformation
luis-pabon-tf Sep 12, 2024
1a602b7
WIP - add initial implementation of MapLocalObservationCode transform
jbiskie Sep 12, 2024
7514d5b
Merge branch 'main' into story/1277-observation-local-codes-to-loinc
luis-pabon-tf Sep 13, 2024
aa5a9b8
Adding unit test for a single observation
jbiskie Sep 13, 2024
5c177f9
Adding test for local code mappable to a PLT
jbiskie Sep 13, 2024
9300a11
Update MapLocalObservationCodes.java
luis-pabon-tf Sep 14, 2024
330c551
Small code cleanup
luis-pabon-tf Sep 14, 2024
4af8afd
Merge branch 'main' into story/1277-observation-local-codes-to-loinc
luis-pabon-tf Sep 14, 2024
cb74b8f
Add tests for when the initial code is a LOINC code
jbiskie Sep 16, 2024
4955e1b
Refactor tests to remove duplicated code
jbiskie Sep 16, 2024
00ef323
Add assertion to evaluateCoding
jbiskie Sep 16, 2024
1902884
Adding test for when primary code is also populated
jbiskie Sep 16, 2024
93aad56
Merge branch 'main' into story/1277-observation-local-codes-to-loinc
luis-pabon-tf Sep 16, 2024
5285140
Add tests for an unmapped local code and empty observation identifer
jbiskie Sep 16, 2024
fc64d6f
Merge branch 'story/1277-observation-local-codes-to-loinc' of https:/…
jbiskie Sep 16, 2024
3a0c081
Updates to transformation to pass first 2 test cases
luis-pabon-tf Sep 16, 2024
5eb84cc
WIP - multiple observation test
jbiskie Sep 16, 2024
e67a48d
Merge branch 'story/1277-observation-local-codes-to-loinc' of https:/…
jbiskie Sep 16, 2024
2dcffa0
All curent tests pass, but the code is not pretty and has at least on…
luis-pabon-tf Sep 16, 2024
ffe4dfb
Merge branch 'story/1277-observation-local-codes-to-loinc' of https:/…
luis-pabon-tf Sep 16, 2024
85ba878
Complete the multiple observations test, add the remaining mappings t…
jbiskie Sep 17, 2024
a3b4f5d
Minor cleanup and refactoring
jbiskie Sep 17, 2024
79e1b30
Replace for() loop with if check for a single coding on the observation
jbiskie Sep 18, 2024
653ad68
Add MapLocalObservationCodes to transformation_definitions.json
jbiskie Sep 18, 2024
4e3bbcc
refactor:
jorg3lopez Sep 18, 2024
e349818
Update sender in log warning, restore mock logger in unit tests
jbiskie Sep 18, 2024
b8c5adb
Commenting out the PLT codes that haven't been mapped yet so they wil…
jbiskie Sep 18, 2024
fc4b6b2
refactor helper method for msh10
jorg3lopez Sep 18, 2024
b44c407
Minor naming tweaks and comment updates
jbiskie Sep 19, 2024
586e0ed
Merge branch 'story/1277-observation-local-codes-to-loinc' of https:/…
jbiskie Sep 19, 2024
37f38c8
add null check for cwe extension
jorg3lopez Sep 19, 2024
5764334
Address null check for getSystem method
tjohnson7021 Sep 19, 2024
a94698f
Missing paren
tjohnson7021 Sep 19, 2024
377d2a7
refactor:
jorg3lopez Sep 19, 2024
92cfa89
refactor:
jorg3lopez Sep 19, 2024
f8f6592
Removed comments and empty lines
tjohnson7021 Sep 19, 2024
581a900
spotlessApply
tjohnson7021 Sep 19, 2024
6b15821
WIP: transform refactoring - flatten conditional blocks
jbiskie Sep 19, 2024
6eb52dd
Refactor transform into simpler methods
jbiskie Sep 20, 2024
2206175
Update minor dependencies to v5.4 (#1339)
renovate[bot] Sep 19, 2024
4b627cc
Update HapiHelper.java
luis-pabon-tf Sep 20, 2024
40c3862
Update dependency com.azure:azure-storage-blob to v12.28.0 (#1344)
renovate[bot] Sep 21, 2024
88eb350
Update Terraform azurerm to v4.3.0 (#1345)
renovate[bot] Sep 23, 2024
1ab14fe
Move urlForCodeType to HapiHelper for future reusability
luis-pabon-tf Sep 23, 2024
ebf0c78
Adding tests for code coverage and addressing Sonar findings
jbiskie Sep 23, 2024
43a3196
Adding tests for code coverage and addressing Sonar findings
jbiskie Sep 23, 2024
2321333
Adding tests for null and empty bundle for resourcesInBundle
jbiskie Sep 23, 2024
e03598f
Adding tests for urlForCodeType
jbiskie Sep 23, 2024
70bec92
HapiHelper: unit test for when bundle is null
jorg3lopez Sep 23, 2024
f16bf25
Updated RS Hurl Script to Replace MSH Header When Not Toggled (#1342)
pluckyswan Sep 23, 2024
5741735
Add control id test, update test name
jbiskie Sep 23, 2024
3522ae4
Fix order of mockLogger registration and injection
jbiskie Sep 23, 2024
bc882ec
Fixed prod prefix mapping
jherrflexion Sep 23, 2024
f353974
Add post-TI and post-RS example files
jbiskie Sep 23, 2024
6b01894
Update dependency gradle to v8.10.2 (#1347)
renovate[bot] Sep 24, 2024
cb222dd
Add final 5 PLT mappings; update mapping for 99717-50
jbiskie Sep 25, 2024
d328552
Merge branch 'main' into story/1277-observation-local-codes-to-loinc
jorg3lopez Sep 27, 2024
bb1dc2c
Add comment to clarify behavior of adding the mapped code
jbiskie Sep 27, 2024
9e281dd
Merge branch 'main' into story/1277-observation-local-codes-to-loinc
jorg3lopez Sep 28, 2024
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package gov.hhs.cdc.trustedintermediary.etor.messages;

/**
* Represents an identifier triplet, consisting of a code, display value, and coding system.
* Identifiers are used in HL7 fields such as OBR-4 and OBX-3. These fields contain two identifier
* triplets, one in subfields 1/2/3, and an alternate in subfields 4/5/6. For reference, see: <a
* href="https://hl7-definition.caristix.com/v2/HL7v2.5.1/Fields/OBX.3"</a>
*/
public record IdentifierCode(String code, String display, String codingSystem) {}
jcrichlake marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
package gov.hhs.cdc.trustedintermediary.etor.ruleengine.transformation.custom;

import gov.hhs.cdc.trustedintermediary.context.ApplicationContext;
import gov.hhs.cdc.trustedintermediary.etor.messages.IdentifierCode;
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 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;
import org.hl7.fhir.r4.model.StringType;

/**
* Maps local observation codes to LOINC/PLT codes. In order to map a code, the observation must
* have a single local code with cwe-coding extension 'alt-coding'. The mapped LOINC or PLT code is
* added to the observation as an additional observation coding with cwe-coding extension 'coding'.
* When converted to/from HL7, these codings correspond to OBX-3.4/5/6 for the local code, and
* OBX-3.1/2/3 for the LOINC/PLT code.
*/
public class MapLocalObservationCodes implements CustomFhirTransformation {
protected final Logger logger = ApplicationContext.getImplementation(Logger.class);

private HashMap<String, IdentifierCode> codingMap;

public MapLocalObservationCodes() {
initMap();
}

@Override
public void transform(FhirResource<?> resource, Map<String, String> args) {
jorg3lopez marked this conversation as resolved.
Show resolved Hide resolved
var bundle = (Bundle) resource.getUnderlyingResource();
var observations = HapiHelper.resourcesInBundle(bundle, Observation.class);

for (Observation obv : observations.toList()) {
var codingList = obv.getCode().getCoding();

if (codingList.size() != 1) {
continue;
}

var coding = codingList.get(0);
if (!hasLocalCodeInAlternateCoding(coding)) {
continue;
}

var identifier = codingMap.get(coding.getCode());
if (identifier == null) {
logUnmappedLocalCode(bundle, coding);
continue;
}

var mappedCoding = getMappedCoding(identifier);

// Add the mapped code as the first in the list, ahead of the existing alternate code
codingList.add(0, mappedCoding);
jbiskie marked this conversation as resolved.
Show resolved Hide resolved
}
}

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;

logger.logWarning(
"Unmapped local code detected: '{}', from sender: '{}', message Id: '{}'",
coding.getCode(),
msh41Value,
HapiHelper.getMessageControlId(bundle));
}

private Coding getMappedCoding(IdentifierCode identifierCode) {
var mappedCoding =
new Coding(
HapiHelper.urlForCodeType(identifierCode.codingSystem()),
identifierCode.code(),
identifierCode.display());
mappedCoding.addExtension(HapiHelper.EXTENSION_CWE_CODING, new StringType("coding"));

mappedCoding.addExtension(
HapiHelper.EXTENSION_CODING_SYSTEM, new StringType(identifierCode.codingSystem()));

return mappedCoding;
}

private void initMap() {
this.codingMap = new HashMap<>();
// ALD
codingMap.put(
"99717-32",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd extract these identifier codes out into an enum so that if they get re-used and updated we don't have multiple places to change the code.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I'd prefer if we do constants or leave as a HashMap until we move the config to the yaml or db.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think these local codes only belong to UCSD. For that reason, we took the simple approach of having the map as part of the class. If other clients need to add their local codes as well, then that's when we can probably inject the map or enum to the class. But since the codes pertain to only one client, I think we can get away with leaving it as is. Thoughts?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we put a comment on this map so that other future devs know that these are all UCSD only values?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

new IdentifierCode(
"85269-9",
"X-linked Adrenoleukodystrophy (X- ALD) newborn screen interpretation",
HapiHelper.LOINC_CODE));
codingMap.put(
"99717-33",
new IdentifierCode(
"85268-1",
"X-linked Adrenoleukodystrophy (X- ALD) newborn screening comment-discussion",
HapiHelper.LOINC_CODE));
codingMap.put(
"99717-34",
new IdentifierCode(
"PLT325",
"ABCD1 gene mutation found [Identifier] in DBS by Sequencing",
HapiHelper.PLT_CODE));
// CAH
codingMap.put(
"99717-6",
new IdentifierCode(
"53340-6",
"17-Hydroxyprogesterone [Moles/volume] in DBS",
HapiHelper.LOINC_CODE));
// CF
codingMap.put(
"99717-35",
new IdentifierCode(
"PLT3289",
"CFTR gene mutation found [Interpretation] in DBS by Sequencing",
HapiHelper.PLT_CODE));
codingMap.put(
"99717-36",
new IdentifierCode(
"PLT3290",
"CFTR gene variant found [Identifier] in DBS by Sequencing comments/discussion",
HapiHelper.PLT_CODE));
// MPS I
codingMap.put(
"99717-48",
new IdentifierCode(
"PLT3258",
"IDUA gene mutations found [Identifier] in DBS by Sequencing",
HapiHelper.PLT_CODE));
codingMap.put(
"99717-44",
new IdentifierCode(
"PLT3291",
"IDUA gene variant analysis in DBS by Sequencing comments/discussion",
HapiHelper.PLT_CODE));
// MPS II
codingMap.put(
"99717-50",
new IdentifierCode(
"PLT3294",
"IDS gene mutations found [Identifier] in Dried Bloodspot by Molecular genetics method",
HapiHelper.PLT_CODE));
// Pompe
codingMap.put(
"99717-47",
new IdentifierCode(
"PLT3252",
"GAA gene mutation found [Identifier] in DBS by Sequencing",
HapiHelper.PLT_CODE));
codingMap.put(
"99717-46",
new IdentifierCode(
"PLT3292",
"GAA gene variant analysis in DBS by Sequencing comments/discussion",
HapiHelper.PLT_CODE));
// SMA
codingMap.put(
"99717-60",
new IdentifierCode(
"PLT3293",
"SMN1 exon 7 deletion analysis in DBS by Sequencing",
HapiHelper.PLT_CODE));
}
}
15 changes: 15 additions & 0 deletions etor/src/main/resources/transformation_definitions.json
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,21 @@
}
]
},
{
"name": "ucsdOruMapLocalObservationCodes",
"description": "Maps local observation codes in OBX-3.4/5/6 to LOINC/PLT codes in OBX-3.1/2/3",
"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": "MapLocalObservationCodes",
"args": {}
}
]
},
{
"name": "ucsdOruCopyOrcOrderProviderToObrOrderProvider",
"description": "Copies the value from ORC12 and uses this value to replace the value in OBR16",
Expand Down
Loading