diff --git a/CHANGELOG.md b/CHANGELOG.md index a4d1c5c86..ac311748f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,9 +12,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ### Added * Added support for mapping different EhrSupplyType (e.g. NHS prescription, OTC sale) into the Medication Statement Prescribing Agency extension -* Added fallback for Condition.asserter to use the EHRComposition / author / agent field when EHRComposition / participant2 is absent. -* +* Added fallback for Condition.asserter to use the EHRComposition / author / agent field when EHRComposition / participant2 is absent. + ### Fixed +* Fixed handling of multiple `` name fields in practitioner XML to JSON mapping - now correctly captures all given names as an array in FHIR HumanName. * Improved error handling in SkeletonProcessingService to throw a meaningful IllegalArgumentException when a payload node cannot be matched to a skeleton document ID, replacing an uninformative NullPointerException. diff --git a/gp2gp-translator/src/main/java/uk/nhs/adaptors/pss/translator/mapper/AgentDirectoryMapper.java b/gp2gp-translator/src/main/java/uk/nhs/adaptors/pss/translator/mapper/AgentDirectoryMapper.java index 3db0f0f93..72d43e450 100644 --- a/gp2gp-translator/src/main/java/uk/nhs/adaptors/pss/translator/mapper/AgentDirectoryMapper.java +++ b/gp2gp-translator/src/main/java/uk/nhs/adaptors/pss/translator/mapper/AgentDirectoryMapper.java @@ -130,17 +130,23 @@ private List getPractitionerName(PN name) { } private static void setTextFromAvailableNameFields(PN name, HumanName humanName) { - var requiresSeparator = StringUtils.isNotBlank(name.getPrefix()) && StringUtils.isNotBlank(name.getGiven()); - var text = StringUtils.join(name.getPrefix(), (requiresSeparator ? " " : ""), name.getGiven()); + var givenNames = getGivenNamesAsString(name); + var requiresSeparator = StringUtils.isNotBlank(name.getPrefix()) && StringUtils.isNotBlank(givenNames); + var text = StringUtils.join(name.getPrefix(), (requiresSeparator ? " " : ""), givenNames); humanName.setText(text); } private void setHumanNameValuesFromName(PN name, HumanName humanName) { humanName.setFamily(name.getFamily()); - var given = getPractitionerGiven(name.getGiven()); - if (given != null) { - humanName.getGiven().add(given); + var givenList = name.getGiven(); + if (givenList != null && !givenList.isEmpty()) { + for (String givenName : givenList) { + var given = getPractitionerGiven(givenName); + if (given != null) { + humanName.getGiven().add(given); + } + } } var prefix = getPractitionerPrefix(name.getPrefix()); @@ -149,10 +155,23 @@ private void setHumanNameValuesFromName(PN name, HumanName humanName) { } } + private static String getGivenNamesAsString(PN name) { + if (name == null) { + return null; + } + + var givenList = name.getGiven().stream() + .filter(StringUtils::isNotBlank) + .toList(); + + return givenList.isEmpty() ? null : StringUtils.join(givenList, " "); + } + private static boolean hasNoName(PN name) { + var hasGiven = name != null && name.getGiven() != null && !name.getGiven().isEmpty(); return name == null || (StringUtils.isBlank(name.getFamily()) - && StringUtils.isBlank(name.getGiven()) + && !hasGiven && StringUtils.isBlank(name.getPrefix())); } diff --git a/gp2gp-translator/src/main/java/uk/nhs/adaptors/pss/translator/util/ParticipantReferenceUtil.java b/gp2gp-translator/src/main/java/uk/nhs/adaptors/pss/translator/util/ParticipantReferenceUtil.java index d4cc10b06..37b5112d2 100644 --- a/gp2gp-translator/src/main/java/uk/nhs/adaptors/pss/translator/util/ParticipantReferenceUtil.java +++ b/gp2gp-translator/src/main/java/uk/nhs/adaptors/pss/translator/util/ParticipantReferenceUtil.java @@ -71,19 +71,16 @@ public static Map> fetchRecorderAndAsserter(RCMRMT03 public static Reference getParticipant2Reference(RCMRMT030101UKEhrComposition ehrComposition, String typeCode) { - var participant2Reference = ehrComposition.getParticipant2().stream() + return ehrComposition.getParticipant2().stream() .filter(participant2 -> participant2.getNullFlavor() == null) .filter(participant2 -> typeCode.equals(participant2.getTypeCode().getFirst())) .map(RCMRMT030101UKParticipant2::getAgentRef) .map(RCMRMT030101UKAgentRef::getId) .filter(II::hasRoot) .map(II::getRoot) - .findFirst(); - - if (participant2Reference.isPresent()) { - return new Reference(PRACTITIONER_REFERENCE_PREFIX.formatted(participant2Reference.get())); - } - return null; + .findFirst() + .map(ref -> new Reference(PRACTITIONER_REFERENCE_PREFIX.formatted(ref))) + .orElse(null); } private static Optional getParticipant2Reference(RCMRMT030101UKEhrComposition ehrComposition) { diff --git a/gp2gp-translator/src/test/java/uk/nhs/adaptors/pss/translator/mapper/AgentDirectoryMapperTest.java b/gp2gp-translator/src/test/java/uk/nhs/adaptors/pss/translator/mapper/AgentDirectoryMapperTest.java index f506f7d3e..e5171ef8e 100644 --- a/gp2gp-translator/src/test/java/uk/nhs/adaptors/pss/translator/mapper/AgentDirectoryMapperTest.java +++ b/gp2gp-translator/src/test/java/uk/nhs/adaptors/pss/translator/mapper/AgentDirectoryMapperTest.java @@ -252,6 +252,245 @@ public void When_MapAgentDirectoryOnlyAgentPersonWithNameElementWithOnlyGiven_Ex ); } + @Test + public void When_MapAgentWithMultipleGivenAndFamilyName_Expect_AllGivenNamesInArray() { + var agentDirectoryXml = """ + + + + + + + + Minire + E + Clarkson + + + + + """; + var agentDirectory = unmarshallAgentDirectoryFromXmlString(agentDirectoryXml); + + var mappedAgents = agentDirectoryMapper.mapAgentDirectory(agentDirectory); + + assertThat(mappedAgents).hasSize(1); + + var practitioner = (Practitioner) mappedAgents.getFirst(); + + assertAll( + () -> assertThat(practitioner.getId()).isEqualTo("CD8E40B3-6A3C-11F1-AE7C-00155D75C807"), + () -> assertThat(practitioner.getNameFirstRep().getFamily()).isEqualTo("Clarkson"), + () -> assertThat(practitioner.getNameFirstRep().getGiven()).hasSize(2), + () -> assertThat(practitioner.getNameFirstRep().getGiven().getFirst().getValue()).isEqualTo("Minire"), + () -> assertThat(practitioner.getNameFirstRep().getGiven().get(1).getValue()).isEqualTo("E"), + () -> assertThat(practitioner.getNameFirstRep().getText()).isNull(), + () -> assertThat(practitioner.getMeta().getProfile().getFirst().getValue()).isEqualTo(PRACT_META_PROFILE) + ); + } + + @Test + public void When_MapAgentWithThreeGivenNames_Expect_AllThreeGivenNamesInArray() { + final var expectedGivenCount = 3; + var agentDirectoryXml = """ + + + + + + + John + Paul + George + Smith + + + + + """; + var agentDirectory = unmarshallAgentDirectoryFromXmlString(agentDirectoryXml); + + var mappedAgents = agentDirectoryMapper.mapAgentDirectory(agentDirectory); + + assertThat(mappedAgents).hasSize(1); + + var practitioner = (Practitioner) mappedAgents.getFirst(); + + assertAll( + () -> assertThat(practitioner.getNameFirstRep().getFamily()).isEqualTo("Smith"), + () -> assertThat(practitioner.getNameFirstRep().getGiven()).hasSize(expectedGivenCount), + () -> assertThat(practitioner.getNameFirstRep().getGiven().getFirst().getValue()).isEqualTo("John"), + () -> assertThat(practitioner.getNameFirstRep().getGiven().get(1).getValue()).isEqualTo("Paul"), + () -> assertThat(practitioner.getNameFirstRep().getGiven().get(2).getValue()).isEqualTo("George"), + () -> assertThat(practitioner.getNameFirstRep().getText()).isNull() + ); + } + + @Test + public void When_MapAgentWithEmptyGivenNameInList_Expect_OnlyNonEmptyGivenNamesAdded() { + var agentDirectoryXml = """ + + + + + + + John + + Paul + Smith + + + + + """; + var agentDirectory = unmarshallAgentDirectoryFromXmlString(agentDirectoryXml); + + var mappedAgents = agentDirectoryMapper.mapAgentDirectory(agentDirectory); + + assertThat(mappedAgents).hasSize(1); + + var practitioner = (Practitioner) mappedAgents.getFirst(); + + // Should only have 2 given names since one is empty + assertAll( + () -> assertThat(practitioner.getNameFirstRep().getFamily()).isEqualTo("Smith"), + () -> assertThat(practitioner.getNameFirstRep().getGiven()).hasSize(2), + () -> assertThat(practitioner.getNameFirstRep().getGiven().getFirst().getValue()).isEqualTo("John"), + () -> assertThat(practitioner.getNameFirstRep().getGiven().get(1).getValue()).isEqualTo("Paul") + ); + } + + @Test + public void When_MapAgentWithMultipleGivenNamesAndPrefix_Expect_AllNamesAndPrefix() { + var agentDirectoryXml = """ + + + + + + + Dr + John + Robert + Smith + + + + + """; + var agentDirectory = unmarshallAgentDirectoryFromXmlString(agentDirectoryXml); + + var mappedAgents = agentDirectoryMapper.mapAgentDirectory(agentDirectory); + + assertThat(mappedAgents).hasSize(1); + + var practitioner = (Practitioner) mappedAgents.getFirst(); + + assertAll( + () -> assertThat(practitioner.getNameFirstRep().getPrefix()).hasSize(1), + () -> assertThat(practitioner.getNameFirstRep().getPrefix().getFirst().getValue()).isEqualTo("Dr"), + () -> assertThat(practitioner.getNameFirstRep().getGiven()).hasSize(2), + () -> assertThat(practitioner.getNameFirstRep().getGiven().getFirst().getValue()).isEqualTo("John"), + () -> assertThat(practitioner.getNameFirstRep().getGiven().get(1).getValue()).isEqualTo("Robert"), + () -> assertThat(practitioner.getNameFirstRep().getFamily()).isEqualTo("Smith") + ); + } + + @Test + public void When_MapAgentWithPrefixButNullGivenNames_Expect_TextSetToPrefix() { + var agentDirectoryXml = """ + + + + + + + Prof + + + + + """; + var agentDirectory = unmarshallAgentDirectoryFromXmlString(agentDirectoryXml); + + var mappedAgents = agentDirectoryMapper.mapAgentDirectory(agentDirectory); + + assertThat(mappedAgents).hasSize(1); + + var practitioner = (Practitioner) mappedAgents.getFirst(); + + assertAll( + () -> assertThat(practitioner.getNameFirstRep().getText()).isEqualTo("Prof"), + () -> assertThat(practitioner.getNameFirstRep().getGiven()).isEmpty(), + () -> assertThat(practitioner.getNameFirstRep().getFamily()).isNull(), + () -> assertThat(practitioner.getNameFirstRep().getPrefix()).isEmpty() + ); + } + + @Test + public void When_MapAgentWithAllGivenNamesEmpty_Expect_TextSetToEmpty() { + // This test ensures that if getGiven() returns an empty list (all items filtered out), + // hasNoName() correctly identifies it as having NO name + var agentDirectoryXml = """ + + + + + + + + + + + + + """; + var agentDirectory = unmarshallAgentDirectoryFromXmlString(agentDirectoryXml); + + var mappedAgents = agentDirectoryMapper.mapAgentDirectory(agentDirectory); + + assertThat(mappedAgents).hasSize(1); + + var practitioner = (Practitioner) mappedAgents.getFirst(); + + assertThat(practitioner.getNameFirstRep().getGiven()).isEmpty(); + } + + @Test + public void When_MapAgentWithPrefixAndEmptyGivenNames_Expect_TextSetOnlyToPrefix() { + var agentDirectoryXml = """ + + + + + + + Dr + + Smith + + + + + """; + var agentDirectory = unmarshallAgentDirectoryFromXmlString(agentDirectoryXml); + + var mappedAgents = agentDirectoryMapper.mapAgentDirectory(agentDirectory); + + assertThat(mappedAgents).hasSize(1); + + var practitioner = (Practitioner) mappedAgents.getFirst(); + + assertAll( + () -> assertThat(practitioner.getNameFirstRep().getFamily()).isEqualTo("Smith"), + () -> assertThat(practitioner.getNameFirstRep().getPrefix()).hasSize(1), + () -> assertThat(practitioner.getNameFirstRep().getPrefix().getFirst().getValue()).isEqualTo("Dr"), + () -> assertThat(practitioner.getNameFirstRep().getGiven()).isEmpty(), + () -> assertThat(practitioner.getNameFirstRep().getText()).isNull() + ); + } + @Test public void When_MapAgentDirectoryOnlyAgentPersonWithNameElementWithOnlyPrefix_Expect_TextSetToPrefix() { var agentDirectoryXml = """ diff --git a/schema/src/main/java/org/hl7/v3/EN.java b/schema/src/main/java/org/hl7/v3/EN.java index c391bd42b..adead270e 100644 --- a/schema/src/main/java/org/hl7/v3/EN.java +++ b/schema/src/main/java/org/hl7/v3/EN.java @@ -69,7 +69,7 @@ public class EN { protected List use; protected String delimiter; protected String family; - protected String given; + protected List given; protected String prefix; protected String suffix; protected IVLTS validTime; @@ -120,12 +120,40 @@ public void setFamily(String family) { this.family = family; } - public String getGiven() { - return given; + /** + * Gets the value of the given property. + * + *

+ * This accessor method returns a reference to the live list, + * not a snapshot. Therefore any modification you make to the + * returned list will be present inside the JAXB object. + * This is why there is not a set method for the given property. + * + *

+ * For example, to add a new item, do as follows: + *

+     *    getGiven().add(newItem);
+     * 
+ * + *

+ * Objects of the following type(s) are allowed in the list + * {@link String } + */ + public List getGiven() { + if (given == null) { + given = new ArrayList<>(); + } + return this.given; } - public void setGiven(String given) { - this.given = given; + /** + * Convenience method for backward compatibility - returns the first given name as a string. + */ + public String getFirstGiven() { + if (given != null && !given.isEmpty()) { + return given.get(0); + } + return null; } public String getPrefix() { diff --git a/schema/src/test/java/JaxbTest.java b/schema/src/test/java/JaxbTest.java index 759873637..fd625500a 100644 --- a/schema/src/test/java/JaxbTest.java +++ b/schema/src/test/java/JaxbTest.java @@ -40,7 +40,8 @@ void When_RCMRIN030000UK06MessageIsUnmarshalled_Expect_FieldsToBeParsable() thro // then assertThat(person.getName().getFamily()).isEqualTo("Whitcombe"); - assertThat(person.getName().getGiven()).isEqualTo("Peter"); + assertThat(person.getName().getGiven()).hasSize(1); + assertThat(person.getName().getGiven().getFirst()).isEqualTo("Peter"); assertThat(person.getName().getPrefix()).isEqualTo("Dr"); assertThat(person.getName().getValidTime().getCenter().getValue()).isEqualTo("20100114"); assertThat(place.getName()).isEqualTo("EMIS Test Practice Location");