EMPI to MDM

This commit is contained in:
Nick Goupinets 2020-11-27 15:12:21 -05:00
parent 03643a7b3c
commit 8a7dc4e80b
60 changed files with 284 additions and 295 deletions

View File

@ -1710,7 +1710,7 @@ public enum Pointcut {
* Hooks may accept the following parameters:
* <ul>
* <li>ca.uhn.fhir.rest.server.messaging.ResourceOperationMessage - This parameter should not be modified as processing is complete when this hook is invoked.</li>
* <li>ca.uhn.fhir.empi.model.TransactionLogMessages - This parameter is for informational messages provided by the EMPI module during EMPI procesing. .</li>
* <li>ca.uhn.fhir.rest.server.TransactionLogMessages - This parameter is for informational messages provided by the MDM module during MDM procesing.</li>
* </ul>
* </p>
* <p>

View File

@ -28,7 +28,7 @@ This automatic linking is done via configurable matching rules that create links
of the match configured in these rules, the link will be set to either POSSIBLE_MATCH or MATCH.
It is important to note that before a resource is processed by MDM, it is first checked to ensure that it has at least
one attribute that the MDM system cares about, as defined in the `empi-rules.json` file. If the incoming resource has
one attribute that the MDM system cares about, as defined in the `mdm-rules.json` file. If the incoming resource has
no such attributes, then MDM processing does not occur on it. In this case, no Golden Resource Patient is created for
them. If in the future that Patient is updated to contain attributes the MDM system does concern itself with, it will
be processed at that time.
@ -110,6 +110,6 @@ no longer consider them as a POSSIBLE_DUPLICATE going forward. POSSIBLE_DUPLICAT
When MDM is enabled, the HAPI FHIR JPA Server does the following things on startup:
1. It enables the MESSAGE subscription type and starts up the internal subscription engine.
1. It creates two MESSAGE subscriptions, called 'empi-patient' and 'empi-practitioner' that match all incoming Patient and Practitioner resources and send them to an internal queue called "empi". The JPA Server listens to this queue and links incoming resources to Persons.
1. It creates two MESSAGE subscriptions, called 'mdm-patient' and 'mdm-practitioner' that match all incoming MDM managed resources and send them to an internal queue called "mdm". The JPA Server listens to this queue and links incoming resources to the appropriate golden resources.
1. The [MDM Operations](/hapi-fhir/docs/server_jpa_mdm/mdm_operations.html) are registered with the server.
1. It registers a new dao interceptor that restricts access to MDM managed Golden Patient records.

View File

@ -11,6 +11,7 @@ Here is an example of a full HAPI MDM rules json document:
```json
{
"version": "1",
"mdmTypes" : ["Patient", "Practitioner"],
"candidateSearchParams": [
{
"resourceType": "Patient",

View File

@ -42,9 +42,11 @@ import java.util.stream.Collectors;
@Service
public class MdmSubscriptionLoader {
private static final Logger ourLog = Logs.getMdmTroubleshootingLog();
public static final String MDM_SUBSCIPRION_ID_PREFIX = "empi-";
public static final String MDM_SUBSCIPRION_ID_PREFIX = "mdm-";
@Autowired
public FhirContext myFhirContext;
@Autowired

View File

@ -267,7 +267,7 @@ public class MdmLinkDaoSvc {
}
/**
* Persist an EmpiLink to the database.
* Persist an MDM link to the database.
*
* @param theMdmLink the link to save.
* @return the persisted {@link MdmLink} entity.
@ -284,7 +284,7 @@ public class MdmLinkDaoSvc {
/**
* Given an example {@link MdmLink}, return all links from the database which match the example.
*
* @param theExampleLink The EmpiLink containing the data we would like to search for.
* @param theExampleLink The MDM link containing the data we would like to search for.
* @return a list of {@link MdmLink} entities which match the example.
*/
public List<MdmLink> findMdmLinkByExample(Example<MdmLink> theExampleLink) {

View File

@ -43,6 +43,7 @@ import java.util.Optional;
@Service
public class GoldenResourceMergerSvcImpl implements IGoldenResourceMergerSvc {
private static final Logger ourLog = Logs.getMdmTroubleshootingLog();
@Autowired
@ -50,7 +51,7 @@ public class GoldenResourceMergerSvcImpl implements IGoldenResourceMergerSvc {
@Autowired
MdmLinkDaoSvc myMdmLinkDaoSvc;
@Autowired
IMdmLinkSvc myEmpiLinkSvc;
IMdmLinkSvc myMdmLinkSvc;
@Autowired
IdHelperService myIdHelperService;
@Autowired
@ -170,7 +171,7 @@ public class GoldenResourceMergerSvcImpl implements IGoldenResourceMergerSvc {
private Optional<MdmLink> findFirstLinkWithMatchingTarget(List<MdmLink> theMdmLinks, MdmLink theLinkWithTargetToMatch) {
return theMdmLinks.stream()
.filter(empiLink -> empiLink.getTargetPid().equals(theLinkWithTargetToMatch.getTargetPid()))
.filter(mdmLink -> mdmLink.getTargetPid().equals(theLinkWithTargetToMatch.getTargetPid()))
.findFirst();
}

View File

@ -44,6 +44,7 @@ import java.util.Optional;
@Service
public class MdmEidUpdateService {
private static final Logger ourLog = Logs.getMdmTroubleshootingLog();
@Autowired
@ -129,7 +130,7 @@ public class MdmEidUpdateService {
}
/**
* Data class to hold context surrounding an update operation for an EMPI target.
* Data class to hold context surrounding an update operation for an MDM target.
*/
class MdmUpdateContext {
private final boolean myHasEidsInCommon;

View File

@ -45,8 +45,8 @@ public class MdmLinkQuerySvcImpl implements IMdmLinkQuerySvc {
MdmLinkDaoSvc myMdmLinkDaoSvc;
@Override
public Stream<MdmLinkJson> queryLinks(IIdType thePersonId, IIdType theTargetId, MdmMatchResultEnum theMatchResult, MdmLinkSourceEnum theLinkSource, MdmTransactionContext theMdmTransactionContext) {
Example<MdmLink> exampleLink = exampleLinkFromParameters(thePersonId, theTargetId, theMatchResult, theLinkSource);
public Stream<MdmLinkJson> queryLinks(IIdType theGoldenResourceId, IIdType theTargetId, MdmMatchResultEnum theMatchResult, MdmLinkSourceEnum theLinkSource, MdmTransactionContext theMdmContext) {
Example<MdmLink> exampleLink = exampleLinkFromParameters(theGoldenResourceId, theTargetId, theMatchResult, theLinkSource);
return myMdmLinkDaoSvc.findMdmLinkByExample(exampleLink).stream()
.filter(mdmLink -> mdmLink.getMatchResult() != MdmMatchResultEnum.POSSIBLE_DUPLICATE)
.map(this::toJson);

View File

@ -159,7 +159,7 @@ public class MdmLinkUpdaterSvcImpl implements IMdmLinkUpdaterSvc {
}
if (!MdmUtil.isMdmManaged(theGoldenResource) || !MdmUtil.isMdmManaged(theTarget)) {
throw new InvalidRequestException("Only MDM Managed Golden Resources may be updated via this operation. The resource provided is not tagged as managed by hapi-empi");
throw new InvalidRequestException("Only MDM Managed Golden Resources may be updated via this operation. The resource provided is not tagged as managed by hapi-mdm");
}
}
}

View File

@ -59,10 +59,10 @@ public class MdmMatchLinkSvc {
/**
* Given an MDM Target (consisting of either a Patient or a Practitioner), find a suitable Person candidate for them,
* or create one if one does not exist. Performs matching based on rules defined in empi-rules.json.
* or create one if one does not exist. Performs matching based on rules defined in mdm-rules.json.
* Does nothing if resource is determined to be not managed by MDM.
*
* @param theResource the incoming MDM target, which is either a Patient or Practitioner.
* @param theResource the incoming MDM target, which can be any supported MDM type.
* @param theMdmTransactionContext
* @return an {@link TransactionLogMessages} which contains all informational messages related to MDM processing of this resource.
*/

View File

@ -48,6 +48,7 @@ import static ca.uhn.fhir.mdm.api.MdmConstants.ALL_RESOURCE_SEARCH_PARAM_TYPE;
@Service
public class MdmCandidateSearchSvc {
private static final Logger ourLog = Logs.getMdmTroubleshootingLog();
@Autowired
@ -65,7 +66,7 @@ public class MdmCandidateSearchSvc {
}
/**
* Given a target resource, search for all resources that are considered an EMPI match based on defined EMPI rules.
* Given a target resource, search for all resources that are considered an MDM match based on defined MDM rules.
*
*
* @param theResourceType
@ -79,8 +80,8 @@ public class MdmCandidateSearchSvc {
List<String> filterCriteria = buildFilterQuery(filterSearchParams, theResourceType);
List<MdmResourceSearchParamJson> candidateSearchParams = myMdmSettings.getMdmRules().getCandidateSearchParams();
//If there are zero EmpiResourceSearchParamJson, we end up only making a single search, otherwise we
//must perform one search per EmpiResourceSearchParamJson.
//If there are zero MdmResourceSearchParamJson, we end up only making a single search, otherwise we
//must perform one search per MdmResourceSearchParamJson.
if (candidateSearchParams.isEmpty()) {
searchForIdsAndAddToMap(theResourceType, theResource, matchedPidsToResources, filterCriteria, null);
} else {
@ -129,7 +130,7 @@ public class MdmCandidateSearchSvc {
searchParameterMap.setLoadSynchronous(true);
//TODO EMPI this will blow up under large scale i think.
//TODO MDM this will blow up under large scale i think.
//3.
IFhirResourceDao<?> resourceDao = myDaoRegistry.getResourceDao(theResourceType);
IBundleProvider search = resourceDao.search(searchParameterMap);

View File

@ -31,6 +31,7 @@ import org.springframework.stereotype.Service;
@Service
public class MdmGoldenResourceFindingSvc {
private static final Logger ourLog = Logs.getMdmTroubleshootingLog();
@Autowired
@ -52,8 +53,8 @@ public class MdmGoldenResourceFindingSvc {
* 0. First, check the incoming Resource for an EID. If it is present, and we can find a Person with this EID, it automatically matches.
* 1. First, check link table for any entries where this baseresource is the target of a person. If found, return.
* 2. If none are found, attempt to find Person Resources which link to this theResource.
* 3. If none are found, attempt to find Person Resources similar to our incoming resource based on the EMPI rules and field matchers.
* 4. If none are found, attempt to find Persons that are linked to Patients/Practitioners that are similar to our incoming resource based on the EMPI rules and
* 3. If none are found, attempt to find Person Resources similar to our incoming resource based on the MDM rules and field matchers.
* 4. If none are found, attempt to find Persons that are linked to Patients/Practitioners that are similar to our incoming resource based on the MDM rules and
* field matchers.
*
* @param theResource the {@link IBaseResource} we are attempting to find matching candidate Persons for.
@ -67,9 +68,8 @@ public class MdmGoldenResourceFindingSvc {
}
if (matchedSourceResourceCandidates.isEmpty()) {
//OK, so we have not found any links in the EmpiLink table with us as a target. Next, let's find possible Patient/Practitioner
//matches by following EMPI rules.
//OK, so we have not found any links in the MdmLink table with us as a target. Next, let's find
//possible Golden Resources matches by following MDM rules.
matchedSourceResourceCandidates = myFindCandidateByScoreSvc.findCandidates(theResource);
}

View File

@ -139,12 +139,6 @@ abstract public class BaseMdmR4Test extends BaseJpaR4Test {
protected void saveLink(MdmLink theMdmLink) {
myMdmLinkDaoSvc.save(theMdmLink);
}
//
// @Nonnull
// protected Patient createUnmanagedSourceResource() {
// // return createGoldenPatient(new Patient(), false);
// return createGoldenPatient(new Patient(), false);
// }
@Nonnull
protected Patient createGoldenPatient() {
@ -169,7 +163,7 @@ abstract public class BaseMdmR4Test extends BaseJpaR4Test {
@Nonnull
protected Patient createPatient(Patient thePatient, boolean theMdmManaged, boolean isRedirect) {
if (theMdmManaged) {
MdmUtil.setEmpiManaged(thePatient);
MdmUtil.setMdmManaged(thePatient);
if (isRedirect) {
MdmUtil.setGoldenResourceRedirected(thePatient);
} else {
@ -185,7 +179,7 @@ abstract public class BaseMdmR4Test extends BaseJpaR4Test {
@Nonnull
protected Patient createPatient(Patient thePatient) {
//Note that since our empi-rules block on active=true, all patients must be active.
//Note that since our mdm-rules block on active=true, all patients must be active.
thePatient.setActive(true);
DaoMethodOutcome outcome = myPatientDao.create(thePatient);
@ -197,7 +191,7 @@ abstract public class BaseMdmR4Test extends BaseJpaR4Test {
@Nonnull
protected Medication createMedication(Medication theMedication) {
//Note that since our empi-rules block on active=true, all patients must be active.
//Note that since our mdm-rules block on active=true, all patients must be active.
DaoMethodOutcome outcome = myMedicationDao.create(theMedication);
Medication medication = (Medication) outcome.getResource();
medication.setId(outcome.getId());
@ -206,7 +200,7 @@ abstract public class BaseMdmR4Test extends BaseJpaR4Test {
@Nonnull
protected Practitioner createPractitioner(Practitioner thePractitioner) {
//Note that since our empi-rules block on active=true, all patients must be active.
//Note that since our mdm-rules block on active=true, all patients must be active.
thePractitioner.setActive(true);
DaoMethodOutcome daoMethodOutcome = myPractitionerDao.create(thePractitioner);
thePractitioner.setId(daoMethodOutcome.getId());
@ -329,7 +323,7 @@ abstract public class BaseMdmR4Test extends BaseJpaR4Test {
}
}
protected <T extends IBaseResource> T getTargetResourceFromEmpiLink(MdmLink theMdmLink, String theResourceType) {
protected <T extends IBaseResource> T getTargetResourceFromMdmLink(MdmLink theMdmLink, String theResourceType) {
IFhirResourceDao resourceDao = myDaoRegistry.getResourceDao(theResourceType);
return (T) resourceDao.readByPid(new ResourcePersistentId(theMdmLink.getGoldenResourcePid()));
}
@ -470,7 +464,7 @@ abstract public class BaseMdmR4Test extends BaseJpaR4Test {
}
protected void logAllLinks() {
ourLog.info("Logging all EMPI Links:");
ourLog.info("Logging all MDM Links:");
List<MdmLink> links = myMdmLinkDao.findAll();
for (MdmLink link : links) {
ourLog.info(link.toString());
@ -530,8 +524,8 @@ abstract public class BaseMdmR4Test extends BaseJpaR4Test {
protected void printLinks() {
myMdmLinkDao.findAll().forEach(empiLink -> {
ourLog.info(String.valueOf(empiLink));
myMdmLinkDao.findAll().forEach(mdmLink -> {
ourLog.info(String.valueOf(mdmLink));
});
}

View File

@ -17,16 +17,16 @@ import java.io.IOException;
@Configuration
public abstract class BaseTestMdmConfig {
@Value("${empi.prevent_eid_updates:true}")
@Value("${mdm.prevent_eid_updates:true}")
boolean myPreventEidUpdates;
@Value("${empi.prevent_multiple_eids:true}")
@Value("${mdm.prevent_multiple_eids:true}")
boolean myPreventMultipleEids;
@Bean
IMdmSettings mdmSettings(MdmRuleValidator theMdmRuleValidator) throws IOException {
DefaultResourceLoader resourceLoader = new DefaultResourceLoader();
Resource resource = resourceLoader.getResource("empi/empi-rules.json");
Resource resource = resourceLoader.getResource("mdm/mdm-rules.json");
String json = IOUtils.toString(resource.getInputStream(), Charsets.UTF_8);
return new MdmSettings(theMdmRuleValidator)
.setEnabled(false)

View File

@ -18,10 +18,6 @@ import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class MdmLinkDaoSvcTest extends BaseMdmR4Test {
@Autowired
MdmLinkDaoSvc myMdmLinkDaoSvc;
@Autowired
IMdmSettings myEmpiSettings;
@Test
public void testCreate() {
@ -47,7 +43,7 @@ public class MdmLinkDaoSvcTest extends BaseMdmR4Test {
@Test
public void testNew() {
MdmLink newLink = myMdmLinkDaoSvc.newMdmLink();
MdmRulesJson rules = myEmpiSettings.getMdmRules();
MdmRulesJson rules = myMdmSettings.getMdmRules();
assertEquals("1", rules.getVersion());
assertEquals(rules.getVersion(), newLink.getVersion());
}

View File

@ -7,10 +7,10 @@ import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class MdmEnumTest {
@Test
public void empiEnumOrdinals() {
// This test is here to enforce that new values in these enums are always added to the end
@Test
public void mdmEnumOrdinals() {
// This test is here to enforce that new values in these enums are always added to the end
assertEquals(6, MdmMatchResultEnum.values().length);
assertEquals(MdmMatchResultEnum.REDIRECT, MdmMatchResultEnum.values()[MdmMatchResultEnum.values().length - 1]);

View File

@ -27,9 +27,9 @@ public class MdmHelperConfig {
@Primary
@Bean
IMdmSettings empiSettings(MdmRuleValidator theMdmRuleValidator) throws IOException {
IMdmSettings mdmSettings(MdmRuleValidator theMdmRuleValidator) throws IOException {
DefaultResourceLoader resourceLoader = new DefaultResourceLoader();
Resource resource = resourceLoader.getResource("empi/empi-rules.json");
Resource resource = resourceLoader.getResource("mdm/mdm-rules.json");
String json = IOUtils.toString(resource.getInputStream(), Charsets.UTF_8);
// Set Enabled to true, and set strict mode.

View File

@ -26,7 +26,7 @@ public class MdmExpungeTest extends BaseMdmR4Test {
@Autowired
IInterceptorService myInterceptorService;
@Autowired
IMdmStorageInterceptor myEmpiStorageInterceptor;
IMdmStorageInterceptor myMdmStorageInterceptor;
@Autowired
DaoConfig myDaoConfig;
private ResourceTable myTargetEntity;
@ -50,7 +50,7 @@ public class MdmExpungeTest extends BaseMdmR4Test {
}
@Test
public void testUninterceptedDeleteRemovesEMPIReference() {
public void testUninterceptedDeleteRemovesMdmReference() {
assertEquals(1, myMdmLinkDao.count());
myPatientDao.delete(myTargetEntity.getIdDt());
assertEquals(1, myMdmLinkDao.count());
@ -63,14 +63,14 @@ public class MdmExpungeTest extends BaseMdmR4Test {
assertThat(e.getMessage(), containsString("ViolationException"));
assertThat(e.getMessage(), containsString("FK_EMPI_LINK_TARGET"));
}
myInterceptorService.registerInterceptor(myEmpiStorageInterceptor);
myInterceptorService.registerInterceptor(myMdmStorageInterceptor);
myPatientDao.expunge(myTargetId.toVersionless(), expungeOptions, null);
assertEquals(0, myMdmLinkDao.count());
}
@AfterEach
public void afterUnregisterInterceptor() {
myInterceptorService.unregisterInterceptor(myEmpiStorageInterceptor);
myInterceptorService.unregisterInterceptor(myMdmStorageInterceptor);
}
}

View File

@ -179,7 +179,7 @@ public class MdmStorageInterceptorIT extends BaseMdmR4Test {
assertNotNull(daoMethodOutcome.getId());
//Updating that person to set them as MDM managed is not allowed.
person.getMeta().addTag(SYSTEM_MDM_MANAGED, CODE_HAPI_MDM_MANAGED, "User is managed by EMPI");
person.getMeta().addTag(SYSTEM_MDM_MANAGED, CODE_HAPI_MDM_MANAGED, "User is managed by MDM");
try {
myMdmHelper.doUpdateResource(person, true);
fail();
@ -190,19 +190,19 @@ public class MdmStorageInterceptorIT extends BaseMdmR4Test {
@Test
public void testMdmManagedPersonCannotBeModifiedByPersonUpdateRequest() throws InterruptedException {
// When EMPI is enabled, only the EMPI system is allowed to modify Person links of Persons with the EMPI-MANAGED tag.
// When MDM is enabled, only the MDM system is allowed to modify Person links of Persons with the MDM-MANAGED tag.
Patient patient = new Patient();
IIdType patientId = myMdmHelper.createWithLatch(buildPaulPatient()).getDaoMethodOutcome().getId().toUnqualifiedVersionless();
patient.setId(patientId);
//Updating a Person who was created via EMPI should fail.
// Updating a Golden Resource Patient who was created via MDM should fail.
MdmLink mdmLink = myMdmLinkDaoSvc.getMatchedLinkForTargetPid(myIdHelperService.getPidOrNull(patient)).get();
Long sourcePatientPid = mdmLink.getGoldenResourcePid();
Patient empiSourcePatient = (Patient) myPatientDao.readByPid(new ResourcePersistentId(sourcePatientPid));
empiSourcePatient.setGender(Enumerations.AdministrativeGender.MALE);
Patient goldenResourcePatient = (Patient) myPatientDao.readByPid(new ResourcePersistentId(sourcePatientPid));
goldenResourcePatient.setGender(Enumerations.AdministrativeGender.MALE);
try {
myMdmHelper.doUpdateResource(empiSourcePatient, true);
myMdmHelper.doUpdateResource(goldenResourcePatient, true);
fail();
} catch (ForbiddenOperationException e) {
assertThat(e.getMessage(), startsWith("Cannot create or modify Resources that are managed by MDM."));
@ -213,12 +213,12 @@ public class MdmStorageInterceptorIT extends BaseMdmR4Test {
public void testMdmPointcutReceivesTransactionLogMessages() throws InterruptedException {
MdmHelperR4.OutcomeAndLogMessageWrapper wrapper = myMdmHelper.createWithLatch(buildJanePatient());
TransactionLogMessages empiTransactionLogMessages = wrapper.getLogMessages();
TransactionLogMessages mdmTransactionLogMessages = wrapper.getLogMessages();
//There is no TransactionGuid here as there is no TransactionLog in this context.
assertThat(empiTransactionLogMessages.getTransactionGuid(), is(nullValue()));
assertThat(mdmTransactionLogMessages.getTransactionGuid(), is(nullValue()));
List<String> messages = empiTransactionLogMessages.getValues();
List<String> messages = mdmTransactionLogMessages.getValues();
assertThat(messages.isEmpty(), is(false));
}

View File

@ -18,6 +18,7 @@ import java.util.List;
import java.util.stream.Collectors;
public abstract class BaseSourceResourceMatcher extends TypeSafeMatcher<IAnyResource> {
private static final Logger ourLog = LoggerFactory.getLogger(BaseSourceResourceMatcher.class);
protected IdHelperService myIdHelperService;
@ -39,7 +40,7 @@ public abstract class BaseSourceResourceMatcher extends TypeSafeMatcher<IAnyReso
if (isGoldenRecord) {
return myIdHelperService.getPidOrNull(theResource);
}
MdmLink matchLink = getMatchedEmpiLink(theResource);
MdmLink matchLink = getMatchedMdmLink(theResource);
if (matchLink == null) {
return null;
@ -51,11 +52,11 @@ public abstract class BaseSourceResourceMatcher extends TypeSafeMatcher<IAnyReso
}
protected List<Long> getPossibleMatchedSourceResourcePidsFromTarget(IAnyResource theBaseResource) {
return getEmpiLinksForTarget(theBaseResource, MdmMatchResultEnum.POSSIBLE_MATCH).stream().map(MdmLink::getGoldenResourcePid).collect(Collectors.toList());
return getMdmLinksForTarget(theBaseResource, MdmMatchResultEnum.POSSIBLE_MATCH).stream().map(MdmLink::getGoldenResourcePid).collect(Collectors.toList());
}
protected MdmLink getMatchedEmpiLink(IAnyResource thePatientOrPractitionerResource) {
List<MdmLink> mdmLinks = getEmpiLinksForTarget(thePatientOrPractitionerResource, MdmMatchResultEnum.MATCH);
protected MdmLink getMatchedMdmLink(IAnyResource thePatientOrPractitionerResource) {
List<MdmLink> mdmLinks = getMdmLinksForTarget(thePatientOrPractitionerResource, MdmMatchResultEnum.MATCH);
if (mdmLinks.size() == 0) {
return null;
} else if (mdmLinks.size() == 1) {
@ -65,7 +66,7 @@ public abstract class BaseSourceResourceMatcher extends TypeSafeMatcher<IAnyReso
}
}
protected List<MdmLink> getEmpiLinksForTarget(IAnyResource theTargetResource, MdmMatchResultEnum theMatchResult) {
protected List<MdmLink> getMdmLinksForTarget(IAnyResource theTargetResource, MdmMatchResultEnum theMatchResult) {
Long pidOrNull = myIdHelperService.getPidOrNull(theTargetResource);
List<MdmLink> matchLinkForTarget = myMdmLinkDaoSvc.getMdmLinksByTargetPidAndMatchResult(pidOrNull, theMatchResult);
if (!matchLinkForTarget.isEmpty()) {

View File

@ -23,7 +23,7 @@ public class IsPossibleMatchWith extends BaseSourceResourceMatcher {
@Override
protected boolean matchesSafely(IAnyResource theIncomingResource) {
List<MdmLink> mdmLinks = getEmpiLinksForTarget(theIncomingResource, MdmMatchResultEnum.POSSIBLE_MATCH);
List<MdmLink> mdmLinks = getMdmLinksForTarget(theIncomingResource, MdmMatchResultEnum.POSSIBLE_MATCH);
List<Long> personPidsToMatch = myBaseResources.stream()
.map(this::getMatchedResourcePidFromResource)
@ -36,8 +36,11 @@ public class IsPossibleMatchWith extends BaseSourceResourceMatcher {
.collect(Collectors.toList());
}
List<Long> empiLinkSourcePersonPids = mdmLinks.stream().map(MdmLink::getGoldenResourcePid).collect(Collectors.toList());
return empiLinkSourcePersonPids.containsAll(personPidsToMatch);
List<Long> mdmLinkGoldenResourcePids = mdmLinks
.stream().map(MdmLink::getGoldenResourcePid)
.collect(Collectors.toList());
return mdmLinkGoldenResourcePids.containsAll(personPidsToMatch);
}
@Override
@ -48,7 +51,7 @@ public class IsPossibleMatchWith extends BaseSourceResourceMatcher {
@Override
protected void describeMismatchSafely(IAnyResource item, Description mismatchDescription) {
super.describeMismatchSafely(item, mismatchDescription);
mismatchDescription.appendText("No Empi Link With POSSIBLE_MATCH was found");
mismatchDescription.appendText("No MDM Link With POSSIBLE_MATCH was found");
}
public static Matcher<IAnyResource> possibleMatchWith(IdHelperService theIdHelperService, MdmLinkDaoSvc theMdmLinkDaoSvc, IAnyResource... theBaseResource) {

View File

@ -32,7 +32,7 @@ public abstract class BaseProviderR4Test extends BaseMdmR4Test {
private String defaultScript;
protected void setEmpiRuleJson(String theString) throws IOException {
protected void setMdmRuleJson(String theString) throws IOException {
DefaultResourceLoader resourceLoader = new DefaultResourceLoader();
Resource resource = resourceLoader.getResource(theString);
String json = IOUtils.toString(resource.getInputStream(), Charsets.UTF_8);

View File

@ -73,7 +73,7 @@ public class MdmProviderBatchR4Test extends BaseLinkR4Test {
StringType criteria = null;
myMdmProviderR4.clearMdmLinks(null, myRequestDetails);
afterMdmLatch.runWithExpectedCount(1, () -> myMdmProviderR4.empiBatchOnAllTargets(new StringType("Medication"), criteria, null));
afterMdmLatch.runWithExpectedCount(1, () -> myMdmProviderR4.mdmBatchOnAllTargets(new StringType("Medication"), criteria, null));
assertLinkCount(1);
}
@ -82,13 +82,13 @@ public class MdmProviderBatchR4Test extends BaseLinkR4Test {
StringType criteria = null;
myMdmProviderR4.clearMdmLinks(null, myRequestDetails);
afterMdmLatch.runWithExpectedCount(1, () -> myMdmProviderR4.empiBatchPractitionerType(criteria, null));
afterMdmLatch.runWithExpectedCount(1, () -> myMdmProviderR4.mdmBatchPractitionerType(criteria, null));
assertLinkCount(1);
}
@Test
public void testBatchRunOnSpecificPractitioner() throws InterruptedException {
myMdmProviderR4.clearMdmLinks(null, myRequestDetails);
afterMdmLatch.runWithExpectedCount(1, () -> myMdmProviderR4.empiBatchPractitionerInstance(myPractitioner.getIdElement(), null));
afterMdmLatch.runWithExpectedCount(1, () -> myMdmProviderR4.mdmBatchPractitionerInstance(myPractitioner.getIdElement(), null));
assertLinkCount(1);
}
@ -96,7 +96,7 @@ public class MdmProviderBatchR4Test extends BaseLinkR4Test {
public void testBatchRunOnNonExistentSpecificPractitioner() {
myMdmProviderR4.clearMdmLinks(null, myRequestDetails);
try {
myMdmProviderR4.empiBatchPractitionerInstance(new IdType("Practitioner/999"), null);
myMdmProviderR4.mdmBatchPractitionerInstance(new IdType("Practitioner/999"), null);
fail();
} catch (ResourceNotFoundException e){}
}
@ -106,7 +106,7 @@ public class MdmProviderBatchR4Test extends BaseLinkR4Test {
assertLinkCount(3);
StringType criteria = null;
myMdmProviderR4.clearMdmLinks(null, myRequestDetails);
afterMdmLatch.runWithExpectedCount(1, () -> myMdmProviderR4.empiBatchPatientType(criteria, null));
afterMdmLatch.runWithExpectedCount(1, () -> myMdmProviderR4.mdmBatchPatientType(criteria, null));
assertLinkCount(1);
}
@ -114,7 +114,7 @@ public class MdmProviderBatchR4Test extends BaseLinkR4Test {
public void testBatchRunOnSpecificPatient() throws InterruptedException {
assertLinkCount(3);
myMdmProviderR4.clearMdmLinks(null, myRequestDetails);
afterMdmLatch.runWithExpectedCount(1, () -> myMdmProviderR4.empiBatchPatientInstance(myPatient.getIdElement(), null));
afterMdmLatch.runWithExpectedCount(1, () -> myMdmProviderR4.mdmBatchPatientInstance(myPatient.getIdElement(), null));
assertLinkCount(1);
}
@ -123,7 +123,7 @@ public class MdmProviderBatchR4Test extends BaseLinkR4Test {
assertLinkCount(3);
myMdmProviderR4.clearMdmLinks(null, myRequestDetails);
try {
myMdmProviderR4.empiBatchPatientInstance(new IdType("Patient/999"), null);
myMdmProviderR4.mdmBatchPatientInstance(new IdType("Patient/999"), null);
fail();
} catch (ResourceNotFoundException e){}
}
@ -134,7 +134,7 @@ public class MdmProviderBatchR4Test extends BaseLinkR4Test {
StringType criteria = new StringType("");
myMdmProviderR4.clearMdmLinks(null, myRequestDetails);
afterMdmLatch.runWithExpectedCount(3, () -> {
myMdmProviderR4.empiBatchOnAllTargets(null, criteria, null);
myMdmProviderR4.mdmBatchOnAllTargets(null, criteria, null);
});
assertLinkCount(3);
}
@ -146,7 +146,7 @@ public class MdmProviderBatchR4Test extends BaseLinkR4Test {
myMdmProviderR4.clearMdmLinks(null, myRequestDetails);
try {
myMdmProviderR4.empiBatchPractitionerType(criteria, null);
myMdmProviderR4.mdmBatchPractitionerType(criteria, null);
fail();
} catch(InvalidRequestException e) {
assertThat(e.getMessage(), is(equalTo("Failed to parse match URL[death-date=2020-06-01] - Resource type Practitioner does not have a parameter with name: death-date")));

View File

@ -145,7 +145,7 @@ public class MdmProviderMatchR4Test extends BaseProviderR4Test {
@Test
public void testMatchWithEmptySearchParamCandidates() throws Exception {
setEmpiRuleJson("empi/empty-candidate-search-params.json");
setMdmRuleJson("mdm/empty-candidate-search-params.json");
Patient jane = buildJanePatient();
jane.setActive(true);
Patient createdJane = createPatient(jane);

View File

@ -1,9 +1,9 @@
package ca.uhn.fhir.jpa.mdm.provider;
import ca.uhn.fhir.jpa.entity.MdmLink;
import ca.uhn.fhir.mdm.api.MdmLinkSourceEnum;
import ca.uhn.fhir.mdm.api.MdmMatchResultEnum;
import ca.uhn.fhir.mdm.util.MdmUtil;
import ca.uhn.fhir.jpa.entity.MdmLink;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import org.hl7.fhir.r4.model.Patient;
@ -14,7 +14,8 @@ import org.junit.jupiter.api.Test;
import java.util.List;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertTrue;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.fail;
@ -65,11 +66,6 @@ public class MdmProviderMergePersonsR4Test extends BaseProviderR4Test {
assertEquals(link.getGoldenResourcePid(), myToSourcePatient.getIdElement().toUnqualifiedVersionless().getIdPartAsLong());
assertEquals(link.getMatchResult(), MdmMatchResultEnum.REDIRECT);
assertEquals(link.getLinkSource(), MdmLinkSourceEnum.MANUAL);
// assertThat(links.get(0).getAssurance(), is (AssuranceLevelUtil.getAssuranceLevel(EmpiMatchResultEnum.REDIRECT, EmpiLinkSourceEnum.MANUAL).toR4()));
//List<Person.PersonLinkComponent> links = fromSourcePatient.getLink();
//assertThat(links, hasSize(1));
//assertThat(links.get(0).getTarget().getReference(), is (myToSourcePatient.getIdElement().toUnqualifiedVersionless().getValue()));
//assertThat(links.get(0).getAssurance(), is (AssuranceLevelUtil.getAssuranceLevel(EmpiMatchResultEnum.REDIRECT, EmpiLinkSourceEnum.MANUAL).toR4()));
}
@Test
@ -84,20 +80,6 @@ public class MdmProviderMergePersonsR4Test extends BaseProviderR4Test {
}
}
// INVALID ANYMORE - we support merging patients to patients now
// @Test
// public void testMergePatients() {
// try {
// StringType patientId = new StringType(createPatient().getIdElement().getValue());
// StringType otherPatientId = new StringType(createPatient().getIdElement().getValue());
// myEmpiProviderR4.mergeGoldenResources(patientId, otherPatientId, myRequestDetails);
// fail();
// } catch (InvalidRequestException e) {
// assertThat(e.getMessage(), endsWith("must have form Person/<id> where <id> is the id of the person"));
// }
//
// }
@Test
public void testNullParams() {
try {
@ -122,24 +104,6 @@ public class MdmProviderMergePersonsR4Test extends BaseProviderR4Test {
@Test
public void testBadParams() {
print(myFromSourcePatient);
print(myToSourcePatient);
// TODO NG - THESE ARE NOW INVALID
// try {
// myEmpiProviderR4.mergeGoldenResources(new StringType("Patient/1"), new StringType("Patient/2"), myRequestDetails);
// fail();
// } catch (InvalidRequestException e) {
// assertThat(e.getMessage(), endsWith(" must have form Person/<id> where <id> is the id of the person"));
// }
//
// try {
// myEmpiProviderR4.mergeGoldenResources(myFromSourcePatientId, new StringType("Patient/2"), myRequestDetails);
// fail();
// } catch (InvalidRequestException e) {
// assertThat(e.getMessage(), endsWith(" must have form Person/<id> where <id> is the id of the person"));
// }
try {
myMdmProviderR4.mergeGoldenResources(new StringType("Person/1"), new StringType("Person/1"), myRequestDetails);
fail();

View File

@ -160,7 +160,7 @@ public class MdmProviderUpdateLinkR4Test extends BaseLinkR4Test {
myMdmProviderR4.updateLink(mySourcePatientId, new StringType(patient.getIdElement().getValue()), NO_MATCH_RESULT, myRequestDetails);
fail();
} catch (InvalidRequestException e) {
assertEquals("The target is marked with the " + MdmConstants.CODE_NO_MDM_MANAGED + " tag which means it may not be EMPI linked.", e.getMessage());
assertEquals("The target is marked with the " + MdmConstants.CODE_NO_MDM_MANAGED + " tag which means it may not be MDM linked.", e.getMessage());
}
}
}

View File

@ -1,10 +0,0 @@
package ca.uhn.fhir.jpa.mdm.svc;
import ca.uhn.fhir.jpa.mdm.provider.MdmProviderUpdateLinkR4Test;
/**
* Tests for this service are in the test for the provider that wraps this service:
* @see MdmProviderUpdateLinkR4Test
*/
public class EmpiLinkUpdaterSvcImplTest {
}

View File

@ -153,7 +153,7 @@ public class MdmMatchLinkSvcMultipleEidModeTest extends BaseMdmR4Test {
.map(myIdHelperService::getPidOrNull)
.collect(Collectors.toList());
//The two Persons related to the patients should both show up in the only existing POSSIBLE_DUPLICATE EmpiLink.
//The two Persons related to the patients should both show up in the only existing POSSIBLE_DUPLICATE MdmLink.
MdmLink mdmLink = possibleDuplicates.get(0);
assertThat(mdmLink.getGoldenResourcePid(), is(in(duplicatePids)));
assertThat(mdmLink.getTargetPid(), is(in(duplicatePids)));

View File

@ -53,15 +53,11 @@ public class MdmMatchLinkSvcTest extends BaseMdmR4Test {
private static final Logger ourLog = getLogger(MdmMatchLinkSvcTest.class);
@Autowired
IMdmLinkSvc myEmpiLinkSvc;
IMdmLinkSvc myMdmLinkSvc;
@Autowired
private EIDHelper myEidHelper;
@Autowired
private GoldenResourceHelper myGoldenResourceHelper;
@Autowired
private IMdmLinkDao myEmpiLinkDao;
@Autowired
private DaoRegistry myDaoRegistry;
@BeforeEach
public void before() {
@ -123,9 +119,9 @@ public class MdmMatchLinkSvcTest extends BaseMdmR4Test {
//Create a manual NO_MATCH between janePerson and unmatchedJane.
Patient unmatchedJane = createPatient(buildJanePatient());
myEmpiLinkSvc.updateLink(janePerson, unmatchedJane, MdmMatchOutcome.NO_MATCH, MdmLinkSourceEnum.MANUAL, createContextForCreate("Patient"));
myMdmLinkSvc.updateLink(janePerson, unmatchedJane, MdmMatchOutcome.NO_MATCH, MdmLinkSourceEnum.MANUAL, createContextForCreate("Patient"));
//rerun EMPI rules against unmatchedJane.
//rerun MDM rules against unmatchedJane.
myMdmMatchLinkSvc.updateMdmLinksForMdmTarget(unmatchedJane, createContextForCreate("Patient"));
assertThat(unmatchedJane, is(not(sameSourceResourceAs(janePerson))));
@ -146,7 +142,7 @@ public class MdmMatchLinkSvcTest extends BaseMdmR4Test {
Patient unmatchedPatient = createPatient(buildJanePatient());
// This simulates an admin specifically saying that unmatchedPatient does NOT match janePerson.
myEmpiLinkSvc.updateLink(janePerson, unmatchedPatient, MdmMatchOutcome.NO_MATCH, MdmLinkSourceEnum.MANUAL, createContextForCreate("Patient"));
myMdmLinkSvc.updateLink(janePerson, unmatchedPatient, MdmMatchOutcome.NO_MATCH, MdmLinkSourceEnum.MANUAL, createContextForCreate("Patient"));
// TODO change this so that it will only partially match.
//Now normally, when we run update links, it should link to janePerson. However, this manual NO_MATCH link
@ -167,10 +163,10 @@ public class MdmMatchLinkSvcTest extends BaseMdmR4Test {
Patient janePatient = addExternalEID(buildJanePatient(), sampleEID);
janePatient = createPatientAndUpdateLinks(janePatient);
Optional<MdmLink> empiLink = myMdmLinkDaoSvc.getMatchedLinkForTargetPid(janePatient.getIdElement().getIdPartAsLong());
assertThat(empiLink.isPresent(), is(true));
Optional<MdmLink> mdmLink = myMdmLinkDaoSvc.getMatchedLinkForTargetPid(janePatient.getIdElement().getIdPartAsLong());
assertThat(mdmLink.isPresent(), is(true));
Patient patient = getTargetResourceFromEmpiLink(empiLink.get(), "Patient");
Patient patient = getTargetResourceFromMdmLink(mdmLink.get(), "Patient");
List<CanonicalEID> externalEid = myEidHelper.getExternalEid(patient);
assertThat(externalEid.get(0).getSystem(), is(equalTo(myMdmSettings.getMdmRules().getEnterpriseEIDSystem())));
@ -182,7 +178,7 @@ public class MdmMatchLinkSvcTest extends BaseMdmR4Test {
Patient patient = createPatientAndUpdateLinks(buildJanePatient());
MdmLink mdmLink = myMdmLinkDaoSvc.getMatchedLinkForTargetPid(patient.getIdElement().getIdPartAsLong()).get();
Patient targetPatient = getTargetResourceFromEmpiLink(mdmLink, "Patient");
Patient targetPatient = getTargetResourceFromMdmLink(mdmLink, "Patient");
Identifier identifierFirstRep = targetPatient.getIdentifierFirstRep();
assertThat(identifierFirstRep.getSystem(), is(equalTo(MdmConstants.HAPI_ENTERPRISE_IDENTIFIER_SYSTEM)));
assertThat(identifierFirstRep.getValue(), not(blankOrNullString()));
@ -192,8 +188,8 @@ public class MdmMatchLinkSvcTest extends BaseMdmR4Test {
public void testPatientAttributesAreCopiedOverWhenPersonIsCreatedFromPatient() {
Patient patient = createPatientAndUpdateLinks(buildPatientWithNameIdAndBirthday("Gary", "GARY_ID", new Date()));
Optional<MdmLink> empiLink = myMdmLinkDaoSvc.getMatchedLinkForTargetPid(patient.getIdElement().getIdPartAsLong());
Patient read = getTargetResourceFromEmpiLink(empiLink.get(), "Patient");
Optional<MdmLink> mdmLink = myMdmLinkDaoSvc.getMatchedLinkForTargetPid(patient.getIdElement().getIdPartAsLong());
Patient read = getTargetResourceFromMdmLink(mdmLink.get(), "Patient");
// TODO NG - rules haven't been determined yet revisit once implemented...
// assertThat(read.getNameFirstRep().getFamily(), is(equalTo(patient.getNameFirstRep().getFamily())));
@ -290,17 +286,17 @@ public class MdmMatchLinkSvcTest extends BaseMdmR4Test {
.map(myIdHelperService::getPidOrNull)
.collect(Collectors.toList());
//The two Persons related to the patients should both show up in the only existing POSSIBLE_DUPLICATE EmpiLink.
//The two Persons related to the patients should both show up in the only existing POSSIBLE_DUPLICATE MdmLink.
MdmLink mdmLink = possibleDuplicates.get(0);
assertThat(mdmLink.getGoldenResourcePid(), is(in(duplicatePids)));
assertThat(mdmLink.getTargetPid(), is(in(duplicatePids)));
}
@Test
public void testPatientWithNoEmpiTagIsNotMatched() {
// Patient with "no-empi" tag is not matched
public void testPatientWithNoMdmTagIsNotMatched() {
// Patient with "no-mdm" tag is not matched
Patient janePatient = buildJanePatient();
janePatient.getMeta().addTag(MdmConstants.SYSTEM_MDM_MANAGED, MdmConstants.CODE_NO_MDM_MANAGED, "Don't EMPI on me!");
janePatient.getMeta().addTag(MdmConstants.SYSTEM_MDM_MANAGED, MdmConstants.CODE_NO_MDM_MANAGED, "Don't MDM on me!");
createPatientAndUpdateLinks(janePatient);
assertLinkCount(0);
}
@ -364,7 +360,7 @@ public class MdmMatchLinkSvcTest extends BaseMdmR4Test {
//In a normal situation, janePatient2 would just match to jane patient, but here we need to hack it so they are their
//own individual Persons for the purpose of this test.
IAnyResource person = myGoldenResourceHelper.createGoldenResourceFromMdmTarget(janePatient2);
myEmpiLinkSvc.updateLink(person, janePatient2, MdmMatchOutcome.NEW_PERSON_MATCH, MdmLinkSourceEnum.AUTO, createContextForCreate("Patient"));
myMdmLinkSvc.updateLink(person, janePatient2, MdmMatchOutcome.NEW_PERSON_MATCH, MdmLinkSourceEnum.AUTO, createContextForCreate("Patient"));
assertThat(janePatient, is(not(sameSourceResourceAs(janePatient2))));
//In theory, this will match both Persons!
@ -389,7 +385,7 @@ public class MdmMatchLinkSvcTest extends BaseMdmR4Test {
@Test
public void testWhenAllMatchResultsArePOSSIBLE_MATCHThattheyAreLinkedAndNoSourceResourceIsCreated() {
/**
* CASE 4: Only POSSIBLE_MATCH outcomes -> In this case, empi-link records are created with POSSIBLE_MATCH
* CASE 4: Only POSSIBLE_MATCH outcomes -> In this case, mdm-link records are created with POSSIBLE_MATCH
* outcome and await manual assignment to either NO_MATCH or MATCHED. Person link is added.
*/
Patient patient = buildJanePatient();
@ -451,7 +447,7 @@ public class MdmMatchLinkSvcTest extends BaseMdmR4Test {
}
@Test
public void testCreateSourceResourceFromEmpiTarget() {
public void testCreateSourceResourceFromMdmTarget() {
// Create Use Case #2 - adding patient with no EID
Patient janePatient = buildJanePatient();
Patient janeSourceResourcePatient = myGoldenResourceHelper.createGoldenResourceFromMdmTarget(janePatient);

View File

@ -76,7 +76,7 @@ public class MdmPersonMergerSvcTest extends BaseMdmR4Test {
myTargetPatient2 = createPatient();
myTargetPatient3 = createPatient();
// Register the empi storage interceptor after the creates so the delete hook is fired when we merge
// Register the mdm storage interceptor after the creates so the delete hook is fired when we merge
myInterceptorService.registerInterceptor(myMdmStorageInterceptor);
}
@ -102,7 +102,7 @@ public class MdmPersonMergerSvcTest extends BaseMdmR4Test {
private Patient mergeGoldenPatients() {
assertEquals(0, redirectLinkCount());
Patient retval = (Patient) myGoldenResourceMergerSvc.mergeGoldenResources(myFromGoldenPatient, myToGoldenPatient, createEmpiContext());
Patient retval = (Patient) myGoldenResourceMergerSvc.mergeGoldenResources(myFromGoldenPatient, myToGoldenPatient, createMdmContext());
assertEquals(1, redirectLinkCount());
return retval;
}
@ -113,7 +113,7 @@ public class MdmPersonMergerSvcTest extends BaseMdmR4Test {
return myMdmLinkDao.findAll(example).size();
}
private MdmTransactionContext createEmpiContext() {
private MdmTransactionContext createMdmContext() {
MdmTransactionContext mdmTransactionContext = new MdmTransactionContext(TransactionLogMessages.createFromTransactionGuid(UUID.randomUUID().toString()), MdmTransactionContext.OperationType.MERGE_PERSONS);
mdmTransactionContext.setResourceType("Patient");
return mdmTransactionContext;

View File

@ -17,8 +17,9 @@ import static org.mockito.Mockito.when;
@ExtendWith(SpringExtension.class)
class MdmResourceFilteringSvcMockTest {
@MockBean
private IMdmSettings myEmpiSettings;
private IMdmSettings myMdmSettings;
@MockBean
MdmSearchParamSvc myMdmSearchParamSvc;
@MockBean
@ -29,14 +30,14 @@ class MdmResourceFilteringSvcMockTest {
@Configuration
static class SpringConfig {
@Bean
MdmResourceFilteringSvc empiResourceFilteringSvc() {
MdmResourceFilteringSvc mdmResourceFilteringSvc() {
return new MdmResourceFilteringSvc();
}
}
@Test
public void testEmptyCriteriaShouldBeProcessed() {
when(myEmpiSettings.getMdmRules()).thenReturn(new MdmRulesJson());
when(myMdmSettings.getMdmRules()).thenReturn(new MdmRulesJson());
assertTrue(myMdmResourceFilteringSvc.shouldBeProcessed(new Patient()));
}
}

View File

@ -18,7 +18,7 @@ class MdmResourceFilteringSvcTest extends BaseMdmR4Test {
@Test
public void testFilterResourcesWhichHaveNoRelevantAttributes() {
Patient patient = new Patient();
patient.setDeceased(new BooleanType(true)); //EMPI rules defined do not care about the deceased attribute.
patient.setDeceased(new BooleanType(true)); // MDM rules defined do not care about the deceased attribute.
//SUT
boolean shouldBeProcessed = myMdmResourceFilteringSvc.shouldBeProcessed(patient);
@ -27,9 +27,9 @@ class MdmResourceFilteringSvcTest extends BaseMdmR4Test {
}
@Test
public void testDoNotFilterResourcesWithEMPIAttributes() {
public void testDoNotFilterResourcesWithMdmAttributes() {
Patient patient = new Patient();
patient.addIdentifier().setValue("Hey I'm an ID! rules defined in empi-rules.json care about me!");
patient.addIdentifier().setValue("Hey I'm an ID! rules defined in mdm-rules.json care about me!");
//SUT
boolean shouldBeProcessed = myMdmResourceFilteringSvc.shouldBeProcessed(patient);

View File

@ -48,15 +48,14 @@
<appender-ref ref="STDOUT" />
</logger>
<!--
Configuration for EMPI troubleshooting log
Configuration for MDM troubleshooting log
-->
<appender name="EMPI_TROUBLESHOOTING" class="ch.qos.logback.core.rolling.RollingFileAppender">
<appender name="MDM_TROUBLESHOOTING" class="ch.qos.logback.core.rolling.RollingFileAppender">
<filter class="ch.qos.logback.classic.filter.ThresholdFilter"><level>DEBUG</level></filter>
<file>${smile.basedir}/log/empi-troubleshooting.log</file>
<file>${smile.basedir}/log/mdm-troubleshooting.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy">
<fileNamePattern>${smile.basedir}/log/empi-troubleshooting.log.%i.gz</fileNamePattern>
<fileNamePattern>${smile.basedir}/log/mdm-troubleshooting.log.%i.gz</fileNamePattern>
<minIndex>1</minIndex>
<maxIndex>9</maxIndex>
</rollingPolicy>
@ -67,12 +66,11 @@
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<logger name="ca.uhn.fhir.log.empi_troubleshooting" level="TRACE">
<appender-ref ref="EMPI_TROUBLESHOOTING"/>
<logger name="ca.uhn.fhir.log.mdm_troubleshooting" level="TRACE">
<appender-ref ref="MDM_TROUBLESHOOTING"/>
</logger>
<root level="info">
<appender-ref ref="STDOUT" />
</root>

View File

@ -26,9 +26,9 @@ import org.hl7.fhir.instance.model.api.IIdType;
import java.util.stream.Stream;
/**
* This service supports the EMPI Operation providers for those services that return multiple empi links.
* This service supports the MDM operation providers for those services that return multiple MDM links.
*/
public interface IMdmLinkQuerySvc {
Stream<MdmLinkJson> queryLinks(IIdType thePersonId, IIdType theTargetId, MdmMatchResultEnum theMatchResult, MdmLinkSourceEnum theLinkSource, MdmTransactionContext theEmpiContext);
Stream<MdmLinkJson> getDuplicatePersons(MdmTransactionContext theEmpiContext);
Stream<MdmLinkJson> queryLinks(IIdType theGoldenResourceId, IIdType theTargetId, MdmMatchResultEnum theMatchResult, MdmLinkSourceEnum theLinkSource, MdmTransactionContext theMdmContext);
Stream<MdmLinkJson> getDuplicatePersons(MdmTransactionContext theMdmContext);
}

View File

@ -24,6 +24,6 @@ import ca.uhn.fhir.mdm.model.MdmTransactionContext;
import org.hl7.fhir.instance.model.api.IAnyResource;
public interface IMdmLinkUpdaterSvc {
IAnyResource updateLink(IAnyResource thePerson, IAnyResource theTarget, MdmMatchResultEnum theMatchResult, MdmTransactionContext theEmpiContext);
void notDuplicatePerson(IAnyResource thePerson, IAnyResource theTarget, MdmTransactionContext theEmpiContext);
IAnyResource updateLink(IAnyResource theGoldenResource, IAnyResource theTarget, MdmMatchResultEnum theMatchResult, MdmTransactionContext theMdmContext);
void notDuplicatePerson(IAnyResource theGoldenResource, IAnyResource theTarget, MdmTransactionContext theMdmContext);
}

View File

@ -25,7 +25,9 @@ import ca.uhn.fhir.mdm.rules.json.MdmRulesJson;
import java.util.stream.Collectors;
public interface IMdmSettings {
String MDM_CHANNEL_NAME = "empi";
String MDM_CHANNEL_NAME = "mdm";
// Parallel processing of MDM can result in missed matches. Best to single-thread.
int MDM_DEFAULT_CONCURRENT_CONSUMERS = 1;

View File

@ -108,13 +108,13 @@ public final class MdmMatchOutcome {
/**
* Sets the number of MDM rules checked for this match outcome
*
* @param theEmpiRuleCount
* @param theMdmRuleCount
* Number of MDM rules that were checked for this match outcome
* @return
* Returns this instance
*/
public MdmMatchOutcome setMdmRuleCount(int theEmpiRuleCount) {
myMdmRuleCount = theEmpiRuleCount;
public MdmMatchOutcome setMdmRuleCount(int theMdmRuleCount) {
myMdmRuleCount = theMdmRuleCount;
return this;
}

View File

@ -59,7 +59,7 @@ public class CanonicalEID {
/**
* Get the appropriate FHIRPath expression to extract the EID identifier value, regardless of resource type.
* e.g. if theBaseResource is a patient, and the EMPI EID system is test-system, this will return
* e.g. if theBaseResource is a patient, and the MDM EID system is test-system, this will return
*
* Patient.identifier.where(system='test-system').value
*

View File

@ -36,7 +36,7 @@ public class MdmTransactionContext {
}
/**
* Any EMPI methods may add transaction log messages.
* Any MDM methods may add transaction log messages.
*/
private TransactionLogMessages myTransactionLogMessages;

View File

@ -39,14 +39,14 @@ import org.springframework.stereotype.Service;
public class MdmControllerHelper {
private final FhirContext myFhirContext;
private final IResourceLoader myResourceLoader;
private final IMdmSettings myEmpiSettings;
private final IMdmSettings myMdmSettings;
private final MessageHelper myMessageHelper;
@Autowired
public MdmControllerHelper(FhirContext theFhirContext, IResourceLoader theResourceLoader, IMdmSettings theEmpiSettings, MessageHelper theMessageHelper) {
public MdmControllerHelper(FhirContext theFhirContext, IResourceLoader theResourceLoader, IMdmSettings theMdmSettings, MessageHelper theMessageHelper) {
myFhirContext = theFhirContext;
myResourceLoader = theResourceLoader;
myEmpiSettings = theEmpiSettings;
myMdmSettings = theMdmSettings;
myMessageHelper = theMessageHelper;
}
@ -78,17 +78,17 @@ public class MdmControllerHelper {
}
public void validateMergeResources(IAnyResource theFromPerson, IAnyResource theToPerson) {
validateIsEmpiManaged(ProviderConstants.MDM_MERGE_GR_FROM_GOLDEN_RESOURCE_ID, theFromPerson);
validateIsEmpiManaged(ProviderConstants.MDM_MERGE_GR_TO_GOLDEN_RESOURCE_ID, theToPerson);
validateIsMdmManaged(ProviderConstants.MDM_MERGE_GR_FROM_GOLDEN_RESOURCE_ID, theFromPerson);
validateIsMdmManaged(ProviderConstants.MDM_MERGE_GR_TO_GOLDEN_RESOURCE_ID, theToPerson);
}
public String toJson(IAnyResource theAnyResource) {
return myFhirContext.newJsonParser().encodeResourceToString(theAnyResource);
}
public void validateIsEmpiManaged(String theName, IAnyResource theResource) {
public void validateIsMdmManaged(String theName, IAnyResource theResource) {
String resourceType = myFhirContext.getResourceType(theResource);
if (!myEmpiSettings.isSupportedMdmType(resourceType)) {
if (!myMdmSettings.isSupportedMdmType(resourceType)) {
throw new InvalidRequestException(
myMessageHelper.getMessageForUnsupportedResource(theName, resourceType)
);

View File

@ -141,10 +141,10 @@ public class MdmProviderDstu3 extends BaseMdmProvider {
@OperationParam(name=ProviderConstants.MDM_QUERY_LINKS_MATCH_RESULT, min = 0, max = 1) StringType theLinkSource,
ServletRequestDetails theRequestDetails) {
Stream<MdmLinkJson> empiLinkJson = myMdmControllerSvc.queryLinks(extractStringOrNull(theGoldenResourceId), extractStringOrNull(theTargetResourceId),
Stream<MdmLinkJson> mdmLinkJson = myMdmControllerSvc.queryLinks(extractStringOrNull(theGoldenResourceId), extractStringOrNull(theTargetResourceId),
extractStringOrNull(theMatchResult), extractStringOrNull(theLinkSource), createMdmContext(theRequestDetails,
MdmTransactionContext.OperationType.QUERY_LINKS, getResourceType(ProviderConstants.MDM_QUERY_LINKS_GOLDEN_RESOURCE_ID, theGoldenResourceId)));
return (Parameters) parametersFromMdmLinks(empiLinkJson, true);
return (Parameters) parametersFromMdmLinks(mdmLinkJson, true);
}
@Operation(name = ProviderConstants.MDM_DUPLICATE_GOLDEN_RESOURCES)
@ -171,12 +171,12 @@ public class MdmProviderDstu3 extends BaseMdmProvider {
@Operation(name = ProviderConstants.OPERATION_MDM_SUBMIT, idempotent = false, returnParameters = {
@OperationParam(name = ProviderConstants.OPERATION_MDM_BATCH_RUN_OUT_PARAM_SUBMIT_COUNT, type= DecimalType.class)
})
public Parameters empiBatchOnAllTargets(
public Parameters mdmBatchOnAllTargets(
@OperationParam(name= ProviderConstants.MDM_BATCH_RUN_CRITERIA,min = 0 , max = 1) StringType theCriteria,
ServletRequestDetails theRequestDetails) {
String criteria = convertCriteriaToString(theCriteria);
long submittedCount = myMdmSubmitSvc.submitAllTargetTypesToMdm(criteria);
return buildEmpiOutParametersWithCount(submittedCount);
return buildMdmOutParametersWithCount(submittedCount);
}
private String convertCriteriaToString(StringType theCriteria) {
@ -186,8 +186,8 @@ public class MdmProviderDstu3 extends BaseMdmProvider {
@Operation(name = ProviderConstants.MDM_CLEAR, returnParameters = {
@OperationParam(name = ProviderConstants.OPERATION_MDM_BATCH_RUN_OUT_PARAM_SUBMIT_COUNT, type= DecimalType.class)
})
public Parameters clearEmpiLinks(@OperationParam(name=ProviderConstants.MDM_CLEAR_TARGET_TYPE, min = 0, max = 1) StringType theTargetType,
ServletRequestDetails theRequestDetails) {
public Parameters clearMdmLinks(@OperationParam(name=ProviderConstants.MDM_CLEAR_TARGET_TYPE, min = 0, max = 1) StringType theTargetType,
ServletRequestDetails theRequestDetails) {
long resetCount;
if (theTargetType == null || StringUtils.isBlank(theTargetType.getValue())) {
resetCount = myMdmExpungeSvc.expungeAllMdmLinks(theRequestDetails);
@ -203,49 +203,49 @@ public class MdmProviderDstu3 extends BaseMdmProvider {
@Operation(name = ProviderConstants.OPERATION_MDM_SUBMIT, idempotent = false, type = Patient.class, returnParameters = {
@OperationParam(name = ProviderConstants.OPERATION_MDM_BATCH_RUN_OUT_PARAM_SUBMIT_COUNT, type = DecimalType.class)
})
public Parameters empiBatchPatientInstance(
public Parameters mdmBatchPatientInstance(
@IdParam IIdType theIdParam,
RequestDetails theRequest) {
long submittedCount = myMdmSubmitSvc.submitTargetToMdm(theIdParam);
return buildEmpiOutParametersWithCount(submittedCount);
return buildMdmOutParametersWithCount(submittedCount);
}
@Operation(name = ProviderConstants.OPERATION_MDM_SUBMIT, idempotent = false, type = Patient.class, returnParameters = {
@OperationParam(name = ProviderConstants.OPERATION_MDM_BATCH_RUN_OUT_PARAM_SUBMIT_COUNT, type = DecimalType.class)
})
public Parameters empiBatchPatientType(
public Parameters mdmBatchPatientType(
@OperationParam(name = ProviderConstants.MDM_BATCH_RUN_CRITERIA) StringType theCriteria,
RequestDetails theRequest) {
String criteria = convertCriteriaToString(theCriteria);
long submittedCount = myMdmSubmitSvc.submitPatientTypeToMdm(criteria);
return buildEmpiOutParametersWithCount(submittedCount);
return buildMdmOutParametersWithCount(submittedCount);
}
@Operation(name = ProviderConstants.OPERATION_MDM_SUBMIT, idempotent = false, type = Practitioner.class, returnParameters = {
@OperationParam(name = ProviderConstants.OPERATION_MDM_BATCH_RUN_OUT_PARAM_SUBMIT_COUNT, type = DecimalType.class)
})
public Parameters empiBatchPractitionerInstance(
public Parameters mdmBatchPractitionerInstance(
@IdParam IIdType theIdParam,
RequestDetails theRequest) {
long submittedCount = myMdmSubmitSvc.submitTargetToMdm(theIdParam);
return buildEmpiOutParametersWithCount(submittedCount);
return buildMdmOutParametersWithCount(submittedCount);
}
@Operation(name = ProviderConstants.OPERATION_MDM_SUBMIT, idempotent = false, type = Practitioner.class, returnParameters = {
@OperationParam(name = ProviderConstants.OPERATION_MDM_BATCH_RUN_OUT_PARAM_SUBMIT_COUNT, type = DecimalType.class)
})
public Parameters empiBatchPractitionerType(
public Parameters mdmBatchPractitionerType(
@OperationParam(name = ProviderConstants.MDM_BATCH_RUN_CRITERIA) StringType theCriteria,
RequestDetails theRequest) {
String criteria = convertCriteriaToString(theCriteria);
long submittedCount = myMdmSubmitSvc.submitPractitionerTypeToMdm(criteria);
return buildEmpiOutParametersWithCount(submittedCount);
return buildMdmOutParametersWithCount(submittedCount);
}
/**
* Helper function to build the out-parameters for all batch EMPI operations.
* Helper function to build the out-parameters for all batch MDM operations.
*/
private Parameters buildEmpiOutParametersWithCount(long theCount) {
private Parameters buildMdmOutParametersWithCount(long theCount) {
Parameters parameters = new Parameters();
parameters.addParameter()
.setName(ProviderConstants.OPERATION_MDM_BATCH_RUN_OUT_PARAM_SUBMIT_COUNT)

View File

@ -194,12 +194,12 @@ public class MdmProviderR4 extends BaseMdmProvider {
@OperationParam(name=ProviderConstants.MDM_QUERY_LINKS_LINK_SOURCE, min = 0, max = 1) StringType theLinkSource,
ServletRequestDetails theRequestDetails) {
Stream<MdmLinkJson> empiLinkJson = myMdmControllerSvc.queryLinks(extractStringOrNull(theGoldenResourceId),
Stream<MdmLinkJson> mdmLinkJson = myMdmControllerSvc.queryLinks(extractStringOrNull(theGoldenResourceId),
extractStringOrNull(theResourceId), extractStringOrNull(theMatchResult), extractStringOrNull(theLinkSource),
createMdmContext(theRequestDetails, MdmTransactionContext.OperationType.QUERY_LINKS,
getResourceType(ProviderConstants.MDM_QUERY_LINKS_GOLDEN_RESOURCE_ID, theGoldenResourceId))
);
return (Parameters) parametersFromMdmLinks(empiLinkJson, true);
return (Parameters) parametersFromMdmLinks(mdmLinkJson, true);
}
@Operation(name = ProviderConstants.MDM_DUPLICATE_GOLDEN_RESOURCES, idempotent = true)
@ -229,7 +229,7 @@ public class MdmProviderR4 extends BaseMdmProvider {
@Operation(name = ProviderConstants.OPERATION_MDM_SUBMIT, idempotent = false, returnParameters = {
@OperationParam(name = ProviderConstants.OPERATION_MDM_BATCH_RUN_OUT_PARAM_SUBMIT_COUNT, type= IntegerType.class)
})
public Parameters empiBatchOnAllTargets(
public Parameters mdmBatchOnAllTargets(
//TODO GGG MDM: also have to take it an optional resourceType here, to clarify which resources should have MDM run on them.
@OperationParam(name= ProviderConstants.MDM_BATCH_RUN_RESOURCE_TYPE, min = 0 , max = 1) StringType theResourceType,
@OperationParam(name= ProviderConstants.MDM_BATCH_RUN_CRITERIA,min = 0 , max = 1) StringType theCriteria,
@ -255,7 +255,7 @@ public class MdmProviderR4 extends BaseMdmProvider {
@Operation(name = ProviderConstants.OPERATION_MDM_SUBMIT, idempotent = false, type = Patient.class, returnParameters = {
@OperationParam(name = ProviderConstants.OPERATION_MDM_BATCH_RUN_OUT_PARAM_SUBMIT_COUNT, type = IntegerType.class)
})
public Parameters empiBatchPatientInstance(
public Parameters mdmBatchPatientInstance(
@IdParam IIdType theIdParam,
RequestDetails theRequest) {
long submittedCount = myMdmSubmitSvc.submitTargetToMdm(theIdParam);
@ -265,7 +265,7 @@ public class MdmProviderR4 extends BaseMdmProvider {
@Operation(name = ProviderConstants.OPERATION_MDM_SUBMIT, idempotent = false, type = Patient.class, returnParameters = {
@OperationParam(name = ProviderConstants.OPERATION_MDM_BATCH_RUN_OUT_PARAM_SUBMIT_COUNT, type = IntegerType.class)
})
public Parameters empiBatchPatientType(
public Parameters mdmBatchPatientType(
@OperationParam(name = ProviderConstants.MDM_BATCH_RUN_CRITERIA) StringType theCriteria,
RequestDetails theRequest) {
String criteria = convertStringTypeToString(theCriteria);
@ -276,7 +276,7 @@ public class MdmProviderR4 extends BaseMdmProvider {
@Operation(name = ProviderConstants.OPERATION_MDM_SUBMIT, idempotent = false, type = Practitioner.class, returnParameters = {
@OperationParam(name = ProviderConstants.OPERATION_MDM_BATCH_RUN_OUT_PARAM_SUBMIT_COUNT, type = IntegerType.class)
})
public Parameters empiBatchPractitionerInstance(
public Parameters mdmBatchPractitionerInstance(
@IdParam IIdType theIdParam,
RequestDetails theRequest) {
long submittedCount = myMdmSubmitSvc.submitTargetToMdm(theIdParam);
@ -286,7 +286,7 @@ public class MdmProviderR4 extends BaseMdmProvider {
@Operation(name = ProviderConstants.OPERATION_MDM_SUBMIT, idempotent = false, type = Practitioner.class, returnParameters = {
@OperationParam(name = ProviderConstants.OPERATION_MDM_BATCH_RUN_OUT_PARAM_SUBMIT_COUNT, type = IntegerType.class)
})
public Parameters empiBatchPractitionerType(
public Parameters mdmBatchPractitionerType(
@OperationParam(name = ProviderConstants.MDM_BATCH_RUN_CRITERIA) StringType theCriteria,
RequestDetails theRequest) {
String criteria = convertStringTypeToString(theCriteria);
@ -295,7 +295,7 @@ public class MdmProviderR4 extends BaseMdmProvider {
}
/**
* Helper function to build the out-parameters for all batch EMPI operations.
* Helper function to build the out-parameters for all batch MDM operations.
*/
private Parameters buildMdmOutParametersWithCount(long theCount) {
Parameters parameters = new Parameters();

View File

@ -72,6 +72,8 @@ public class MdmRuleValidator implements IMdmRuleValidator {
}
public void validateMdmTypes(MdmRulesJson theMdmRulesJson) {
ourLog.info("Validating MDM types {}", theMdmRulesJson.getMdmTypes());
if (theMdmRulesJson.getMdmTypes() == null) {
throw new ConfigurationException("mdmTypes must be set to a list of resource types.");
}
@ -87,6 +89,8 @@ public class MdmRuleValidator implements IMdmRuleValidator {
}
private void validateSearchParams(MdmRulesJson theMdmRulesJson) {
ourLog.info("Validating search parameters {}", theMdmRulesJson.getCandidateSearchParams());
for (MdmResourceSearchParamJson searchParams : theMdmRulesJson.getCandidateSearchParams()) {
searchParams.iterator().forEachRemaining(
searchParam -> validateSearchParam("candidateSearchParams", searchParams.getResourceType(), searchParam));
@ -112,6 +116,8 @@ public class MdmRuleValidator implements IMdmRuleValidator {
}
private void validateMatchFields(MdmRulesJson theMdmRulesJson) {
ourLog.info("Validating match fields {}", theMdmRulesJson.getMatchFields());
Set<String> names = new HashSet<>();
for (MdmFieldMatchJson fieldMatch : theMdmRulesJson.getMatchFields()) {
if (names.contains(fieldMatch.getName())) {
@ -137,7 +143,6 @@ public class MdmRuleValidator implements IMdmRuleValidator {
private void validatePath(List<String> theMdmTypes, MdmFieldMatchJson theFieldMatch) {
String resourceType = theFieldMatch.getResourceType();
if (MdmConstants.ALL_RESOURCE_SEARCH_PARAM_TYPE.equals(resourceType)) {
validateFieldPathForAllTypes(theMdmTypes, theFieldMatch);
} else {
@ -153,18 +158,20 @@ public class MdmRuleValidator implements IMdmRuleValidator {
}
private void validateFieldPathForType(String theResourceType, MdmFieldMatchJson theFieldMatch) {
ourLog.debug(" validating resource {} for {} ", theResourceType, theFieldMatch.getResourcePath());
try {
RuntimeResourceDefinition resourceDefinition = myFhirContext.getResourceDefinition(theResourceType);
Class<? extends IBaseResource> implementingClass = resourceDefinition.getImplementingClass();
myTerser.getDefinition(implementingClass, theResourceType + "." + theFieldMatch.getResourcePath());
} catch (DataFormatException | ConfigurationException e) {
String path = theResourceType + "." + theFieldMatch.getResourcePath();
myTerser.getDefinition(implementingClass, path);
} catch (DataFormatException | ConfigurationException | ClassCastException e) {
throw new ConfigurationException("MatchField " +
theFieldMatch.getName() +
" resourceType " +
theFieldMatch.getResourceType() +
" has invalid path '" + theFieldMatch.getResourcePath() + "'. " +
e.getMessage());
}
}
@ -177,6 +184,8 @@ public class MdmRuleValidator implements IMdmRuleValidator {
return;
}
ourLog.info("Validating system URI {}", theMdmRulesJson.getEnterpriseEIDSystem());
try {
new URI(theMdmRulesJson.getEnterpriseEIDSystem());
} catch (URISyntaxException e) {

View File

@ -40,11 +40,10 @@ public class MdmSettings implements IMdmSettings {
private boolean myPreventEidUpdates;
/**
* If disabled, the underlying EMPI system will operate under the following assumptions:
*
* 1. Patients/Practitioners may have more than 1 EID of the same system simultaneously.
* 2. During linking, incoming patient EIDs will be merged with existing Person EIDs.
* If disabled, the underlying MDM system will operate under the following assumptions:
*
* 1. Target resource may have more than 1 EID of the same system simultaneously.
* 2. During linking, incoming patient EIDs will be merged with existing Golden Resource EIDs.
*/
private boolean myPreventMultipleEids;

View File

@ -25,8 +25,9 @@ import ca.uhn.fhir.rest.param.TokenParamModifier;
import com.fasterxml.jackson.annotation.JsonProperty;
/**
* This SearchParamJson, unlike EmpiREsourceSearchParamJson, is responsible for doing inclusions during empi
* candidate searching. e.g. When doing candidate matching, only consider candidates that match all EmpiFilterSearchParams.
* This class, unlike {@link MdmResourceSearchParamJson}, is responsible for doing inclusions during MDM
* candidate searching. e.g. When doing candidate matching, only consider candidates that match all
* MdmFilterSearchParams.
*/
public class MdmFilterSearchParamJson implements IModelJson {
@JsonProperty(value = "resourceType", required = true)

View File

@ -34,8 +34,9 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
@JsonDeserialize(converter = MdmRulesJson.EmpiRulesJsonConverter.class)
@JsonDeserialize(converter = MdmRulesJson.MdmRulesJsonConverter.class)
public class MdmRulesJson implements IModelJson {
@JsonProperty(value = "version", required = true)
String myVersion;
@JsonProperty(value = "candidateSearchParams", required = true)
@ -163,12 +164,12 @@ public class MdmRulesJson implements IModelJson {
/**
* Ensure the vector map is initialized after we deserialize
*/
static class EmpiRulesJsonConverter extends StdConverter<MdmRulesJson, MdmRulesJson> {
static class MdmRulesJsonConverter extends StdConverter<MdmRulesJson, MdmRulesJson> {
/**
* This empty constructor is required by Jackson
*/
public EmpiRulesJsonConverter() {
public MdmRulesJsonConverter() {
}
@Override

View File

@ -38,7 +38,7 @@ public class VectorMatchResultMap {
VectorMatchResultMap(MdmRulesJson theMdmRulesJson) {
myMdmRulesJson = theMdmRulesJson;
//no reason to hold the entire empirulesjson here
// no reason to hold the entire mdmRulesJson here
initMap();
}

View File

@ -26,9 +26,11 @@ import org.hl7.fhir.instance.model.api.IBase;
/**
* Enum for holding all the known FHIR Element matchers that we support in HAPI. The string matchers first
* encode the string using an Apache Encoder before comparing them. https://commons.apache.org/proper/commons-codec/userguide.html
* encode the string using an Apache Encoder before comparing them.
* https://commons.apache.org/proper/commons-codec/userguide.html
*/
public enum MdmMatcherEnum {
CAVERPHONE1(new HapiStringMatcher(new PhoneticEncoderMatcher(PhoneticEncoderEnum.CAVERPHONE1))),
CAVERPHONE2(new HapiStringMatcher(new PhoneticEncoderMatcher(PhoneticEncoderEnum.CAVERPHONE2))),
COLOGNE(new HapiStringMatcher(new PhoneticEncoderMatcher(PhoneticEncoderEnum.COLOGNE))),
@ -48,22 +50,23 @@ public enum MdmMatcherEnum {
IDENTIFIER(new IdentifierMatcher());
private final IMdmFieldMatcher myEmpiFieldMatcher;
private final IMdmFieldMatcher myMdmFieldMatcher;
MdmMatcherEnum(IMdmFieldMatcher theEmpiFieldMatcher) {
myEmpiFieldMatcher = theEmpiFieldMatcher;
MdmMatcherEnum(IMdmFieldMatcher theMdmFieldMatcher) {
myMdmFieldMatcher = theMdmFieldMatcher;
}
/**
* Determines whether two FHIR elements match according using the provided IEmpiFieldMatcher
* Determines whether two FHIR elements match according using the provided {@link IMdmFieldMatcher}
*
* @param theFhirContext
* @param theLeftBase left FHIR element to compare
* @param theRightBase right FHIR element to compare
* @param theExact used by String matchers. If "false" then the string is normalized (case, accents) before comparing. If "true" then an exact string comparison is performed.
* @param theLeftBase left FHIR element to compare
* @param theRightBase right FHIR element to compare
* @param theExact used by String matchers. If "false" then the string is normalized (case, accents) before comparing. If "true" then an exact string comparison is performed.
* @param theIdentifierSystem used optionally by the IDENTIFIER matcher, when present, only matches the identifiers if they belong to this system.
* @return
*/
public boolean match(FhirContext theFhirContext, IBase theLeftBase, IBase theRightBase, boolean theExact, String theIdentifierSystem) {
return myEmpiFieldMatcher.matches(theFhirContext, theLeftBase, theRightBase, theExact, theIdentifierSystem);
return myMdmFieldMatcher.matches(theFhirContext, theLeftBase, theRightBase, theExact, theIdentifierSystem);
}
}

View File

@ -32,20 +32,21 @@ import org.hl7.fhir.instance.model.api.IBase;
import javax.annotation.Nullable;
public enum MdmSimilarityEnum {
JARO_WINKLER(new HapiStringSimilarity(new JaroWinkler())),
COSINE(new HapiStringSimilarity(new Cosine())),
JACCARD(new HapiStringSimilarity(new Jaccard())),
LEVENSCHTEIN(new HapiStringSimilarity(new NormalizedLevenshtein())),
SORENSEN_DICE(new HapiStringSimilarity(new SorensenDice()));
private final IMdmFieldSimilarity myEmpiFieldSimilarity;
private final IMdmFieldSimilarity myMdmFieldSimilarity;
MdmSimilarityEnum(IMdmFieldSimilarity theEmpiFieldSimilarity) {
myEmpiFieldSimilarity = theEmpiFieldSimilarity;
MdmSimilarityEnum(IMdmFieldSimilarity theMdmFieldSimilarity) {
myMdmFieldSimilarity = theMdmFieldSimilarity;
}
public MdmMatchEvaluation match(FhirContext theFhirContext, IBase theLeftBase, IBase theRightBase, boolean theExact, @Nullable Double theThreshold) {
return matchBySimilarity(myEmpiFieldSimilarity, theFhirContext, theLeftBase, theRightBase, theExact, theThreshold);
return matchBySimilarity(myMdmFieldSimilarity, theFhirContext, theLeftBase, theRightBase, theExact, theThreshold);
}
private MdmMatchEvaluation matchBySimilarity(IMdmFieldSimilarity theSimilarity, FhirContext theFhirContext, IBase theLeftBase, IBase theRightBase, boolean theExact, Double theThreshold) {

View File

@ -38,6 +38,7 @@ import static ca.uhn.fhir.mdm.api.MdmConstants.ALL_RESOURCE_SEARCH_PARAM_TYPE;
* This class is responsible for performing matching between raw-typed values of a left record and a right record.
*/
public class MdmResourceFieldMatcher {
private final FhirContext myFhirContext;
private final MdmFieldMatchJson myMdmFieldMatchJson;
private final String myResourceType;
@ -55,7 +56,8 @@ public class MdmResourceFieldMatcher {
}
/**
* Compares two {@link IBaseResource}s and determines if they match, using the algorithm defined in this object's EmpiFieldMatchJson.
* Compares two {@link IBaseResource}s and determines if they match, using the algorithm defined in this object's
* {@link MdmFieldMatchJson}.
*
* In this implementation, it determines whether a given field matches between two resources. Internally this is evaluated using FhirPath. If any of the elements of theLeftResource
* match any of the elements of theRightResource, will return true. Otherwise, false.

View File

@ -53,7 +53,7 @@ public final class AssuranceLevelUtil {
case POSSIBLE_DUPLICATE:
case NO_MATCH:
default:
throw new InvalidRequestException("An AUTO EMPI Link may not have a match result of " + theMatchResult);
throw new InvalidRequestException("An AUTO MDM Link may not have a match result of " + theMatchResult);
}
}
@ -66,7 +66,7 @@ public final class AssuranceLevelUtil {
case POSSIBLE_DUPLICATE:
case POSSIBLE_MATCH:
default:
throw new InvalidRequestException("A MANUAL EMPI Link may not have a match result of " + theMatchResult);
throw new InvalidRequestException("A MANUAL MDM Link may not have a match result of " + theMatchResult);
}
}
}

View File

@ -62,6 +62,7 @@ import static ca.uhn.fhir.context.FhirVersionEnum.R4;
@Service
public class GoldenResourceHelper {
private static final Logger ourLog = Logs.getMdmTroubleshootingLog();
private static final String FIELD_NAME_IDENTIFIER = "identifier";
@ -100,7 +101,7 @@ public class GoldenResourceHelper {
addHapiEidIfNoExternalEidIsPresent(newSourceResource, sourceResourceIdentifier, theIncomingResource);
MdmUtil.setEmpiManaged(newSourceResource);
MdmUtil.setMdmManaged(newSourceResource);
MdmUtil.setGoldenResource(newSourceResource);
return (T) newSourceResource;
@ -192,12 +193,11 @@ public class GoldenResourceHelper {
for (IBase base : sourceResourceIdentifiers) {
Optional<IPrimitiveType> system = fhirPath.evaluateFirst(base, "system", IPrimitiveType.class);
if (system.isPresent()) {
String empiSystem = myMdmSettings.getMdmRules().getEnterpriseEIDSystem();
String mdmSystem = myMdmSettings.getMdmRules().getEnterpriseEIDSystem();
String baseSystem = system.get().getValueAsString();
if (Objects.equals(baseSystem, empiSystem)) {
if (Objects.equals(baseSystem, mdmSystem)) {
cloneEidIntoResource(theSourceResourceIdentifier, base, theNewSourceResource);
} else if (ourLog.isDebugEnabled()) {
ourLog.debug(String.format("System %s differs from system in the EMPI rules %s", baseSystem, empiSystem));
ourLog.debug("System {} differs from system in the MDM rules {}", baseSystem, mdmSystem);
}
} else {
ourLog.debug("System is missing, skipping");
@ -257,12 +257,10 @@ public class GoldenResourceHelper {
for (IBase base : sourceResourceIdentifiers) {
Optional<IPrimitiveType> system = fhirPath.evaluateFirst(base, "system", IPrimitiveType.class);
if (system.isPresent()) {
String empiSystem = myMdmSettings.getMdmRules().getEnterpriseEIDSystem();
String mdmSystem = myMdmSettings.getMdmRules().getEnterpriseEIDSystem();
String baseSystem = system.get().getValueAsString();
if (Objects.equals(baseSystem, empiSystem)) {
if (ourLog.isDebugEnabled()) {
ourLog.debug(String.format("Found EID confirming to EMPI rules %s. It should not be copied, skipping", baseSystem));
}
if (Objects.equals(baseSystem, mdmSystem)) {
ourLog.debug(String.format("Found EID confirming to MDM rules {}. It should not be copied, skipping", baseSystem));
continue;
}
}

View File

@ -29,23 +29,26 @@ import javax.annotation.Nonnull;
import java.util.Optional;
public final class MdmUtil {
private MdmUtil() {}
private MdmUtil() {
}
/**
* If the resource is tagged as not managed by empi, return false. Otherwise true.
* @param theBaseResource The Patient/Practitioner that is potentially managed by EMPI.
* @return A boolean indicating whether EMPI should manage this resource.
* If the resource is tagged as not managed by MDM, return false. Otherwise true.
*
* @param theBaseResource The FHIR resource that is potentially managed by MDM.
* @return A boolean indicating whether MDM can manage this resource.
*/
public static boolean isMdmAllowed(IBaseResource theBaseResource) {
return theBaseResource.getMeta().getTag(MdmConstants.SYSTEM_MDM_MANAGED, MdmConstants.CODE_NO_MDM_MANAGED) == null;
}
/**
* Checks for the presence of the EMPI-managed tag, indicating the EMPI system has ownership
* Checks for the presence of the MDM-managed tag, indicating the MDM system has ownership
* of this Person's links.
*
* @param theBaseResource the resource to check.
* @return a boolean indicating whether or not EMPI manages this Person.
* @return a boolean indicating whether or not MDM manages this FHIR resource.
*/
public static boolean isMdmManaged(IBaseResource theBaseResource) {
return resourceHasTag(theBaseResource, MdmConstants.SYSTEM_MDM_MANAGED, MdmConstants.CODE_HAPI_MDM_MANAGED);
@ -85,14 +88,13 @@ public final class MdmUtil {
/**
* Sets the EMPI-managed tag, indicating the EMPI system has ownership of this
* Resource. No changes are made if resource is already maanged by EMPI.
* Sets the MDM-managed tag, indicating the MDM system has ownership of this
* Resource. No changes are made if resource is already maanged by MDM.
*
* @param theBaseResource resource to set the tag for
* @return
* Returns resource with the tag set.
* @return Returns resource with the tag set.
*/
public static IBaseResource setEmpiManaged(IBaseResource theBaseResource) {
public static IBaseResource setMdmManaged(IBaseResource theBaseResource) {
return setTagOnResource(theBaseResource, MdmConstants.SYSTEM_MDM_MANAGED, MdmConstants.CODE_HAPI_MDM_MANAGED, MdmConstants.DISPLAY_HAPI_MDM_MANAGED);
}
@ -125,10 +127,4 @@ public final class MdmUtil {
}
return theGoldenResource;
}
public static boolean isEmpiManagedPerson(FhirContext theFhirContext, IBaseResource theResource) {
String resourceType = theFhirContext.getResourceType(theResource);
return "Person".equals(resourceType) && isMdmManaged(theResource);
}
}

View File

@ -33,13 +33,13 @@ import org.springframework.stereotype.Service;
public class MessageHelper {
@Autowired
private final IMdmSettings myEmpiSettings;
private final IMdmSettings myMdmSettings;
@Autowired
private final FhirContext myFhirContext;
public MessageHelper(IMdmSettings theEmpiSettings, FhirContext theFhirContext) {
myEmpiSettings = theEmpiSettings;
public MessageHelper(IMdmSettings theMdmSettings, FhirContext theFhirContext) {
myMdmSettings = theMdmSettings;
myFhirContext = theFhirContext;
}
@ -55,7 +55,7 @@ public class MessageHelper {
public String getMessageForUnsupportedResource(String theName, String theResourceType) {
return String.format("Only %s resources can be merged. The %s points to a %s",
myEmpiSettings.getSupportedMdmTypes(), theName, theResourceType);
myMdmSettings.getSupportedMdmTypes(), theName, theResourceType);
}
public String getMessageForUnsupportedMatchResult() {
@ -64,12 +64,12 @@ public class MessageHelper {
public String getMessageForUnsupportedFirstArgumentTypeInUpdate(String goldenRecordType) {
return "First argument to " + ProviderConstants.MDM_UPDATE_LINK + " must be a "
+ myEmpiSettings.getSupportedMdmTypes() + ". Was " + goldenRecordType;
+ myMdmSettings.getSupportedMdmTypes() + ". Was " + goldenRecordType;
}
public String getMessageForUnsupportedSecondArgumentTypeInUpdate(String theGoldenRecordType) {
return "First argument to " + ProviderConstants.MDM_UPDATE_LINK + " must be a "
+ myEmpiSettings.getSupportedMdmTypes() + ". Was " + theGoldenRecordType;
+ myMdmSettings.getSupportedMdmTypes() + ". Was " + theGoldenRecordType;
}
public String getMessageForArgumentTypeMismatchInUpdate(String theGoldenRecordType, String theTargetType) {
@ -79,7 +79,7 @@ public class MessageHelper {
public String getMessageForUnsupportedTarget() {
return "The target is marked with the " + MdmConstants.CODE_NO_MDM_MANAGED
+ " tag which means it may not be EMPI linked.";
+ " tag which means it may not be MDM linked.";
}
public String getMessageForNoLink(IAnyResource theGoldenRecord, IAnyResource theTarget) {

View File

@ -27,12 +27,14 @@ public class MdmRuleValidatorTest extends BaseR4Test {
when(mySearchParamRetriever.getActiveSearchParam("Practitioner", "identifier")).thenReturn(mock(RuntimeSearchParam.class));
when(mySearchParamRetriever.getActiveSearchParam("Medication", "identifier")).thenReturn(mock(RuntimeSearchParam.class));
when(mySearchParamRetriever.getActiveSearchParam("AllergyIntolerance", "identifier")).thenReturn(null);
when(mySearchParamRetriever.getActiveSearchParam("Organization", "identifier")).thenReturn(mock(RuntimeSearchParam.class));
when(mySearchParamRetriever.getActiveSearchParam("Organization", "active")).thenReturn(mock(RuntimeSearchParam.class));
}
@Test
public void testValidate() throws IOException {
try {
setEmpiRuleJson("bad-rules-bad-url.json");
setMdmRuleJson("bad-rules-bad-url.json");
fail();
} catch (ConfigurationException e){
assertThat(e.getMessage(), is("Enterprise Identifier System (eidSystem) must be a valid URI"));
@ -42,7 +44,7 @@ public class MdmRuleValidatorTest extends BaseR4Test {
@Test
public void testNonExistentMatchField() throws IOException {
try {
setEmpiRuleJson("bad-rules-missing-name.json");
setMdmRuleJson("bad-rules-missing-name.json");
fail();
} catch (ConfigurationException e) {
assertThat(e.getMessage(), is("There is no matchField with name foo"));
@ -52,7 +54,7 @@ public class MdmRuleValidatorTest extends BaseR4Test {
@Test
public void testSimilarityHasThreshold() throws IOException {
try {
setEmpiRuleJson("bad-rules-missing-threshold.json");
setMdmRuleJson("bad-rules-missing-threshold.json");
fail();
} catch (ConfigurationException e) {
assertThat(e.getMessage(), is("MatchField given-name similarity COSINE requires a matchThreshold"));
@ -62,7 +64,7 @@ public class MdmRuleValidatorTest extends BaseR4Test {
@Test
public void testMatcherBadPath() throws IOException {
try {
setEmpiRuleJson("bad-rules-bad-path.json");
setMdmRuleJson("bad-rules-bad-path.json");
fail();
} catch (ConfigurationException e) {
assertThat(e.getMessage(), startsWith("MatchField given-name resourceType Patient has invalid path 'name.first'. Unknown child name 'first' in element HumanName"));
@ -72,7 +74,7 @@ public class MdmRuleValidatorTest extends BaseR4Test {
@Test
public void testMatcherBadSearchParam() throws IOException {
try {
setEmpiRuleJson("bad-rules-bad-searchparam.json");
setMdmRuleJson("bad-rules-bad-searchparam.json");
fail();
} catch (ConfigurationException e) {
assertThat(e.getMessage(), startsWith("Error in candidateSearchParams: Patient does not have a search parameter called 'foo'"));
@ -82,7 +84,7 @@ public class MdmRuleValidatorTest extends BaseR4Test {
@Test
public void testMatcherBadFilter() throws IOException {
try {
setEmpiRuleJson("bad-rules-bad-filter.json");
setMdmRuleJson("bad-rules-bad-filter.json");
fail();
} catch (ConfigurationException e) {
assertThat(e.getMessage(), startsWith("Error in candidateFilterSearchParams: Patient does not have a search parameter called 'foo'"));
@ -92,7 +94,7 @@ public class MdmRuleValidatorTest extends BaseR4Test {
@Test
public void testInvalidMdmType() throws IOException {
try {
setEmpiRuleJson("bad-rules-missing-mdm-types.json");
setMdmRuleJson("bad-rules-missing-mdm-types.json");
fail();
} catch (ConfigurationException e) {
assertThat(e.getMessage(), startsWith("mdmTypes must be set to a list of resource types."));
@ -102,15 +104,24 @@ public class MdmRuleValidatorTest extends BaseR4Test {
@Test
public void testMatcherduplicateName() throws IOException {
try {
setEmpiRuleJson("bad-rules-duplicate-name.json");
setMdmRuleJson("bad-rules-duplicate-name.json");
fail();
} catch (ConfigurationException e) {
assertThat(e.getMessage(), startsWith("Two MatchFields have the same name 'foo'"));
}
}
@Test
public void testInvalidPath() throws IOException {
try {
setMdmRuleJson("bad-rules-invalid-path.json");
fail();
} catch (ConfigurationException e) {
assertThat(e.getMessage(), startsWith("MatchField name-prefix resourceType Organization has invalid path"));
}
}
private void setEmpiRuleJson(String theTheS) throws IOException {
private void setMdmRuleJson(String theTheS) throws IOException {
MdmRuleValidator mdmRuleValidator = new MdmRuleValidator(ourFhirContext, mySearchParamRetriever);
MdmSettings mdmSettings = new MdmSettings(mdmRuleValidator);
DefaultResourceLoader resourceLoader = new DefaultResourceLoader();

View File

@ -34,31 +34,31 @@ public class AssuranceLevelUtilTest {
AssuranceLevelUtil.getAssuranceLevel(NO_MATCH, AUTO);
fail();
} catch (InvalidRequestException e) {
assertEquals("An AUTO EMPI Link may not have a match result of NO_MATCH", e.getMessage());
assertEquals("An AUTO MDM Link may not have a match result of NO_MATCH", e.getMessage());
}
try {
AssuranceLevelUtil.getAssuranceLevel(POSSIBLE_DUPLICATE, AUTO);
fail();
} catch (InvalidRequestException e) {
assertEquals("An AUTO EMPI Link may not have a match result of POSSIBLE_DUPLICATE", e.getMessage());
assertEquals("An AUTO MDM Link may not have a match result of POSSIBLE_DUPLICATE", e.getMessage());
}
try {
AssuranceLevelUtil.getAssuranceLevel(NO_MATCH, MANUAL);
fail();
} catch (InvalidRequestException e) {
assertEquals("A MANUAL EMPI Link may not have a match result of NO_MATCH", e.getMessage());
assertEquals("A MANUAL MDM Link may not have a match result of NO_MATCH", e.getMessage());
}
try {
AssuranceLevelUtil.getAssuranceLevel(POSSIBLE_MATCH, MANUAL);
fail();
} catch (InvalidRequestException e) {
assertEquals("A MANUAL EMPI Link may not have a match result of POSSIBLE_MATCH", e.getMessage());
assertEquals("A MANUAL MDM Link may not have a match result of POSSIBLE_MATCH", e.getMessage());
}
try {
AssuranceLevelUtil.getAssuranceLevel(POSSIBLE_DUPLICATE, MANUAL);
fail();
} catch (InvalidRequestException e) {
assertEquals("A MANUAL EMPI Link may not have a match result of POSSIBLE_DUPLICATE", e.getMessage());
assertEquals("A MANUAL MDM Link may not have a match result of POSSIBLE_DUPLICATE", e.getMessage());
}
}

View File

@ -0,0 +1,17 @@
{
"version": "1",
"mdmTypes": ["Organization"],
"candidateSearchParams": [],
"candidateFilterSearchParams": [],
"matchFields": [
{
"name": "name-prefix",
"resourceType": "Organization",
"resourcePath": "name.prefix",
"matcher": {
"algorithm": "STRING"
}
}
],
"matchResultMap": {}
}

View File

@ -136,8 +136,8 @@ public abstract class BaseResourceMessage implements IResourceMessage, IModelJso
* Adds a transaction ID to this message. This ID can be used for many purposes. For example, performing tracing
* across asynchronous hooks, tying data together, or downstream logging purposes.
*
* One current internal implementation uses this field to tie back EMPI processing results (which are asynchronous)
* to the original transaction log that caused the EMPI processing to occur.
* One current internal implementation uses this field to tie back MDM processing results (which are asynchronous)
* to the original transaction log that caused the MDM processing to occur.
*
* @param theTransactionId An ID representing a transaction of relevance to this message.
*/