diff --git a/backend/src/main/java/gov/cdc/usds/simplereport/api/Translators.java b/backend/src/main/java/gov/cdc/usds/simplereport/api/Translators.java index a08d20198a..243d08114a 100644 --- a/backend/src/main/java/gov/cdc/usds/simplereport/api/Translators.java +++ b/backend/src/main/java/gov/cdc/usds/simplereport/api/Translators.java @@ -441,12 +441,64 @@ public static PersonName consolidateNameArguments( Map.entry(OTHER, "Other")); private static final Set ORGANIZATION_TYPE_KEYS = ORGANIZATION_TYPES.keySet(); + private static final String DETECTED_SNOMED = "260373001"; + private static final String NOT_DETECTED_SNOMED = "260415000"; + private static final String INVALID_SNOMED = "455371000124106"; + public static final Map ABNORMAL_SNOMEDS = + Map.ofEntries( + Map.entry( + DETECTED_SNOMED, + new SnomedConceptRecord("Detected", DETECTED_SNOMED, TestResult.POSITIVE)), + Map.entry( + "720735008", + new SnomedConceptRecord("Presumptive positive", "720735008", TestResult.POSITIVE)), + Map.entry( + "10828004", new SnomedConceptRecord("Positive", "10828004", TestResult.POSITIVE)), + Map.entry( + "11214006", new SnomedConceptRecord("Reactive", "11214006", TestResult.POSITIVE))); + + public static final Map NORMAL_SNOMEDS = + Map.ofEntries( + Map.entry( + NOT_DETECTED_SNOMED, + new SnomedConceptRecord("Not detected", NOT_DETECTED_SNOMED, TestResult.NEGATIVE)), + Map.entry( + "260385009", new SnomedConceptRecord("Negative", "260385009", TestResult.NEGATIVE)), + Map.entry( + "895231008", + new SnomedConceptRecord( + "Not detected in pooled specimen", "895231008", TestResult.NEGATIVE)), + Map.entry( + "131194007", + new SnomedConceptRecord("Non-Reactive", "131194007", TestResult.NEGATIVE)), + // certain UNDETERMINED codes are also flagged as normal + // https://github.com/CDCgov/prime-reportstream/blob/1ffae4ca0b04cd0aa9f169e26813ecd86df71bb5/prime-router/src/main/kotlin/metadata/Mappers.kt#L768 + Map.entry( + "419984006", + new SnomedConceptRecord("Inconclusive", "419984006", TestResult.UNDETERMINED)), + Map.entry( + "42425007", + new SnomedConceptRecord("Equivocal", "42425007", TestResult.UNDETERMINED)), + Map.entry( + "82334004", + new SnomedConceptRecord("Indeterminate", "82334004", TestResult.UNDETERMINED)), + Map.entry( + "373121007", + new SnomedConceptRecord("Test not done", "373121007", TestResult.UNDETERMINED)), + Map.entry( + INVALID_SNOMED, + new SnomedConceptRecord("Invalid result", INVALID_SNOMED, TestResult.UNDETERMINED)), + Map.entry( + "125154007", + new SnomedConceptRecord( + "Specimen unsatisfactory for evaluation", "125154007", TestResult.UNDETERMINED))); + public static final SnomedConceptRecord DETECTED_SNOMED_CONCEPT = - new SnomedConceptRecord("Detected", "260373001", TestResult.POSITIVE); + ABNORMAL_SNOMEDS.get(DETECTED_SNOMED); private static final SnomedConceptRecord NOT_DETECTED_SNOMED_CONCEPT = - new SnomedConceptRecord("Not detected", "260415000", TestResult.NEGATIVE); + NORMAL_SNOMEDS.get(NOT_DETECTED_SNOMED); private static final SnomedConceptRecord INVALID_SNOMED_CONCEPT = - new SnomedConceptRecord("Invalid result", "455371000124106", TestResult.UNDETERMINED); + NORMAL_SNOMEDS.get(INVALID_SNOMED); private static final List RESULTS_SNOMED_CONCEPTS = List.of(DETECTED_SNOMED_CONCEPT, NOT_DETECTED_SNOMED_CONCEPT, INVALID_SNOMED_CONCEPT); diff --git a/backend/src/main/java/gov/cdc/usds/simplereport/api/converter/FhirConverter.java b/backend/src/main/java/gov/cdc/usds/simplereport/api/converter/FhirConverter.java index 9e6f4552b9..bf2094f959 100644 --- a/backend/src/main/java/gov/cdc/usds/simplereport/api/converter/FhirConverter.java +++ b/backend/src/main/java/gov/cdc/usds/simplereport/api/converter/FhirConverter.java @@ -1,10 +1,11 @@ package gov.cdc.usds.simplereport.api.converter; -import static gov.cdc.usds.simplereport.api.Translators.DETECTED_SNOMED_CONCEPT; +import static gov.cdc.usds.simplereport.api.Translators.ABNORMAL_SNOMEDS; import static gov.cdc.usds.simplereport.api.Translators.FEMALE; import static gov.cdc.usds.simplereport.api.Translators.GENDER_IDENTITIES; import static gov.cdc.usds.simplereport.api.Translators.MALE; import static gov.cdc.usds.simplereport.api.Translators.NON_BINARY; +import static gov.cdc.usds.simplereport.api.Translators.NORMAL_SNOMEDS; import static gov.cdc.usds.simplereport.api.Translators.OTHER; import static gov.cdc.usds.simplereport.api.Translators.REFUSED; import static gov.cdc.usds.simplereport.api.Translators.TRANS_MAN; @@ -159,6 +160,7 @@ import org.hl7.fhir.r4.model.Specimen; import org.hl7.fhir.r4.model.StringType; import org.hl7.fhir.r4.model.Type; +import org.owasp.encoder.Encode; import org.springframework.boot.info.GitProperties; import org.springframework.data.util.Pair; import org.springframework.stereotype.Component; @@ -743,9 +745,10 @@ public Observation convertToObservation(ConvertToObservationProps props) { props.getCorrectionReason(), observation); - observation - .addInterpretation() - .addCoding(convertToAbnormalFlagInterpretation(props.getResultCode())); + Coding abnormalFlagCode = convertToAbnormalFlagInterpretation(props.getResultCode()); + if (abnormalFlagCode != null) { + observation.addInterpretation().addCoding(abnormalFlagCode); + } observation.setIssued(props.getIssued()); observation.getIssuedElement().setTimeZoneZulu(true); @@ -753,19 +756,21 @@ public Observation convertToObservation(ConvertToObservationProps props) { return observation; } - private Coding convertToAbnormalFlagInterpretation(String resultCode) { + public Coding convertToAbnormalFlagInterpretation(String resultCode) { Coding abnormalFlag = new Coding(); abnormalFlag.setSystem(ABNORMAL_FLAGS_CODE_SYSTEM); - if (resultCode.equals(DETECTED_SNOMED_CONCEPT.code())) { + if (ABNORMAL_SNOMEDS.keySet().contains(resultCode)) { abnormalFlag.setCode(ABNORMAL_FLAG_ABNORMAL.code()); abnormalFlag.setDisplay(ABNORMAL_FLAG_ABNORMAL.displayName()); - } else { + } else if (NORMAL_SNOMEDS.keySet().contains(resultCode)) { abnormalFlag.setCode(ABNORMAL_FLAG_NORMAL.code()); abnormalFlag.setDisplay(ABNORMAL_FLAG_NORMAL.displayName()); + } else { + log.info("Unsupported SNOMED result code: {}", Encode.forJava(resultCode)); + return null; } - return abnormalFlag; } diff --git a/backend/src/test/java/gov/cdc/usds/simplereport/api/converter/FhirConverterTest.java b/backend/src/test/java/gov/cdc/usds/simplereport/api/converter/FhirConverterTest.java index c70cd7ff74..e53764efd4 100644 --- a/backend/src/test/java/gov/cdc/usds/simplereport/api/converter/FhirConverterTest.java +++ b/backend/src/test/java/gov/cdc/usds/simplereport/api/converter/FhirConverterTest.java @@ -62,6 +62,7 @@ import org.apache.commons.io.IOUtils; import org.hl7.fhir.r4.model.Bundle.BundleEntryComponent; import org.hl7.fhir.r4.model.Bundle.BundleType; +import org.hl7.fhir.r4.model.Coding; import org.hl7.fhir.r4.model.ContactPoint; import org.hl7.fhir.r4.model.ContactPoint.ContactPointSystem; import org.hl7.fhir.r4.model.ContactPoint.ContactPointUse; @@ -2045,4 +2046,27 @@ void convertToSpecimen_withoutCollectionCodeAndName_useDefaults() { .isEqualTo(DEFAULT_LOCATION_CODE); assertThat(specimen.getCollection().getBodySite().getText()).isEqualTo(DEFAULT_LOCATION_NAME); } + + @Test + void convertToAbnormalFlagInterpretation_withUnsupportedSnomed_returnsNull() { + String snomed = "1111"; + Coding actual = fhirConverter.convertToAbnormalFlagInterpretation(snomed); + assertThat(actual).isNull(); + } + + @ParameterizedTest + @MethodSource("snomedArgs") + void convertToAbnormalFlagInterpretation_matches( + String snomed, String expectedCode, String expectedCodeName) { + Coding actual = fhirConverter.convertToAbnormalFlagInterpretation(snomed); + assertThat(actual.getCode()).isEqualTo(expectedCode); + assertThat(actual.getDisplay()).isEqualTo(expectedCodeName); + } + + private static Stream snomedArgs() { + return Stream.of( + arguments("10828004", "A", "Abnormal"), + arguments("131194007", "N", "Normal"), + arguments("455371000124106", "N", "Normal")); + } }