All Downloads are FREE. Search and download functionalities are using the official Maven repository.

ca.uhn.fhir.mdm.rules.svc.MdmResourceMatcherSvc Maven / Gradle / Ivy

/*-
 * #%L
 * HAPI FHIR - Master Data Management
 * %%
 * Copyright (C) 2014 - 2024 Smile CDR, Inc.
 * %%
 * 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%
 */
package ca.uhn.fhir.mdm.rules.svc;

import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.mdm.api.IMdmSettings;
import ca.uhn.fhir.mdm.api.MdmConstants;
import ca.uhn.fhir.mdm.api.MdmMatchEvaluation;
import ca.uhn.fhir.mdm.api.MdmMatchOutcome;
import ca.uhn.fhir.mdm.api.MdmMatchResultEnum;
import ca.uhn.fhir.mdm.log.Logs;
import ca.uhn.fhir.mdm.rules.json.MdmFieldMatchJson;
import ca.uhn.fhir.mdm.rules.json.MdmRulesJson;
import ca.uhn.fhir.mdm.rules.matcher.IMatcherFactory;
import com.google.common.annotations.VisibleForTesting;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.slf4j.Logger;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;

/**
 * The MdmResourceComparator is in charge of performing actual comparisons between left and right records.
 * It does so by calling individual comparators, and returning a vector based on the combination of
 * field comparators that matched.
 */
@Service
public class MdmResourceMatcherSvc {
	private static final Logger ourLog = Logs.getMdmTroubleshootingLog();

	private final FhirContext myFhirContext;
	private final IMatcherFactory myMatcherFactory;
	private final List myFieldMatchers = new ArrayList<>();

	private MdmRulesJson myMdmRulesJson;

	public MdmResourceMatcherSvc(
			FhirContext theFhirContext, IMatcherFactory theIMatcherFactory, IMdmSettings theMdmSettings) {
		myFhirContext = theFhirContext;
		myMatcherFactory = theIMatcherFactory;
		myMdmRulesJson = theMdmSettings.getMdmRules();

		addFieldMatchers();
	}

	private void addFieldMatchers() {
		if (myMdmRulesJson == null) {
			throw new ConfigurationException(Msg.code(1521)
					+ "Failed to load MDM Rules.  If MDM is enabled, then MDM rules must be available in context.");
		}
		myFieldMatchers.clear();
		for (MdmFieldMatchJson matchFieldJson : myMdmRulesJson.getMatchFields()) {
			myFieldMatchers.add(
					new MdmResourceFieldMatcher(myFhirContext, myMatcherFactory, matchFieldJson, myMdmRulesJson));
		}
	}

	/**
	 * Given two {@link IBaseResource}s, perform all comparisons on them to determine an {@link MdmMatchResultEnum}, indicating
	 * to what level the two resources are considered to be matching.
	 *
	 * @param theLeftResource  The first {@link IBaseResource}.
	 * @param theRightResource The second {@link IBaseResource}
	 * @return an {@link MdmMatchResultEnum} indicating the result of the comparison.
	 */
	public MdmMatchOutcome getMatchResult(IBaseResource theLeftResource, IBaseResource theRightResource) {
		return match(theLeftResource, theRightResource);
	}

	MdmMatchOutcome match(IBaseResource theLeftResource, IBaseResource theRightResource) {
		MdmMatchOutcome matchResult = getMatchOutcome(theLeftResource, theRightResource);
		MdmMatchResultEnum matchResultEnum = myMdmRulesJson.getMatchResult(matchResult.getVector());
		matchResult.setMatchResultEnum(matchResultEnum);
		if (ourLog.isDebugEnabled()) {
			ourLog.debug(
					"{} {}: {}",
					matchResult.getMatchResultEnum(),
					theRightResource.getIdElement().toUnqualifiedVersionless(),
					matchResult);
			if (ourLog.isTraceEnabled()) {
				ourLog.trace(
						"Field matcher results:\n{}",
						myMdmRulesJson.getDetailedFieldMatchResultWithSuccessInformation(matchResult.getVector()));
			}
		}
		return matchResult;
	}

	/**
	 * This function generates a `match vector`, which is a long representation of a binary string
	 * generated by the results of each of the given comparator matches. For example.
	 * start with a binary representation of the value 0 for long: 0000
	 * first_name matches, so the value `1` is bitwise-ORed to the current value (0) in right-most position.
	 * `0001`
	 * 

* Next, we look at the second field comparator, and see if it matches. If it does, we left-shift 1 by the index * of the comparator, in this case also 1. * `0010` *

* Then, we bitwise-or it with the current retval: * 0001|0010 = 0011 * The binary string is now `0011`, which when you return it as a long becomes `3`. */ private MdmMatchOutcome getMatchOutcome(IBaseResource theLeftResource, IBaseResource theRightResource) { long vector = 0; double score = 0.0; 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; } ourLog.trace( "Matcher {} is valid for resource type: {}. Evaluating match.", fieldComparator.getName(), resourceType); MdmMatchEvaluation matchEvaluation = fieldComparator.match(theLeftResource, theRightResource); if (matchEvaluation.match) { vector |= (1L << i); ourLog.trace( "Match: Successfully matched matcher {} with score {}. New vector: {}", fieldComparator.getName(), matchEvaluation.score, vector); score += matchEvaluation.score; } else { ourLog.trace( "No match: Matcher {} did not match (score: {}).", fieldComparator.getName(), matchEvaluation.score); } appliedRuleCount += 1; } MdmMatchOutcome retVal = new MdmMatchOutcome(vector, score); retVal.setMdmRuleCount(appliedRuleCount); return retVal; } private boolean isValidResourceType(String theResourceType, String theFieldComparatorType) { return (theFieldComparatorType.equalsIgnoreCase(MdmConstants.ALL_RESOURCE_SEARCH_PARAM_TYPE) || theFieldComparatorType.equalsIgnoreCase(theResourceType)); } @VisibleForTesting public void setMdmRulesJson(MdmRulesJson theMdmRulesJson) { myMdmRulesJson = theMdmRulesJson; addFieldMatchers(); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy