Review updates

This commit is contained in:
Nick 2020-12-07 18:03:56 -05:00
parent af461cd343
commit fd90eb699f
71 changed files with 460 additions and 688 deletions

View File

@ -4,7 +4,7 @@
A Master Data Management (MDM) module allows for links to be created and maintained among FHIR resources. These links indicate the fact that different FHIR resources are known or believed to refer to the same actual (real world) resource. The links are created and updated using different combinations of automatic and manual linking.
The real-world resource is referred to as the Golden Resource in this context. The resource believed to be a duplicate is said to be a target resource.
The real-world resource is referred to as the Golden Resource in this context. The resource believed to be a duplicate is said to be a source resource.
## Working Example
@ -16,10 +16,9 @@ To get up and running with HAPI MDM, either enable it using the `hapi.properties
Once MDM is enabled, the next thing you will want to do is configure your [MDM Rules](/hapi-fhir/docs/server_jpa_mdm/mdm_rules.html)
HAPI MDM watches for incoming target resources and automatically links them to the appropriate Golden Resources based on these rules. For example, if the rules indicate that any two patients with the same SSN, birthdate and first and last name are the same patient, then two different Patient resources with matching values for these attributes will automatically be linked to the same Golden Patient resource. If no existing resources match the incoming Patient, then a new Golden Patient resource will be created and linked to the incoming Patient.
HAPI MDM watches for incoming source resources and automatically links them to the appropriate Golden Resources based on these rules. For example, if the rules indicate that any two patients with the same SSN, birthdate and first and last name are the same patient, then two different Patient resources with matching values for these attributes will automatically be linked to the same Golden Patient resource. If no existing resources match the incoming Patient, then a new Golden Patient resource will be created and linked to the incoming Patient.
Based on how well two patients match, the MDM Rules may link the Patient to the Golden Patient as a MATCH or a
POSSIBLE_MATCH. In the case of a POSSIBLE_MATCH, a user will need to later use [MDM Operations](/hapi-fhir/docs/server_jpa_mdm/mdm_operations.html) to either confirm the link as a MATCH, or mark the link as a NO_MATCH in which case HAPI MDM will create a new Person for them.
Based on how well two patients match, the MDM Rules may link the Patient to the Golden Patient as a MATCH or a POSSIBLE_MATCH. In the case of a POSSIBLE_MATCH, a user will need to later use [MDM Operations](/hapi-fhir/docs/server_jpa_mdm/mdm_operations.html) to either confirm the link as a MATCH, or mark the link as a NO_MATCH in which case HAPI MDM will create a new Golden Resource Patient record for them.
Another thing that can happen in the linking process is HAPI MDM can determine that two Patients resources may be duplicates. In this case, it marks them as POSSIBLE_DUPLICATE and the user can use [MDM Operations](/hapi-fhir/docs/server_jpa_mdm/mdm_operations.html) to either merge the two Patients or mark them as NO_MATCH in which case HAPI MDM will know not to mark them as possible duplicates in the future.

View File

@ -10,75 +10,57 @@ Because HAPI MDM is implemented on the HAPI JPA Server, it uses the FHIR model t
There are several resources that are used:
* Target resource - Represents the record in being matched. For example, it can be a Patient resource who receives healthcare services and that should be mapped to a master record.
* Golden resource - Represents a master record that the target record should point to. For example, it can be a real-world patient Patient resource that multiple duplicate Patient resources point to.
* Source resource - Represents the record in being matched. For example, it can be a Patient resource who receives healthcare services and that should be mapped to a master record.
* Golden Resource - Represents a master record that the source record should point to. For example, it can be a real-world Patient resource that multiple duplicate Patient resources point to.
# Automatic Linking
With MDM enabled, the default behavior of the MDM is to create a new Golden record for every target record that is created such that there is a 1:1 relationship between them. Any relinking is then expected to be done manually via the [MDM Operations](/hapi-fhir/docs/server_jpa_mdm/mdm_operations.html).
With MDM enabled, the default behavior of the MDM is to create a new Golden Record for every source record that is created such that there is a 1:1 relationship between them. Any relinking is then expected to be done manually via the [MDM Operations](/hapi-fhir/docs/server_jpa_mdm/mdm_operations.html).
In a typical configuration it is often desirable to have links created automatically using matching rules. For example, you might decide that if a Patient shares the same name, gender, and date of birth as another Patient, you have at least a little confidence that they are the same Patient.
This automatic linking is done via configurable matching rules that create links between Target record and Golden Record. Based on the strength of the match configured in these rules, the link will be set to either POSSIBLE_MATCH or MATCH.
This automatic linking is done via configurable matching rules that create links between source record and Golden Record. Based on the strength 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 `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 is created for this target resource. If in the future the target resource is updated to contain attributes the MDM system does concern itself with, it will be processed at that time.
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 `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 is created for this source resource. If in the future the source resource is updated to contain attributes the MDM system does concern itself with, it will be processed at that time.
## Design
Below are some simplifying principles HAPI MDM follows to reduce complexity and ensure data integrity.
1. When MDM is enabled on a HAPI FHIR server, any Golden Resource in the repository that has the "hapi-mdm" tag is considered read-only by the FHIR endpoint. These Golden Resources are managed exclusively by HAPI MDM. Users can only change them via [MDM Operations](/hapi-fhir/docs/server_jpa_mdm/mdm_operations.html). In most cases, users will indirectly change them by creating and updating the corresponding target resources.
1. When MDM is enabled on a HAPI FHIR server, any Golden Resource in the repository that has the "hapi-mdm" tag is considered read-only by the FHIR endpoint. These Golden Resources are managed exclusively by HAPI MDM. Users can only change them via [MDM Operations](/hapi-fhir/docs/server_jpa_mdm/mdm_operations.html). In most cases, users will indirectly change them by creating and updating the corresponding source resources.
1. Every target resource in the system has a MATCH link to at most one Golden resource.
1. Every source resource in the system has a MATCH link to at most one Golden Resource.
1. The only target resources in the system that do not have a MATCH link are those that have the 'NO-MDM' tag or those that have POSSIBLE_MATCH links pending review.
1. The only source resources in the system that do not have a MATCH link are those that have the 'NO-MDM' tag or those that have POSSIBLE_MATCH links pending review.
1. The HAPI MDM rules define a single identifier system that holds the external enterprise id ("EID"). If a target resource has an external EID, then the Golden resource it links to always has the same EID. If a target resource has no EID when it arrives, a unique UUID will be assigned as that target resource's EID.
1. The HAPI MDM rules define a single identifier system that holds the external enterprise id ("EID"). If a source resource has an external EID, then the Golden Resource it links to always has the same EID. If a source resource has no EID when it arrives, a unique UUID will be assigned as that source resource's EID.
1. A Golden Resource can have both an internal EID (auto-created by HAPI), and an external EID (provided by an
external system).
1. Two different Golden resources cannot have the same EID.
1. Two different Golden Resources cannot have the same EID.
1. Target resources are only ever compared to Golden resources via this EID.
1. Source resources are only ever compared to Golden Resources via this EID.
## Links
1. HAPI MDM manages mdm-link records ("links") that link a target resource to a Golden resource. When these are
created/updated by matching rules, the links are marked as AUTO. When these links are changed manually, they are marked as MANUAL.
1. HAPI MDM manages mdm-link records ("links") that link a source resource to a Golden Resource. When these are created/updated by matching rules, the links are marked as AUTO. When these links are changed manually, they are marked as MANUAL.
1. Once a link has been manually assigned as NO_MATCH or MATCH, the system will not change it.
1. When a new target resource is created/updated it is then compared to all other target resources of the same type in the repository. The outcome of each of these comparisons is either NO_MATCH, POSSIBLE_MATCH or MATCH.
1. When a new source resource is created/updated it is then compared to all other source resources of the same type in the repository. The outcome of each of these comparisons is either NO_MATCH, POSSIBLE_MATCH or MATCH.
1. HAPI MDM stores these extra link details in a table called `MPI_LINK`.
<!---
1. Whenever a MATCH link is established between a Patient resource and a Golden Patient resource, that Patient is always
added to that Golden Patient resource links. All MATCH links have corresponding Golden Patient resource links and all
Golden Patient resource links have corresponding MATCH mdm-link records. You can think of the fields of the mdm-link
records as extra meta-data associated with each Person.link.target.
1. Each record in the `MPI_LINK` table corresponds to a `link.target` entry on a Person resource unless it is a NO_MATCH record. HAPI MDM uses the following convention for the Person.link.assurance level:
1. Level 1: POSSIBLE_MATCH
1. Level 2: AUTO MATCH
1. Level 3: MANUAL MATCH
1. Level 4: GOLDEN RECORD
-->
### Possible rule match outcomes:
When a new target resource is compared with all other resources of the same type in the repository, there are four possible outcomes:
When a new source resource is compared with all other resources of the same type in the repository, there are four possible outcomes:
<!---
All fields are copied from the Patient to the Golden Patient.
-->
* CASE 1: No MATCH and no POSSIBLE_MATCH outcomes -> a new Golden Resource is created and linked to that source resource as MATCH. If the incoming resource has an EID, it is copied to the Golden Resource. Otherwise a new UUID is generated and used as the internal EID.
* CASE 1: No MATCH and no POSSIBLE_MATCH outcomes -> a new Golden resource is created and linked to that target resource as MATCH. If the incoming resource has an EID, it is copied to the Golden resource. Otherwise a new UUID is generated and used as the internal EID.
* CASE 2: All of the MATCH source resources are already linked to the same Golden Resource -> a new Link is created between the new source resource and that Golden Resource and is set to MATCH.
* CASE 2: All of the MATCH target resources are already linked to the same Golden Resource -> a new Link is created between the new target resource and that Golden Resource and is set to MATCH.
* CASE 3: The MATCH target resources link to more than one Golden resource -> Mark all links as POSSIBLE_MATCH. All other Golden resources are marked as POSSIBLE_DUPLICATE of this first Golden Resource. These duplicates are manually reviewed later and either merged or marked as NO_MATCH and the system will no longer consider them as a POSSIBLE_DUPLICATE going forward. POSSIBLE_DUPLICATE is the only link type that can have a Golden Resource as both the source and target of the link.
* CASE 3: The MATCH source resources link to more than one Golden Resource -> Mark all links as POSSIBLE_MATCH. All other Golden Resources are marked as POSSIBLE_DUPLICATE of this first Golden Resource. These duplicates are manually reviewed later and either merged or marked as NO_MATCH and the system will no longer consider them as a POSSIBLE_DUPLICATE going forward. POSSIBLE_DUPLICATE is the only link type that can have a Golden Resource as both the source and target of the link.
* CASE 4: Only POSSIBLE_MATCH outcomes -> In this case, new POSSIBLE_MATCH links are created and await manual reassignment to either NO_MATCH or MATCH.

View File

@ -1,6 +1,6 @@
# MDM Enterprise Identifiers
An Enterprise Identifier (EID) is a unique identifier that can be attached to target resources. Each implementation is expected to use exactly one EID system for incoming resources, defined in the MDM Rules file. If a target resource with a valid EID is submitted, that EID will be copied over to the Golden resource that was matched. In the case that the incoming target resource had no EID assigned, an internal EID will be created for it. There are thus two classes of EID:
An Enterprise Identifier (EID) is a unique identifier that can be attached to source resources. Each implementation is expected to use exactly one EID system for incoming resources, defined in the MDM Rules file. If a source resource with a valid EID is submitted, that EID will be copied over to the Golden Resource that was matched. In the case that the incoming source resource had no EID assigned, an internal EID will be created for it. There are thus two classes of EID:
* Internal EIDs, created by HAPI-MDM, and
* External EIDs, provided by the submitted resources.
@ -15,7 +15,7 @@ contains two EID related settings. Both are enabled by default.
## MDM EID Scenarios
MDM EID management follows a complex set of rules to link related target records via their Enterprise Id. The following diagrams outline how EIDs are replicated from Patient resources to their linked Golden Patient resources under various scenarios according to the values of the EID Settings.
MDM EID management follows a complex set of rules to link related source records via their Enterprise Id. The following diagrams outline how EIDs are replicated from Patient resources to their linked Golden Patient resources under various scenarios according to the values of the EID Settings.
## MDM EID Create Scenarios

View File

@ -31,7 +31,7 @@ Use the `$mdm-query-links` operation to view MDM links. The results returned are
<td>String</td>
<td>0..1</td>
<td>
The id of the target resource (e.g. Patient resource).
The id of the source resource (e.g. Patient resource).
</td>
</tr>
<tr>
@ -82,9 +82,9 @@ This operation returns a `Parameters` resource that looks like the following:
"name": "link",
"part": [ {
"name": "goldenResourceId",
"valueString": "Person/123"
"valueString": "Patient/123"
}, {
"name": "targetResourceId",
"name": "sourceResourceId",
"valueString": "Patient/456"
}, {
"name": "matchResult",
@ -129,10 +129,10 @@ This operation returns `Parameters` similar to `$mdm-query-links`:
"name": "link",
"part": [ {
"name": "goldenResourceId",
"valueString": "Person/123"
"valueString": "Patient/123"
}, {
"name": "targetResourceId",
"valueString": "Person/456"
"name": "sourceResourceId",
"valueString": "Patient/456"
}, {
"name": "matchResult",
"valueString": "POSSIBLE_DUPLICATE"
@ -164,7 +164,7 @@ This operation takes the following parameters:
<td>String</td>
<td>1..1</td>
<td>
The id of the Golden resource.
The id of the Golden Resource.
</td>
</tr>
<tr>
@ -172,7 +172,7 @@ This operation takes the following parameters:
<td>String</td>
<td>1..1</td>
<td>
The id of the Person that personId has a possible duplicate link to.
The id of the source resource that has a possible duplicate link to.
</td>
</tr>
</tbody>
@ -232,7 +232,7 @@ Use the `$mdm-update-link` operation to change the `matchResult` update of an md
<td>String</td>
<td>1..1</td>
<td>
The id of the Golden resource.
The id of the Golden Resource.
</td>
</tr>
<tr>
@ -240,7 +240,7 @@ Use the `$mdm-update-link` operation to change the `matchResult` update of an md
<td>String</td>
<td>1..1</td>
<td>
The id of the target resource.
The id of the source resource.
</td>
</tr>
<tr>
@ -284,14 +284,9 @@ Any supported MDM type can be used. The following request body shows how to upda
The operation returns the updated Golden Resource. For the query above `Patient` resource will be returned. Note that this is the only way to modify MDM-managed Golden Resources.
## Merge Persons
## Merge Golden Resources
<!---
In most cases, fields will be merged (e.g. names, identifiers, and links will be the union of two).
However when there is a conflict (e.g. birthday), fields in the toPerson will take precedence over fields in the fromPerson
-->
The `$mdm-merge-golden-resources` operation can be used to merge one Golden resource with another. When
doing this, you will need to decide which resource to merge from and which one to merge to.
The `$mdm-merge-golden-resources` operation can be used to merge one Golden Resource with another. When doing this, you will need to decide which resource to merge from and which one to merge to.
After the merge is complete, `fromGoldenResourceId` will be deactivated by assigning a metadata tag `REDIRECTED`.
@ -451,7 +446,7 @@ MDM will respond with the appropriate resource bundle.
## Clearing MDM Links
The `$mdm-clear` operation is used to batch-delete MDM links and related persons from the database. This operation is meant to be used during the rules-tuning phase of the MDM implementation so that you can quickly test your ruleset. It permits the user to reset the state of their MDM system without manual deletion of all related links and golden resources.
The `$mdm-clear` operation is used to batch-delete MDM links and related Golden Resources from the database. This operation is meant to be used during the rules-tuning phase of the MDM implementation so that you can quickly test your ruleset. It permits the user to reset the state of their MDM system without manual deletion of all related links and golden resources.
After the operation is complete, all targeted MDM links are removed from the system, and their related Golden Resources are deleted and expunged from the server.

View File

@ -2,8 +2,7 @@
HAPI MDM rules are defined in a single json document.
Note that in all the following configuration, valid options for `resourceType` include any supported resource, such as `Organization`, `Patient`, `Practitioner`, and `*`. Use `*` if the
criteria is identical across both resource types and you would like to apply it to all resources.
Note that in all the following configuration, valid options for `resourceType` include any supported resource, such as `Organization`, `Patient`, `Practitioner`, and `*`. Use `*` if the criteria is identical across both resource types and you would like to apply it to all resources.
Here is an example of a full HAPI MDM rules json document:

View File

@ -31,10 +31,10 @@ import org.springframework.stereotype.Repository;
@Repository
public interface IMdmLinkDao extends JpaRepository<MdmLink, Long> {
@Modifying
@Query("DELETE FROM MdmLink f WHERE myPersonPid = :pid OR myTargetPid = :pid")
@Query("DELETE FROM MdmLink f WHERE myGoldenResourcePid = :pid OR mySourcePid = :pid")
int deleteWithAnyReferenceToPid(@Param("pid") Long thePid);
@Modifying
@Query("DELETE FROM MdmLink f WHERE (myPersonPid = :pid OR myTargetPid = :pid) AND myMatchResult <> :matchResult")
@Query("DELETE FROM MdmLink f WHERE (myGoldenResourcePid = :pid OR mySourcePid = :pid) AND myMatchResult <> :matchResult")
int deleteWithAnyReferenceToPidAndMatchResultNot(@Param("pid") Long thePid, @Param("matchResult") MdmMatchResultEnum theMatchResult);
}

View File

@ -54,9 +54,8 @@ public class ResourceTableFKProvider {
retval.add(new ResourceForeignKey("HFJ_SPIDX_TOKEN", "RES_ID"));
retval.add(new ResourceForeignKey("HFJ_SPIDX_URI", "RES_ID"));
retval.add(new ResourceForeignKey("HFJ_SUBSCRIPTION_STATS", "RES_ID"));
retval.add(new ResourceForeignKey("MPI_LINK", "PERSON_PID"));
retval.add(new ResourceForeignKey("MPI_LINK", "GOLDEN_RESOURCE_PID"));
retval.add(new ResourceForeignKey("MPI_LINK", "TARGET_PID"));
retval.add(new ResourceForeignKey("MPI_LINK", "SOURCE_PID"));
retval.add(new ResourceForeignKey("NPM_PACKAGE_VER", "BINARY_RES_ID"));
retval.add(new ResourceForeignKey("NPM_PACKAGE_VER_RES", "BINARY_RES_ID"));
retval.add(new ResourceForeignKey("TRM_CODESYSTEM", "RES_ID"));

View File

@ -45,13 +45,13 @@ import java.util.Date;
@Entity
@Table(name = "MPI_LINK", uniqueConstraints = {
@UniqueConstraint(name = "IDX_EMPI_PERSON_TGT", columnNames = {"PERSON_PID", "TARGET_PID"}),
@UniqueConstraint(name = "IDX_MDM_GOLDEN_RESOURCE_SRC", columnNames = {"GOLDEN_RESOURCE_PID", "SOURCE_PID"}),
})
public class MdmLink {
public static final int VERSION_LENGTH = 16;
private static final int MATCH_RESULT_LENGTH = 16;
private static final int LINK_SOURCE_LENGTH = 16;
public static final int TARGET_TYPE_LENGTH = 40;
public static final int SOURCE_TYPE_LENGTH = 40;
@SequenceGenerator(name = "SEQ_EMPI_LINK_ID", sequenceName = "SEQ_EMPI_LINK_ID")
@GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_EMPI_LINK_ID")
@ -60,27 +60,18 @@ public class MdmLink {
private Long myId;
@ManyToOne(optional = false, fetch = FetchType.LAZY, cascade = {})
@JoinColumn(name = "GOLDEN_RESOURCE_PID", referencedColumnName = "RES_ID", foreignKey = @ForeignKey(name = "FK_EMPI_LINK_GOLDEN_RESOURCE"), insertable=false, updatable=false, nullable=false)
@JoinColumn(name = "GOLDEN_RESOURCE_PID", referencedColumnName = "RES_ID", foreignKey = @ForeignKey(name = "FK_MDM_LINK_GOLDEN_RESOURCE"), insertable=false, updatable=false, nullable=false)
private ResourceTable myGoldenResource;
@Column(name = "GOLDEN_RESOURCE_PID", nullable=false)
private Long myGoldenResourcePid;
@Deprecated
@ManyToOne(optional = false, fetch = FetchType.LAZY, cascade = {})
@JoinColumn(name = "PERSON_PID", referencedColumnName = "RES_ID", foreignKey = @ForeignKey(name = "FK_EMPI_LINK_PERSON"), insertable=false, updatable=false, nullable=false)
private ResourceTable myPerson;
@JoinColumn(name = "SOURCE_PID", referencedColumnName = "RES_ID", foreignKey = @ForeignKey(name = "FK_MDM_LINK_SOURCE"), insertable=false, updatable=false, nullable=false)
private ResourceTable mySource;
@Deprecated
@Column(name = "PERSON_PID", nullable=false)
private Long myPersonPid;
@ManyToOne(optional = false, fetch = FetchType.LAZY, cascade = {})
@JoinColumn(name = "TARGET_PID", referencedColumnName = "RES_ID", foreignKey = @ForeignKey(name = "FK_EMPI_LINK_TARGET"), insertable=false, updatable=false, nullable=false)
private ResourceTable myTarget;
@Column(name = "TARGET_PID", updatable=false, nullable=false)
private Long myTargetPid;
@Column(name = "SOURCE_PID", updatable=false, nullable=false)
private Long mySourcePid;
@Column(name = "MATCH_RESULT", nullable = false)
@Enumerated(EnumType.ORDINAL)
@ -106,8 +97,8 @@ public class MdmLink {
private Boolean myEidMatch;
/** This link created a new person **/
@Column(name = "NEW_PERSON")
private Boolean myHadToCreateNewResource;
@Column(name = "NEW_GOLDEN_RESOURCE")
private Boolean myHadToCreateNewGoldenResource;
@Column(name = "VECTOR")
private Long myVector;
@ -125,8 +116,8 @@ public class MdmLink {
myVersion = theVersion;
}
@Column(name = "TARGET_TYPE", nullable = true, length = TARGET_TYPE_LENGTH)
private String myMdmTargetType;
@Column(name = "SOURCE_TYPE", nullable = true, length = SOURCE_TYPE_LENGTH)
private String myMdmSourceType;
public Long getId() {
return myId;
@ -151,34 +142,27 @@ public class MdmLink {
return myGoldenResourcePid;
}
public MdmLink setPersonPid(Long thePersonPid) {
myPersonPid = thePersonPid;
return this;
}
public MdmLink setGoldenResourcePid(Long theGoldenResourcePid) {
setPersonPid(theGoldenResourcePid);
myGoldenResourcePid = theGoldenResourcePid;
return this;
}
public ResourceTable getTarget() {
return myTarget;
public ResourceTable getSource() {
return mySource;
}
public MdmLink setTarget(ResourceTable theTarget) {
myTarget = theTarget;
myTargetPid = theTarget.getId();
public MdmLink setSource(ResourceTable theSource) {
mySource = theSource;
mySourcePid = theSource.getId();
return this;
}
public Long getTargetPid() {
return myTargetPid;
public Long getSourcePid() {
return mySourcePid;
}
public MdmLink setTargetPid(Long theTargetPid) {
myTargetPid = theTargetPid;
public MdmLink setSourcePid(Long theSourcePid) {
mySourcePid = theSourcePid;
return this;
}
@ -286,17 +270,17 @@ public class MdmLink {
return this;
}
public boolean getHadToCreateNewResource() {
return myHadToCreateNewResource != null && myHadToCreateNewResource;
public boolean getHadToCreateNewGoldenResource() {
return myHadToCreateNewGoldenResource != null && myHadToCreateNewGoldenResource;
}
public MdmLink setHadToCreateNewResource(Boolean theHadToCreateNewResource) {
myHadToCreateNewResource = theHadToCreateNewResource;
public MdmLink setHadToCreateNewGoldenResource(Boolean theHadToCreateNewResource) {
myHadToCreateNewGoldenResource = theHadToCreateNewResource;
return this;
}
public MdmLink setMdmTargetType(String mdmTargetType) {
myMdmTargetType = mdmTargetType;
public MdmLink setMdmSourceType(String mdmSourceType) {
myMdmSourceType = mdmSourceType;
return this;
}
@ -305,19 +289,19 @@ public class MdmLink {
return new ToStringBuilder(this)
.append("myId", myId)
.append("myGoldenResource", myGoldenResourcePid)
.append("myTargetPid", myTargetPid)
.append("myMdmTargetType", myMdmTargetType)
.append("mySourcePid", mySourcePid)
.append("myMdmSourceType", myMdmSourceType)
.append("myMatchResult", myMatchResult)
.append("myLinkSource", myLinkSource)
.append("myEidMatch", myEidMatch)
.append("myHadToCreateNewResource", myHadToCreateNewResource)
.append("myHadToCreateNewResource", myHadToCreateNewGoldenResource)
.append("myScore", myScore)
.append("myRuleCount", myRuleCount)
.toString();
}
public String getMdmTargetType() {
return myMdmTargetType;
public String getMdmSourceType() {
return myMdmSourceType;
}
public Long getRuleCount() {

View File

@ -1,5 +1,25 @@
package ca.uhn.fhir.jpa.search.cache;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2020 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
public enum SearchCacheStatusEnum {
NOT_TRIED,

View File

@ -138,7 +138,7 @@ public class MdmMessageHandler implements MessageHandler {
}
private void handleCreatePatientOrPractitioner(ResourceModifiedMessage theMsg, MdmTransactionContext theMdmTransactionContext) {
myMdmMatchLinkSvc.updateMdmLinksForMdmTarget(getResourceFromPayload(theMsg), theMdmTransactionContext);
myMdmMatchLinkSvc.updateMdmLinksForMdmSource(getResourceFromPayload(theMsg), theMdmTransactionContext);
}
private IAnyResource getResourceFromPayload(ResourceModifiedMessage theMsg) {
@ -146,7 +146,7 @@ public class MdmMessageHandler implements MessageHandler {
}
private void handleUpdatePatientOrPractitioner(ResourceModifiedMessage theMsg, MdmTransactionContext theMdmTransactionContext) {
myMdmMatchLinkSvc.updateMdmLinksForMdmTarget(getResourceFromPayload(theMsg), theMdmTransactionContext);
myMdmMatchLinkSvc.updateMdmLinksForMdmSource(getResourceFromPayload(theMsg), theMdmTransactionContext);
}
private void log(MdmTransactionContext theMdmContext, String theMessage) {

View File

@ -61,7 +61,7 @@ import ca.uhn.fhir.jpa.mdm.svc.candidate.MdmCandidateSearchSvc;
import ca.uhn.fhir.jpa.mdm.svc.candidate.MdmGoldenResourceFindingSvc;
import ca.uhn.fhir.jpa.mdm.svc.candidate.FindCandidateByEidSvc;
import ca.uhn.fhir.jpa.mdm.svc.candidate.FindCandidateByLinkSvc;
import ca.uhn.fhir.jpa.mdm.svc.candidate.FindCandidateByScoreSvc;
import ca.uhn.fhir.jpa.mdm.svc.candidate.FindCandidateByExampleSvc;
import ca.uhn.fhir.rest.server.util.ISearchParamRetriever;
import ca.uhn.fhir.validation.IResourceLoader;
import org.slf4j.Logger;
@ -122,11 +122,6 @@ public class MdmConsumerConfig {
return new MdmSubscriptionLoader();
}
@Bean
MdmSearchParameterLoader mdmSearchParameterLoader() {
return new MdmSearchParameterLoader();
}
@Bean
MdmGoldenResourceFindingSvc mdmGoldenResourceFindingSvc() {
return new MdmGoldenResourceFindingSvc();
@ -143,8 +138,8 @@ public class MdmConsumerConfig {
}
@Bean
FindCandidateByScoreSvc findCandidateByScoreSvc() {
return new FindCandidateByScoreSvc();
FindCandidateByExampleSvc findCandidateByScoreSvc() {
return new FindCandidateByExampleSvc();
}
@Bean

View File

@ -40,8 +40,6 @@ public class MdmLoader {
MdmProviderLoader myMdmProviderLoader;
@Autowired
MdmSubscriptionLoader myMdmSubscriptionLoader;
@Autowired
MdmSearchParameterLoader myMdmSearchParameterLoader;
@EventListener(classes = {ContextRefreshedEvent.class})
// This @Order is here to ensure that MatchingQueueSubscriberLoader has initialized before we initialize this.
@ -57,9 +55,5 @@ public class MdmLoader {
myMdmSubscriptionLoader.daoUpdateMdmSubscriptions();
ourLog.info("MDM subscriptions updated");
//TODO GGG MDM: Do we need these search parameters, or equivalent, anymore? Don't think so... ask @fil512
myMdmSearchParameterLoader.daoUpdateMdmSearchParameters();
ourLog.info("MDM search parameters updated");
}
}

View File

@ -1,117 +0,0 @@
package ca.uhn.fhir.jpa.mdm.config;
/*-
* #%L
* HAPI FHIR JPA Server - Master Data Management
* %%
* Copyright (C) 2014 - 2020 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.mdm.api.MdmConstants;
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.model.Enumerations;
import org.hl7.fhir.r4.model.SearchParameter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class MdmSearchParameterLoader {
public static final String MDM_PERSON_ASSURANCE_SEARCH_PARAMETER_ID = "person-assurance";
public static final String MDM_PERSON_ACTIVE_SEARCH_PARAMETER_ID = "person-active";
@Autowired
public FhirContext myFhirContext;
@Autowired
public DaoRegistry myDaoRegistry;
synchronized public void daoUpdateMdmSearchParameters() {
IBaseResource goldenResourceAssurance;
IBaseResource goldenResourceActive;
switch (myFhirContext.getVersion().getVersion()) {
case DSTU3:
goldenResourceAssurance = buildAssuranceMdmSearchParameterDstu3();
goldenResourceActive = buildActiveMdmSearchParameterDstu3();
break;
case R4:
goldenResourceAssurance = buildAssuranceMdmSearchParameterR4();
goldenResourceActive = buildActiveMdmSearchParameterR4();
break;
default:
throw new ConfigurationException("MDM not supported for FHIR version " + myFhirContext.getVersion().getVersion());
}
IFhirResourceDao<IBaseResource> searchParameterDao = myDaoRegistry.getResourceDao("SearchParameter");
searchParameterDao.update(goldenResourceAssurance);
searchParameterDao.update(goldenResourceActive);
}
private org.hl7.fhir.dstu3.model.SearchParameter buildAssuranceMdmSearchParameterDstu3() {
org.hl7.fhir.dstu3.model.SearchParameter retval = new org.hl7.fhir.dstu3.model.SearchParameter();
retval.setId(MDM_PERSON_ASSURANCE_SEARCH_PARAMETER_ID);
retval.setStatus(org.hl7.fhir.dstu3.model.Enumerations.PublicationStatus.ACTIVE);
retval.getMeta().addTag().setSystem(MdmConstants.SYSTEM_MDM_MANAGED).setCode(MdmConstants.CODE_HAPI_MDM_MANAGED);
retval.setCode("assurance");
retval.addBase("Person");
retval.setType(org.hl7.fhir.dstu3.model.Enumerations.SearchParamType.TOKEN);
retval.setDescription("The assurance level of the link on a Person");
retval.setExpression("Person.link.assurance");
return retval;
}
private SearchParameter buildAssuranceMdmSearchParameterR4() {
SearchParameter retval = new SearchParameter();
retval.setId(MDM_PERSON_ASSURANCE_SEARCH_PARAMETER_ID);
retval.setStatus(Enumerations.PublicationStatus.ACTIVE);
retval.getMeta().addTag().setSystem(MdmConstants.SYSTEM_MDM_MANAGED).setCode(MdmConstants.CODE_HAPI_MDM_MANAGED);
retval.setCode("assurance");
retval.addBase("Person");
retval.setType(Enumerations.SearchParamType.TOKEN);
retval.setDescription("The assurance level of the link on a Person");
retval.setExpression("Person.link.assurance");
return retval;
}
private org.hl7.fhir.dstu3.model.SearchParameter buildActiveMdmSearchParameterDstu3() {
org.hl7.fhir.dstu3.model.SearchParameter retval = new org.hl7.fhir.dstu3.model.SearchParameter();
retval.setId(MDM_PERSON_ACTIVE_SEARCH_PARAMETER_ID);
retval.setStatus(org.hl7.fhir.dstu3.model.Enumerations.PublicationStatus.ACTIVE);
retval.getMeta().addTag().setSystem(MdmConstants.SYSTEM_MDM_MANAGED).setCode(MdmConstants.CODE_HAPI_MDM_MANAGED);
retval.setCode("active");
retval.addBase("Person");
retval.setType(org.hl7.fhir.dstu3.model.Enumerations.SearchParamType.TOKEN);
retval.setDescription("The active status of a Person");
retval.setExpression("Person.active");
return retval;
}
private SearchParameter buildActiveMdmSearchParameterR4() {
SearchParameter retval = new SearchParameter();
retval.setId(MDM_PERSON_ACTIVE_SEARCH_PARAMETER_ID);
retval.setStatus(Enumerations.PublicationStatus.ACTIVE);
retval.getMeta().addTag().setSystem(MdmConstants.SYSTEM_MDM_MANAGED).setCode(MdmConstants.CODE_HAPI_MDM_MANAGED);
retval.setCode("active");
retval.addBase("Person");
retval.setType(Enumerations.SearchParamType.TOKEN);
retval.setDescription("The active status of a Person");
retval.setExpression("Person.active");
return retval;
}
}

View File

@ -60,24 +60,24 @@ public class MdmLinkDaoSvc {
private FhirContext myFhirContext;
@Transactional
public MdmLink createOrUpdateLinkEntity(IBaseResource theGoldenResource, IBaseResource theTargetResource, MdmMatchOutcome theMatchOutcome, MdmLinkSourceEnum theLinkSource, @Nullable MdmTransactionContext theMdmTransactionContext) {
public MdmLink createOrUpdateLinkEntity(IBaseResource theGoldenResource, IBaseResource theSourceResource, MdmMatchOutcome theMatchOutcome, MdmLinkSourceEnum theLinkSource, @Nullable MdmTransactionContext theMdmTransactionContext) {
Long goldenResourcePid = myIdHelperService.getPidOrNull(theGoldenResource);
Long targetResourcePid = myIdHelperService.getPidOrNull(theTargetResource);
Long sourceResourcePid = myIdHelperService.getPidOrNull(theSourceResource);
MdmLink mdmLink = getOrCreateMdmLinkByGoldenResourcePidAndTargetResourcePid(goldenResourcePid, targetResourcePid);
MdmLink mdmLink = getOrCreateMdmLinkByGoldenResourcePidAndSourceResourcePid(goldenResourcePid, sourceResourcePid);
mdmLink.setLinkSource(theLinkSource);
mdmLink.setMatchResult(theMatchOutcome.getMatchResultEnum());
// Preserve these flags for link updates
mdmLink.setEidMatch(theMatchOutcome.isEidMatch() | mdmLink.isEidMatch());
mdmLink.setHadToCreateNewResource(theMatchOutcome.isCreatedNewResource() | mdmLink.getHadToCreateNewResource());
mdmLink.setMdmTargetType(myFhirContext.getResourceType(theTargetResource));
mdmLink.setHadToCreateNewGoldenResource(theMatchOutcome.isCreatedNewResource() | mdmLink.getHadToCreateNewGoldenResource());
mdmLink.setMdmSourceType(myFhirContext.getResourceType(theSourceResource));
if (mdmLink.getScore() != null) {
mdmLink.setScore(Math.max(theMatchOutcome.score, mdmLink.getScore()));
} else {
mdmLink.setScore(theMatchOutcome.score);
}
String message = String.format("Creating MdmLink from %s to %s -> %s", theGoldenResource.getIdElement().toUnqualifiedVersionless(), theTargetResource.getIdElement().toUnqualifiedVersionless(), theMatchOutcome);
String message = String.format("Creating MdmLink from %s to %s -> %s", theGoldenResource.getIdElement().toUnqualifiedVersionless(), theSourceResource.getIdElement().toUnqualifiedVersionless(), theMatchOutcome);
theMdmTransactionContext.addTransactionLogMessage(message);
ourLog.debug(message);
save(mdmLink);
@ -85,54 +85,54 @@ public class MdmLinkDaoSvc {
}
@Nonnull
public MdmLink getOrCreateMdmLinkByGoldenResourcePidAndTargetResourcePid(Long theGoldenResourcePid, Long theTargetResourcePid) {
Optional<MdmLink> oExisting = getLinkByGoldenResourcePidAndTargetResourcePid(theGoldenResourcePid, theTargetResourcePid);
public MdmLink getOrCreateMdmLinkByGoldenResourcePidAndSourceResourcePid(Long theGoldenResourcePid, Long theSourceResourcePid) {
Optional<MdmLink> oExisting = getLinkByGoldenResourcePidAndSourceResourcePid(theGoldenResourcePid, theSourceResourcePid);
if (oExisting.isPresent()) {
return oExisting.get();
} else {
MdmLink newLink = myMdmLinkFactory.newMdmLink();
newLink.setGoldenResourcePid(theGoldenResourcePid);
newLink.setTargetPid(theTargetResourcePid);
newLink.setSourcePid(theSourceResourcePid);
return newLink;
}
}
public Optional<MdmLink> getLinkByGoldenResourcePidAndTargetResourcePid(Long theGoldenResourcePid, Long theTargetResourcePid) {
if (theTargetResourcePid == null || theGoldenResourcePid == null) {
public Optional<MdmLink> getLinkByGoldenResourcePidAndSourceResourcePid(Long theGoldenResourcePid, Long theSourceResourcePid) {
if (theSourceResourcePid == null || theGoldenResourcePid == null) {
return Optional.empty();
}
MdmLink link = myMdmLinkFactory.newMdmLink();
link.setTargetPid(theTargetResourcePid);
link.setSourcePid(theSourceResourcePid);
link.setGoldenResourcePid(theGoldenResourcePid);
Example<MdmLink> example = Example.of(link);
return myMdmLinkDao.findOne(example);
}
/**
* Given a Target Pid, and a match result, return all links that match these criteria.
* Given a source resource Pid, and a match result, return all links that match these criteria.
*
* @param theTargetPid the target of the relationship.
* @param theSourcePid the source of the relationship.
* @param theMatchResult the Match Result of the relationship
* @return a list of {@link MdmLink} entities matching these criteria.
*/
public List<MdmLink> getMdmLinksByTargetPidAndMatchResult(Long theTargetPid, MdmMatchResultEnum theMatchResult) {
public List<MdmLink> getMdmLinksBySourcePidAndMatchResult(Long theSourcePid, MdmMatchResultEnum theMatchResult) {
MdmLink exampleLink = myMdmLinkFactory.newMdmLink();
exampleLink.setTargetPid(theTargetPid);
exampleLink.setSourcePid(theSourcePid);
exampleLink.setMatchResult(theMatchResult);
Example<MdmLink> example = Example.of(exampleLink);
return myMdmLinkDao.findAll(example);
}
/**
* Given a target Pid, return its Matched {@link MdmLink}. There can only ever be at most one of these, but its possible
* the target has no matches, and may return an empty optional.
* Given a source Pid, return its Matched {@link MdmLink}. There can only ever be at most one of these, but its possible
* the source has no matches, and may return an empty optional.
*
* @param theTargetPid The Pid of the target you wish to find the matching link for.
* @return the {@link MdmLink} that contains the Match information for the target.
* @param theSourcePid The Pid of the source you wish to find the matching link for.
* @return the {@link MdmLink} that contains the Match information for the source.
*/
public Optional<MdmLink> getMatchedLinkForTargetPid(Long theTargetPid) {
public Optional<MdmLink> getMatchedLinkForSourcePid(Long theSourcePid) {
MdmLink exampleLink = myMdmLinkFactory.newMdmLink();
exampleLink.setTargetPid(theTargetPid);
exampleLink.setSourcePid(theSourcePid);
exampleLink.setMatchResult(MdmMatchResultEnum.MATCH);
Example<MdmLink> example = Example.of(exampleLink);
return myMdmLinkDao.findOne(example);
@ -140,36 +140,37 @@ public class MdmLinkDaoSvc {
/**
* Given an IBaseResource, return its Matched {@link MdmLink}. There can only ever be at most one of these, but its possible
* the target has no matches, and may return an empty optional.
* the source has no matches, and may return an empty optional.
*
* @param theTarget The IBaseResource representing the target you wish to find the matching link for.
* @return the {@link MdmLink} that contains the Match information for the target.
* @param theSourceResource The IBaseResource representing the source you wish to find the matching link for.
* @return the {@link MdmLink} that contains the Match information for the source.
*/
public Optional<MdmLink> getMatchedLinkForTarget(IBaseResource theTarget) {
Long pid = myIdHelperService.getPidOrNull(theTarget);
public Optional<MdmLink> getMatchedLinkForSource(IBaseResource theSourceResource) {
Long pid = myIdHelperService.getPidOrNull(theSourceResource);
if (pid == null) {
return Optional.empty();
}
MdmLink exampleLink = myMdmLinkFactory.newMdmLink();
exampleLink.setTargetPid(pid);
exampleLink.setSourcePid(pid);
exampleLink.setMatchResult(MdmMatchResultEnum.MATCH);
Example<MdmLink> example = Example.of(exampleLink);
return myMdmLinkDao.findOne(example);
}
/**
* Given a golden resource a target and a match result, return the matching {@link MdmLink}, if it exists.
* Given a golden resource a source and a match result, return the matching {@link MdmLink}, if it exists.
*
* @param theGoldenResourcePid The Pid of the Golden Resource in the relationship
* @param theTargetPid The Pid of the target in the relationship
* @param theSourcePid The Pid of the source in the relationship
* @param theMatchResult The MatchResult you are looking for.
* @return an Optional {@link MdmLink} containing the matched link if it exists.
*/
public Optional<MdmLink> getMdmLinksByGoldenResourcePidTargetPidAndMatchResult(Long theGoldenResourcePid, Long theTargetPid, MdmMatchResultEnum theMatchResult) {
public Optional<MdmLink> getMdmLinksByGoldenResourcePidSourcePidAndMatchResult(Long theGoldenResourcePid,
Long theSourcePid, MdmMatchResultEnum theMatchResult) {
MdmLink exampleLink = myMdmLinkFactory.newMdmLink();
exampleLink.setGoldenResourcePid(theGoldenResourcePid);
exampleLink.setTargetPid(theTargetPid);
exampleLink.setSourcePid(theSourcePid);
exampleLink.setMatchResult(theMatchResult);
Example<MdmLink> example = Example.of(exampleLink);
return myMdmLinkDao.findOne(example);
@ -187,12 +188,12 @@ public class MdmLinkDaoSvc {
return myMdmLinkDao.findAll(example);
}
public Optional<MdmLink> findMdmLinkByTarget(IBaseResource theTargetResource) {
@Nullable Long pid = myIdHelperService.getPidOrNull(theTargetResource);
public Optional<MdmLink> findMdmLinkBySource(IBaseResource theSourceResource) {
@Nullable Long pid = myIdHelperService.getPidOrNull(theSourceResource);
if (pid == null) {
return Optional.empty();
}
MdmLink exampleLink = myMdmLinkFactory.newMdmLink().setTargetPid(pid);
MdmLink exampleLink = myMdmLinkFactory.newMdmLink().setSourcePid(pid);
Example<MdmLink> example = Example.of(exampleLink);
return myMdmLinkDao.findOne(example);
}
@ -239,11 +240,10 @@ public class MdmLinkDaoSvc {
Set<Long> goldenResources = theLinks.stream().map(MdmLink::getGoldenResourcePid).collect(Collectors.toSet());
//TODO GGG this is probably invalid... we are essentially looking for GOLDEN -> GOLDEN links, which are either POSSIBLE_DUPLICATE
//and REDIRECT
//goldenResources.addAll(theLinks.stream().filter(link -> "Person".equals(link.getEmpiTargetType())).map(EmpiLink::getTargetPid).collect(Collectors.toSet()));
goldenResources.addAll(theLinks.stream()
.filter(link -> link.getMatchResult().equals(MdmMatchResultEnum.REDIRECT)
|| link.getMatchResult().equals(MdmMatchResultEnum.POSSIBLE_DUPLICATE))
.map(MdmLink::getTargetPid).collect(Collectors.toSet()));
.map(MdmLink::getSourcePid).collect(Collectors.toSet()));
ourLog.info("Deleting {} MDM link records...", theLinks.size());
myMdmLinkDao.deleteAll(theLinks);
ourLog.info("{} MDM link records deleted", theLinks.size());
@ -254,12 +254,12 @@ public class MdmLinkDaoSvc {
* Given a valid {@link String}, delete all {@link MdmLink} entities for that type, and get the Pids
* for the Golden Resources which were the sources of the links.
*
* @param theTargetType the type of relationship you would like to delete.
* @param theSourceType the type of relationship you would like to delete.
* @return A list of longs representing the Pids of the Golden Resources resources used as the sources of the relationships that were deleted.
*/
public List<Long> deleteAllMdmLinksOfTypeAndReturnGoldenResourcePids(String theTargetType) {
public List<Long> deleteAllMdmLinksOfTypeAndReturnGoldenResourcePids(String theSourceType) {
MdmLink link = new MdmLink();
link.setMdmTargetType(theTargetType);
link.setMdmSourceType(theSourceType);
Example<MdmLink> exampleLink = Example.of(link);
List<MdmLink> allOfType = myMdmLinkDao.findAll(exampleLink);
return deleteMdmLinksAndReturnGoldenResourcePids(allOfType);
@ -291,18 +291,18 @@ public class MdmLinkDaoSvc {
}
/**
* Given a target {@link IBaseResource}, return all {@link MdmLink} entities in which this target is the target
* Given a source {@link IBaseResource}, return all {@link MdmLink} entities in which this source is the source
* of the relationship. This will show you all links for a given Patient/Practitioner.
*
* @param theTargetResource the target resource to find links for.
* @return all links for the target.
* @param theSourceResource the source resource to find links for.
* @return all links for the source.
*/
public List<MdmLink> findMdmLinksByTarget(IBaseResource theTargetResource) {
Long pid = myIdHelperService.getPidOrNull(theTargetResource);
public List<MdmLink> findMdmLinksBySourceResource(IBaseResource theSourceResource) {
Long pid = myIdHelperService.getPidOrNull(theSourceResource);
if (pid == null) {
return Collections.emptyList();
}
MdmLink exampleLink = myMdmLinkFactory.newMdmLink().setTargetPid(pid);
MdmLink exampleLink = myMdmLinkFactory.newMdmLink().setSourcePid(pid);
Example<MdmLink> example = Example.of(exampleLink);
return myMdmLinkDao.findAll(example);
}
@ -314,7 +314,7 @@ public class MdmLinkDaoSvc {
* @param theGoldenResource the source resource to find links for.
* @return all links for the source.
*/
public List<MdmLink> findMdmMatchLinksBySource(IBaseResource theGoldenResource) {
public List<MdmLink> findMdmMatchLinksByGoldenResource(IBaseResource theGoldenResource) {
Long pid = myIdHelperService.getPidOrNull(theGoldenResource);
if (pid == null) {
return Collections.emptyList();

View File

@ -86,31 +86,12 @@ public class GoldenResourceMergerSvcImpl implements IGoldenResourceMergerSvc {
return theToGoldenResource;
}
/**
* Removes non-manual links from source to target
*
* @param theFrom Target of the link
* @param theTo Source resource of the link
* @param theMdmTransactionContext Context to keep track of the deletions
*/
private void removeTargetLinks(IAnyResource theFrom, IAnyResource theTo, MdmTransactionContext theMdmTransactionContext) {
List<MdmLink> allLinksWithTheFromAsTarget = myMdmLinkDaoSvc.findMdmLinksByGoldenResource(theFrom);
allLinksWithTheFromAsTarget
.stream()
//TODO GGG NG MDM: Why are we keeping manual links? Haven't we already copied those over in the previous merge step?
.filter(MdmLink::isAuto) // only keep manual links
.forEach(l -> {
theMdmTransactionContext.addTransactionLogMessage(String.format("Deleting link %s", l));
myMdmLinkDaoSvc.deleteLink(l);
});
}
private void addMergeLink(Long theGoldenResourcePidAkaActive, Long theTargetResourcePidAkaDeactivated, String theResourceType) {
MdmLink mdmLink = myMdmLinkDaoSvc
.getOrCreateMdmLinkByGoldenResourcePidAndTargetResourcePid(theGoldenResourcePidAkaActive, theTargetResourcePidAkaDeactivated);
.getOrCreateMdmLinkByGoldenResourcePidAndSourceResourcePid(theGoldenResourcePidAkaActive, theTargetResourcePidAkaDeactivated);
mdmLink
.setMdmTargetType(theResourceType)
.setMdmSourceType(theResourceType)
.setMatchResult(MdmMatchResultEnum.REDIRECT)
.setLinkSource(MdmLinkSourceEnum.MANUAL);
myMdmLinkDaoSvc.save(mdmLink);
@ -137,7 +118,7 @@ public class GoldenResourceMergerSvcImpl implements IGoldenResourceMergerSvc {
List<MdmLink> toDelete = new ArrayList<>();
for (MdmLink fromLink : fromLinks) {
Optional<MdmLink> optionalToLink = findFirstLinkWithMatchingTarget(toLinks, fromLink);
Optional<MdmLink> optionalToLink = findFirstLinkWithMatchingSource(toLinks, fromLink);
if (optionalToLink.isPresent()) {
// The original links already contain this target, so move it over to the toResource
@ -151,7 +132,7 @@ public class GoldenResourceMergerSvcImpl implements IGoldenResourceMergerSvc {
break;
case MANUAL:
if (fromLink.getMatchResult() != toLink.getMatchResult()) {
throw new InvalidRequestException("A MANUAL " + fromLink.getMatchResult() + " link may not be merged into a MANUAL " + toLink.getMatchResult() + " link for the same target");
throw new InvalidRequestException("A MANUAL " + fromLink.getMatchResult() + " link may not be merged into a MANUAL " + toLink.getMatchResult() + " link for the same source resource");
}
}
} else {
@ -169,9 +150,9 @@ public class GoldenResourceMergerSvcImpl implements IGoldenResourceMergerSvc {
toDelete.forEach(link -> myMdmLinkDaoSvc.deleteLink(link));
}
private Optional<MdmLink> findFirstLinkWithMatchingTarget(List<MdmLink> theMdmLinks, MdmLink theLinkWithTargetToMatch) {
private Optional<MdmLink> findFirstLinkWithMatchingSource(List<MdmLink> theMdmLinks, MdmLink theLinkWithSourceToMatch) {
return theMdmLinks.stream()
.filter(mdmLink -> mdmLink.getTargetPid().equals(theLinkWithTargetToMatch.getTargetPid()))
.filter(mdmLink -> mdmLink.getSourcePid().equals(theLinkWithSourceToMatch.getSourcePid()))
.findFirst();
}

View File

@ -51,16 +51,16 @@ public class MdmClearSvcImpl implements IMdmExpungeSvc {
}
@Override
public long expungeAllMdmLinksOfTargetType(String theResourceType, ServletRequestDetails theRequestDetails) {
throwExceptionIfInvalidTargetType(theResourceType);
ourLog.info("Clearing all MDM Links for resource type {}...", theResourceType);
List<Long> goldenResourcePids = myMdmLinkDaoSvc.deleteAllMdmLinksOfTypeAndReturnGoldenResourcePids(theResourceType);
DeleteMethodOutcome deleteOutcome = myMdmGoldenResourceDeletingSvcImpl.expungeGoldenResourcePids(goldenResourcePids, theResourceType, theRequestDetails);
public long expungeAllMdmLinksOfSourceType(String theSourceResourceType, ServletRequestDetails theRequestDetails) {
throwExceptionIfInvalidSourceResourceType(theSourceResourceType);
ourLog.info("Clearing all MDM Links for resource type {}...", theSourceResourceType);
List<Long> goldenResourcePids = myMdmLinkDaoSvc.deleteAllMdmLinksOfTypeAndReturnGoldenResourcePids(theSourceResourceType);
DeleteMethodOutcome deleteOutcome = myMdmGoldenResourceDeletingSvcImpl.expungeGoldenResourcePids(goldenResourcePids, theSourceResourceType, theRequestDetails);
ourLog.info("MDM clear operation complete. Removed {} MDM links and {} Golden Resources.", goldenResourcePids.size(), deleteOutcome.getExpungedResourcesCount());
return goldenResourcePids.size();
}
private void throwExceptionIfInvalidTargetType(String theResourceType) {
private void throwExceptionIfInvalidSourceResourceType(String theResourceType) {
if (!myMdmSettings.isSupportedMdmType(theResourceType)) {
throw new InvalidRequestException(ProviderConstants.MDM_CLEAR + " does not support resource type: " + theResourceType);
}

View File

@ -65,13 +65,13 @@ public class MdmControllerSvcImpl implements IMdmControllerSvc {
}
@Override
public Stream<MdmLinkJson> queryLinks(@Nullable String theGoldenResourceId, @Nullable String theTargetId, @Nullable String theMatchResult, @Nullable String theLinkSource, MdmTransactionContext theMdmTransactionContext) {
public Stream<MdmLinkJson> queryLinks(@Nullable String theGoldenResourceId, @Nullable String theSourceResourceId, @Nullable String theMatchResult, @Nullable String theLinkSource, MdmTransactionContext theMdmTransactionContext) {
IIdType goldenResourceId = MdmControllerUtil.extractGoldenResourceIdDtOrNull(ProviderConstants.MDM_QUERY_LINKS_GOLDEN_RESOURCE_ID, theGoldenResourceId);
IIdType targetId = MdmControllerUtil.extractTargetIdDtOrNull(ProviderConstants.MDM_QUERY_LINKS_RESOURCE_ID, theTargetId);
IIdType sourceId = MdmControllerUtil.extractSourceIdDtOrNull(ProviderConstants.MDM_QUERY_LINKS_RESOURCE_ID, theSourceResourceId);
MdmMatchResultEnum matchResult = MdmControllerUtil.extractMatchResultOrNull(theMatchResult);
MdmLinkSourceEnum linkSource = MdmControllerUtil.extractLinkSourceOrNull(theLinkSource);
return myMdmLinkQuerySvc.queryLinks(goldenResourceId, targetId, matchResult, linkSource, theMdmTransactionContext);
return myMdmLinkQuerySvc.queryLinks(goldenResourceId, sourceId, matchResult, linkSource, theMdmTransactionContext);
}
@Override
@ -80,14 +80,14 @@ public class MdmControllerSvcImpl implements IMdmControllerSvc {
}
@Override
public IAnyResource updateLink(String theGoldenResourceId, String theTargetId, String theMatchResult, MdmTransactionContext theMdmTransactionContext) {
public IAnyResource updateLink(String theGoldenResourceId, String theSourceResourceId, String theMatchResult, MdmTransactionContext theMdmTransactionContext) {
MdmMatchResultEnum matchResult = MdmControllerUtil.extractMatchResultOrNull(theMatchResult);
IAnyResource goldenResource = myMdmControllerHelper.getLatestGoldenResourceFromIdOrThrowException(ProviderConstants.MDM_UPDATE_LINK_GOLDEN_RESOURCE_ID, theGoldenResourceId);
IAnyResource target = myMdmControllerHelper.getLatestTargetFromIdOrThrowException(ProviderConstants.MDM_UPDATE_LINK_RESOURCE_ID, theTargetId);
IAnyResource source = myMdmControllerHelper.getLatestSourceFromIdOrThrowException(ProviderConstants.MDM_UPDATE_LINK_RESOURCE_ID, theSourceResourceId);
myMdmControllerHelper.validateSameVersion(goldenResource, theGoldenResourceId);
myMdmControllerHelper.validateSameVersion(target, theTargetId);
myMdmControllerHelper.validateSameVersion(source, theSourceResourceId);
return myIMdmLinkUpdaterSvc.updateLink(goldenResource, target, matchResult, theMdmTransactionContext);
return myIMdmLinkUpdaterSvc.updateLink(goldenResource, source, matchResult, theMdmTransactionContext);
}
@Override

View File

@ -85,7 +85,7 @@ public class MdmEidUpdateService {
// the user is simply updating their EID. We propagate this change to the GoldenResource.
//overwrite. No EIDS in common, but still same GoldenResource.
if (myMdmSettings.isPreventMultipleEids()) {
if (myMdmLinkDaoSvc.findMdmMatchLinksBySource(theUpdateContext.getMatchedGoldenResource()).size() <= 1) { // If there is only 0/1 link on the GoldenResource, we can safely overwrite the EID.
if (myMdmLinkDaoSvc.findMdmMatchLinksByGoldenResource(theUpdateContext.getMatchedGoldenResource()).size() <= 1) { // If there is only 0/1 link on the GoldenResource, we can safely overwrite the EID.
handleExternalEidOverwrite(theUpdateContext.getMatchedGoldenResource(), theResource, theMdmTransactionContext);
} else { // If the GoldenResource has multiple targets tied to it, we can't just overwrite the EID, so we split the GoldenResource.
createNewGoldenResourceAndFlagAsDuplicate(theResource, theMdmTransactionContext, theUpdateContext.getExistingGoldenResource());
@ -109,7 +109,7 @@ public class MdmEidUpdateService {
private void createNewGoldenResourceAndFlagAsDuplicate(IAnyResource theResource, MdmTransactionContext theMdmTransactionContext, IAnyResource theOldGoldenResource) {
log(theMdmTransactionContext, "Duplicate detected based on the fact that both resources have different external EIDs.");
IAnyResource newGoldenResource = myGoldenResourceHelper.createGoldenResourceFromMdmTarget(theResource);
IAnyResource newGoldenResource = myGoldenResourceHelper.createGoldenResourceFromMdmSourceResource(theResource);
myMdmLinkSvc.updateLink(newGoldenResource, theResource, MdmMatchOutcome.NEW_GOLDEN_RESOURCE_MATCH, MdmLinkSourceEnum.AUTO, theMdmTransactionContext);
myMdmLinkSvc.updateLink(newGoldenResource, theOldGoldenResource, MdmMatchOutcome.POSSIBLE_DUPLICATE, MdmLinkSourceEnum.AUTO, theMdmTransactionContext);
@ -150,7 +150,7 @@ public class MdmEidUpdateService {
myHasEidsInCommon = myEIDHelper.hasEidOverlap(myMatchedGoldenResource, theResource);
myIncomingResourceHasAnEid = !myEIDHelper.getExternalEid(theResource).isEmpty();
Optional<MdmLink> theExistingMatchLink = myMdmLinkDaoSvc.getMatchedLinkForTarget(theResource);
Optional<MdmLink> theExistingMatchLink = myMdmLinkDaoSvc.getMatchedLinkForSource(theResource);
myExistingGoldenResource = null;
if (theExistingMatchLink.isPresent()) {

View File

@ -37,6 +37,7 @@ import org.springframework.data.domain.Example;
import java.util.stream.Stream;
public class MdmLinkQuerySvcImpl implements IMdmLinkQuerySvc {
private static final Logger ourLog = LoggerFactory.getLogger(MdmLinkQuerySvcImpl.class);
@Autowired
@ -45,8 +46,8 @@ public class MdmLinkQuerySvcImpl implements IMdmLinkQuerySvc {
MdmLinkDaoSvc myMdmLinkDaoSvc;
@Override
public Stream<MdmLinkJson> queryLinks(IIdType theGoldenResourceId, IIdType theTargetId, MdmMatchResultEnum theMatchResult, MdmLinkSourceEnum theLinkSource, MdmTransactionContext theMdmContext) {
Example<MdmLink> exampleLink = exampleLinkFromParameters(theGoldenResourceId, theTargetId, theMatchResult, theLinkSource);
public Stream<MdmLinkJson> queryLinks(IIdType theGoldenResourceId, IIdType theSourceResourceId, MdmMatchResultEnum theMatchResult, MdmLinkSourceEnum theLinkSource, MdmTransactionContext theMdmContext) {
Example<MdmLink> exampleLink = exampleLinkFromParameters(theGoldenResourceId, theSourceResourceId, theMatchResult, theLinkSource);
return myMdmLinkDaoSvc.findMdmLinkByExample(exampleLink).stream()
.filter(mdmLink -> mdmLink.getMatchResult() != MdmMatchResultEnum.POSSIBLE_DUPLICATE)
.map(this::toJson);
@ -60,15 +61,15 @@ public class MdmLinkQuerySvcImpl implements IMdmLinkQuerySvc {
private MdmLinkJson toJson(MdmLink theLink) {
MdmLinkJson retval = new MdmLinkJson();
String targetId = myIdHelperService.resourceIdFromPidOrThrowException(theLink.getTargetPid()).toVersionless().getValue();
retval.setTargetId(targetId);
String sourceId = myIdHelperService.resourceIdFromPidOrThrowException(theLink.getSourcePid()).toVersionless().getValue();
retval.setSourceId(sourceId);
String goldenResourceId = myIdHelperService.resourceIdFromPidOrThrowException(theLink.getGoldenResourcePid()).toVersionless().getValue();
retval.setGoldenResourceId(goldenResourceId);
retval.setCreated(theLink.getCreated());
retval.setEidMatch(theLink.getEidMatch());
retval.setLinkSource(theLink.getLinkSource());
retval.setMatchResult(theLink.getMatchResult());
retval.setLinkCreatedNewResource(theLink.getHadToCreateNewResource());
retval.setLinkCreatedNewResource(theLink.getHadToCreateNewGoldenResource());
retval.setScore(theLink.getScore());
retval.setUpdated(theLink.getUpdated());
retval.setVector(theLink.getVector());
@ -76,13 +77,13 @@ public class MdmLinkQuerySvcImpl implements IMdmLinkQuerySvc {
return retval;
}
private Example<MdmLink> exampleLinkFromParameters(IIdType theGoldenResourceId, IIdType theTargetId, MdmMatchResultEnum theMatchResult, MdmLinkSourceEnum theLinkSource) {
private Example<MdmLink> exampleLinkFromParameters(IIdType theGoldenResourceId, IIdType theSourceId, MdmMatchResultEnum theMatchResult, MdmLinkSourceEnum theLinkSource) {
MdmLink mdmLink = myMdmLinkDaoSvc.newMdmLink();
if (theGoldenResourceId != null) {
mdmLink.setGoldenResourcePid(myIdHelperService.getPidOrThrowException(theGoldenResourceId));
}
if (theTargetId != null) {
mdmLink.setTargetPid(myIdHelperService.getPidOrThrowException(theTargetId));
if (theSourceId != null) {
mdmLink.setSourcePid(myIdHelperService.getPidOrThrowException(theSourceId));
}
if (theMatchResult != null) {
mdmLink.setMatchResult(theMatchResult);

View File

@ -40,7 +40,7 @@ import javax.transaction.Transactional;
import java.util.Optional;
/**
* This class is in charge of managing MdmLinks between Golden Resources and target resources
* This class is in charge of managing MdmLinks between Golden Resources and source resources
*/
@Service
public class MdmLinkSvcImpl implements IMdmLinkSvc {
@ -56,36 +56,36 @@ public class MdmLinkSvcImpl implements IMdmLinkSvc {
@Override
@Transactional
public void updateLink(IAnyResource theGoldenResource, IAnyResource theTarget, MdmMatchOutcome theMatchOutcome, MdmLinkSourceEnum theLinkSource, MdmTransactionContext theMdmTransactionContext) {
if (theMatchOutcome.isPossibleDuplicate() && goldenResourceLinkedAsNoMatch(theGoldenResource, theTarget)) {
public void updateLink(IAnyResource theGoldenResource, IAnyResource theSourceResource, MdmMatchOutcome theMatchOutcome, MdmLinkSourceEnum theLinkSource, MdmTransactionContext theMdmTransactionContext) {
if (theMatchOutcome.isPossibleDuplicate() && goldenResourceLinkedAsNoMatch(theGoldenResource, theSourceResource)) {
log(theMdmTransactionContext, theGoldenResource.getIdElement().toUnqualifiedVersionless() +
" is linked as NO_MATCH with " +
theTarget.getIdElement().toUnqualifiedVersionless() +
theSourceResource.getIdElement().toUnqualifiedVersionless() +
" not linking as POSSIBLE_DUPLICATE.");
return;
}
MdmMatchResultEnum matchResultEnum = theMatchOutcome.getMatchResultEnum();
validateRequestIsLegal(theGoldenResource, theTarget, matchResultEnum, theLinkSource);
validateRequestIsLegal(theGoldenResource, theSourceResource, matchResultEnum, theLinkSource);
myMdmResourceDaoSvc.upsertGoldenResource(theGoldenResource, theMdmTransactionContext.getResourceType());
createOrUpdateLinkEntity(theGoldenResource, theTarget, theMatchOutcome, theLinkSource, theMdmTransactionContext);
createOrUpdateLinkEntity(theGoldenResource, theSourceResource, theMatchOutcome, theLinkSource, theMdmTransactionContext);
}
private boolean goldenResourceLinkedAsNoMatch(IAnyResource theGoldenResource, IAnyResource theTarget) {
private boolean goldenResourceLinkedAsNoMatch(IAnyResource theGoldenResource, IAnyResource theSourceResource) {
Long goldenResourceId = myIdHelperService.getPidOrThrowException(theGoldenResource);
Long targetId = myIdHelperService.getPidOrThrowException(theTarget);
Long sourceId = myIdHelperService.getPidOrThrowException(theSourceResource);
// TODO perf collapse into one query
return myMdmLinkDaoSvc.getMdmLinksByGoldenResourcePidTargetPidAndMatchResult(goldenResourceId, targetId, MdmMatchResultEnum.NO_MATCH).isPresent() ||
myMdmLinkDaoSvc.getMdmLinksByGoldenResourcePidTargetPidAndMatchResult(targetId, goldenResourceId, MdmMatchResultEnum.NO_MATCH).isPresent();
return myMdmLinkDaoSvc.getMdmLinksByGoldenResourcePidSourcePidAndMatchResult(goldenResourceId, sourceId, MdmMatchResultEnum.NO_MATCH).isPresent() ||
myMdmLinkDaoSvc.getMdmLinksByGoldenResourcePidSourcePidAndMatchResult(sourceId, goldenResourceId, MdmMatchResultEnum.NO_MATCH).isPresent();
}
@Override
public void deleteLink(IAnyResource theGoldenResource, IAnyResource theTargetResource, MdmTransactionContext theMdmTransactionContext) {
Optional<MdmLink> optionalMdmLink = getMdmLinkForGoldenResourceTargetPair(theGoldenResource, theTargetResource);
public void deleteLink(IAnyResource theGoldenResource, IAnyResource theSourceResource, MdmTransactionContext theMdmTransactionContext) {
Optional<MdmLink> optionalMdmLink = getMdmLinkForGoldenResourceSourceResourcePair(theGoldenResource, theSourceResource);
if (optionalMdmLink.isPresent()) {
MdmLink mdmLink = optionalMdmLink.get();
log(theMdmTransactionContext, "Deleting MdmLink [" + theGoldenResource.getIdElement().toVersionless() + " -> " + theTargetResource.getIdElement().toVersionless() + "] with result: " + mdmLink.getMatchResult());
log(theMdmTransactionContext, "Deleting MdmLink [" + theGoldenResource.getIdElement().toVersionless() + " -> " + theSourceResource.getIdElement().toVersionless() + "] with result: " + mdmLink.getMatchResult());
myMdmLinkDaoSvc.deleteLink(mdmLink);
}
}
@ -94,7 +94,7 @@ public class MdmLinkSvcImpl implements IMdmLinkSvc {
* Helper function which runs various business rules about what types of requests are allowed.
*/
private void validateRequestIsLegal(IAnyResource theGoldenResource, IAnyResource theResource, MdmMatchResultEnum theMatchResult, MdmLinkSourceEnum theLinkSource) {
Optional<MdmLink> oExistingLink = getMdmLinkForGoldenResourceTargetPair(theGoldenResource, theResource);
Optional<MdmLink> oExistingLink = getMdmLinkForGoldenResourceSourceResourcePair(theGoldenResource, theResource);
if (oExistingLink.isPresent() && systemIsAttemptingToModifyManualLink(theLinkSource, oExistingLink.get())) {
throw new InternalErrorException("MDM system is not allowed to modify links on manually created links");
}
@ -118,19 +118,19 @@ public class MdmLinkSvcImpl implements IMdmLinkSvc {
return theIncomingSource == MdmLinkSourceEnum.AUTO && theExistingSource.isManual();
}
private Optional<MdmLink> getMdmLinkForGoldenResourceTargetPair(IAnyResource theGoldenResource, IAnyResource theCandidate) {
private Optional<MdmLink> getMdmLinkForGoldenResourceSourceResourcePair(IAnyResource theGoldenResource, IAnyResource theCandidate) {
if (theGoldenResource.getIdElement().getIdPart() == null || theCandidate.getIdElement().getIdPart() == null) {
return Optional.empty();
} else {
return myMdmLinkDaoSvc.getLinkByGoldenResourcePidAndTargetResourcePid(
return myMdmLinkDaoSvc.getLinkByGoldenResourcePidAndSourceResourcePid(
myIdHelperService.getPidOrNull(theGoldenResource),
myIdHelperService.getPidOrNull(theCandidate)
);
}
}
private void createOrUpdateLinkEntity(IBaseResource theGoldenResource, IBaseResource theTargetResource, MdmMatchOutcome theMatchOutcome, MdmLinkSourceEnum theLinkSource, MdmTransactionContext theMdmTransactionContext) {
myMdmLinkDaoSvc.createOrUpdateLinkEntity(theGoldenResource, theTargetResource, theMatchOutcome, theLinkSource, theMdmTransactionContext);
private void createOrUpdateLinkEntity(IBaseResource theGoldenResource, IBaseResource theSourceResource, MdmMatchOutcome theMatchOutcome, MdmLinkSourceEnum theLinkSource, MdmTransactionContext theMdmTransactionContext) {
myMdmLinkDaoSvc.createOrUpdateLinkEntity(theGoldenResource, theSourceResource, theMatchOutcome, theLinkSource, theMdmTransactionContext);
}
private void log(MdmTransactionContext theMdmTransactionContext, String theMessage) {

View File

@ -66,38 +66,38 @@ public class MdmLinkUpdaterSvcImpl implements IMdmLinkUpdaterSvc {
@Transactional
@Override
public IAnyResource updateLink(IAnyResource theGoldenResource, IAnyResource theTarget, MdmMatchResultEnum theMatchResult, MdmTransactionContext theMdmContext) {
String targetType = myFhirContext.getResourceType(theTarget);
public IAnyResource updateLink(IAnyResource theGoldenResource, IAnyResource theSourceResource, MdmMatchResultEnum theMatchResult, MdmTransactionContext theMdmContext) {
String sourceType = myFhirContext.getResourceType(theSourceResource);
validateUpdateLinkRequest(theGoldenResource, theTarget, theMatchResult, targetType);
validateUpdateLinkRequest(theGoldenResource, theSourceResource, theMatchResult, sourceType);
Long goldenResourceId = myIdHelperService.getPidOrThrowException(theGoldenResource);
Long targetId = myIdHelperService.getPidOrThrowException(theTarget);
Long targetId = myIdHelperService.getPidOrThrowException(theSourceResource);
Optional<MdmLink> optionalMdmLink = myMdmLinkDaoSvc.getLinkByGoldenResourcePidAndTargetResourcePid(goldenResourceId, targetId);
Optional<MdmLink> optionalMdmLink = myMdmLinkDaoSvc.getLinkByGoldenResourcePidAndSourceResourcePid(goldenResourceId, targetId);
if (!optionalMdmLink.isPresent()) {
throw new InvalidRequestException(myMessageHelper.getMessageForNoLink(theGoldenResource, theTarget));
throw new InvalidRequestException(myMessageHelper.getMessageForNoLink(theGoldenResource, theSourceResource));
}
MdmLink mdmLink = optionalMdmLink.get();
if (mdmLink.getMatchResult() == theMatchResult) {
ourLog.warn("MDM Link for " + theGoldenResource.getIdElement().toVersionless() + ", " + theTarget.getIdElement().toVersionless() + " already has value " + theMatchResult + ". Nothing to do.");
ourLog.warn("MDM Link for " + theGoldenResource.getIdElement().toVersionless() + ", " + theSourceResource.getIdElement().toVersionless() + " already has value " + theMatchResult + ". Nothing to do.");
return theGoldenResource;
}
ourLog.info("Manually updating MDM Link for " + theGoldenResource.getIdElement().toVersionless() + ", " + theTarget.getIdElement().toVersionless() + " from " + mdmLink.getMatchResult() + " to " + theMatchResult + ".");
ourLog.info("Manually updating MDM Link for " + theGoldenResource.getIdElement().toVersionless() + ", " + theSourceResource.getIdElement().toVersionless() + " from " + mdmLink.getMatchResult() + " to " + theMatchResult + ".");
mdmLink.setMatchResult(theMatchResult);
mdmLink.setLinkSource(MdmLinkSourceEnum.MANUAL);
myMdmLinkDaoSvc.save(mdmLink);
myMdmResourceDaoSvc.upsertGoldenResource(theGoldenResource, theMdmContext.getResourceType());
if (theMatchResult == MdmMatchResultEnum.NO_MATCH) {
// Need to find a new Golden Resource to link this target to
myMdmMatchLinkSvc.updateMdmLinksForMdmTarget(theTarget, theMdmContext);
myMdmMatchLinkSvc.updateMdmLinksForMdmSource(theSourceResource, theMdmContext);
}
return theGoldenResource;
}
private void validateUpdateLinkRequest(IAnyResource theGoldenRecord, IAnyResource theTarget, MdmMatchResultEnum theMatchResult, String theTargetType) {
private void validateUpdateLinkRequest(IAnyResource theGoldenRecord, IAnyResource theSourceResource, MdmMatchResultEnum theMatchResult, String theSourceType) {
String goldenRecordType = myFhirContext.getResourceType(theGoldenRecord);
if (theMatchResult != MdmMatchResultEnum.NO_MATCH &&
@ -109,39 +109,39 @@ public class MdmLinkUpdaterSvcImpl implements IMdmLinkUpdaterSvc {
throw new InvalidRequestException(myMessageHelper.getMessageForUnsupportedFirstArgumentTypeInUpdate(goldenRecordType));
}
if (!myMdmSettings.isSupportedMdmType(theTargetType)) {
throw new InvalidRequestException(myMessageHelper.getMessageForUnsupportedSecondArgumentTypeInUpdate(theTargetType));
if (!myMdmSettings.isSupportedMdmType(theSourceType)) {
throw new InvalidRequestException(myMessageHelper.getMessageForUnsupportedSecondArgumentTypeInUpdate(theSourceType));
}
if (!Objects.equals(goldenRecordType, theTargetType)) {
throw new InvalidRequestException(myMessageHelper.getMessageForArgumentTypeMismatchInUpdate(goldenRecordType, theTargetType));
if (!Objects.equals(goldenRecordType, theSourceType)) {
throw new InvalidRequestException(myMessageHelper.getMessageForArgumentTypeMismatchInUpdate(goldenRecordType, theSourceType));
}
if (!MdmUtil.isMdmManaged(theGoldenRecord)) {
throw new InvalidRequestException(myMessageHelper.getMessageForUnmanagedResource());
}
if (!MdmUtil.isMdmAllowed(theTarget)) {
throw new InvalidRequestException(myMessageHelper.getMessageForUnsupportedTarget());
if (!MdmUtil.isMdmAllowed(theSourceResource)) {
throw new InvalidRequestException(myMessageHelper.getMessageForUnsupportedSourceResource());
}
}
@Transactional
@Override
public void notDuplicateGoldenResource(IAnyResource theGoldenResource, IAnyResource theTarget, MdmTransactionContext theMdmContext) {
validateNotDuplicateGoldenResourceRequest(theGoldenResource, theTarget);
public void notDuplicateGoldenResource(IAnyResource theGoldenResource, IAnyResource theTargetGoldenResource, MdmTransactionContext theMdmContext) {
validateNotDuplicateGoldenResourceRequest(theGoldenResource, theTargetGoldenResource);
Long goldenResourceId = myIdHelperService.getPidOrThrowException(theGoldenResource);
Long targetId = myIdHelperService.getPidOrThrowException(theTarget);
Long targetId = myIdHelperService.getPidOrThrowException(theTargetGoldenResource);
Optional<MdmLink> oMdmLink = myMdmLinkDaoSvc.getLinkByGoldenResourcePidAndTargetResourcePid(goldenResourceId, targetId);
Optional<MdmLink> oMdmLink = myMdmLinkDaoSvc.getLinkByGoldenResourcePidAndSourceResourcePid(goldenResourceId, targetId);
if (!oMdmLink.isPresent()) {
throw new InvalidRequestException("No link exists between " + theGoldenResource.getIdElement().toVersionless() + " and " + theTarget.getIdElement().toVersionless());
throw new InvalidRequestException("No link exists between " + theGoldenResource.getIdElement().toVersionless() + " and " + theTargetGoldenResource.getIdElement().toVersionless());
}
MdmLink mdmLink = oMdmLink.get();
if (!mdmLink.isPossibleDuplicate()) {
throw new InvalidRequestException(theGoldenResource.getIdElement().toVersionless() + " and " + theTarget.getIdElement().toVersionless() + " are not linked as POSSIBLE_DUPLICATE.");
throw new InvalidRequestException(theGoldenResource.getIdElement().toVersionless() + " and " + theTargetGoldenResource.getIdElement().toVersionless() + " are not linked as POSSIBLE_DUPLICATE.");
}
mdmLink.setMatchResult(MdmMatchResultEnum.NO_MATCH);
mdmLink.setLinkSource(MdmLinkSourceEnum.MANUAL);

View File

@ -22,9 +22,11 @@ package ca.uhn.fhir.jpa.mdm.svc;
import ca.uhn.fhir.mdm.api.IMdmMatchFinderSvc;
import ca.uhn.fhir.mdm.api.MatchedTarget;
import ca.uhn.fhir.mdm.log.Logs;
import ca.uhn.fhir.mdm.rules.svc.MdmResourceMatcherSvc;
import ca.uhn.fhir.jpa.mdm.svc.candidate.MdmCandidateSearchSvc;
import org.hl7.fhir.instance.model.api.IAnyResource;
import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@ -36,6 +38,8 @@ import java.util.stream.Collectors;
@Service
public class MdmMatchFinderSvcImpl implements IMdmMatchFinderSvc {
private static final Logger ourLog = Logs.getMdmTroubleshootingLog();
@Autowired
private MdmCandidateSearchSvc myMdmCandidateSearchSvc;
@Autowired
@ -46,9 +50,12 @@ public class MdmMatchFinderSvcImpl implements IMdmMatchFinderSvc {
public List<MatchedTarget> getMatchedTargets(String theResourceType, IAnyResource theResource) {
Collection<IAnyResource> targetCandidates = myMdmCandidateSearchSvc.findCandidates(theResourceType, theResource);
return targetCandidates.stream()
List<MatchedTarget> matches = targetCandidates.stream()
.map(candidate -> new MatchedTarget(candidate, myMdmResourceMatcherSvc.getMatchResult(theResource, candidate)))
.collect(Collectors.toList());
ourLog.info("Found {} matched targets for {}", matches.size(), theResourceType);
return matches;
}
}

View File

@ -41,8 +41,8 @@ import java.util.List;
/**
* MdmMatchLinkSvc is the entrypoint for HAPI's MDM system. An incoming resource can call
* updateMdmLinksForMdmTarget and the underlying MDM system will take care of matching it to a GoldenResource, or creating a
* new GoldenResource if a suitable one was not found.
* updateMdmLinksForMdmSource and the underlying MDM system will take care of matching it to a GoldenResource,
* or creating a new GoldenResource if a suitable one was not found.
*/
@Service
public class MdmMatchLinkSvc {
@ -58,15 +58,15 @@ public class MdmMatchLinkSvc {
private MdmEidUpdateService myEidUpdateService;
/**
* Given an MDM Target (consisting of either a Patient or a Practitioner), find a suitable Golden Resource candidate for them,
* Given an MDM source (consisting of any supported MDM type), find a suitable Golden Resource candidate for them,
* 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 can be any supported MDM type.
* @param theResource the incoming MDM source, 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.
*/
public MdmTransactionContext updateMdmLinksForMdmTarget(IAnyResource theResource, MdmTransactionContext theMdmTransactionContext) {
public MdmTransactionContext updateMdmLinksForMdmSource(IAnyResource theResource, MdmTransactionContext theMdmTransactionContext) {
if (MdmUtil.isMdmAllowed(theResource)) {
return doMdmUpdate(theResource, theMdmTransactionContext);
} else {
@ -122,30 +122,29 @@ public class MdmMatchLinkSvc {
private void handleMdmWithNoCandidates(IAnyResource theResource, MdmTransactionContext theMdmTransactionContext) {
log(theMdmTransactionContext, String.format("There were no matched candidates for MDM, creating a new %s.", theResource.getIdElement().getResourceType()));
IAnyResource newGoldenResource = myGoldenResourceHelper.createGoldenResourceFromMdmTarget(theResource);
IAnyResource newGoldenResource = myGoldenResourceHelper.createGoldenResourceFromMdmSourceResource(theResource);
// TODO GGG :)
// 1. Get the right helper
// 2. Create source resoruce for the MDM target
// 2. Create source resource for the MDM source
// 3. UPDATE MDM LINK TABLE
myMdmLinkSvc.updateLink(newGoldenResource, theResource, MdmMatchOutcome.NEW_GOLDEN_RESOURCE_MATCH, MdmLinkSourceEnum.AUTO, theMdmTransactionContext);
}
private void handleMdmCreate(IAnyResource theTargetResource, MatchedGoldenResourceCandidate theGoldenResourceCandidate, MdmTransactionContext theMdmTransactionContext) {
private void handleMdmCreate(IAnyResource theSourceResource, MatchedGoldenResourceCandidate theGoldenResourceCandidate, MdmTransactionContext theMdmTransactionContext) {
log(theMdmTransactionContext, "MDM has narrowed down to one candidate for matching.");
IAnyResource golenResource = myMdmGoldenResourceFindingSvc.getGoldenResourceFromMatchedGoldenResourceCandidate(theGoldenResourceCandidate, theMdmTransactionContext.getResourceType());
if (myGoldenResourceHelper.isPotentialDuplicate(golenResource, theTargetResource)) {
if (myGoldenResourceHelper.isPotentialDuplicate(golenResource, theSourceResource)) {
log(theMdmTransactionContext, "Duplicate detected based on the fact that both resources have different external EIDs.");
IAnyResource newGoldenResource = myGoldenResourceHelper.createGoldenResourceFromMdmTarget(theTargetResource);
myMdmLinkSvc.updateLink(newGoldenResource, theTargetResource, MdmMatchOutcome.NEW_GOLDEN_RESOURCE_MATCH, MdmLinkSourceEnum.AUTO, theMdmTransactionContext);
IAnyResource newGoldenResource = myGoldenResourceHelper.createGoldenResourceFromMdmSourceResource(theSourceResource);
myMdmLinkSvc.updateLink(newGoldenResource, theSourceResource, MdmMatchOutcome.NEW_GOLDEN_RESOURCE_MATCH, MdmLinkSourceEnum.AUTO, theMdmTransactionContext);
myMdmLinkSvc.updateLink(newGoldenResource, golenResource, MdmMatchOutcome.POSSIBLE_DUPLICATE, MdmLinkSourceEnum.AUTO, theMdmTransactionContext);
} else {
if (theGoldenResourceCandidate.isMatch()) {
myGoldenResourceHelper.handleExternalEidAddition(golenResource, theTargetResource, theMdmTransactionContext);
myGoldenResourceHelper.handleExternalEidAddition(golenResource, theSourceResource, theMdmTransactionContext);
//TODO MDM GGG/NG: eventually we need to add survivorship rules of attributes here. Currently no data is copied over except EIDs.
}
myMdmLinkSvc.updateLink(golenResource, theTargetResource, theGoldenResourceCandidate.getMatchResult(), MdmLinkSourceEnum.AUTO, theMdmTransactionContext);
myMdmLinkSvc.updateLink(golenResource, theSourceResource, theGoldenResourceCandidate.getMatchResult(), MdmLinkSourceEnum.AUTO, theMdmTransactionContext);
}
}

View File

@ -72,27 +72,27 @@ public class MdmSearchParamSvc implements ISearchParamRetriever {
}
/**
* Given a target type, and a criteria string of the shape name=x&birthDate=y, generate a {@link SearchParameterMap}
* Given a source type, and a criteria string of the shape name=x&birthDate=y, generate a {@link SearchParameterMap}
* that represents this query.
*
* @param theTargetType the resource type to execute the search on
* @param theSourceType the resource type to execute the search on
* @param theCriteria the string search criteria.
*
* @return the generated SearchParameterMap, or an empty one if there is no criteria.
*/
public SearchParameterMap getSearchParameterMapFromCriteria(String theTargetType, @Nullable String theCriteria) {
public SearchParameterMap getSearchParameterMapFromCriteria(String theSourceType, @Nullable String theCriteria) {
SearchParameterMap spMap;
if (StringUtils.isBlank(theCriteria)) {
spMap = new SearchParameterMap();
} else {
spMap = mapFromCriteria(theTargetType, theCriteria);
spMap = mapFromCriteria(theSourceType, theCriteria);
}
return spMap;
}
public ISearchBuilder generateSearchBuilderForType(String theTargetType) {
IFhirResourceDao resourceDao = myDaoRegistry.getResourceDao(theTargetType);
return mySearchBuilderFactory.newSearchBuilder(resourceDao, theTargetType, resourceDao.getResourceType());
public ISearchBuilder generateSearchBuilderForType(String theSourceType) {
IFhirResourceDao resourceDao = myDaoRegistry.getResourceDao(theSourceType);
return mySearchBuilderFactory.newSearchBuilder(resourceDao, theSourceType, resourceDao.getResourceType());
}
/**

View File

@ -69,9 +69,9 @@ public class MdmSubmitSvcImpl implements IMdmSubmitSvc {
@Override
@Transactional
public long submitAllTargetTypesToMdm(@Nullable String theCriteria) {
public long submitAllSourceTypesToMdm(@Nullable String theCriteria) {
long submittedCount = myMdmSettings.getMdmRules().getMdmTypes().stream()
.mapToLong(targetType -> submitTargetTypeToMdm(targetType, theCriteria))
.mapToLong(type -> submitSourceResourceTypeToMdm(type, theCriteria))
.sum();
return submittedCount;
@ -79,17 +79,17 @@ public class MdmSubmitSvcImpl implements IMdmSubmitSvc {
@Override
@Transactional
public long submitTargetTypeToMdm(String theTargetType, @Nullable String theCriteria) {
public long submitSourceResourceTypeToMdm(String theSourceResourceType, @Nullable String theCriteria) {
if (theCriteria == null) {
ourLog.info("Submitting all resources of type {} to MDM", theTargetType);
ourLog.info("Submitting all resources of type {} to MDM", theSourceResourceType);
} else {
ourLog.info("Submitting resources of type {} with criteria {} to MDM", theTargetType, theCriteria);
ourLog.info("Submitting resources of type {} with criteria {} to MDM", theSourceResourceType, theCriteria);
}
validateTargetType(theTargetType);
SearchParameterMap spMap = myMdmSearchParamSvc.getSearchParameterMapFromCriteria(theTargetType, theCriteria);
validateSourceType(theSourceResourceType);
SearchParameterMap spMap = myMdmSearchParamSvc.getSearchParameterMapFromCriteria(theSourceResourceType, theCriteria);
spMap.setLoadSynchronousUpTo(BUFFER_SIZE);
ISearchBuilder searchBuilder = myMdmSearchParamSvc.generateSearchBuilderForType(theTargetType);
ISearchBuilder searchBuilder = myMdmSearchParamSvc.generateSearchBuilderForType(theSourceResourceType);
return submitAllMatchingResourcesToMdmChannel(spMap, searchBuilder);
}
@ -130,19 +130,19 @@ public class MdmSubmitSvcImpl implements IMdmSubmitSvc {
@Override
@Transactional
public long submitPractitionerTypeToMdm(@Nullable String theCriteria) {
return submitTargetTypeToMdm("Practitioner", theCriteria);
return submitSourceResourceTypeToMdm("Practitioner", theCriteria);
}
@Override
@Transactional
public long submitPatientTypeToMdm(@Nullable String theCriteria) {
return submitTargetTypeToMdm("Patient", theCriteria);
return submitSourceResourceTypeToMdm("Patient", theCriteria);
}
@Override
@Transactional
public long submitTargetToMdm(IIdType theId) {
validateTargetType(theId.getResourceType());
public long submitSourceResourceToMdm(IIdType theId) {
validateSourceType(theId.getResourceType());
IFhirResourceDao resourceDao = myDaoRegistry.getResourceDao(theId.getResourceType());
IBaseResource read = resourceDao.read(theId);
myMdmChannelSubmitterSvc.submitResourceToMdmChannel(read);
@ -154,7 +154,7 @@ public class MdmSubmitSvcImpl implements IMdmSubmitSvc {
myMdmSettings = theMdmSettings;
}
private void validateTargetType(String theResourceType) {
private void validateSourceType(String theResourceType) {
if(!myMdmSettings.getMdmRules().getMdmTypes().contains(theResourceType)) {
throw new InvalidRequestException(ProviderConstants.OPERATION_MDM_SUBMIT + " does not support resource type: " + theResourceType);
}

View File

@ -23,9 +23,9 @@ package ca.uhn.fhir.jpa.mdm.svc.candidate;
public enum CandidateStrategyEnum {
/** Find Golden Resource candidates based on matching EID */
EID,
/** Find Golden Resource candidates based on a link already existing for the target resource */
/** Find Golden Resource candidates based on a link already existing for the source resource */
LINK,
/** Find Golden Resource candidates based on other targets that match the incoming target using the MDM Matching rules */
/** Find Golden Resource candidates based on other sources that match the incoming source using the MDM Matching rules */
SCORE;
public boolean isEidMatch() {

View File

@ -41,7 +41,7 @@ import java.util.Optional;
import java.util.stream.Collectors;
@Service
public class FindCandidateByScoreSvc extends BaseCandidateFinder {
public class FindCandidateByExampleSvc extends BaseCandidateFinder {
private static final Logger ourLog = Logs.getMdmTroubleshootingLog();
@Autowired
@ -75,7 +75,7 @@ public class FindCandidateByScoreSvc extends BaseCandidateFinder {
// MatchedTargetCandidate -> Golden Resource -> MdmLink -> MatchedGoldenResourceCandidate
matchedCandidates = matchedCandidates.stream().filter(mc -> mc.isMatch() || mc.isPossibleMatch()).collect(Collectors.toList());
for (MatchedTarget match : matchedCandidates) {
Optional<MdmLink> optionalMdmLink = myMdmLinkDaoSvc.getMatchedLinkForTargetPid(myIdHelperService.getPidOrNull(match.getTarget()));
Optional<MdmLink> optionalMdmLink = myMdmLinkDaoSvc.getMatchedLinkForSourcePid(myIdHelperService.getPidOrNull(match.getTarget()));
if (!optionalMdmLink.isPresent()) {
continue;
}
@ -94,7 +94,7 @@ public class FindCandidateByScoreSvc extends BaseCandidateFinder {
private List<Long> getNoMatchGoldenResourcePids(IBaseResource theBaseResource) {
Long targetPid = myIdHelperService.getPidOrNull(theBaseResource);
return myMdmLinkDaoSvc.getMdmLinksByTargetPidAndMatchResult(targetPid, MdmMatchResultEnum.NO_MATCH)
return myMdmLinkDaoSvc.getMdmLinksBySourcePidAndMatchResult(targetPid, MdmMatchResultEnum.NO_MATCH)
.stream()
.map(MdmLink::getGoldenResourcePid)
.collect(Collectors.toList());

View File

@ -47,7 +47,7 @@ public class FindCandidateByLinkSvc extends BaseCandidateFinder {
Long targetPid = myIdHelperService.getPidOrNull(theTarget);
if (targetPid != null) {
Optional<MdmLink> oLink = myMdmLinkDaoSvc.getMatchedLinkForTargetPid(targetPid);
Optional<MdmLink> oLink = myMdmLinkDaoSvc.getMatchedLinkForSourcePid(targetPid);
if (oLink.isPresent()) {
ResourcePersistentId goldenResourcePid = new ResourcePersistentId(oLink.get().getGoldenResourcePid());
ourLog.debug("Resource previously linked. Using existing link.");

View File

@ -66,11 +66,11 @@ public class MdmCandidateSearchSvc {
}
/**
* Given a target resource, search for all resources that are considered an MDM match based on defined MDM rules.
* Given a source resource, search for all resources that are considered an MDM match based on defined MDM rules.
*
*
* @param theResourceType
* @param theResource the target {@link IBaseResource} we are attempting to match.
* @param theResource the {@link IBaseResource} we are attempting to match.
*
* @return the list of candidate {@link IBaseResource} which could be matches to theResource
*/
@ -100,6 +100,8 @@ public class MdmCandidateSearchSvc {
if (theResource.getIdElement().getIdPart() != null) {
matchedPidsToResources.remove(myIdHelperService.getPidOrNull(theResource));
}
ourLog.info("Found {} resources for {}", matchedPidsToResources.size(), theResourceType);
return matchedPidsToResources.values();
}

View File

@ -44,17 +44,17 @@ public class MdmGoldenResourceFindingSvc {
private FindCandidateByLinkSvc myFindCandidateByLinkSvc;
@Autowired
private FindCandidateByScoreSvc myFindCandidateByScoreSvc;
private FindCandidateByExampleSvc myFindCandidateByExampleSvc;
/**
* Given an incoming IBaseResource, limited to the supported MDM type, return a list of {@link MatchedGoldenResourceCandidate}
* indicating possible candidates for a matching Golden Resource. Uses several separate methods for finding candidates:
* <p>
* 0. First, check the incoming Resource for an EID. If it is present, and we can find a Golden Resource with this EID, it automatically matches.
* 1. First, check link table for any entries where this baseresource is the target of a Golden Resource. If found, return.
* 1. First, check link table for any entries where this baseresource is the source of a Golden Resource. If found, return.
* 2. If none are found, attempt to find Golden Resources which link to this theResource.
* 3. If none are found, attempt to find Golden Resources similar to our incoming resource based on the MDM rules and field matchers.
* 4. If none are found, attempt to find Golden Resources that are linked to targets that are similar to our incoming resource based on the MDM rules and
* 4. If none are found, attempt to find Golden Resources that are linked to sources 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 Golden Resources for.
@ -68,9 +68,9 @@ public class MdmGoldenResourceFindingSvc {
}
if (matchedGoldenResourceCandidates.isEmpty()) {
//OK, so we have not found any links in the MdmLink table with us as a target. Next, let's find
//OK, so we have not found any links in the MdmLink table with us as a source. Next, let's find
//possible Golden Resources matches by following MDM rules.
matchedGoldenResourceCandidates = myFindCandidateByScoreSvc.findCandidates(theResource);
matchedGoldenResourceCandidates = myFindCandidateByExampleSvc.findCandidates(theResource);
}
return matchedGoldenResourceCandidates;

View File

@ -1,22 +1,14 @@
package ca.uhn.fhir.jpa.mdm;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.mdm.api.MdmConstants;
import ca.uhn.fhir.mdm.api.MdmLinkSourceEnum;
import ca.uhn.fhir.mdm.api.MdmMatchResultEnum;
import ca.uhn.fhir.mdm.api.IMdmSettings;
import ca.uhn.fhir.mdm.model.MdmTransactionContext;
import ca.uhn.fhir.mdm.rules.svc.MdmResourceMatcherSvc;
import ca.uhn.fhir.mdm.util.EIDHelper;
import ca.uhn.fhir.mdm.util.MdmUtil;
import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.api.model.DaoMethodOutcome;
import ca.uhn.fhir.jpa.dao.data.IMdmLinkDao;
import ca.uhn.fhir.jpa.dao.index.IdHelperService;
import ca.uhn.fhir.jpa.entity.MdmLink;
import ca.uhn.fhir.jpa.mdm.config.MdmConsumerConfig;
import ca.uhn.fhir.jpa.mdm.config.MdmSearchParameterLoader;
import ca.uhn.fhir.jpa.mdm.config.MdmSubmitterConfig;
import ca.uhn.fhir.jpa.mdm.config.TestMdmConfigR4;
import ca.uhn.fhir.jpa.mdm.dao.MdmLinkDaoSvc;
@ -27,11 +19,18 @@ import ca.uhn.fhir.jpa.mdm.matcher.IsPossibleLinkedTo;
import ca.uhn.fhir.jpa.mdm.matcher.IsPossibleMatchWith;
import ca.uhn.fhir.jpa.mdm.matcher.IsSameGoldenResourceAs;
import ca.uhn.fhir.jpa.mdm.svc.MdmMatchLinkSvc;
import ca.uhn.fhir.jpa.entity.MdmLink;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.jpa.searchparam.registry.SearchParamRegistryImpl;
import ca.uhn.fhir.jpa.subscription.match.config.SubscriptionProcessorConfig;
import ca.uhn.fhir.jpa.test.BaseJpaR4Test;
import ca.uhn.fhir.mdm.api.IMdmSettings;
import ca.uhn.fhir.mdm.api.MdmConstants;
import ca.uhn.fhir.mdm.api.MdmLinkSourceEnum;
import ca.uhn.fhir.mdm.api.MdmMatchResultEnum;
import ca.uhn.fhir.mdm.model.MdmTransactionContext;
import ca.uhn.fhir.mdm.rules.svc.MdmResourceMatcherSvc;
import ca.uhn.fhir.mdm.util.EIDHelper;
import ca.uhn.fhir.mdm.util.MdmUtil;
import ca.uhn.fhir.model.api.TemporalPrecisionEnum;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId;
@ -112,8 +111,6 @@ abstract public class BaseMdmR4Test extends BaseJpaR4Test {
@Autowired
protected EIDHelper myEIDHelper;
@Autowired
MdmSearchParameterLoader myMdmSearchParameterLoader;
@Autowired
SearchParamRegistryImpl mySearchParamRegistry;
@Autowired
private IInterceptorBroadcaster myInterceptorBroadcaster;
@ -314,7 +311,7 @@ abstract public class BaseMdmR4Test extends BaseJpaR4Test {
String resourceType = theBaseResource.getIdElement().getResourceType();
IFhirResourceDao relevantDao = myDaoRegistry.getResourceDao(resourceType);
Optional<MdmLink> matchedLinkForTargetPid = myMdmLinkDaoSvc.getMatchedLinkForTargetPid(myIdHelperService.getPidOrNull(theBaseResource));
Optional<MdmLink> matchedLinkForTargetPid = myMdmLinkDaoSvc.getMatchedLinkForSourcePid(myIdHelperService.getPidOrNull(theBaseResource));
if (matchedLinkForTargetPid.isPresent()) {
Long goldenResourcePid = matchedLinkForTargetPid.get().getGoldenResourcePid();
return (IAnyResource) relevantDao.readByPid(new ResourcePersistentId(goldenResourcePid));
@ -340,7 +337,7 @@ abstract public class BaseMdmR4Test extends BaseJpaR4Test {
protected Patient createPatientAndUpdateLinks(Patient thePatient) {
thePatient = createPatient(thePatient);
myMdmMatchLinkSvc.updateMdmLinksForMdmTarget(thePatient, createContextForCreate("Patient"));
myMdmMatchLinkSvc.updateMdmLinksForMdmSource(thePatient, createContextForCreate("Patient"));
return thePatient;
}
@ -359,7 +356,7 @@ abstract public class BaseMdmR4Test extends BaseJpaR4Test {
protected Medication createMedicationAndUpdateLinks(Medication theMedication) {
theMedication = createMedication(theMedication);
myMdmMatchLinkSvc.updateMdmLinksForMdmTarget(theMedication, createContextForCreate("Medication"));
myMdmMatchLinkSvc.updateMdmLinksForMdmSource(theMedication, createContextForCreate("Medication"));
return theMedication;
}
@ -381,7 +378,7 @@ abstract public class BaseMdmR4Test extends BaseJpaR4Test {
protected Patient updatePatientAndUpdateLinks(Patient thePatient) {
thePatient = (Patient) myPatientDao.update(thePatient).getResource();
myMdmMatchLinkSvc.updateMdmLinksForMdmTarget(thePatient, createContextForUpdate(thePatient.getIdElement().getResourceType()));
myMdmMatchLinkSvc.updateMdmLinksForMdmSource(thePatient, createContextForUpdate(thePatient.getIdElement().getResourceType()));
return thePatient;
}
@ -389,7 +386,7 @@ abstract public class BaseMdmR4Test extends BaseJpaR4Test {
thePractitioner.setActive(true);
DaoMethodOutcome daoMethodOutcome = myPractitionerDao.create(thePractitioner);
thePractitioner.setId(daoMethodOutcome.getId());
myMdmMatchLinkSvc.updateMdmLinksForMdmTarget(thePractitioner, createContextForCreate("Practitioner"));
myMdmMatchLinkSvc.updateMdmLinksForMdmSource(thePractitioner, createContextForCreate("Practitioner"));
return thePractitioner;
}
@ -454,14 +451,14 @@ abstract public class BaseMdmR4Test extends BaseJpaR4Test {
mdmLink.setLinkSource(MdmLinkSourceEnum.MANUAL);
mdmLink.setMatchResult(MdmMatchResultEnum.MATCH);
mdmLink.setGoldenResourcePid(myIdHelperService.getPidOrNull(sourcePatient));
mdmLink.setTargetPid(myIdHelperService.getPidOrNull(patient));
mdmLink.setSourcePid(myIdHelperService.getPidOrNull(patient));
return mdmLink;
}
protected void loadMdmSearchParameters() {
myMdmSearchParameterLoader.daoUpdateMdmSearchParameters();
mySearchParamRegistry.forceRefresh();
}
// protected void loadMdmSearchParameters() {
// myMdmSearchParameterLoader.daoUpdateMdmSearchParameters();
// mySearchParamRegistry.forceRefresh();
// }
protected void logAllLinks() {
ourLog.info("Logging all MDM Links:");
@ -476,7 +473,7 @@ abstract public class BaseMdmR4Test extends BaseJpaR4Test {
}
protected void assertLinksCreatedNewResource(Boolean... theExpectedValues) {
assertFields(MdmLink::getHadToCreateNewResource, theExpectedValues);
assertFields(MdmLink::getHadToCreateNewGoldenResource, theExpectedValues);
}
protected void assertLinksMatchedByEid(Boolean... theExpectedValues) {

View File

@ -24,7 +24,7 @@ public class MdmLinkHelper {
ourLog.info("All MDM Links:");
for (MdmLink link : links) {
IdDt goldenResourceId = link.getGoldenResource().getIdDt().toVersionless();
IdDt targetId = link.getTarget().getIdDt().toVersionless();
IdDt targetId = link.getSource().getIdDt().toVersionless();
ourLog.info("{}: {}, {}, {}, {}", link.getId(), goldenResourceId, targetId, link.getMatchResult(), link.getLinkSource());
}
}

View File

@ -45,7 +45,7 @@ public class MdmExpungeTest extends BaseMdmR4Test {
mdmLink.setLinkSource(MdmLinkSourceEnum.MANUAL);
mdmLink.setMatchResult(MdmMatchResultEnum.MATCH);
mdmLink.setGoldenResourcePid(mySourceEntity.getId());
mdmLink.setTargetPid(myTargetEntity.getId());
mdmLink.setSourcePid(myTargetEntity.getId());
saveLink(mdmLink);
}
@ -61,7 +61,7 @@ public class MdmExpungeTest extends BaseMdmR4Test {
fail();
} catch (InternalErrorException e) {
assertThat(e.getMessage(), containsString("ViolationException"));
assertThat(e.getMessage(), containsString("FK_EMPI_LINK_TARGET"));
assertThat(e.getMessage(), containsString("FK_MDM_LINK_SOURCE"));
}
myInterceptorService.registerInterceptor(myMdmStorageInterceptor);
myPatientDao.expunge(myTargetId.toVersionless(), expungeOptions, null);

View File

@ -20,7 +20,6 @@ import org.hl7.fhir.r4.model.Medication;
import org.hl7.fhir.r4.model.Organization;
import org.hl7.fhir.r4.model.Patient;
import org.hl7.fhir.r4.model.SearchParameter;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.slf4j.Logger;
@ -60,11 +59,6 @@ public class MdmStorageInterceptorIT extends BaseMdmR4Test {
@Autowired
private IdHelperService myIdHelperService;
@BeforeEach
public void before() {
super.loadMdmSearchParameters();
}
@Test
public void testCreatePractitioner() throws InterruptedException {
myMdmHelper.createWithLatch(buildPractitionerWithNameAndId("somename", "some_id"));
@ -196,7 +190,7 @@ public class MdmStorageInterceptorIT extends BaseMdmR4Test {
patient.setId(patientId);
// Updating a Golden Resource Patient who was created via MDM should fail.
MdmLink mdmLink = myMdmLinkDaoSvc.getMatchedLinkForTargetPid(myIdHelperService.getPidOrNull(patient)).get();
MdmLink mdmLink = myMdmLinkDaoSvc.getMatchedLinkForSourcePid(myIdHelperService.getPidOrNull(patient)).get();
Long sourcePatientPid = mdmLink.getGoldenResourcePid();
Patient goldenResourcePatient = (Patient) myPatientDao.readByPid(new ResourcePersistentId(sourcePatientPid));
goldenResourcePatient.setGender(Enumerations.AdministrativeGender.MALE);

View File

@ -46,7 +46,7 @@ public abstract class BaseGoldenResourceMatcher extends TypeSafeMatcher<IAnyReso
return null;
} else {
retval = matchLink.getGoldenResourcePid();
myTargetType = matchLink.getMdmTargetType();
myTargetType = matchLink.getMdmSourceType();
}
return retval;
}
@ -68,7 +68,7 @@ public abstract class BaseGoldenResourceMatcher extends TypeSafeMatcher<IAnyReso
protected List<MdmLink> getMdmLinksForTarget(IAnyResource theTargetResource, MdmMatchResultEnum theMatchResult) {
Long pidOrNull = myIdHelperService.getPidOrNull(theTargetResource);
List<MdmLink> matchLinkForTarget = myMdmLinkDaoSvc.getMdmLinksByTargetPidAndMatchResult(pidOrNull, theMatchResult);
List<MdmLink> matchLinkForTarget = myMdmLinkDaoSvc.getMdmLinksBySourcePidAndMatchResult(pidOrNull, theMatchResult);
if (!matchLinkForTarget.isEmpty()) {
return matchLinkForTarget;
} else {

View File

@ -22,7 +22,7 @@ public class IsMatchedToAGoldenResource extends TypeSafeMatcher<IAnyResource> {
@Override
protected boolean matchesSafely(IAnyResource theIncomingResource) {
Optional<MdmLink> matchedLinkForTargetPid = myMdmLinkDaoSvc.getMatchedLinkForTargetPid(myIdHelperService.getPidOrNull(theIncomingResource));
Optional<MdmLink> matchedLinkForTargetPid = myMdmLinkDaoSvc.getMatchedLinkForSourcePid(myIdHelperService.getPidOrNull(theIncomingResource));
return matchedLinkForTargetPid.isPresent();
}

View File

@ -36,9 +36,9 @@ public class IsPossibleDuplicateOf extends BaseGoldenResourceMatcher {
//Returns true if there is a POSSIBLE_DUPLICATE between the incoming resource, and all of the resources passed in via the constructor.
return goldenResourcePidsToMatch.stream()
.map(baseResourcePid -> {
Optional<MdmLink> duplicateLink = myMdmLinkDaoSvc.getMdmLinksByGoldenResourcePidTargetPidAndMatchResult(baseResourcePid, incomingGoldenResourcePid, MdmMatchResultEnum.POSSIBLE_DUPLICATE);
Optional<MdmLink> duplicateLink = myMdmLinkDaoSvc.getMdmLinksByGoldenResourcePidSourcePidAndMatchResult(baseResourcePid, incomingGoldenResourcePid, MdmMatchResultEnum.POSSIBLE_DUPLICATE);
if (!duplicateLink.isPresent()) {
duplicateLink = myMdmLinkDaoSvc.getMdmLinksByGoldenResourcePidTargetPidAndMatchResult(incomingGoldenResourcePid, baseResourcePid, MdmMatchResultEnum.POSSIBLE_DUPLICATE);
duplicateLink = myMdmLinkDaoSvc.getMdmLinksByGoldenResourcePidSourcePidAndMatchResult(incomingGoldenResourcePid, baseResourcePid, MdmMatchResultEnum.POSSIBLE_DUPLICATE);
}
return duplicateLink;
}).allMatch(Optional::isPresent);

View File

@ -61,11 +61,11 @@ public abstract class BaseLinkR4Test extends BaseProviderR4Test {
@Nonnull
protected MdmLink getOnlyPatientLink() {
return myMdmLinkDaoSvc.findMdmLinkByTarget(myPatient).get();
return myMdmLinkDaoSvc.findMdmLinkBySource(myPatient).get();
}
@Nonnull
protected List<MdmLink> getPatientLinks() {
return myMdmLinkDaoSvc.findMdmLinksByTarget(myPatient);
return myMdmLinkDaoSvc.findMdmLinksBySourceResource(myPatient);
}
}

View File

@ -73,7 +73,7 @@ public class MdmProviderBatchR4Test extends BaseLinkR4Test {
StringType criteria = null;
myMdmProviderR4.clearMdmLinks(null, myRequestDetails);
afterMdmLatch.runWithExpectedCount(1, () -> myMdmProviderR4.mdmBatchOnAllTargets(new StringType("Medication"), criteria, null));
afterMdmLatch.runWithExpectedCount(1, () -> myMdmProviderR4.mdmBatchOnAllSourceResources(new StringType("Medication"), criteria, null));
assertLinkCount(1);
}
@ -134,7 +134,7 @@ public class MdmProviderBatchR4Test extends BaseLinkR4Test {
StringType criteria = new StringType("");
myMdmProviderR4.clearMdmLinks(null, myRequestDetails);
afterMdmLatch.runWithExpectedCount(3, () -> {
myMdmProviderR4.mdmBatchOnAllTargets(null, criteria, null);
myMdmProviderR4.mdmBatchOnAllSourceResources(null, criteria, null);
});
assertLinkCount(3);
}

View File

@ -177,6 +177,6 @@ public class MdmProviderClearLinkR4Test extends BaseLinkR4Test {
@Nonnull
protected List<MdmLink> getPractitionerLinks() {
return myMdmLinkDaoSvc.findMdmLinksByTarget(myPractitioner);
return myMdmLinkDaoSvc.findMdmLinksBySourceResource(myPractitioner);
}
}

View File

@ -30,7 +30,6 @@ public class MdmProviderMatchR4Test extends BaseProviderR4Test {
@BeforeEach
public void before() {
super.before();
super.loadMdmSearchParameters();
}
@Test

View File

@ -31,7 +31,6 @@ public class MdmProviderMergeGoldenResourcesR4Test extends BaseProviderR4Test {
@BeforeEach
public void before() {
super.before();
super.loadMdmSearchParameters();
myFromGoldenPatient = createGoldenPatient();
myFromGoldenPatientId = new StringType(myFromGoldenPatient.getIdElement().getValue());
@ -58,11 +57,11 @@ public class MdmProviderMergeGoldenResourcesR4Test extends BaseProviderR4Test {
// Optional<Identifier> redirect = fromSourcePatient.getIdentifier().stream().filter(theIdentifier -> theIdentifier.getSystem().equals("REDIRECT")).findFirst();
// assertThat(redirect.get().getValue(), is(equalTo(myToSourcePatient.getIdElement().toUnqualified().getValue())));
List<MdmLink> links = myMdmLinkDaoSvc.findMdmLinksByTarget(myFromGoldenPatient);
List<MdmLink> links = myMdmLinkDaoSvc.findMdmLinksBySourceResource(myFromGoldenPatient);
assertThat(links, hasSize(1));
MdmLink link = links.get(0);
assertEquals(link.getTargetPid(), myFromGoldenPatient.getIdElement().toUnqualifiedVersionless().getIdPartAsLong());
assertEquals(link.getSourcePid(), myFromGoldenPatient.getIdElement().toUnqualifiedVersionless().getIdPartAsLong());
assertEquals(link.getGoldenResourcePid(), myToGoldenPatient.getIdElement().toUnqualifiedVersionless().getIdPartAsLong());
assertEquals(link.getMatchResult(), MdmMatchResultEnum.REDIRECT);
assertEquals(link.getLinkSource(), MdmLinkSourceEnum.MANUAL);

View File

@ -49,7 +49,7 @@ public class MdmProviderQueryLinkR4Test extends BaseLinkR4Test {
myGoldenResource2Id = new StringType(sourcePatient2.getIdElement().toVersionless().getValue());
Long sourcePatient2Pid = myIdHelperService.getPidOrNull(sourcePatient2);
MdmLink possibleDuplicateMdmLink = myMdmLinkDaoSvc.newMdmLink().setGoldenResourcePid(sourcePatient1Pid).setTargetPid(sourcePatient2Pid).setMatchResult(MdmMatchResultEnum.POSSIBLE_DUPLICATE).setLinkSource(MdmLinkSourceEnum.AUTO);
MdmLink possibleDuplicateMdmLink = myMdmLinkDaoSvc.newMdmLink().setGoldenResourcePid(sourcePatient1Pid).setSourcePid(sourcePatient2Pid).setMatchResult(MdmMatchResultEnum.POSSIBLE_DUPLICATE).setLinkSource(MdmLinkSourceEnum.AUTO);
saveLink(possibleDuplicateMdmLink);
}

View File

@ -1,50 +0,0 @@
package ca.uhn.fhir.jpa.mdm.searchparam;
import ca.uhn.fhir.jpa.mdm.BaseMdmR4Test;
import org.junit.jupiter.api.BeforeEach;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class SearchParameterTest extends BaseMdmR4Test {
private static final Logger ourLog = LoggerFactory.getLogger(SearchParameterTest.class);
@BeforeEach
public void before() {
super.loadMdmSearchParameters();
}
/**
* TODO GGG MDM ask ken if we still need this search parameter.
* The implementation this test tests will instead need to rely on MPI_LINK table?
*/
/*
@Test
public void testCanFindPossibleMatches() {
// Create a possible match
Patient patient = buildJanePatient();
patient.getNameFirstRep().setFamily("familyone");
patient = createPatientAndUpdateLinks(patient);
Patient patient2 = buildJanePatient();
patient2.getNameFirstRep().setFamily("pleasedonotmatchatall");
patient2 = createPatientAndUpdateLinks(patient2);
assertThat(patient2, is(possibleMatchWith(patient)));
// Now confirm we can find it using our custom search parameter
SearchParameterMap map = new SearchParameterMap();
map.setLoadSynchronous(true);
map.add("assurance", new TokenParam(Person.IdentityAssuranceLevel.LEVEL2.toCode()));
IBundleProvider result = myPersonDao.search(map);
assertEquals(1, result.size().intValue());
Person person = (Person) result.getResources(0, 1).get(0);
String encoded = myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(person);
ourLog.info("Search result: {}", encoded);
List<Person.PersonLinkComponent> links = person.getLink();
assertEquals(2, links.size());
assertEquals(Person.IdentityAssuranceLevel.LEVEL2, links.get(0).getAssurance());
assertEquals(Person.IdentityAssuranceLevel.LEVEL1, links.get(1).getAssurance());
}
*/
}

View File

@ -54,7 +54,7 @@ class MdmBatchSvcImplTest extends BaseMdmR4Test {
assertLinkCount(0);
//SUT
afterMdmLatch.runWithExpectedCount(30, () -> myMdmSubmitSvc.submitAllTargetTypesToMdm(null));
afterMdmLatch.runWithExpectedCount(30, () -> myMdmSubmitSvc.submitAllSourceTypesToMdm(null));
assertLinkCount(30);
}
@ -69,7 +69,7 @@ class MdmBatchSvcImplTest extends BaseMdmR4Test {
assertLinkCount(0);
//SUT
afterMdmLatch.runWithExpectedCount(10, () -> myMdmSubmitSvc.submitTargetTypeToMdm("Patient", null));
afterMdmLatch.runWithExpectedCount(10, () -> myMdmSubmitSvc.submitSourceResourceTypeToMdm("Patient", null));
assertLinkCount(10);
}
@ -86,7 +86,7 @@ class MdmBatchSvcImplTest extends BaseMdmR4Test {
assertLinkCount(0);
//SUT
afterMdmLatch.runWithExpectedCount(10, () -> myMdmSubmitSvc.submitTargetTypeToMdm("Medication", null));
afterMdmLatch.runWithExpectedCount(10, () -> myMdmSubmitSvc.submitSourceResourceTypeToMdm("Medication", null));
assertLinkCount(10);
}
@ -101,7 +101,7 @@ class MdmBatchSvcImplTest extends BaseMdmR4Test {
assertLinkCount(0);
//SUT
afterMdmLatch.runWithExpectedCount(10, () -> myMdmSubmitSvc.submitAllTargetTypesToMdm(null));
afterMdmLatch.runWithExpectedCount(10, () -> myMdmSubmitSvc.submitAllSourceTypesToMdm(null));
assertLinkCount(10);
}
@ -114,7 +114,7 @@ class MdmBatchSvcImplTest extends BaseMdmR4Test {
assertLinkCount(0);
//SUT
afterMdmLatch.runWithExpectedCount(1, () -> myMdmSubmitSvc.submitTargetTypeToMdm("Patient", "Patient?name=gary"));
afterMdmLatch.runWithExpectedCount(1, () -> myMdmSubmitSvc.submitSourceResourceTypeToMdm("Patient", "Patient?name=gary"));
assertLinkCount(1);
}

View File

@ -64,8 +64,6 @@ public class MdmGoldenResourceMergerSvcTest extends BaseMdmR4Test {
@BeforeEach
public void before() {
super.loadMdmSearchParameters();
myFromGoldenPatient = createGoldenPatient();
IdType fromSourcePatientId = myFromGoldenPatient.getIdElement().toUnqualifiedVersionless();
myFromGoldenPatientPid = myIdHelperService.getPidOrThrowException(fromSourcePatientId);
@ -124,8 +122,8 @@ public class MdmGoldenResourceMergerSvcTest extends BaseMdmR4Test {
public void mergeRemovesPossibleDuplicatesLink() {
MdmLink mdmLink = myMdmLinkDaoSvc.newMdmLink()
.setGoldenResourcePid(myToGoldenPatientPid)
.setTargetPid(myFromGoldenPatientPid)
.setMdmTargetType("Patient")
.setSourcePid(myFromGoldenPatientPid)
.setMdmSourceType("Patient")
.setMatchResult(MdmMatchResultEnum.POSSIBLE_DUPLICATE)
.setLinkSource(MdmLinkSourceEnum.AUTO);

View File

@ -90,13 +90,13 @@ public class MdmLinkSvcTest extends BaseMdmR4Test {
Long goldenPatient1Pid = myIdHelperService.getPidOrNull(goldenPatient1);
Long goldenPatient2Pid = myIdHelperService.getPidOrNull(goldenPatient2);
assertFalse(myMdmLinkDaoSvc.getLinkByGoldenResourcePidAndTargetResourcePid(goldenPatient1Pid, goldenPatient2Pid).isPresent());
assertFalse(myMdmLinkDaoSvc.getLinkByGoldenResourcePidAndTargetResourcePid(goldenPatient2Pid, goldenPatient1Pid).isPresent());
assertFalse(myMdmLinkDaoSvc.getLinkByGoldenResourcePidAndSourceResourcePid(goldenPatient1Pid, goldenPatient2Pid).isPresent());
assertFalse(myMdmLinkDaoSvc.getLinkByGoldenResourcePidAndSourceResourcePid(goldenPatient2Pid, goldenPatient1Pid).isPresent());
saveNoMatchLink(goldenPatient1Pid, goldenPatient2Pid);
myMdmLinkSvc.updateLink(goldenPatient1, goldenPatient2, MdmMatchOutcome.POSSIBLE_DUPLICATE, MdmLinkSourceEnum.AUTO, createContextForCreate("Patient"));
assertFalse(myMdmLinkDaoSvc.getMdmLinksByGoldenResourcePidTargetPidAndMatchResult(goldenPatient1Pid, goldenPatient2Pid, MdmMatchResultEnum.POSSIBLE_DUPLICATE).isPresent());
assertFalse(myMdmLinkDaoSvc.getMdmLinksByGoldenResourcePidSourcePidAndMatchResult(goldenPatient1Pid, goldenPatient2Pid, MdmMatchResultEnum.POSSIBLE_DUPLICATE).isPresent());
assertLinkCount(1);
}
@ -108,20 +108,20 @@ public class MdmLinkSvcTest extends BaseMdmR4Test {
Long goldenPatient1Pid = myIdHelperService.getPidOrNull(goldenPatient1);
Long goldenPatient2Pid = myIdHelperService.getPidOrNull(goldenPatient2);
assertFalse(myMdmLinkDaoSvc.getLinkByGoldenResourcePidAndTargetResourcePid(goldenPatient1Pid, goldenPatient2Pid).isPresent());
assertFalse(myMdmLinkDaoSvc.getLinkByGoldenResourcePidAndTargetResourcePid(goldenPatient2Pid, goldenPatient1Pid).isPresent());
assertFalse(myMdmLinkDaoSvc.getLinkByGoldenResourcePidAndSourceResourcePid(goldenPatient1Pid, goldenPatient2Pid).isPresent());
assertFalse(myMdmLinkDaoSvc.getLinkByGoldenResourcePidAndSourceResourcePid(goldenPatient2Pid, goldenPatient1Pid).isPresent());
saveNoMatchLink(goldenPatient2Pid, goldenPatient1Pid);
myMdmLinkSvc.updateLink(goldenPatient1, goldenPatient2, MdmMatchOutcome.POSSIBLE_DUPLICATE, MdmLinkSourceEnum.AUTO, createContextForCreate("Patient"));
assertFalse(myMdmLinkDaoSvc.getMdmLinksByGoldenResourcePidTargetPidAndMatchResult(goldenPatient1Pid, goldenPatient2Pid, MdmMatchResultEnum.POSSIBLE_DUPLICATE).isPresent());
assertFalse(myMdmLinkDaoSvc.getMdmLinksByGoldenResourcePidSourcePidAndMatchResult(goldenPatient1Pid, goldenPatient2Pid, MdmMatchResultEnum.POSSIBLE_DUPLICATE).isPresent());
assertLinkCount(1);
}
private void saveNoMatchLink(Long theGoldenResourcePid, Long theTargetPid) {
MdmLink noMatchLink = myMdmLinkDaoSvc.newMdmLink()
.setGoldenResourcePid(theGoldenResourcePid)
.setTargetPid(theTargetPid)
.setSourcePid(theTargetPid)
.setLinkSource(MdmLinkSourceEnum.MANUAL)
.setMatchResult(MdmMatchResultEnum.NO_MATCH);
saveLink(noMatchLink);
@ -177,7 +177,7 @@ public class MdmLinkSvcTest extends BaseMdmR4Test {
//assertEquals(patient1.getIdElement().toVersionless().getValue(), sourcePatient.getLinkFirstRep().getTarget().getReference());
List<String> actual = targets
.stream()
.map(link -> link.getTargetPid().toString())
.map(link -> link.getSourcePid().toString())
.collect(Collectors.toList());
List<String> expected = Arrays.asList(patient1, patient2)

View File

@ -1,14 +1,13 @@
package ca.uhn.fhir.jpa.mdm.svc;
import ca.uhn.fhir.jpa.entity.MdmLink;
import ca.uhn.fhir.jpa.mdm.BaseMdmR4Test;
import ca.uhn.fhir.mdm.api.MdmConstants;
import ca.uhn.fhir.mdm.model.CanonicalEID;
import ca.uhn.fhir.mdm.util.EIDHelper;
import ca.uhn.fhir.jpa.mdm.BaseMdmR4Test;
import ca.uhn.fhir.jpa.entity.MdmLink;
import org.hl7.fhir.instance.model.api.IAnyResource;
import org.hl7.fhir.r4.model.Identifier;
import org.hl7.fhir.r4.model.Patient;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
@ -37,11 +36,6 @@ public class MdmMatchLinkSvcMultipleEidModeTest extends BaseMdmR4Test {
@Autowired
private EIDHelper myEidHelper;
@BeforeEach
public void before() {
super.loadMdmSearchParameters();
}
@Test
public void testIncomingPatientWithEIDThatMatchesGoldenResourceWithHapiEidAddsExternalEidsToGoldenResource() {
// Existing GoldenResource with system-assigned EID found linked from matched Patient. incoming Patient has EID.
@ -156,7 +150,7 @@ public class MdmMatchLinkSvcMultipleEidModeTest extends BaseMdmR4Test {
//The two GoldenResources 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)));
assertThat(mdmLink.getSourcePid(), is(in(duplicatePids)));
}
@Test

View File

@ -1,16 +1,16 @@
package ca.uhn.fhir.jpa.mdm.svc;
import ca.uhn.fhir.jpa.entity.MdmLink;
import ca.uhn.fhir.jpa.mdm.BaseMdmR4Test;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.mdm.api.IMdmLinkSvc;
import ca.uhn.fhir.mdm.api.MdmConstants;
import ca.uhn.fhir.mdm.api.MdmLinkSourceEnum;
import ca.uhn.fhir.mdm.api.MdmMatchOutcome;
import ca.uhn.fhir.mdm.api.IMdmLinkSvc;
import ca.uhn.fhir.mdm.model.CanonicalEID;
import ca.uhn.fhir.mdm.util.EIDHelper;
import ca.uhn.fhir.mdm.util.MdmUtil;
import ca.uhn.fhir.mdm.util.GoldenResourceHelper;
import ca.uhn.fhir.jpa.mdm.BaseMdmR4Test;
import ca.uhn.fhir.jpa.entity.MdmLink;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.mdm.util.MdmUtil;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.param.TokenParam;
import org.hl7.fhir.instance.model.api.IAnyResource;
@ -19,7 +19,6 @@ import org.hl7.fhir.r4.model.HumanName;
import org.hl7.fhir.r4.model.Identifier;
import org.hl7.fhir.r4.model.Patient;
import org.hl7.fhir.r4.model.Practitioner;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
@ -57,11 +56,6 @@ public class MdmMatchLinkSvcTest extends BaseMdmR4Test {
@Autowired
private GoldenResourceHelper myGoldenResourceHelper;
@BeforeEach
public void before() {
super.loadMdmSearchParameters();
}
@Test
public void testAddPatientLinksToNewGoldenResourceIfNoneFound() {
createPatientAndUpdateLinks(buildJanePatient());
@ -120,7 +114,7 @@ public class MdmMatchLinkSvcTest extends BaseMdmR4Test {
myMdmLinkSvc.updateLink(janeGoldenResource, unmatchedJane, MdmMatchOutcome.NO_MATCH, MdmLinkSourceEnum.MANUAL, createContextForCreate("Patient"));
//rerun MDM rules against unmatchedJane.
myMdmMatchLinkSvc.updateMdmLinksForMdmTarget(unmatchedJane, createContextForCreate("Patient"));
myMdmMatchLinkSvc.updateMdmLinksForMdmSource(unmatchedJane, createContextForCreate("Patient"));
assertThat(unmatchedJane, is(not(sameGoldenResourceAs(janeGoldenResource))));
assertThat(unmatchedJane, is(not(linkedTo(originalJane))));
@ -145,7 +139,7 @@ public class MdmMatchLinkSvcTest extends BaseMdmR4Test {
//Now normally, when we run update links, it should link to janeGoldenResource. However, this manual NO_MATCH link
//should cause a whole new GoldenResource to be created.
myMdmMatchLinkSvc.updateMdmLinksForMdmTarget(unmatchedPatient, createContextForCreate("Patient"));
myMdmMatchLinkSvc.updateMdmLinksForMdmSource(unmatchedPatient, createContextForCreate("Patient"));
assertThat(unmatchedPatient, is(not(sameGoldenResourceAs(janeGoldenResource))));
assertThat(unmatchedPatient, is(not(linkedTo(originalJane))));
@ -161,7 +155,7 @@ public class MdmMatchLinkSvcTest extends BaseMdmR4Test {
Patient janePatient = addExternalEID(buildJanePatient(), sampleEID);
janePatient = createPatientAndUpdateLinks(janePatient);
Optional<MdmLink> mdmLink = myMdmLinkDaoSvc.getMatchedLinkForTargetPid(janePatient.getIdElement().getIdPartAsLong());
Optional<MdmLink> mdmLink = myMdmLinkDaoSvc.getMatchedLinkForSourcePid(janePatient.getIdElement().getIdPartAsLong());
assertThat(mdmLink.isPresent(), is(true));
Patient patient = getTargetResourceFromMdmLink(mdmLink.get(), "Patient");
@ -174,7 +168,7 @@ public class MdmMatchLinkSvcTest extends BaseMdmR4Test {
@Test
public void testWhenPatientIsCreatedWithoutAnEIDTheGoldenResourceGetsAutomaticallyAssignedOne() {
Patient patient = createPatientAndUpdateLinks(buildJanePatient());
MdmLink mdmLink = myMdmLinkDaoSvc.getMatchedLinkForTargetPid(patient.getIdElement().getIdPartAsLong()).get();
MdmLink mdmLink = myMdmLinkDaoSvc.getMatchedLinkForSourcePid(patient.getIdElement().getIdPartAsLong()).get();
Patient targetPatient = getTargetResourceFromMdmLink(mdmLink, "Patient");
Identifier identifierFirstRep = targetPatient.getIdentifierFirstRep();
@ -186,7 +180,7 @@ public class MdmMatchLinkSvcTest extends BaseMdmR4Test {
public void testPatientAttributesAreCopiedOverWhenGoldenResourceIsCreatedFromPatient() {
Patient patient = createPatientAndUpdateLinks(buildPatientWithNameIdAndBirthday("Gary", "GARY_ID", new Date()));
Optional<MdmLink> mdmLink = myMdmLinkDaoSvc.getMatchedLinkForTargetPid(patient.getIdElement().getIdPartAsLong());
Optional<MdmLink> mdmLink = myMdmLinkDaoSvc.getMatchedLinkForSourcePid(patient.getIdElement().getIdPartAsLong());
Patient read = getTargetResourceFromMdmLink(mdmLink.get(), "Patient");
// TODO NG - rules haven't been determined yet revisit once implemented...
@ -288,7 +282,7 @@ public class MdmMatchLinkSvcTest extends BaseMdmR4Test {
//The two GoldenResources 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)));
assertThat(mdmLink.getSourcePid(), is(in(duplicatePids)));
}
@Test
@ -358,7 +352,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 GoldenResource for the purpose of this test.
IAnyResource goldenResource = myGoldenResourceHelper.createGoldenResourceFromMdmTarget(janePatient2);
IAnyResource goldenResource = myGoldenResourceHelper.createGoldenResourceFromMdmSourceResource(janePatient2);
myMdmLinkSvc.updateLink(goldenResource, janePatient2, MdmMatchOutcome.NEW_GOLDEN_RESOURCE_MATCH, MdmLinkSourceEnum.AUTO, createContextForCreate("Patient"));
assertThat(janePatient, is(not(sameGoldenResourceAs(janePatient2))));
@ -372,7 +366,7 @@ public class MdmMatchLinkSvcTest extends BaseMdmR4Test {
assertThat(incomingJanePatient, is(possibleMatchWith(janePatient, janePatient2)));
//Ensure there is no successful MATCH links for incomingJanePatient
Optional<MdmLink> matchedLinkForTargetPid = myMdmLinkDaoSvc.getMatchedLinkForTargetPid(myIdHelperService.getPidOrNull(incomingJanePatient));
Optional<MdmLink> matchedLinkForTargetPid = myMdmLinkDaoSvc.getMatchedLinkForSourcePid(myIdHelperService.getPidOrNull(incomingJanePatient));
assertThat(matchedLinkForTargetPid.isPresent(), is(false));
logAllLinks();
@ -449,7 +443,7 @@ public class MdmMatchLinkSvcTest extends BaseMdmR4Test {
public void testCreateGoldenResourceFromMdmTarget() {
// Create Use Case #2 - adding patient with no EID
Patient janePatient = buildJanePatient();
Patient janeGoldenResourcePatient = myGoldenResourceHelper.createGoldenResourceFromMdmTarget(janePatient);
Patient janeGoldenResourcePatient = myGoldenResourceHelper.createGoldenResourceFromMdmSourceResource(janePatient);
// golden record now contains HAPI-generated EID and HAPI tag
assertTrue(MdmUtil.isMdmManaged(janeGoldenResourcePatient));

View File

@ -1,10 +1,9 @@
package ca.uhn.fhir.jpa.mdm.svc;
import ca.uhn.fhir.mdm.util.MdmUtil;
import ca.uhn.fhir.jpa.mdm.BaseMdmR4Test;
import ca.uhn.fhir.mdm.util.MdmUtil;
import org.hl7.fhir.instance.model.api.IAnyResource;
import org.hl7.fhir.r4.model.Patient;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
@ -19,11 +18,6 @@ public class MdmResourceDaoSvcTest extends BaseMdmR4Test {
@Autowired
MdmResourceDaoSvc myResourceDaoSvc;
@BeforeEach
public void before() {
super.loadMdmSearchParameters();
}
@Test
public void testSearchPatientByEidExcludesNonGoldenPatients() {
Patient goodSourcePatient = addExternalEID(createGoldenPatient(), TEST_EID);

View File

@ -21,11 +21,6 @@ package ca.uhn.fhir.jpa.migrate.tasks;
*/
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.entity.MdmLink;
import ca.uhn.fhir.jpa.entity.Search;
import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion;
import ca.uhn.fhir.jpa.entity.TermConceptMap;
import ca.uhn.fhir.jpa.entity.TermValueSet;
import ca.uhn.fhir.jpa.migrate.DriverTypeEnum;
import ca.uhn.fhir.jpa.migrate.taskdef.ArbitrarySqlTask;
import ca.uhn.fhir.jpa.migrate.taskdef.CalculateHashesTask;
@ -85,9 +80,27 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks<VersionEnum> {
mdmLink.addColumn("20201029.1", "GOLDEN_RESOURCE_PID").nonNullable().type(ColumnTypeEnum.LONG);
mdmLink.addColumn("20201029.2", "RULE_COUNT").nullable().type(ColumnTypeEnum.LONG);
mdmLink
.addForeignKey("20201029.3", "FK_EMPI_LINK_GOLDEN_RESOURCE")
.addForeignKey("20201029.3", "FK_MDM_LINK_GOLDEN_RESOURCE")
.toColumn("GOLDEN_RESOURCE_PID")
.references("HFJ_RESOURCE", "RES_ID");
mdmLink.dropIndex("20201029.4", "IDX_EMPI_PERSON_TGT");
mdmLink.dropForeignKey("20201029.5", "FK_EMPI_LINK_TARGET", "HFJ_RESOURCE");
mdmLink.dropForeignKey("20201029.6", "FK_EMPI_LINK_PERSON", "HFJ_RESOURCE");
mdmLink.renameColumn("20201029.7", "NEW_PERSON", "NEW_GOLDEN_RESOURCE");
mdmLink.renameColumn("20201029.8", "TARGET_PID", "SOURCE_PID");
mdmLink.renameColumn("20201029.9", "TARGET_TYPE", "SOURCE_TYPE");
mdmLink
.addForeignKey("20201029.10", "FK_MDM_LINK_SOURCE")
.toColumn("SOURCE_PID")
.references("HFJ_RESOURCE", "RES_ID");
mdmLink.addIndex("20201029.11", "IDX_MDM_GOLDEN_RESOURCE_SRC")
.unique(true).withColumns("GOLDEN_RESOURCE_PID", "SOURCE_PID");
mdmLink.dropColumn("20201029.12", "PERSON_PID");
}
protected void init510() {

View File

@ -28,7 +28,7 @@ import java.util.stream.Stream;
public interface IMdmControllerSvc {
Stream<MdmLinkJson> queryLinks(@Nullable String theGoldenResourceId, @Nullable String theTargetId, @Nullable String theMatchResult, @Nullable String theLinkSource, MdmTransactionContext theMdmTransactionContext);
Stream<MdmLinkJson> queryLinks(@Nullable String theGoldenResourceId, @Nullable String theSourceResourceId, @Nullable String theMatchResult, @Nullable String theLinkSource, MdmTransactionContext theMdmTransactionContext);
Stream<MdmLinkJson> getDuplicateGoldenResources(MdmTransactionContext theMdmTransactionContext);
@ -36,5 +36,5 @@ public interface IMdmControllerSvc {
IAnyResource mergeGoldenResources(String theFromGoldenResourceId, String theToGoldenResourceId, MdmTransactionContext theMdmTransactionContext);
IAnyResource updateLink(String theGoldenResourceId, String theTargetId, String theMatchResult, MdmTransactionContext theMdmTransactionContext);
IAnyResource updateLink(String theGoldenResourceId, String theSourceResourceId, String theMatchResult, MdmTransactionContext theMdmTransactionContext);
}

View File

@ -27,11 +27,11 @@ public interface IMdmExpungeSvc {
/**
* Given a resource type, delete the underlying MDM links, and their related golden resource objects.
*
* @param theResourceType The type of resources
* @param theSourceResourceType The type of resources
* @param theRequestDetails
* @return the count of deleted MDM links
*/
long expungeAllMdmLinksOfTargetType(String theResourceType, ServletRequestDetails theRequestDetails);
long expungeAllMdmLinksOfSourceType(String theSourceResourceType, ServletRequestDetails theRequestDetails);
/**
* Delete all MDM links, and their related golden resource objects.

View File

@ -29,6 +29,6 @@ import java.util.stream.Stream;
* This service supports the MDM operation providers for those services that return multiple MDM links.
*/
public interface IMdmLinkQuerySvc {
Stream<MdmLinkJson> queryLinks(IIdType theGoldenResourceId, IIdType theTargetId, MdmMatchResultEnum theMatchResult, MdmLinkSourceEnum theLinkSource, MdmTransactionContext theMdmContext);
Stream<MdmLinkJson> queryLinks(IIdType theGoldenResourceId, IIdType theSourceResourceId, MdmMatchResultEnum theMatchResult, MdmLinkSourceEnum theLinkSource, MdmTransactionContext theMdmContext);
Stream<MdmLinkJson> getDuplicateGoldenResources(MdmTransactionContext theMdmContext);
}

View File

@ -26,22 +26,23 @@ import org.hl7.fhir.instance.model.api.IAnyResource;
public interface IMdmLinkSvc {
/**
* Update a link between a Golden Resource record and its target record. If a link does not exist between
* Update a link between a Golden Resource record and its source resource record. If a link does not exist between
* these two records, create it.
*
* @param theGoldenResource the Golden Resource to link the target resource to.
* @param theTargetResource the target resource, which can be of the MDM supported types
* @param theGoldenResource the Golden Resource to link the source resource to.
* @param theSourceResource the source resource, which can be of any of the MDM supported types
* @param theMatchResult the current status of the match to set the link to.
* @param theLinkSource MANUAL or AUTO: what caused the link.
* @param theMdmTransactionContext
*/
void updateLink(IAnyResource theGoldenResource, IAnyResource theTargetResource, MdmMatchOutcome theMatchResult, MdmLinkSourceEnum theLinkSource, MdmTransactionContext theMdmTransactionContext);
void updateLink(IAnyResource theGoldenResource, IAnyResource theSourceResource, MdmMatchOutcome theMatchResult, MdmLinkSourceEnum theLinkSource, MdmTransactionContext theMdmTransactionContext);
/**
* Delete a link between given Golden Resource and the corresponing target
* Delete a link between given Golden Resource and the corresponding source resource
*
* @param theExistingGoldenResource
* @param theResource
* @param theSourceResource
* @param theMdmTransactionContext
*/
void deleteLink(IAnyResource theExistingGoldenResource, IAnyResource theResource, MdmTransactionContext theMdmTransactionContext);
void deleteLink(IAnyResource theExistingGoldenResource, IAnyResource theSourceResource, MdmTransactionContext theMdmTransactionContext);
}

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 theGoldenResource, IAnyResource theTarget, MdmMatchResultEnum theMatchResult, MdmTransactionContext theMdmContext);
void notDuplicateGoldenResource(IAnyResource theGoldenResource, IAnyResource theTarget, MdmTransactionContext theMdmContext);
IAnyResource updateLink(IAnyResource theGoldenResource, IAnyResource theSourceResource, MdmMatchResultEnum theMatchResult, MdmTransactionContext theMdmContext);
void notDuplicateGoldenResource(IAnyResource theGoldenResource, IAnyResource theTargetGoldenResource, MdmTransactionContext theMdmContext);
}

View File

@ -26,6 +26,7 @@ import javax.annotation.Nonnull;
import java.util.List;
public interface IMdmMatchFinderSvc {
/**
* Retrieve a list of possible target candidates for matching, based on the given {@link IAnyResource}
* Internally, performs all MDM matching rules on the type of the resource.

View File

@ -28,6 +28,7 @@ public interface IMdmSubmitSvc {
/**
* Submit all eligible resources for MDM processing.
*
* @param theCriteria The FHIR search critieria for filtering the resources to be submitted for MDM processing.
* NOTE:
* When using this function, the criteria supplied must be valid for all MDM types. e.g. , if you
@ -36,19 +37,19 @@ public interface IMdmSubmitSvc {
*
* @return
*/
long submitAllTargetTypesToMdm(@Nullable String theCriteria);
long submitAllSourceTypesToMdm(@Nullable String theCriteria);
/**
* Given a type and a search criteria, submit all found resources for MDM processing.
*
* @param theTargetType the resource type that you wish to execute a search over for submission to MDM.
* @param theSourceResourceType the resource type that you wish to execute a search over for submission to MDM.
* @param theCriteria The FHIR search critieria for filtering the resources to be submitted for MDM processing..
* @return the number of resources submitted for MDM processing.
*/
long submitTargetTypeToMdm(String theTargetType, String theCriteria);
long submitSourceResourceTypeToMdm(String theSourceResourceType, String theCriteria);
/**
* Convenience method that calls {@link #submitTargetTypeToMdm(String, String)} with the type pre-populated.
* Convenience method that calls {@link #submitSourceResourceTypeToMdm(String, String)} with the type pre-populated.
*
* @param theCriteria The FHIR search critieria for filtering the resources to be submitted for MDM processing.
* @return the number of resources submitted for MDM processing.
@ -56,7 +57,7 @@ public interface IMdmSubmitSvc {
long submitPractitionerTypeToMdm(String theCriteria);
/**
* Convenience method that calls {@link #submitTargetTypeToMdm(String, String)} with the type pre-populated.
* Convenience method that calls {@link #submitSourceResourceTypeToMdm(String, String)} with the type pre-populated.
*
* @param theCriteria The FHIR search critieria for filtering the resources to be submitted for MDM processing.
* @return the number of resources submitted for MDM processing.
@ -64,11 +65,12 @@ public interface IMdmSubmitSvc {
long submitPatientTypeToMdm(String theCriteria);
/**
* Given an ID and a target type valid for MDM, manually submit the given ID for MDM processing.
* Given an ID and a source resource type valid for MDM, manually submit the given ID for MDM processing.
*
* @param theId the ID of the resource to process for MDM.
* @return the constant `1`, as if this function returns successfully, it will have processed one resource for MDM.
*/
long submitTargetToMdm(IIdType theId);
long submitSourceResourceToMdm(IIdType theId);
/**
* This setter exists to allow imported modules to override settings.

View File

@ -30,8 +30,8 @@ public class MdmLinkJson implements IModelJson {
@JsonProperty("goldenResourceId")
private String myGoldenResourceId;
@JsonProperty("targetId")
private String myTargetId;
@JsonProperty("sourceId")
private String mySourceId;
@JsonProperty("matchResult")
private MdmMatchResultEnum myMatchResult;
@ -71,12 +71,12 @@ public class MdmLinkJson implements IModelJson {
return this;
}
public String getTargetId() {
return myTargetId;
public String getSourceId() {
return mySourceId;
}
public MdmLinkJson setTargetId(String theTargetId) {
myTargetId = theTargetId;
public MdmLinkJson setSourceId(String theSourceId) {
mySourceId = theSourceId;
return this;
}

View File

@ -43,14 +43,13 @@ public enum MdmMatchResultEnum {
POSSIBLE_DUPLICATE,
/**
* Link between Golden Record and Target pointing to the Golden Record for that Target
* Link between Golden Record and Source Resource pointing to the Golden Record for that Source Resource
*/
GOLDEN_RECORD,
/**
* Link between two Golden Resources resulting from a merge. One golden resource is deactivated. The inactive golden
* resource points to the active golden resource after the merge. The target resource points to the inactive golden
* resource points to the active golden resource after the merge. The source resource points to the inactive golden
* resource after the merge.
*/
REDIRECT

View File

@ -100,7 +100,7 @@ public abstract class BaseMdmProvider {
theMdmLinkStream.forEach(mdmLink -> {
IBase resultPart = ParametersUtil.addParameterToParameters(myFhirContext, retval, "link");
ParametersUtil.addPartString(myFhirContext, resultPart, "goldenResourceId", mdmLink.getGoldenResourceId());
ParametersUtil.addPartString(myFhirContext, resultPart, "targetResourceId", mdmLink.getTargetId());
ParametersUtil.addPartString(myFhirContext, resultPart, "sourceResourceId", mdmLink.getSourceId());
if (includeResultAndSource) {
ParametersUtil.addPartString(myFhirContext, resultPart, "matchResult", mdmLink.getMatchResult().name());

View File

@ -67,9 +67,9 @@ public class MdmControllerHelper {
}
public IAnyResource getLatestTargetFromIdOrThrowException(String theParamName, String theId) {
IIdType targetId = MdmControllerUtil.getTargetIdDtOrThrowException(theParamName, theId);
return loadResource(targetId.toUnqualifiedVersionless());
public IAnyResource getLatestSourceFromIdOrThrowException(String theParamName, String theSourceId) {
IIdType sourceId = MdmControllerUtil.getSourceIdDtOrThrowException(theParamName, theSourceId);
return loadResource(sourceId.toUnqualifiedVersionless());
}
protected IAnyResource loadResource(IIdType theResourceId) {

View File

@ -48,11 +48,11 @@ public class MdmControllerUtil {
return getGoldenIdDtOrThrowException(theName, theGoldenResourceId);
}
public static IIdType extractTargetIdDtOrNull(String theName, String theTargetId) {
if (theTargetId == null) {
public static IIdType extractSourceIdDtOrNull(String theName, String theSourceId) {
if (theSourceId == null) {
return null;
}
return getTargetIdDtOrThrowException(theName, theTargetId);
return getSourceIdDtOrThrowException(theName, theSourceId);
}
static IdDt getGoldenIdDtOrThrowException(String theParamName, String theId) {
@ -64,11 +64,11 @@ public class MdmControllerUtil {
return goldenResourceId;
}
public static IIdType getTargetIdDtOrThrowException(String theParamName, String theId) {
IdDt targetId = new IdDt(theId);
if (targetId.getIdPart() == null) {
throw new InvalidRequestException(theParamName + " is '" + theId + "'. must have form <resourceType>/<id> where <id> is the id of the resource and <resourceType> is the type of the resource");
public static IIdType getSourceIdDtOrThrowException(String theParamName, String theSourceId) {
IdDt sourceId = new IdDt(theSourceId);
if (sourceId.getIdPart() == null) {
throw new InvalidRequestException(theParamName + " is '" + theSourceId + "'. must have form <resourceType>/<id> where <id> is the id of the resource and <resourceType> is the type of the resource");
}
return targetId;
return sourceId;
}
}

View File

@ -163,13 +163,13 @@ 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 clearMdmLinks(@OperationParam(name=ProviderConstants.MDM_CLEAR_TARGET_TYPE, min = 0, max = 1) StringType theTargetType,
public Parameters clearMdmLinks(@OperationParam(name=ProviderConstants.MDM_CLEAR_SOURCE_TYPE, min = 0, max = 1) StringType theSourceResourceType,
ServletRequestDetails theRequestDetails) {
long resetCount;
if (theTargetType == null || StringUtils.isBlank(theTargetType.getValue())) {
if (theSourceResourceType == null || StringUtils.isBlank(theSourceResourceType.getValue())) {
resetCount = myMdmExpungeSvc.expungeAllMdmLinks(theRequestDetails);
} else {
resetCount = myMdmExpungeSvc.expungeAllMdmLinksOfTargetType(theTargetType.getValueNotNull(), theRequestDetails);
resetCount = myMdmExpungeSvc.expungeAllMdmLinksOfSourceType(theSourceResourceType.getValueNotNull(), theRequestDetails);
}
Parameters parameters = new Parameters();
parameters.addParameter().setName(ProviderConstants.OPERATION_MDM_CLEAR_OUT_PARAM_DELETED_COUNT)
@ -219,7 +219,7 @@ 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= IntegerType.class)
})
public Parameters mdmBatchOnAllTargets(
public Parameters mdmBatchOnAllSourceResources(
@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,
ServletRequestDetails theRequestDetails) {
@ -228,9 +228,9 @@ public class MdmProviderDstu3 extends BaseMdmProvider {
long submittedCount;
if (resourceType != null) {
submittedCount = myMdmSubmitSvc.submitTargetTypeToMdm(resourceType, criteria);
submittedCount = myMdmSubmitSvc.submitSourceResourceTypeToMdm(resourceType, criteria);
} else {
submittedCount = myMdmSubmitSvc.submitAllTargetTypesToMdm(criteria);
submittedCount = myMdmSubmitSvc.submitAllSourceTypesToMdm(criteria);
}
return buildMdmOutParametersWithCount(submittedCount);
}
@ -245,7 +245,7 @@ public class MdmProviderDstu3 extends BaseMdmProvider {
public Parameters mdmBatchPatientInstance(
@IdParam IIdType theIdParam,
RequestDetails theRequest) {
long submittedCount = myMdmSubmitSvc.submitTargetToMdm(theIdParam);
long submittedCount = myMdmSubmitSvc.submitSourceResourceToMdm(theIdParam);
return buildMdmOutParametersWithCount(submittedCount);
}
@ -266,7 +266,7 @@ public class MdmProviderDstu3 extends BaseMdmProvider {
public Parameters mdmBatchPractitionerInstance(
@IdParam IIdType theIdParam,
RequestDetails theRequest) {
long submittedCount = myMdmSubmitSvc.submitTargetToMdm(theIdParam);
long submittedCount = myMdmSubmitSvc.submitSourceResourceToMdm(theIdParam);
return buildMdmOutParametersWithCount(submittedCount);
}

View File

@ -164,13 +164,13 @@ public class MdmProviderR4 extends BaseMdmProvider {
@Operation(name = ProviderConstants.MDM_CLEAR, returnParameters = {
@OperationParam(name = ProviderConstants.OPERATION_MDM_BATCH_RUN_OUT_PARAM_SUBMIT_COUNT, type=DecimalType.class)
})
public Parameters clearMdmLinks(@OperationParam(name=ProviderConstants.MDM_CLEAR_TARGET_TYPE, min = 0, max = 1) StringType theTargetType,
public Parameters clearMdmLinks(@OperationParam(name=ProviderConstants.MDM_CLEAR_SOURCE_TYPE, min = 0, max = 1) StringType theSourceType,
ServletRequestDetails theRequestDetails) {
long resetCount;
if (theTargetType == null || StringUtils.isBlank(theTargetType.getValue())) {
if (theSourceType == null || StringUtils.isBlank(theSourceType.getValue())) {
resetCount = myMdmExpungeSvc.expungeAllMdmLinks(theRequestDetails);
} else {
resetCount = myMdmExpungeSvc.expungeAllMdmLinksOfTargetType(theTargetType.getValueNotNull(), theRequestDetails);
resetCount = myMdmExpungeSvc.expungeAllMdmLinksOfSourceType(theSourceType.getValueNotNull(), theRequestDetails);
}
Parameters parameters = new Parameters();
parameters.addParameter().setName(ProviderConstants.OPERATION_MDM_CLEAR_OUT_PARAM_DELETED_COUNT)
@ -221,7 +221,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 mdmBatchOnAllTargets(
public Parameters mdmBatchOnAllSourceResources(
@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,
ServletRequestDetails theRequestDetails) {
@ -230,9 +230,9 @@ public class MdmProviderR4 extends BaseMdmProvider {
long submittedCount;
if (resourceType != null) {
submittedCount = myMdmSubmitSvc.submitTargetTypeToMdm(resourceType, criteria);
submittedCount = myMdmSubmitSvc.submitSourceResourceTypeToMdm(resourceType, criteria);
} else {
submittedCount = myMdmSubmitSvc.submitAllTargetTypesToMdm(criteria);
submittedCount = myMdmSubmitSvc.submitAllSourceTypesToMdm(criteria);
}
return buildMdmOutParametersWithCount(submittedCount);
}
@ -248,7 +248,7 @@ public class MdmProviderR4 extends BaseMdmProvider {
public Parameters mdmBatchPatientInstance(
@IdParam IIdType theIdParam,
RequestDetails theRequest) {
long submittedCount = myMdmSubmitSvc.submitTargetToMdm(theIdParam);
long submittedCount = myMdmSubmitSvc.submitSourceResourceToMdm(theIdParam);
return buildMdmOutParametersWithCount(submittedCount);
}
@ -269,7 +269,7 @@ public class MdmProviderR4 extends BaseMdmProvider {
public Parameters mdmBatchPractitionerInstance(
@IdParam IIdType theIdParam,
RequestDetails theRequest) {
long submittedCount = myMdmSubmitSvc.submitTargetToMdm(theIdParam);
long submittedCount = myMdmSubmitSvc.submitSourceResourceToMdm(theIdParam);
return buildMdmOutParametersWithCount(submittedCount);
}

View File

@ -42,7 +42,7 @@ public class MdmSettings implements IMdmSettings {
/**
* 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.
* 1. Source 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

@ -120,25 +120,22 @@ public class MdmResourceMatcherSvc {
int appliedRuleCount = 0;
//TODO GGG MDM: This grabs ALL comparators, not just the ones we care about (e.g. the ones for Medication)
String resourceType = myFhirContext.getResourceType(theLeftResource);
for (int i = 0; i < myFieldMatchers.size(); ++i) {
//any that are not for the resourceType in question.
MdmResourceFieldMatcher fieldComparator = myFieldMatchers.get(i);
if (!isValidResourceType(resourceType, fieldComparator.getResourceType())) {
ourLog.debug("Matcher {} is not valid for resource type: {}. Skipping it.", fieldComparator.getName(), resourceType);
continue;
} else {
ourLog.debug("Matcher {} is valid for resource type: {}. Evaluating match.", fieldComparator.getName(), resourceType);
MdmMatchEvaluation matchEvaluation = fieldComparator.match(theLeftResource, theRightResource);
if (matchEvaluation.match) {
vector |= (1 << i);
}
score += matchEvaluation.score;
appliedRuleCount += 1;
}
ourLog.debug("Matcher {} is valid for resource type: {}. Evaluating match.", fieldComparator.getName(), resourceType);
MdmMatchEvaluation matchEvaluation = fieldComparator.match(theLeftResource, theRightResource);
if (matchEvaluation.match) {
vector |= (1 << i);
}
score += matchEvaluation.score;
appliedRuleCount += 1;
}
MdmMatchOutcome retVal = new MdmMatchOutcome(vector, score);

View File

@ -97,7 +97,7 @@ public final class EIDHelper {
}
/**
* An incoming resource is a potential duplicate if it matches a target resource that has a golden resource with an
* An incoming resource is a potential duplicate if it matches a source resource that has a golden resource with an
* official EID, but the incoming resource also has an EID that does not match.
*/
public boolean hasEidOverlap(IAnyResource theExistingGoldenResource, IAnyResource theComparingGoldenResource) {

View File

@ -76,7 +76,7 @@ public class GoldenResourceHelper {
* @param <T> Supported MDM resource type (e.g. Patient, Practitioner)
* @param theIncomingResource The resource that will be used as the starting point for the MDM linking.
*/
public <T extends IAnyResource> T createGoldenResourceFromMdmTarget(T theIncomingResource) {
public <T extends IAnyResource> T createGoldenResourceFromMdmSourceResource(T theIncomingResource) {
validateContextSupported();
// get a ref to the actual ID Field
@ -101,7 +101,7 @@ public class GoldenResourceHelper {
*/
//TODO GGG ask james if there is any way we can convert this canonical EID into a generic STU-agnostic IBase.
private <T extends IAnyResource> void addHapiEidIfNoExternalEidIsPresent(
IBaseResource theNewGoldenResource, BaseRuntimeChildDefinition theGoldenResourceIdentifier, IAnyResource theTargetResource) {
IBaseResource theNewGoldenResource, BaseRuntimeChildDefinition theGoldenResourceIdentifier, IAnyResource theSourceResource) {
List<CanonicalEID> eidsToApply = myEIDHelper.getExternalEid(theNewGoldenResource);
if (!eidsToApply.isEmpty()) {
@ -111,8 +111,8 @@ public class GoldenResourceHelper {
CanonicalEID hapiEid = myEIDHelper.createHapiEid();
theGoldenResourceIdentifier.getMutator().addValue(theNewGoldenResource, toId(hapiEid));
// set identifier on the target resource
cloneEidIntoResource(theTargetResource, hapiEid);
// set identifier on the source resource
cloneEidIntoResource(theSourceResource, hapiEid);
}
private void cloneEidIntoResource(IBaseResource theResourceToCloneInto, CanonicalEID theEid) {
@ -204,35 +204,35 @@ public class GoldenResourceHelper {
}
/**
* Updates EID on Golden Resource, based on the incoming target resource. If the incoming resource has an external EID, it is applied
* Updates EID on Golden Resource, based on the incoming source resource. If the incoming resource has an external EID, it is applied
* to the Golden Resource, unless that golden resource already has an external EID which does not match, in which case throw {@link IllegalArgumentException}
* <p>
* If running in multiple EID mode, then incoming EIDs are simply added to the Golden Resource without checking for matches.
*
* @param theGoldenResource The golden resource to update the external EID on.
* @param theTargetResource The target we will retrieve the external EID from.
* @param theSourceResource The source we will retrieve the external EID from.
* @return the modified {@link IBaseResource} representing the Golden Resource.
*/
public IAnyResource updateGoldenResourceExternalEidFromTargetResource(IAnyResource theGoldenResource, IAnyResource
theTargetResource, MdmTransactionContext theMdmTransactionContext) {
public IAnyResource updateGoldenResourceExternalEidFromSourceResource(IAnyResource theGoldenResource, IAnyResource
theSourceResource, MdmTransactionContext theMdmTransactionContext) {
//This handles overwriting an automatically assigned EID if a patient that links is coming in with an official EID.
List<CanonicalEID> incomingTargetEid = myEIDHelper.getExternalEid(theTargetResource);
List<CanonicalEID> incomingSourceEid = myEIDHelper.getExternalEid(theSourceResource);
List<CanonicalEID> goldenResourceOfficialEid = myEIDHelper.getExternalEid(theGoldenResource);
if (incomingTargetEid.isEmpty()) {
if (incomingSourceEid.isEmpty()) {
return theGoldenResource;
}
if (goldenResourceOfficialEid.isEmpty() || !myMdmSettings.isPreventMultipleEids()) {
log(theMdmTransactionContext, "Incoming resource:" + theTargetResource.getIdElement().toUnqualifiedVersionless() + " + with EID " + incomingTargetEid.stream().map(CanonicalEID::toString).collect(Collectors.joining(","))
+ " is applying this EIDs to its related Target Resource, as this Target Resource does not yet have an external EID");
addCanonicalEidsToGoldenResourceIfAbsent(theGoldenResource, incomingTargetEid);
} else if (!goldenResourceOfficialEid.isEmpty() && myEIDHelper.eidMatchExists(goldenResourceOfficialEid, incomingTargetEid)) {
log(theMdmTransactionContext, "incoming resource:" + theTargetResource.getIdElement().toVersionless() + " with EIDs " + incomingTargetEid.stream().map(CanonicalEID::toString).collect(Collectors.joining(",")) + " does not need to overwrite Golden Resource, as this EID is already present");
log(theMdmTransactionContext, "Incoming resource:" + theSourceResource.getIdElement().toUnqualifiedVersionless() + " + with EID " + incomingSourceEid.stream().map(CanonicalEID::toString).collect(Collectors.joining(","))
+ " is applying this EIDs to its related Source Resource, as this Source Resource does not yet have an external EID");
addCanonicalEidsToGoldenResourceIfAbsent(theGoldenResource, incomingSourceEid);
} else if (!goldenResourceOfficialEid.isEmpty() && myEIDHelper.eidMatchExists(goldenResourceOfficialEid, incomingSourceEid)) {
log(theMdmTransactionContext, "incoming resource:" + theSourceResource.getIdElement().toVersionless() + " with EIDs " + incomingSourceEid.stream().map(CanonicalEID::toString).collect(Collectors.joining(",")) + " does not need to overwrite Golden Resource, as this EID is already present");
} else {
throw new IllegalArgumentException(
String.format("Target EIDs %s would create a duplicate golden resource, as EIDs %s already exist!",
incomingTargetEid.toString(), goldenResourceOfficialEid.toString()));
String.format("Source EIDs %s would create a duplicate golden resource, as EIDs %s already exist!",
incomingSourceEid.toString(), goldenResourceOfficialEid.toString()));
}
return theGoldenResource;
}
@ -285,10 +285,10 @@ public class GoldenResourceHelper {
/**
* Given a list of incoming External EIDs, and a Golden Resource, apply all the EIDs to this resource, which did not already exist on it.
*/
private void addCanonicalEidsToGoldenResourceIfAbsent(IBaseResource theGoldenResource, List<CanonicalEID> theIncomingTargetExternalEids) {
private void addCanonicalEidsToGoldenResourceIfAbsent(IBaseResource theGoldenResource, List<CanonicalEID> theIncomingSourceExternalEids) {
List<CanonicalEID> goldenResourceExternalEids = myEIDHelper.getExternalEid(theGoldenResource);
for (CanonicalEID incomingExternalEid : theIncomingTargetExternalEids) {
for (CanonicalEID incomingExternalEid : theIncomingSourceExternalEids) {
if (goldenResourceExternalEids.contains(incomingExternalEid)) {
continue;
} else {
@ -345,7 +345,7 @@ public class GoldenResourceHelper {
}
/**
* An incoming resource is a potential duplicate if it matches a target that has a golden resource with an official
* An incoming resource is a potential duplicate if it matches a source that has a golden resource with an official
* EID, but the incoming resource also has an EID that does not match.
*/
public boolean isPotentialDuplicate(IAnyResource theExistingGoldenResource, IAnyResource theComparingGoldenResource) {
@ -359,11 +359,11 @@ public class GoldenResourceHelper {
ourLog.debug(theMessage);
}
public void handleExternalEidAddition(IAnyResource theGoldenResource, IAnyResource theTargetResource, MdmTransactionContext
public void handleExternalEidAddition(IAnyResource theGoldenResource, IAnyResource theSourceResource, MdmTransactionContext
theMdmTransactionContext) {
List<CanonicalEID> eidFromResource = myEIDHelper.getExternalEid(theTargetResource);
List<CanonicalEID> eidFromResource = myEIDHelper.getExternalEid(theSourceResource);
if (!eidFromResource.isEmpty()) {
updateGoldenResourceExternalEidFromTargetResource(theGoldenResource, theTargetResource, theMdmTransactionContext);
updateGoldenResourceExternalEidFromSourceResource(theGoldenResource, theSourceResource, theMdmTransactionContext);
}
}

View File

@ -72,22 +72,22 @@ public class MessageHelper {
+ myMdmSettings.getSupportedMdmTypes() + ". Was " + theGoldenRecordType;
}
public String getMessageForArgumentTypeMismatchInUpdate(String theGoldenRecordType, String theTargetType) {
public String getMessageForArgumentTypeMismatchInUpdate(String theGoldenRecordType, String theSourceResourceType) {
return "Arguments to " + ProviderConstants.MDM_UPDATE_LINK + " must be of the same type. Were " +
theGoldenRecordType + " and " + theTargetType;
theGoldenRecordType + " and " + theSourceResourceType;
}
public String getMessageForUnsupportedTarget() {
return "The target is marked with the " + MdmConstants.CODE_NO_MDM_MANAGED
public String getMessageForUnsupportedSourceResource() {
return "The source resource is marked with the " + MdmConstants.CODE_NO_MDM_MANAGED
+ " tag which means it may not be MDM linked.";
}
public String getMessageForNoLink(IAnyResource theGoldenRecord, IAnyResource theTarget) {
public String getMessageForNoLink(IAnyResource theGoldenRecord, IAnyResource theSourceResource) {
return getMessageForNoLink(theGoldenRecord.getIdElement().toVersionless().toString(),
theTarget.getIdElement().toVersionless().toString());
theSourceResource.getIdElement().toVersionless().toString());
}
public String getMessageForNoLink(String theGoldenRecord, String theTarget) {
return "No link exists between " + theGoldenRecord + " and " + theTarget;
public String getMessageForNoLink(String theGoldenRecord, String theSourceResource) {
return "No link exists between " + theGoldenRecord + " and " + theSourceResource;
}
}

View File

@ -87,7 +87,7 @@ public class ProviderConstants {
public static final String MDM_NOT_DUPLICATE = "$mdm-not-duplicate";
public static final String MDM_CLEAR = "$mdm-clear";
public static final String MDM_CLEAR_TARGET_TYPE = "targetType";
public static final String MDM_CLEAR_SOURCE_TYPE = "sourceType";
public static final String OPERATION_MDM_SUBMIT = "$mdm-submit";
public static final String MDM_BATCH_RUN_CRITERIA = "criteria" ;
public static final String OPERATION_MDM_BATCH_RUN_OUT_PARAM_SUBMIT_COUNT = "submitted" ;