4312 add support for member identifier parameter return from $member match operation (#4314)

* added failing test

* implemented solution

* added change log

* removed special character in change log file name

* added check for member identifier of type MB

* changed msg code to latest

* fixed test for updated msg code

Co-authored-by: Steven Li <steven@smilecdr.com>
This commit is contained in:
StevenXLi 2022-12-07 15:55:53 -05:00 committed by GitHub
parent 403016f846
commit 0e59665711
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 91 additions and 24 deletions

View File

@ -211,6 +211,8 @@ public class Constants {
* $member-match operation * $member-match operation
*/ */
public static final String PARAM_MEMBER_PATIENT = "MemberPatient"; public static final String PARAM_MEMBER_PATIENT = "MemberPatient";
public static final String PARAM_MEMBER_IDENTIFIER = "MemberIdentifier";
public static final String PARAM_OLD_COVERAGE = "OldCoverage"; public static final String PARAM_OLD_COVERAGE = "OldCoverage";
public static final String PARAM_NEW_COVERAGE = "NewCoverage"; public static final String PARAM_NEW_COVERAGE = "NewCoverage";
public static final String PARAM_CONSENT = "Consent"; public static final String PARAM_CONSENT = "Consent";

View File

@ -0,0 +1,5 @@
---
type: add
issue: 4312
title: "Previously, when $member-match operation is executed, the parameters that were returned did not include the member identifier as a parameter.
This has now been added, and the consent resource is now updated with this identifier that's being returned."

View File

@ -29,10 +29,10 @@ import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.rest.server.provider.ProviderConstants; import ca.uhn.fhir.rest.server.provider.ProviderConstants;
import org.hl7.fhir.r4.model.Consent;
import org.hl7.fhir.r4.model.Coverage; import org.hl7.fhir.r4.model.Coverage;
import org.hl7.fhir.r4.model.Parameters; import org.hl7.fhir.r4.model.Parameters;
import org.hl7.fhir.r4.model.Patient; import org.hl7.fhir.r4.model.Patient;
import org.hl7.fhir.r4.model.Consent;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import java.util.Optional; import java.util.Optional;
@ -120,7 +120,7 @@ public class MemberMatchR4ResourceProvider {
} }
myMemberMatcherR4Helper.addMemberIdentifierToMemberPatient(theMemberPatient, patient.getIdentifierFirstRep()); myMemberMatcherR4Helper.addMemberIdentifierToMemberPatient(theMemberPatient, patient.getIdentifierFirstRep());
myMemberMatcherR4Helper.updateConsentForMemberMatch(theConsent, patient); myMemberMatcherR4Helper.updateConsentForMemberMatch(theConsent, patient, theMemberPatient);
return myMemberMatcherR4Helper.buildSuccessReturnParameters(theMemberPatient, theCoverageToLink, theConsent); return myMemberMatcherR4Helper.buildSuccessReturnParameters(theMemberPatient, theCoverageToLink, theConsent);
} }

View File

@ -1,14 +1,15 @@
package ca.uhn.fhir.jpa.provider.r4; package ca.uhn.fhir.jpa.provider.r4;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.api.model.DaoMethodOutcome;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.param.DateParam; import ca.uhn.fhir.rest.param.DateParam;
import ca.uhn.fhir.rest.param.StringOrListParam; import ca.uhn.fhir.rest.param.StringOrListParam;
import ca.uhn.fhir.rest.param.StringParam; import ca.uhn.fhir.rest.param.StringParam;
import ca.uhn.fhir.rest.param.TokenOrListParam; import ca.uhn.fhir.rest.param.TokenOrListParam;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.util.ParametersUtil; import ca.uhn.fhir.util.ParametersUtil;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
@ -26,16 +27,14 @@ import org.hl7.fhir.r4.model.Identifier;
import org.hl7.fhir.r4.model.Parameters; import org.hl7.fhir.r4.model.Parameters;
import org.hl7.fhir.r4.model.Patient; import org.hl7.fhir.r4.model.Patient;
import org.hl7.fhir.r4.model.Reference; import org.hl7.fhir.r4.model.Reference;
import org.hl7.fhir.r4.model.StringType;
import org.springframework.beans.factory.annotation.Autowired;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.UUID;
import static ca.uhn.fhir.rest.api.Constants.PARAM_CONSENT; import static ca.uhn.fhir.rest.api.Constants.PARAM_CONSENT;
import static ca.uhn.fhir.rest.api.Constants.PARAM_MEMBER_IDENTIFIER;
import static ca.uhn.fhir.rest.api.Constants.PARAM_MEMBER_PATIENT; import static ca.uhn.fhir.rest.api.Constants.PARAM_MEMBER_PATIENT;
import static ca.uhn.fhir.rest.api.Constants.PARAM_NEW_COVERAGE; import static ca.uhn.fhir.rest.api.Constants.PARAM_NEW_COVERAGE;
@ -102,13 +101,13 @@ public class MemberMatcherR4Helper {
// search by received old coverage id // search by received old coverage id
List<IBaseResource> foundCoverages = findCoverageByCoverageId(theCoverageToMatch); List<IBaseResource> foundCoverages = findCoverageByCoverageId(theCoverageToMatch);
if (foundCoverages.size() == 1 && isCoverage(foundCoverages.get(0))) { if (foundCoverages.size() == 1 && isCoverage(foundCoverages.get(0))) {
return Optional.of( (Coverage) foundCoverages.get(0) ); return Optional.of((Coverage) foundCoverages.get(0));
} }
// search by received old coverage identifier // search by received old coverage identifier
foundCoverages = findCoverageByCoverageIdentifier(theCoverageToMatch); foundCoverages = findCoverageByCoverageIdentifier(theCoverageToMatch);
if (foundCoverages.size() == 1 && isCoverage(foundCoverages.get(0))) { if (foundCoverages.size() == 1 && isCoverage(foundCoverages.get(0))) {
return Optional.of( (Coverage) foundCoverages.get(0) ); return Optional.of((Coverage) foundCoverages.get(0));
} }
return Optional.empty(); return Optional.empty();
@ -142,9 +141,9 @@ public class MemberMatcherR4Helper {
return retVal.getAllResources(); return retVal.getAllResources();
} }
public void updateConsentForMemberMatch(Consent theConsent, Patient thePatient) { public void updateConsentForMemberMatch(Consent theConsent, Patient thePatient, Patient theMemberPatient) {
addClientIdAsExtensionToConsentIfAvailable(theConsent); addClientIdAsExtensionToConsentIfAvailable(theConsent);
addIdentifierToConsent(theConsent); addIdentifierToConsent(theConsent, theMemberPatient);
updateConsentPatientAndPerformer(theConsent, thePatient); updateConsentPatientAndPerformer(theConsent, thePatient);
// save the resource // save the resource
@ -156,9 +155,29 @@ public class MemberMatcherR4Helper {
ParametersUtil.addParameterToParameters(myFhirContext, parameters, PARAM_MEMBER_PATIENT, theMemberPatient); ParametersUtil.addParameterToParameters(myFhirContext, parameters, PARAM_MEMBER_PATIENT, theMemberPatient);
ParametersUtil.addParameterToParameters(myFhirContext, parameters, PARAM_NEW_COVERAGE, theCoverage); ParametersUtil.addParameterToParameters(myFhirContext, parameters, PARAM_NEW_COVERAGE, theCoverage);
ParametersUtil.addParameterToParameters(myFhirContext, parameters, PARAM_CONSENT, theConsent); ParametersUtil.addParameterToParameters(myFhirContext, parameters, PARAM_CONSENT, theConsent);
ParametersUtil.addParameterToParameters(myFhirContext, parameters, PARAM_MEMBER_IDENTIFIER, getIdentifier(theMemberPatient));
return (Parameters) parameters; return (Parameters) parameters;
} }
private Identifier getIdentifier(Patient theMemberPatient) {
List<Identifier> memberIdentifiers = theMemberPatient.getIdentifier();
if (memberIdentifiers != null && memberIdentifiers.size() > 0) {
for (Identifier memberIdentifier : memberIdentifiers) {
List<Coding> typeCodings = memberIdentifier.getType().getCoding();
if (memberIdentifier.getType() != null && typeCodings != null && typeCodings.size() > 0) {
for (Coding typeCoding : typeCodings) {
if (typeCoding.getCode().equals("MB")) {
return memberIdentifier;
}
}
}
}
}
String i18nMessage = myFhirContext.getLocalizer().getMessage(
"operation.member.match.error.beneficiary.without.identifier");
throw new UnprocessableEntityException(Msg.code(2219) + i18nMessage);
}
public void addMemberIdentifierToMemberPatient(Patient theMemberPatient, Identifier theNewIdentifier) { public void addMemberIdentifierToMemberPatient(Patient theMemberPatient, Identifier theNewIdentifier) {
Coding coding = new Coding() Coding coding = new Coding()
.setSystem(OUT_COVERAGE_IDENTIFIER_CODE_SYSTEM) .setSystem(OUT_COVERAGE_IDENTIFIER_CODE_SYSTEM)
@ -170,7 +189,7 @@ public class MemberMatcherR4Helper {
.setCoding(Lists.newArrayList(coding)) .setCoding(Lists.newArrayList(coding))
.setText(OUT_COVERAGE_IDENTIFIER_TEXT); .setText(OUT_COVERAGE_IDENTIFIER_TEXT);
Identifier newIdentifier = new Identifier() Identifier newIdentifier = new Identifier()
.setUse(Identifier.IdentifierUse.USUAL) .setUse(Identifier.IdentifierUse.USUAL)
.setType(concept) .setType(concept)
.setSystem(theNewIdentifier.getSystem()) .setSystem(theNewIdentifier.getSystem())
@ -181,6 +200,7 @@ public class MemberMatcherR4Helper {
/** /**
* If there is a client id * If there is a client id
*
* @param theConsent - the consent to modify * @param theConsent - the consent to modify
*/ */
private void addClientIdAsExtensionToConsentIfAvailable(Consent theConsent) { private void addClientIdAsExtensionToConsentIfAvailable(Consent theConsent) {
@ -208,7 +228,7 @@ public class MemberMatcherR4Helper {
} }
if (theCoverage.getBeneficiaryTarget() != null if (theCoverage.getBeneficiaryTarget() != null
&& ! theCoverage.getBeneficiaryTarget().getIdentifier().isEmpty()) { && !theCoverage.getBeneficiaryTarget().getIdentifier().isEmpty()) {
return Optional.of(theCoverage.getBeneficiaryTarget()); return Optional.of(theCoverage.getBeneficiaryTarget());
} }
@ -230,7 +250,7 @@ public class MemberMatcherR4Helper {
} }
/** /**
* Matching by member patient demographics - family name and birthdate only * Matching by member patient demographics - family name and birthdate only
*/ */
public boolean validPatientMember(Patient thePatientFromContract, Patient thePatientToMatch) { public boolean validPatientMember(Patient thePatientFromContract, Patient thePatientToMatch) {
if (thePatientFromContract == null || thePatientFromContract.getIdElement() == null) { if (thePatientFromContract == null || thePatientFromContract.getIdElement() == null) {
@ -254,10 +274,10 @@ public class MemberMatcherR4Helper {
} }
public boolean validConsentDataAccess(Consent theConsent) { public boolean validConsentDataAccess(Consent theConsent) {
if (theConsent.getPolicy().isEmpty()) { if (theConsent.getPolicy().isEmpty()) {
return false; return false;
} }
for (Consent.ConsentPolicyComponent policyComponent: theConsent.getPolicy()) { for (Consent.ConsentPolicyComponent policyComponent : theConsent.getPolicy()) {
if (policyComponent.getUri() == null || !validConsentPolicy(policyComponent.getUri())) { if (policyComponent.getUri() == null || !validConsentPolicy(policyComponent.getUri())) {
return false; return false;
} }
@ -280,8 +300,8 @@ public class MemberMatcherR4Helper {
return false; return false;
} }
private void addIdentifierToConsent(Consent theConsent) { private void addIdentifierToConsent(Consent theConsent, Patient thePatient) {
String consentId = UUID.randomUUID().toString(); String consentId = getIdentifier(thePatient).getValue();
Identifier consentIdentifier = new Identifier().setSystem(CONSENT_IDENTIFIER_CODE_SYSTEM).setValue(consentId); Identifier consentIdentifier = new Identifier().setSystem(CONSENT_IDENTIFIER_CODE_SYSTEM).setValue(consentId);
theConsent.addIdentifier(consentIdentifier); theConsent.addIdentifier(consentIdentifier);
} }

View File

@ -1,6 +1,7 @@
package ca.uhn.fhir.jpa.provider; package ca.uhn.fhir.jpa.provider;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.provider.r4.IConsentExtensionProvider; import ca.uhn.fhir.jpa.provider.r4.IConsentExtensionProvider;
import ca.uhn.fhir.jpa.provider.r4.MemberMatcherR4Helper; import ca.uhn.fhir.jpa.provider.r4.MemberMatcherR4Helper;
@ -15,6 +16,8 @@ import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.read.ListAppender; import ch.qos.logback.core.read.ListAppender;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.model.CodeableConcept;
import org.hl7.fhir.r4.model.Coding;
import org.hl7.fhir.r4.model.Consent; import org.hl7.fhir.r4.model.Consent;
import org.hl7.fhir.r4.model.Coverage; import org.hl7.fhir.r4.model.Coverage;
import org.hl7.fhir.r4.model.DateType; import org.hl7.fhir.r4.model.DateType;
@ -44,8 +47,11 @@ import java.util.Optional;
import static ca.uhn.fhir.jpa.provider.r4.MemberMatcherR4Helper.CONSENT_IDENTIFIER_CODE_SYSTEM; import static ca.uhn.fhir.jpa.provider.r4.MemberMatcherR4Helper.CONSENT_IDENTIFIER_CODE_SYSTEM;
import static ca.uhn.fhir.rest.api.Constants.PARAM_CONSENT; import static ca.uhn.fhir.rest.api.Constants.PARAM_CONSENT;
import static ca.uhn.fhir.rest.api.Constants.PARAM_MEMBER_IDENTIFIER;
import static ca.uhn.fhir.rest.api.Constants.PARAM_MEMBER_PATIENT; import static ca.uhn.fhir.rest.api.Constants.PARAM_MEMBER_PATIENT;
import static ca.uhn.fhir.rest.api.Constants.PARAM_NEW_COVERAGE; import static ca.uhn.fhir.rest.api.Constants.PARAM_NEW_COVERAGE;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.startsWith;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNotNull;
@ -164,9 +170,14 @@ public class MemberMatcherR4HelperTest {
@Test @Test
void buildSuccessReturnParameters() { void buildSuccessReturnParameters() {
Identifier identifier = new Identifier();
CodeableConcept identifierType = new CodeableConcept();
identifierType.addCoding(new Coding("", "MB", ""));
identifier.setType(identifierType);
Patient patient = new Patient(); Patient patient = new Patient();
Coverage coverage = new Coverage(); Coverage coverage = new Coverage();
Consent consent = new Consent(); Consent consent = new Consent();
patient.addIdentifier(identifier);
Parameters result = myHelper.buildSuccessReturnParameters(patient, coverage, consent); Parameters result = myHelper.buildSuccessReturnParameters(patient, coverage, consent);
@ -178,8 +189,25 @@ public class MemberMatcherR4HelperTest {
assertEquals(PARAM_CONSENT, result.getParameter().get(2).getName()); assertEquals(PARAM_CONSENT, result.getParameter().get(2).getName());
assertEquals(consent, result.getParameter().get(2).getResource()); assertEquals(consent, result.getParameter().get(2).getResource());
assertEquals(PARAM_MEMBER_IDENTIFIER, result.getParameter().get(3).getName());
assertEquals(identifier, result.getParameter().get(3).getValue());
} }
@Test
void buildNotSuccessReturnParameters_IncorrectPatientIdentifier() {
Identifier identifier = new Identifier();
Patient patient = new Patient();
Coverage coverage = new Coverage();
Consent consent = new Consent();
patient.addIdentifier(identifier);
try {
myHelper.buildSuccessReturnParameters(patient, coverage, consent);
} catch (Exception e) {
assertThat(e.getMessage(), startsWith(Msg.code(2219)));
}
}
@Test @Test
void addMemberIdentifierToMemberPatient() { void addMemberIdentifierToMemberPatient() {
@ -478,9 +506,17 @@ public class MemberMatcherR4HelperTest {
return new Consent.ConsentPolicyComponent().setUri(uri + uriAccess); return new Consent.ConsentPolicyComponent().setUri(uri + uriAccess);
} }
private Patient createPatientForMemberMatchUpdate() { private Patient createPatientForMemberMatchUpdate(boolean addIdentifier) {
Patient patient = new Patient(); Patient patient = new Patient();
patient.setId("Patient/RED"); patient.setId("Patient/RED");
Identifier identifier = new Identifier();
if (addIdentifier) {
CodeableConcept identifierType = new CodeableConcept();
identifierType.addCoding(new Coding("", "MB", ""));
identifier.setType(identifierType);
}
identifier.setValue("RED-Patient");
patient.addIdentifier(identifier);
return patient; return patient;
} }
@ -488,12 +524,14 @@ public class MemberMatcherR4HelperTest {
private void verifyConsentUpdatedAfterMemberMatch( private void verifyConsentUpdatedAfterMemberMatch(
Consent theConsent, Consent theConsent,
Patient thePatient, Patient thePatient,
Patient theMemberPatient,
List<Extension> theSavedExtensions List<Extension> theSavedExtensions
) { ) {
// check consent identifier // check consent identifier
assertEquals(1, theConsent.getIdentifier().size()); assertEquals(1, theConsent.getIdentifier().size());
assertEquals(CONSENT_IDENTIFIER_CODE_SYSTEM, theConsent.getIdentifier().get(0).getSystem()); assertEquals(CONSENT_IDENTIFIER_CODE_SYSTEM, theConsent.getIdentifier().get(0).getSystem());
assertNotNull(theConsent.getIdentifier().get(0).getValue()); assertNotNull(theConsent.getIdentifier().get(0).getValue());
assertEquals(theConsent.getIdentifier().get(0).getValue(), theMemberPatient.getIdentifier().get(0).getValue());
// check consent patient info // check consent patient info
String patientRef = thePatient.getIdElement().toUnqualifiedVersionless().getValue(); String patientRef = thePatient.getIdElement().toUnqualifiedVersionless().getValue();
@ -528,12 +566,13 @@ public class MemberMatcherR4HelperTest {
// setup // setup
Consent consent = getConsent(); Consent consent = getConsent();
consent.addPolicy(constructConsentPolicyComponent("#sensitive")); consent.addPolicy(constructConsentPolicyComponent("#sensitive"));
Patient patient = createPatientForMemberMatchUpdate(); Patient patient = createPatientForMemberMatchUpdate(false);
Patient memberPatient = createPatientForMemberMatchUpdate(true);
ourLog.setLevel(Level.TRACE); ourLog.setLevel(Level.TRACE);
// test // test
myHelper.updateConsentForMemberMatch(consent, patient); myHelper.updateConsentForMemberMatch(consent, patient, memberPatient);
// verify // verify
verify(myAppender, never()) verify(myAppender, never())
@ -542,7 +581,7 @@ public class MemberMatcherR4HelperTest {
ArgumentCaptor<Consent> consentCaptor = ArgumentCaptor.forClass(Consent.class); ArgumentCaptor<Consent> consentCaptor = ArgumentCaptor.forClass(Consent.class);
verify(myConsentDao).create(consentCaptor.capture()); verify(myConsentDao).create(consentCaptor.capture());
Consent saved = consentCaptor.getValue(); Consent saved = consentCaptor.getValue();
verifyConsentUpdatedAfterMemberMatch(saved, patient, Collections.emptyList()); verifyConsentUpdatedAfterMemberMatch(saved, patient, memberPatient, Collections.emptyList());
} }
} }
@ -571,7 +610,8 @@ public class MemberMatcherR4HelperTest {
Extension ext = new Extension(); Extension ext = new Extension();
ext.setUrl("http://example.com"); ext.setUrl("http://example.com");
ext.setValue(new StringType("value")); ext.setValue(new StringType("value"));
Patient patient = createPatientForMemberMatchUpdate(); Patient patient = createPatientForMemberMatchUpdate(false);
Patient memberPatient = createPatientForMemberMatchUpdate(true);
ourLog.setLevel(Level.TRACE); ourLog.setLevel(Level.TRACE);
@ -580,13 +620,13 @@ public class MemberMatcherR4HelperTest {
.thenReturn(Collections.singleton(ext)); .thenReturn(Collections.singleton(ext));
// test // test
myHelper.updateConsentForMemberMatch(consent, patient); myHelper.updateConsentForMemberMatch(consent, patient, memberPatient);
// verify // verify
ArgumentCaptor<Consent> consentCaptor = ArgumentCaptor.forClass(Consent.class); ArgumentCaptor<Consent> consentCaptor = ArgumentCaptor.forClass(Consent.class);
verify(myConsentDao).create(consentCaptor.capture()); verify(myConsentDao).create(consentCaptor.capture());
Consent saved = consentCaptor.getValue(); Consent saved = consentCaptor.getValue();
verifyConsentUpdatedAfterMemberMatch(saved, patient, Collections.emptyList()); verifyConsentUpdatedAfterMemberMatch(saved, patient, memberPatient, Collections.emptyList());
ArgumentCaptor<ILoggingEvent> eventCaptor = ArgumentCaptor.forClass(ILoggingEvent.class); ArgumentCaptor<ILoggingEvent> eventCaptor = ArgumentCaptor.forClass(ILoggingEvent.class);
verify(myAppender).doAppend(eventCaptor.capture()); verify(myAppender).doAppend(eventCaptor.capture());