Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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 @@ -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;
Expand Down Expand Up @@ -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";
Expand All @@ -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<Optional<List<Extension>>, Coding, Optional<String>> getMainCodeFunction
) {
Optional<Coding> snomedCodeCoding = getSnomedCodeCoding(codeableConcept);

if (snomedCodeCoding.isEmpty()) {
return buildNullFlavourCodeableConceptCd(codeableConcept, snomedCodeCoding);
return mapToNullFlavorCodeableConcept(codeableConcept);
}

var builder = CodeableConceptCdTemplateParameters.builder();
Expand All @@ -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<Coding> 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<String> 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<String> 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<String> getCodingDisplayName(Coding snomedCodeCoding) {
return Optional.ofNullable(snomedCodeCoding.getDisplay());
}

Optional<String> code = extension.stream()
private static Optional<String> 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<String> 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) {
Expand Down Expand Up @@ -343,7 +329,7 @@ private Optional<String> 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()
Expand All @@ -362,52 +348,27 @@ private Optional<String> findOriginalTextForAllergy(
Optional<Coding> 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<String> 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> extension = retrieveDescriptionExtension(coding.get());
if (extension.isPresent()) {
Optional<String> 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<String> 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<String> findDisplayText(Coding coding) {
return Optional.ofNullable(coding.getDisplay());
}
Expand All @@ -420,7 +381,7 @@ private boolean isPrescribingAgency(Coding coding) {
return coding.hasSystem() && coding.getSystem().equals(CARE_CONNECT_PRESCRIBING_AGENCY_SYSTEM);
}

private Optional<Extension> retrieveDescriptionExtension(Coding coding) {
private static Optional<Extension> retrieveDescriptionExtension(Coding coding) {
return coding
.getExtension()
.stream()
Expand All @@ -435,7 +396,6 @@ public String getDisplayFromCodeableConcept(CodeableConcept codeableConcept) {
}

public String mapToNullFlavorCodeableConcept(CodeableConcept codeableConcept) {

var builder = CodeableConceptCdTemplateParameters.builder().nullFlavor(true);
var mainCode = getSnomedCodeCoding(codeableConcept);

Expand All @@ -456,15 +416,6 @@ public String mapToNullFlavorCodeableConceptForAllergy(
return TemplateUtils.fillTemplate(CODEABLE_CONCEPT_CD_TEMPLATE, builder.build());
}

private String buildNullFlavourCodeableConceptCd(CodeableConcept codeableConcept, Optional<Coding> 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<String> getMainCode(Optional<List<Extension>> descriptionExtensions, Coding snomedCodeCoding) {
if (descriptionExtensions.isPresent()) {
var descriptionCode = descriptionExtensions.get().stream()
Expand Down Expand Up @@ -494,4 +445,4 @@ private Optional<String> getMainDisplayName(Optional<List<Extension>> descriptio

return Optional.ofNullable(snomedCodeCoding.getDisplay());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 = """
<code nullFlavor="UNK">
</code>
""";
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)
Expand Down