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
*/
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_NEW_COVERAGE = "NewCoverage";
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.server.exceptions.UnprocessableEntityException;
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.Parameters;
import org.hl7.fhir.r4.model.Patient;
import org.hl7.fhir.r4.model.Consent;
import javax.servlet.http.HttpServletRequest;
import java.util.Optional;
@ -120,7 +120,7 @@ public class MemberMatchR4ResourceProvider {
}
myMemberMatcherR4Helper.addMemberIdentifierToMemberPatient(theMemberPatient, patient.getIdentifierFirstRep());
myMemberMatcherR4Helper.updateConsentForMemberMatch(theConsent, patient);
myMemberMatcherR4Helper.updateConsentForMemberMatch(theConsent, patient, theMemberPatient);
return myMemberMatcherR4Helper.buildSuccessReturnParameters(theMemberPatient, theCoverageToLink, theConsent);
}

View File

@ -1,14 +1,15 @@
package ca.uhn.fhir.jpa.provider.r4;
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.model.DaoMethodOutcome;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.param.DateParam;
import ca.uhn.fhir.rest.param.StringOrListParam;
import ca.uhn.fhir.rest.param.StringParam;
import ca.uhn.fhir.rest.param.TokenOrListParam;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.util.ParametersUtil;
import com.google.common.collect.Lists;
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.Patient;
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 java.util.Collection;
import java.util.List;
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_MEMBER_IDENTIFIER;
import static ca.uhn.fhir.rest.api.Constants.PARAM_MEMBER_PATIENT;
import static ca.uhn.fhir.rest.api.Constants.PARAM_NEW_COVERAGE;
@ -102,13 +101,13 @@ public class MemberMatcherR4Helper {
// search by received old coverage id
List<IBaseResource> foundCoverages = findCoverageByCoverageId(theCoverageToMatch);
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
foundCoverages = findCoverageByCoverageIdentifier(theCoverageToMatch);
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();
@ -142,9 +141,9 @@ public class MemberMatcherR4Helper {
return retVal.getAllResources();
}
public void updateConsentForMemberMatch(Consent theConsent, Patient thePatient) {
public void updateConsentForMemberMatch(Consent theConsent, Patient thePatient, Patient theMemberPatient) {
addClientIdAsExtensionToConsentIfAvailable(theConsent);
addIdentifierToConsent(theConsent);
addIdentifierToConsent(theConsent, theMemberPatient);
updateConsentPatientAndPerformer(theConsent, thePatient);
// save the resource
@ -156,9 +155,29 @@ public class MemberMatcherR4Helper {
ParametersUtil.addParameterToParameters(myFhirContext, parameters, PARAM_MEMBER_PATIENT, theMemberPatient);
ParametersUtil.addParameterToParameters(myFhirContext, parameters, PARAM_NEW_COVERAGE, theCoverage);
ParametersUtil.addParameterToParameters(myFhirContext, parameters, PARAM_CONSENT, theConsent);
ParametersUtil.addParameterToParameters(myFhirContext, parameters, PARAM_MEMBER_IDENTIFIER, getIdentifier(theMemberPatient));
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) {
Coding coding = new Coding()
.setSystem(OUT_COVERAGE_IDENTIFIER_CODE_SYSTEM)
@ -170,7 +189,7 @@ public class MemberMatcherR4Helper {
.setCoding(Lists.newArrayList(coding))
.setText(OUT_COVERAGE_IDENTIFIER_TEXT);
Identifier newIdentifier = new Identifier()
Identifier newIdentifier = new Identifier()
.setUse(Identifier.IdentifierUse.USUAL)
.setType(concept)
.setSystem(theNewIdentifier.getSystem())
@ -181,6 +200,7 @@ public class MemberMatcherR4Helper {
/**
* If there is a client id
*
* @param theConsent - the consent to modify
*/
private void addClientIdAsExtensionToConsentIfAvailable(Consent theConsent) {
@ -208,7 +228,7 @@ public class MemberMatcherR4Helper {
}
if (theCoverage.getBeneficiaryTarget() != null
&& ! theCoverage.getBeneficiaryTarget().getIdentifier().isEmpty()) {
&& !theCoverage.getBeneficiaryTarget().getIdentifier().isEmpty()) {
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) {
if (thePatientFromContract == null || thePatientFromContract.getIdElement() == null) {
@ -254,10 +274,10 @@ public class MemberMatcherR4Helper {
}
public boolean validConsentDataAccess(Consent theConsent) {
if (theConsent.getPolicy().isEmpty()) {
if (theConsent.getPolicy().isEmpty()) {
return false;
}
for (Consent.ConsentPolicyComponent policyComponent: theConsent.getPolicy()) {
for (Consent.ConsentPolicyComponent policyComponent : theConsent.getPolicy()) {
if (policyComponent.getUri() == null || !validConsentPolicy(policyComponent.getUri())) {
return false;
}
@ -280,8 +300,8 @@ public class MemberMatcherR4Helper {
return false;
}
private void addIdentifierToConsent(Consent theConsent) {
String consentId = UUID.randomUUID().toString();
private void addIdentifierToConsent(Consent theConsent, Patient thePatient) {
String consentId = getIdentifier(thePatient).getValue();
Identifier consentIdentifier = new Identifier().setSystem(CONSENT_IDENTIFIER_CODE_SYSTEM).setValue(consentId);
theConsent.addIdentifier(consentIdentifier);
}

View File

@ -1,6 +1,7 @@
package ca.uhn.fhir.jpa.provider;
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.provider.r4.IConsentExtensionProvider;
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 com.google.common.collect.Lists;
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.Coverage;
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.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_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.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
@ -164,9 +170,14 @@ public class MemberMatcherR4HelperTest {
@Test
void buildSuccessReturnParameters() {
Identifier identifier = new Identifier();
CodeableConcept identifierType = new CodeableConcept();
identifierType.addCoding(new Coding("", "MB", ""));
identifier.setType(identifierType);
Patient patient = new Patient();
Coverage coverage = new Coverage();
Consent consent = new Consent();
patient.addIdentifier(identifier);
Parameters result = myHelper.buildSuccessReturnParameters(patient, coverage, consent);
@ -178,8 +189,25 @@ public class MemberMatcherR4HelperTest {
assertEquals(PARAM_CONSENT, result.getParameter().get(2).getName());
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
void addMemberIdentifierToMemberPatient() {
@ -478,9 +506,17 @@ public class MemberMatcherR4HelperTest {
return new Consent.ConsentPolicyComponent().setUri(uri + uriAccess);
}
private Patient createPatientForMemberMatchUpdate() {
private Patient createPatientForMemberMatchUpdate(boolean addIdentifier) {
Patient patient = new Patient();
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;
}
@ -488,12 +524,14 @@ public class MemberMatcherR4HelperTest {
private void verifyConsentUpdatedAfterMemberMatch(
Consent theConsent,
Patient thePatient,
Patient theMemberPatient,
List<Extension> theSavedExtensions
) {
// check consent identifier
assertEquals(1, theConsent.getIdentifier().size());
assertEquals(CONSENT_IDENTIFIER_CODE_SYSTEM, theConsent.getIdentifier().get(0).getSystem());
assertNotNull(theConsent.getIdentifier().get(0).getValue());
assertEquals(theConsent.getIdentifier().get(0).getValue(), theMemberPatient.getIdentifier().get(0).getValue());
// check consent patient info
String patientRef = thePatient.getIdElement().toUnqualifiedVersionless().getValue();
@ -528,12 +566,13 @@ public class MemberMatcherR4HelperTest {
// setup
Consent consent = getConsent();
consent.addPolicy(constructConsentPolicyComponent("#sensitive"));
Patient patient = createPatientForMemberMatchUpdate();
Patient patient = createPatientForMemberMatchUpdate(false);
Patient memberPatient = createPatientForMemberMatchUpdate(true);
ourLog.setLevel(Level.TRACE);
// test
myHelper.updateConsentForMemberMatch(consent, patient);
myHelper.updateConsentForMemberMatch(consent, patient, memberPatient);
// verify
verify(myAppender, never())
@ -542,7 +581,7 @@ public class MemberMatcherR4HelperTest {
ArgumentCaptor<Consent> consentCaptor = ArgumentCaptor.forClass(Consent.class);
verify(myConsentDao).create(consentCaptor.capture());
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();
ext.setUrl("http://example.com");
ext.setValue(new StringType("value"));
Patient patient = createPatientForMemberMatchUpdate();
Patient patient = createPatientForMemberMatchUpdate(false);
Patient memberPatient = createPatientForMemberMatchUpdate(true);
ourLog.setLevel(Level.TRACE);
@ -580,13 +620,13 @@ public class MemberMatcherR4HelperTest {
.thenReturn(Collections.singleton(ext));
// test
myHelper.updateConsentForMemberMatch(consent, patient);
myHelper.updateConsentForMemberMatch(consent, patient, memberPatient);
// verify
ArgumentCaptor<Consent> consentCaptor = ArgumentCaptor.forClass(Consent.class);
verify(myConsentDao).create(consentCaptor.capture());
Consent saved = consentCaptor.getValue();
verifyConsentUpdatedAfterMemberMatch(saved, patient, Collections.emptyList());
verifyConsentUpdatedAfterMemberMatch(saved, patient, memberPatient, Collections.emptyList());
ArgumentCaptor<ILoggingEvent> eventCaptor = ArgumentCaptor.forClass(ILoggingEvent.class);
verify(myAppender).doAppend(eventCaptor.capture());