Custom MDM Matching Algorithms

 

Smile CDR ships with a rich set of built-in matching and similarity algorithms for MDM rule evaluation. For a complete reference of built-in algorithms, see the HAPI FHIR MDM Matching Rules documentation. For details on the core interfaces and the factory/registry pattern, see HAPI FHIR MDM Customizations.

When the built-in algorithms do not meet your requirements, you can implement custom matching and similarity algorithms and deploy them to Smile CDR via a customer JAR. Once registered, custom algorithms can be referenced by name in your MDM rules JSON alongside the built-in algorithms.

Implementing a Custom Matcher

 

A custom matcher determines whether two FHIR field values match (boolean result). It consists of two parts: the matcher logic and a provider that names and supplies it.

Matcher Implementation

Implement IMdmFieldMatcher from the hapi-fhir-server-mdm library:

import ca.uhn.fhir.mdm.rules.json.MdmMatcherJson;
import ca.uhn.fhir.mdm.rules.matcher.models.IMdmFieldMatcher;
import org.hl7.fhir.instance.model.api.IBase;

public class PhoneticMatcher implements IMdmFieldMatcher {

    @Override
    public boolean matches(IBase theLeftBase, IBase theRightBase, MdmMatcherJson theParams) {
        String left = theLeftBase.toString();
        String right = theRightBase.toString();
        // Custom logic: check if the two values produce the same phonetic encoding
        return PhoneticEncoder.encode(left).equals(PhoneticEncoder.encode(right));
    }
}

Matcher Provider

Wrap the matcher in an IMdmFieldMatcherProvider that assigns it a unique name:

import ca.uhn.fhir.mdm.rules.matcher.models.IMdmFieldMatcher;
import ca.uhn.fhir.mdm.rules.matcher.models.IMdmFieldMatcherProvider;

public class PhoneticMatcherProvider implements IMdmFieldMatcherProvider {

    @Override
    public String getName() {
        return "PHONETIC";
    }

    @Override
    public IMdmFieldMatcher getMatcher() {
        return new PhoneticMatcher();
    }
}

The name returned by getName() is the string you will use in your MDM rules JSON to reference this algorithm.

Implementing a Custom Similarity

 

A custom similarity measures how similar two FHIR field values are, returning a double between 0.0 (completely different) and 1.0 (identical).

Similarity Implementation

Implement IMdmFieldSimilarity from the hapi-fhir-server-mdm library:

import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.mdm.rules.similarity.IMdmFieldSimilarity;
import org.hl7.fhir.instance.model.api.IBase;

public class WeightedLevenshteinSimilarity implements IMdmFieldSimilarity {

    @Override
    public double similarity(
            FhirContext theFhirContext, IBase theLeftBase, IBase theRightBase, boolean theExact) {
        String left = theLeftBase.toString();
        String right = theRightBase.toString();
        if (theExact) {
            return left.equals(right) ? 1.0 : 0.0;
        }
        // Custom weighted Levenshtein distance calculation
        return calculateWeightedSimilarity(left, right);
    }

    private double calculateWeightedSimilarity(String theLeft, String theRight) {
        // Custom implementation
        return 0.0;
    }
}

Similarity Provider

Wrap the similarity in an IMdmFieldSimilarityProvider:

import ca.uhn.fhir.mdm.rules.similarity.IMdmFieldSimilarity;
import ca.uhn.fhir.mdm.rules.similarity.IMdmFieldSimilarityProvider;

public class WeightedLevenshteinSimilarityProvider implements IMdmFieldSimilarityProvider {

    @Override
    public String getName() {
        return "WEIGHTED_LEVENSHTEIN";
    }

    @Override
    public IMdmFieldSimilarity getSimilarity() {
        return new WeightedLevenshteinSimilarity();
    }
}

Packaging and Deployment

 

Custom algorithm providers are deployed using the same customerlib mechanism used for custom interceptors.

  1. Package your provider and algorithm classes into a JAR file containing only your classes (do not include library dependencies such as Spring or HAPI FHIR).

  2. Place the JAR file in the smilecdr/customerlib directory.

  3. Configure the custom_matcher_bean_types property on the MDM module with the fully-qualified class names of your provider classes. Multiple providers are separated by commas:

module.mdm.config.custom_matcher_bean_types=com.example.mdm.PhoneticMatcherProvider,com.example.mdm.WeightedLevenshteinSimilarityProvider
  1. Restart Smile CDR. On startup, the MDM module will instantiate each provider and register its algorithm on the appropriate factory.
A single provider class can implement both IMdmFieldMatcherProvider and IMdmFieldSimilarityProvider if it supplies both a matcher and a similarity algorithm.
Algorithm names must be unique. A custom algorithm must not use the same name as a built-in algorithm or another custom algorithm. Duplicate names will cause an error at startup.

Using Custom Algorithms in MDM Rules

 

Once deployed, reference your custom algorithm by name in the matchFields section of your MDM rules JSON, exactly as you would a built-in algorithm:

{
    "name": "phoneticGiven",
    "resourceType": "Patient",
    "resourcePath": "name.given",
    "matcher": {
        "algorithm": "PHONETIC"
    }
}

For a similarity-based field:

{
    "name": "weightedFamilyName",
    "resourceType": "Patient",
    "resourcePath": "name.family",
    "similarity": {
        "algorithm": "WEIGHTED_LEVENSHTEIN",
        "matchThreshold": 0.8
    }
}

Algorithm names are case-sensitive and must exactly match the string returned by the provider's getName() method.

Testing Custom Algorithms

 

You can test custom algorithms interactively using the $mdm-evaluate operation. This operation accepts custom algorithm names in the algorithm parameter, allowing you to verify behavior before using them in your MDM rules.

Example request using a custom matcher:

{
    "resourceType": "Parameters",
    "parameter": [
        {
            "name": "compareTo",
            "valueString": "Smith"
        },
        {
            "name": "compareWith",
            "valueString": "Smyth"
        },
        {
            "name": "algorithmType",
            "valueString": "matcher"
        },
        {
            "name": "algorithm",
            "valueString": "PHONETIC"
        }
    ]
}

Troubleshooting

 
SymptomCauseResolution
ClassNotFoundException at startupJAR not in customerlib/ or wrong fully-qualified class nameVerify the JAR is in smilecdr/customerlib/ and the class name in custom_matcher_bean_types is correct
Duplicate algorithm name errorCustom algorithm name conflicts with a built-in or another custom algorithmChange the getName() return value to a unique string
Algorithm not found in MDM rulesName in JSON does not match provider getName()Verify case-sensitive match between the JSON algorithm value and getName()
Algorithm not found in $mdm-evaluateProvider not registeredVerify custom_matcher_bean_types includes the provider class and check startup logs

On startup, successfully registered custom algorithms are logged:

Registering custom MDM matcher: PHONETIC
Registering custom MDM similarity: WEIGHTED_LEVENSHTEIN

Check the MDM Troubleshooting Log for these messages if algorithms are not being recognized.