diff --git a/service/src/main/java/uk/nhs/adaptors/gp2gp/ehr/mapper/CodeableConceptCdMapper.java b/service/src/main/java/uk/nhs/adaptors/gp2gp/ehr/mapper/CodeableConceptCdMapper.java index 4a9efea3a..c32ff637a 100644 --- a/service/src/main/java/uk/nhs/adaptors/gp2gp/ehr/mapper/CodeableConceptCdMapper.java +++ b/service/src/main/java/uk/nhs/adaptors/gp2gp/ehr/mapper/CodeableConceptCdMapper.java @@ -3,6 +3,7 @@ import java.util.Collections; import java.util.List; import java.util.Optional; +import java.util.function.BiFunction; import org.apache.commons.lang3.StringUtils; import org.hl7.fhir.dstu3.model.AllergyIntolerance; @@ -37,7 +38,6 @@ public class CodeableConceptCdMapper { private static final String FIXED_ACTUAL_PROBLEM_CODE = "55607006"; private static final String PROBLEM_DISPLAY_NAME = "Problem"; private static final String ACTIVE_CLINICAL_STATUS = "active"; - private static final String RESOLVED_CLINICAL_STATUS = "resolved"; private static final String PRESCRIBING_AGENCY_GP_PRACTICE_CODE = "prescribed-at-gp-practice"; private static final String PRESCRIBING_AGENCY_PREVIOUS_PRACTICE_CODE = "prescribed-by-previous-practice"; private static final String PRESCRIBING_AGENCY_ANOTHER_ORGANISATION_CODE = "prescribed-by-another-organisation"; @@ -51,10 +51,23 @@ public class CodeableConceptCdMapper { private static final String OTHER_CATEGORY_DESCRIPTION = "Other category"; public String mapCodeableConceptToCd(CodeableConcept codeableConcept) { + return mapCodeableConcept(codeableConcept, this::getMainCode); + } + + public String mapCodeableConceptForMedication(CodeableConcept codeableConcept) { + return mapCodeableConcept( + codeableConcept, + (descriptionExtensions, snomedCodeCoding) -> Optional.ofNullable(snomedCodeCoding.getCode())); + } + + private String mapCodeableConcept( + CodeableConcept codeableConcept, + BiFunction>, Coding, Optional> getMainCodeFunction + ) { Optional snomedCodeCoding = getSnomedCodeCoding(codeableConcept); if (snomedCodeCoding.isEmpty()) { - return buildNullFlavourCodeableConceptCd(codeableConcept, snomedCodeCoding); + return mapToNullFlavorCodeableConcept(codeableConcept); } var builder = CodeableConceptCdTemplateParameters.builder(); @@ -64,87 +77,60 @@ public String mapCodeableConceptToCd(CodeableConcept codeableConcept) { builder.mainCodeSystem(SNOMED_SYSTEM_CODE); - var mainCode = getMainCode(descriptionExtensions, snomedCodeCoding.get()); - mainCode.ifPresent(builder::mainCode); - var mainDisplayName = getMainDisplayName(descriptionExtensions, snomedCodeCoding.get()); mainDisplayName.ifPresent(builder::mainDisplayName); builder.mainOriginalText(codeableConcept.getText()); builder.translations(getNonSnomedCodeCodings(codeableConcept)); + var mainCode = getMainCodeFunction.apply(descriptionExtensions, snomedCodeCoding.get()); + mainCode.ifPresent(builder::mainCode); + return TemplateUtils.fillTemplate(CODEABLE_CONCEPT_CD_TEMPLATE, builder.build()); } - // Medications are currently using D&T Codes rather than snomed codes but are being passed through as SNOMED codes which is - // creating a degradation on the receiving side. Until the types are configured correctly and agreed to a specification - // we have agreed to use the Concept ID rather than Description Id for medications which will avoided the degradation. - public String mapCodeableConceptForMedication(CodeableConcept codeableConcept) { + public String mapCodeableConceptToCdForAllergy( + CodeableConcept codeableConcept, + AllergyIntolerance.AllergyIntoleranceClinicalStatus allergyIntoleranceClinicalStatus + ) { var builder = CodeableConceptCdTemplateParameters.builder(); - var snomedCodeCoding = getSnomedCodeCoding(codeableConcept); + Optional snomedCodeCoding = getSnomedCodeCoding(codeableConcept); if (snomedCodeCoding.isEmpty()) { - return buildNullFlavourCodeableConceptCd(codeableConcept, snomedCodeCoding); + builder.nullFlavor(true); + return TemplateUtils.fillTemplate(CODEABLE_CONCEPT_CD_TEMPLATE, builder.build()); } - var extension = retrieveDescriptionExtension(snomedCodeCoding.get()) - .map(Extension::getExtension) - .orElse(Collections.emptyList()); - - builder.mainCodeSystem(SNOMED_SYSTEM_CODE); - - Optional code = Optional.ofNullable(snomedCodeCoding.get().getCode()); - code.ifPresent(builder::mainCode); + if (ACTIVE_CLINICAL_STATUS.equals(allergyIntoleranceClinicalStatus.toCode())) { + builder.mainCodeSystem(SNOMED_SYSTEM_CODE); + } else { + builder.nullFlavor(true); + } - Optional displayName = extension.stream() - .filter(displayExtension -> DESCRIPTION_DISPLAY.equals(displayExtension.getUrl())) - .map(description -> description.getValue().toString()) - .findFirst() - .or(() -> Optional.ofNullable(snomedCodeCoding.get().getDisplay())); - displayName.ifPresent(builder::mainDisplayName); + getAllergyMainCode(snomedCodeCoding.get()).ifPresent(builder::mainCode); + getCodingDisplayName(snomedCodeCoding.get()).ifPresent(builder::mainDisplayName); - builder.mainOriginalText(codeableConcept.getText()); + if (codeableConcept.hasText()) { + builder.mainOriginalText(codeableConcept.getText()); + } else { + var originalText = findOriginalTextForAllergy(codeableConcept, snomedCodeCoding, allergyIntoleranceClinicalStatus); + originalText.ifPresent(builder::mainOriginalText); + } - builder.translations(getNonSnomedCodeCodings(codeableConcept)); return TemplateUtils.fillTemplate(CODEABLE_CONCEPT_CD_TEMPLATE, builder.build()); } - public String mapCodeableConceptToCdForAllergy(CodeableConcept codeableConcept, AllergyIntolerance.AllergyIntoleranceClinicalStatus - allergyIntoleranceClinicalStatus) { - var builder = CodeableConceptCdTemplateParameters.builder(); - var mainCode = getSnomedCodeCoding(codeableConcept); - - builder.nullFlavor(mainCode.isEmpty()); - - if (mainCode.isPresent()) { - var extension = retrieveDescriptionExtension(mainCode.get()) - .map(Extension::getExtension) - .orElse(Collections.emptyList()); - - if (ACTIVE_CLINICAL_STATUS.equals(allergyIntoleranceClinicalStatus.toCode())) { - builder.mainCodeSystem(SNOMED_SYSTEM_CODE); - } else { - builder.nullFlavor(true); - } + private static Optional getCodingDisplayName(Coding snomedCodeCoding) { + return Optional.ofNullable(snomedCodeCoding.getDisplay()); + } - Optional code = extension.stream() + private static Optional getAllergyMainCode(Coding snomedCodeCoding) { + return retrieveDescriptionExtension(snomedCodeCoding) + .flatMap(extension -> extension.getExtension().stream() .filter(descriptionExt -> DESCRIPTION_ID.equals(descriptionExt.getUrl())) - .map(description -> description.getValue().toString()) .findFirst() - .or(() -> Optional.ofNullable(mainCode.get().getCode())); - code.ifPresent(builder::mainCode); - - Optional displayName = Optional.ofNullable(mainCode.get().getDisplay()); - displayName.ifPresent(builder::mainDisplayName); - - if (codeableConcept.hasText()) { - builder.mainOriginalText(codeableConcept.getText()); - } else { - var originalText = findOriginalTextForAllergy(codeableConcept, mainCode, allergyIntoleranceClinicalStatus); - originalText.ifPresent(builder::mainOriginalText); - } - } - return TemplateUtils.fillTemplate(CODEABLE_CONCEPT_CD_TEMPLATE, builder.build()); + .map(description -> description.getValue().toString())) + .or(() -> Optional.ofNullable(snomedCodeCoding.getCode())); } public String mapCodeableConceptToCdForTransformedActualProblemHeader(CodeableConcept codeableConcept) { @@ -343,7 +329,7 @@ private Optional findOriginalText(CodeableConcept codeableConcept, Optio return Optional.ofNullable(codeableConcept.getText()); } else { if (coding.get().hasDisplay()) { - return Optional.ofNullable(coding.get().getDisplay()); + return getCodingDisplayName(coding.get()); } else { var extension = retrieveDescriptionExtension(coding.get()); return extension.stream() @@ -362,52 +348,27 @@ private Optional findOriginalTextForAllergy( Optional coding, AllergyIntolerance.AllergyIntoleranceClinicalStatus allergyIntoleranceClinicalStatus ) { - if (!allergyIntoleranceClinicalStatus.toCode().isEmpty()) { - if (RESOLVED_CLINICAL_STATUS.equals(allergyIntoleranceClinicalStatus.toCode())) { - if (coding.isPresent()) { - if (codeableConcept.hasText()) { - return Optional.ofNullable(codeableConcept.getText()); - } else { - var extension = retrieveDescriptionExtension(coding.get()); - if (extension.isPresent()) { - Optional originalText = extension - .get() - .getExtension().stream() - .filter(displayExtension -> DESCRIPTION_DISPLAY.equals(displayExtension.getUrl())) - .map(extension1 -> extension1.getValue().toString()) - .findFirst(); - - if (originalText.isPresent()) { - return originalText; - } else if (coding.get().hasDisplay()) { - return Optional.ofNullable(coding.get().getDisplay()); - } - } else if (coding.get().hasDisplay()) { - return Optional.ofNullable(coding.get().getDisplay()); - } - } - } - } else if (ACTIVE_CLINICAL_STATUS.equals(allergyIntoleranceClinicalStatus.toCode())) { - Optional extension = retrieveDescriptionExtension(coding.get()); - if (extension.isPresent()) { - Optional originalText = extension - .get() - .getExtension().stream() - .filter(displayExtension -> DESCRIPTION_DISPLAY.equals(displayExtension.getUrl())) - .map(extension1 -> extension1.getValue().toString()) - .findFirst(); - if (originalText.isPresent() && StringUtils.isNotBlank(originalText.get())) { - return originalText; - } - } + if (coding.isEmpty()) { + return Optional.empty(); + } - return Optional.empty(); - } + if (ACTIVE_CLINICAL_STATUS.equals(allergyIntoleranceClinicalStatus.toCode())) { + return getOriginalTextForActiveAllergy(coding.get()); } return CodeableConceptMappingUtils.extractTextOrCoding(codeableConcept); } + private Optional getOriginalTextForActiveAllergy(Coding coding) { + return retrieveDescriptionExtension(coding) + .flatMap(value -> value + .getExtension().stream() + .filter(displayExtension -> DESCRIPTION_DISPLAY.equals(displayExtension.getUrl())) + .map(extension1 -> extension1.getValue().toString()) + .findFirst() + ); + } + private Optional findDisplayText(Coding coding) { return Optional.ofNullable(coding.getDisplay()); } @@ -420,7 +381,7 @@ private boolean isPrescribingAgency(Coding coding) { return coding.hasSystem() && coding.getSystem().equals(CARE_CONNECT_PRESCRIBING_AGENCY_SYSTEM); } - private Optional retrieveDescriptionExtension(Coding coding) { + private static Optional retrieveDescriptionExtension(Coding coding) { return coding .getExtension() .stream() @@ -435,7 +396,6 @@ public String getDisplayFromCodeableConcept(CodeableConcept codeableConcept) { } public String mapToNullFlavorCodeableConcept(CodeableConcept codeableConcept) { - var builder = CodeableConceptCdTemplateParameters.builder().nullFlavor(true); var mainCode = getSnomedCodeCoding(codeableConcept); @@ -456,15 +416,6 @@ public String mapToNullFlavorCodeableConceptForAllergy( return TemplateUtils.fillTemplate(CODEABLE_CONCEPT_CD_TEMPLATE, builder.build()); } - private String buildNullFlavourCodeableConceptCd(CodeableConcept codeableConcept, Optional snomedCode) { - var builder = CodeableConceptCdTemplateParameters.builder(); - builder.nullFlavor(true); - var originalText = findOriginalText(codeableConcept, snomedCode); - originalText.ifPresent(builder::mainOriginalText); - - return TemplateUtils.fillTemplate(CODEABLE_CONCEPT_CD_TEMPLATE, builder.build()); - } - private Optional getMainCode(Optional> descriptionExtensions, Coding snomedCodeCoding) { if (descriptionExtensions.isPresent()) { var descriptionCode = descriptionExtensions.get().stream() @@ -494,4 +445,4 @@ private Optional getMainDisplayName(Optional> descriptio return Optional.ofNullable(snomedCodeCoding.getDisplay()); } -} +} \ No newline at end of file diff --git a/service/src/test/java/uk/nhs/adaptors/gp2gp/ehr/mapper/CodeableConceptCdMapperTest.java b/service/src/test/java/uk/nhs/adaptors/gp2gp/ehr/mapper/CodeableConceptCdMapperTest.java index e0ee74097..f6da40e6a 100644 --- a/service/src/test/java/uk/nhs/adaptors/gp2gp/ehr/mapper/CodeableConceptCdMapperTest.java +++ b/service/src/test/java/uk/nhs/adaptors/gp2gp/ehr/mapper/CodeableConceptCdMapperTest.java @@ -127,6 +127,48 @@ void When_MappingCodeableConceptWithNonSnomedCodeSystems_Expect_ManifestedXmlCon assertThat(outputMessageXml).isEqualToIgnoringWhitespace(expectedOutputXml); } + @Test + void When_MapToNullFlavorCodeableConceptForAllergyWithoutSnomedCode_Expect_OriginalTextIsNotPresent() { + var inputJson = """ + { + "resourceType": "AllergyIntolerance", + "id": "0C1232CF-D34B-4C16-A5F4-0F6461C51A41", + "meta": { + "profile": [ + "https://fhir.nhs.uk/STU3/StructureDefinition/CareConnect-GPC-AllergyIntolerance-1" + ] + }, + "identifier": [ + { + "system": "https://EMISWeb/A82038", + "value": "55D2363D57A248F49A745B2E03F5E93D0C1232CFD34B4C16A5F40F6461C51A41" + } + ], + "code": { + "coding": [ + { + "system": "http://read.info/readv2", + "code": "TJ00800", + "display": "Adverse reaction to pivampicillin rt" + } + ], + "text": "Adverse reaction to pivampicillin" + } + }"""; + var expectedOutputXML = """ + + + """; + var codeableConcept = fhirParseService.parseResource(inputJson, AllergyIntolerance.class).getCode(); + + var outputXml = codeableConceptCdMapper.mapToNullFlavorCodeableConceptForAllergy( + codeableConcept, + AllergyIntolerance.AllergyIntoleranceClinicalStatus.ACTIVE + ); + + assertThat(outputXml).isEqualToIgnoringWhitespace(expectedOutputXML); + } + @ParameterizedTest @MethodSource("getTestArgumentsActualProblem") public void When_MappingStubbedCodeableConceptForActualProblemHeader_Expect_HL7CdObjectXml(String inputJson, String outputXml)