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

Add 99717-49 Local Code to 76030-6 LOINC mapping #1453

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 13 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
Original file line number Diff line number Diff line change
Expand Up @@ -23,52 +23,68 @@
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(HealthData<?> resource, Map<String, Object> args) {
var codingMap = getMapFromArgs(args);

var bundle = (Bundle) resource.getUnderlyingData();
var msh41Identifier = extractMsh41Identifier(bundle);
var messageId = HapiHelper.getMessageControlId(bundle);
var observations = HapiHelper.resourcesInBundle(bundle, Observation.class);

for (Observation obv : observations.toList()) {
var codingList = obv.getCode().getCoding();
observations
.filter(this::hasValidCoding)
.forEach(
observation ->
processCoding(observation, codingMap, msh41Identifier, messageId));
}

if (codingList.size() != 1) {
continue;
}
private boolean hasValidCoding(Observation observation) {
var codingList = observation.getCode().getCoding();
return codingList.size() == 1 && isLocalCode(codingList.get(0));
}

var coding = codingList.get(0);
if (!HapiHelper.hasDefinedCoding(
coding, HapiHelper.EXTENSION_ALT_CODING, HapiHelper.LOCAL_CODE)) {
continue;
}
private boolean isLocalCode(Coding coding) {
return HapiHelper.hasDefinedCoding(
coding, HapiHelper.EXTENSION_ALT_CODING, HapiHelper.LOCAL_CODE);
}

var identifier = codingMap.get(coding.getCode());
if (identifier == null) {
logUnmappedLocalCode(bundle, coding);
continue;
}
private String extractMsh41Identifier(Bundle bundle) {
var msh41Identifier = HapiHelper.getMSH4_1Identifier(bundle);
return msh41Identifier != null ? msh41Identifier.getValue() : null;
}

var mappedCoding = getMappedCoding(identifier);
private void processCoding(
Observation observation,
Map<String, IdentifierCode> codingMap,
String msh41Identifier,
String messageId) {
var originalCoding = observation.getCode().getCoding().get(0);
IdentifierCode identifier = codingMap.get(originalCoding.getCode());

if (identifier == null) {
logUnmappedLocalCode(originalCoding, msh41Identifier, messageId);
return;
}

// Add the mapped code as the first in the list, ahead of the existing alternate code
codingList.add(0, mappedCoding);
var mappedCoding = getMappedCoding(identifier);
observation.getCode().getCoding().add(0, mappedCoding);
}

private String validateField(String field, String fieldName) {
if (field == null || field.isBlank()) {
throw new IllegalArgumentException("missing or empty required field " + fieldName);
}
return field;
}

private void logUnmappedLocalCode(Bundle bundle, Coding coding) {
var msh41Identifier = HapiHelper.getMSH4_1Identifier(bundle);
var msh41Value = msh41Identifier != null ? msh41Identifier.getValue() : null;
private void logUnmappedLocalCode(Coding coding, String msh41Identifier, String messageId) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Looks like this is only used once, it has a single line and the content is pretty straightforward. Does it need to extracted into its own function?

Copy link
Contributor

Choose a reason for hiding this comment

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

Good suggestion. I'll inline the extracting method and keep the logging one as is to keep the logging logic in one area.

Copy link
Contributor

Choose a reason for hiding this comment

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

What I meant is that we probably don't need logUnmappedLocalCode as it's adding more abstraction without the benefits. You could directly call logger.logWarning without putting it into its own function


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

private Coding getMappedCoding(IdentifierCode identifierCode) {
Expand All @@ -85,92 +101,30 @@ 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
codingMap.put(
"99717-32",
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));
private Map<String, IdentifierCode> getMapFromArgs(Map<String, Object> args) {
var codingMap = new HashMap<String, IdentifierCode>();

// Suppressing the unchecked cast warning. The assignment below will throw a
// ClassCastException if it fails.
@SuppressWarnings("unchecked")
var argsCodingMap = (Map<String, Map<String, String>>) args.get("codingMap");

for (Map.Entry<String, Map<String, String>> entry : argsCodingMap.entrySet()) {
var localCode = entry.getKey();
var mappedCode = getIdentifierCode(entry);

codingMap.put(localCode, mappedCode);
}

return codingMap;
}

private IdentifierCode getIdentifierCode(Map.Entry<String, Map<String, String>> entry) {
var value = entry.getValue();
var code = validateField(value.get("code"), "code");
var display = validateField(value.get("display"), "display");
var codingSystem = validateField(value.get("codingSystem"), "codingSystem");

return new IdentifierCode(code, display, codingSystem);
}
}
70 changes: 69 additions & 1 deletion etor/src/main/resources/transformation_definitions.json
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,75 @@
"rules": [
{
"name": "MapLocalObservationCodes",
"args": {}
"args": {
"codingMap" : {
"99717-6": {
"code": "53340-6",
"display": "17-Hydroxyprogesterone [Moles/volume] in DBS",
"codingSystem": "LN"
},
"99717-32": {
"code": "85269-9",
"display": "X-linked Adrenoleukodystrophy (X- ALD) newborn screen interpretation",
"codingSystem": "LN"
},
"99717-33": {
"code": "85268-1",
"display": "X-linked Adrenoleukodystrophy (X- ALD) newborn screening comment-discussion",
"codingSystem": "LN"
},
"99717-34": {
"code": "PLT325",
"display": "ABCD1 gene mutation found [Identifier] in DBS by Sequencing",
"codingSystem": "PLT"
},
"99717-35": {
"code": "PLT3289",
"display": "CFTR gene mutation found [Interpretation] in DBS by Sequencing",
"codingSystem": "PLT"
},
"99717-36": {
"code": "PLT3290",
"display": "CFTR gene variant found [Identifier] in DBS by Sequencing comments/discussion",
"codingSystem": "PLT"
},
"99717-44": {
"code": "PLT3291",
"display": "IDUA gene variant analysis in DBS by Sequencing comments/discussion",
"codingSystem": "PLT"
},
"99717-46": {
"code": "PLT3292",
"display": "GAA gene variant analysis in DBS by Sequencing comments/discussion",
"codingSystem": "PLT"
},
"99717-47": {
"code": "PLT3252",
"display": "GAA gene mutation found [Identifier] in DBS by Sequencing",
"codingSystem": "PLT"
},
"99717-48": {
"code": "PLT3258",
"display": "IDUA gene mutations found [Identifier] in DBS by Sequencing",
"codingSystem": "PLT"
},
"99717-49": {
"code": "76030-6",
"display": "IDS gene full mutation analysis in Blood or Tissue by Sequencing",
"codingSystem": "LN"
},
"99717-50": {
"code": "PLT3294",
"display": "IDS gene mutations found [Identifier] in Dried Bloodspot by Molecular genetics method",
"codingSystem": "PLT"
},
"99717-60": {
"code": "PLT3293",
"display": "SMN1 exon 7 deletion analysis in DBS by Sequencing",
"codingSystem": "PLT"
}
}
}
}
]
},
Expand Down
Loading