# Advanced Rule Authoring

The MDM Rule Definition Script is the single JSON document that controls all matching behaviour in your MDM module. Writing it well is the most important configuration task you will perform, because it determines which patient records get linked automatically, which ones are flagged for human review, and which ones are treated as distinct individuals.

This tutorial presents six rule definition scripts in order of increasing complexity. Each example builds on the one before it, introducing new fields, algorithms, and design patterns along the way. By the end you will understand how to combine candidate searches, field comparisons, and result tiers into a rule set that matches confidently on strong evidence while reserving borderline cases for a data steward.

The examples are presented in terms of Golden Record linking, but the same rule definition script drives every MDM matching strategy in Smile CDR, including [deduplication on ingestion](/content/docs/mdm/mdm.html#deduplication-on-ingestion/index.html). See [Selecting a deduplication strategy](/content/docs/mdm/mdm.html#selecting-a-deduplication-strategy/index.html) for an overview of the available strategies.

This tutorial focuses entirely on the content of the rule definition script. For a walkthrough of how to enable the MDM module, enter the script in the Admin Console, and verify the results by querying links, see [Your First Golden Resource](/content/docs/guide_tutorials/first_golden_resource.html).

# How a Rule Definition Script Works

MDM processes each incoming resource in two phases.

**Phase 1 — Candidate Selection.** MDM searches the repository for existing resources that share at least one value with the incoming resource on the fields you list in `candidateSearchParams`. Only those candidates advance to the next phase. Any existing resource that is not retrieved in this search will never be matched against the incoming resource, regardless of how similar it might be. This makes candidate search the most important factor in whether a real match is detected at all.

**Phase 2 — Field-Level Matching.** MDM compares each candidate against the incoming resource using the algorithms defined in `matchFields`. It then consults the `matchResultMap` to decide whether the comparison results constitute a `MATCH`, a `POSSIBLE_MATCH`, or no link at all. A `MATCH` link is created automatically. A `POSSIBLE_MATCH` link is created but flagged for data steward review.

Every rule definition script requires these six top-level fields:

| Field | Purpose |
| --- | --- |
| `version` | A short label you assign to this version of the script. It is recorded on every link created while this script is active. Change it each time you modify the script. |
| `mdmTypes` | The FHIR resource types MDM will process — for example `["Patient"]` or `["Patient", "Practitioner"]`. |
| `candidateSearchParams` | Fields used in Phase 1 to retrieve candidates from the repository. |
| `candidateFilterSearchParams` | Optional additional filters applied on top of the candidate search to narrow the candidate pool further. |
| `matchFields` | The individual field comparisons run in Phase 2. Each entry names a field and specifies an algorithm. |
| `matchResultMap` | Rules that translate Phase 2 comparison results into link types. Each key is a comma-separated list of `matchFields` names; the value is `MATCH` or `POSSIBLE_MATCH`. |

* * *

# Example 1: Family Name Matching

This is the simplest useful rule set. It retrieves candidates by birth date and confirms a match only when the family names are phonetically equivalent.

```json
{
  "version": "example-1",
  "mdmTypes": ["Patient"],
  "candidateSearchParams": [\
    {\
      "resourceType": "Patient",\
      "searchParams": ["birthdate"]\
    }\
  ],
  "candidateFilterSearchParams": [],
  "matchFields": [\
    {\
      "name": "family-name",\
      "resourceType": "Patient",\
      "resourcePath": "name.family",\
      "matcher": {\
        "algorithm": "METAPHONE"\
      }\
    }\
  ],
  "matchResultMap": {
    "family-name": "MATCH"
  }
}
```

Copy

**What this does:**

1. When a new Patient arrives, MDM searches the repository for all existing Patients that share the same `birthdate` value. Those are the candidates.
2. For each candidate, MDM compares the incoming Patient's family name to the candidate's family name using the `METAPHONE` algorithm. Metaphone encodes each name as a phonetic key before comparing, so alternate spellings that sound the same — for example _Smith_ and _Smyth_, or _Dury_ and _Durie_ — produce the same key and therefore match.
3. If the Metaphone comparison passes, MDM creates a `MATCH` link automatically.

**Why `birthdate` for the candidate search?**

A good candidate search parameter is specific enough to return a small group but not so specific that minor errors cause real matches to be missed. Birth date works well because most people share a birthday with relatively few others, it is present on nearly all patient records, and it is not likely to change over time, as name or address fields might. Avoid broad fields such as `gender` or `address-state`, which would return very large candidate pools and slow down processing.

**What matches and what does not:**

| Incoming patient | Candidate | Result |
| --- | --- | --- |
| Born 1985-03-15, family "Smith" | Born 1985-03-15, family "Smyth" | **MATCH** — same birthday, same Metaphone key (`SM0`) |
| Born 1985-03-15, family "Smith" | Born 1985-03-15, family "Jones" | No link — different Metaphone keys |
| Born 1985-03-15, family "Smith" | Born 1986-03-15, family "Smith" | No link — different birthday, not even retrieved as a candidate |

**Limitation:** Matching on family name alone creates false positives when two different patients share the same birth date and surname. Example 2 addresses this.

* * *

# Example 2: Requiring Both Family and Given Name

Adding the given name as a second required comparison makes the rule significantly more precise. Both comparisons must pass for a `MATCH` to be created.

```json
{
  "version": "example-2",
  "mdmTypes": ["Patient"],
  "candidateSearchParams": [\
    {\
      "resourceType": "Patient",\
      "searchParams": ["birthdate"]\
    }\
  ],
  "candidateFilterSearchParams": [],
  "matchFields": [\
    {\
      "name": "family-name",\
      "resourceType": "Patient",\
      "resourcePath": "name.family",\
      "matcher": {\
        "algorithm": "METAPHONE"\
      }\
    },\
    {\
      "name": "given-name",\
      "resourceType": "Patient",\
      "resourcePath": "name.given",\
      "matcher": {\
        "algorithm": "METAPHONE"\
      }\
    }\
  ],
  "matchResultMap": {
    "family-name,given-name": "MATCH"
  }
}
```

Copy

The only changes from Example 1 are the addition of the `given-name` matchField and the updated `matchResultMap` key. The key `"family-name,given-name"` means both comparisons must pass simultaneously for the result to be `MATCH`.

**What matches and what does not:**

| Incoming patient | Candidate | Result |
| --- | --- | --- |
| Born 1985-03-15, "John Smith" | Born 1985-03-15, "Jon Smith" | **MATCH** — same birthday; "Jon" and "John" share the Metaphone key `JN`; "Smith" and "Smith" share `SM0` |
| Born 1985-03-15, "John Smith" | Born 1985-03-15, "Jane Smith" | **MATCH** — same birthday and same family name, and "John" and "Jane" happen to share a Metaphone key (`JN`) — this is actually a **false positive** under Metaphone |
| Born 1985-03-15, "John Smith" | Born 1985-03-15, "John Jones" | No link — family names differ |

The "Jane/John" example above illustrates an important point: no phonetic algorithm is perfect. "John" and "Jane" share the Metaphone key `JN`, which means they are treated as equivalent. If this produces unacceptable false positives in your population, consider replacing METAPHONE with a similarity algorithm such as JARO\_WINKLER — which compares the actual character sequences rather than encoding them — so you can tune how much variation to tolerate. Example 5 demonstrates this approach.

* * *

# Example 3: Adding a POSSIBLE\_MATCH Tier

A rule set with only `MATCH` outcomes is all-or-nothing: either both names match perfectly and a link is created automatically, or no link is created at all. This means a candidate with a matching family name but a missing or differently-entered given name is simply ignored, even though it might be the same person.

Adding a `POSSIBLE_MATCH` tier captures these borderline cases and routes them to a data steward for manual review, rather than silently discarding them.

```json
{
  "version": "example-3",
  "mdmTypes": ["Patient"],
  "candidateSearchParams": [\
    {\
      "resourceType": "Patient",\
      "searchParams": ["birthdate"]\
    }\
  ],
  "candidateFilterSearchParams": [],
  "matchFields": [\
    {\
      "name": "family-name",\
      "resourceType": "Patient",\
      "resourcePath": "name.family",\
      "matcher": {\
        "algorithm": "METAPHONE"\
      }\
    },\
    {\
      "name": "given-name",\
      "resourceType": "Patient",\
      "resourcePath": "name.given",\
      "matcher": {\
        "algorithm": "METAPHONE"\
      }\
    }\
  ],
  "matchResultMap": {
    "family-name,given-name": "MATCH",
    "family-name": "POSSIBLE_MATCH"
  }
}
```

Copy

The `matchFields` array is unchanged. The `matchResultMap` now has two entries:

- `"family-name,given-name": "MATCH"` — both names match phonetically: create a `MATCH` link automatically.
- `"family-name": "POSSIBLE_MATCH"` — only the family name matches: create a `POSSIBLE_MATCH` link for review.

**Precedence rule:**`MATCH` always takes priority over `POSSIBLE_MATCH`. If a candidate satisfies both map entries simultaneously, only the `MATCH` link is created. In this example, a candidate that passes both `family-name` and `given-name` satisfies the first entry and receives a `MATCH` link. The second entry is redundant for that candidate and is ignored.

**When should you use POSSIBLE\_MATCH?**

Use `POSSIBLE_MATCH` when you have weaker evidence — evidence you believe is meaningful but not conclusive enough to link automatically. Common situations include:

- A single identifier type matches but demographic data is sparse
- A family name matches but the given name field is absent on one or both records
- A similarity score falls in a range where the match is plausible but not certain

Patients flagged as `POSSIBLE_MATCH` appear in the MDM data stewardship view, where a reviewer can confirm or dismiss each one.

* * *

# Example 4: Multiple Candidate Searches and Identifier Matching

So far all examples rely on birth date as the sole candidate search parameter. If a patient's birth date is entered incorrectly in one source system, that patient will never be found as a candidate, and the match will be missed entirely.

Adding a second candidate search parameter — such as a medical record number (MRN) identifier — provides a second path into the candidate pool. A candidate is retrieved if it matches on _either_ search parameter. This protects against data entry errors in any one field.

```json
{
  "version": "example-4",
  "mdmTypes": ["Patient"],
  "candidateSearchParams": [\
    {\
      "resourceType": "Patient",\
      "searchParams": ["birthdate"]\
    },\
    {\
      "resourceType": "Patient",\
      "searchParams": ["identifier"]\
    }\
  ],
  "candidateFilterSearchParams": [],
  "matchFields": [\
    {\
      "name": "mrn",\
      "resourceType": "Patient",\
      "resourcePath": "identifier",\
      "matcher": {\
        "algorithm": "IDENTIFIER",\
        "identifierSystem": "http://example.org/fhir/mrn"\
      }\
    },\
    {\
      "name": "family-name",\
      "resourceType": "Patient",\
      "resourcePath": "name.family",\
      "matcher": {\
        "algorithm": "METAPHONE"\
      }\
    },\
    {\
      "name": "given-name",\
      "resourceType": "Patient",\
      "resourcePath": "name.given",\
      "matcher": {\
        "algorithm": "METAPHONE"\
      }\
    },\
    {\
      "name": "birthdate",\
      "resourceType": "Patient",\
      "resourcePath": "birthDate",\
      "matcher": {\
        "algorithm": "DATE"\
      }\
    }\
  ],
  "matchResultMap": {
    "mrn": "MATCH",
    "family-name,given-name,birthdate": "MATCH"
  }
}
```

Copy

**New elements explained:**

**Second `candidateSearchParams` entry** — The two entries are evaluated independently and in parallel. Any existing Patient that shares either the same birth date or the same identifier value as the incoming Patient is included in the candidate pool. The two result sets are merged before Phase 2 begins.

**`mrn` matchField with the `IDENTIFIER` algorithm** — The `IDENTIFIER` algorithm compares two FHIR Identifier values by checking that both their `system` URI and their `value` are identical. Without the `identifierSystem` parameter it would match on _any_ matching identifier. Setting `"identifierSystem": "http://example.org/fhir/mrn"` restricts matching to identifiers in your MRN namespace, preventing accidental matches on identifiers from other namespaces.

Replace `http://example.org/fhir/mrn` with the actual identifier system URI used in your organization's Patient resources.

**`birthdate` matchField with the `DATE` algorithm** — Unlike a string comparison, `DATE` is precision-aware. A value recorded as `1985-03` (year-month only) and a value recorded as `1985-03-15` (full date) are treated as a match, because the less precise value is consistent with the more precise one. This handles the common situation where one source system records only the year and month of birth.

**Two independent `matchResultMap` entries:**

- `"mrn": "MATCH"` — An exact MRN match is sufficient on its own to create an automatic link. A shared unique identifier is strong evidence of identity.
- `"family-name,given-name,birthdate": "MATCH"` — In the absence of an MRN match, both name comparisons plus a birth date comparison together constitute sufficient evidence.

**What matches and what does not:**

| Incoming patient | Candidate | How retrieved | Result |
| --- | --- | --- | --- |
| MRN 1001, born 1985-03-15, "John Smith" | MRN 1001, born 1988-07-22, "Jane Doe" | By identifier | **MATCH** — MRN matches; names and birth date are irrelevant once the MRN rule fires |
| No MRN, born 1985-03-15, "John Smith" | No MRN, born 1985-03-15, "Jon Smith" | By birthdate | **MATCH** — second map entry: family and given names match phonetically, birth dates match |
| No MRN, born 1985-03-15, "John Smith" | No MRN, born 1985-03-16, "John Smith" | Not retrieved | No link — different birth dates, no identifier to retrieve by |

* * *

# Example 5: Fine-Grained Control with Similarity Scoring

Phonetic algorithms such as `METAPHONE` encode names into a compact key that can hide subtle differences. Two names receive the same Metaphone key if they sound similar when spoken aloud, but the algorithm cannot tell you _how_ similar they are — a name either matches or it does not. This coarseness can cause false positives.

Similarity algorithms take a different approach: instead of returning a pass/fail result, they return a decimal score between `0.0` (completely different) and `1.0` (identical). You set a `matchThreshold` that the score must reach for the comparison to pass. Raising the threshold demands more similarity; lowering it tolerates more variation.

This example replaces the phonetic family-name comparison with a `JARO_WINKLER` similarity comparison and uses the resulting precision difference to create a more meaningful `POSSIBLE_MATCH` tier.

```json
{
  "version": "example-5",
  "mdmTypes": ["Patient"],
  "candidateSearchParams": [\
    {\
      "resourceType": "Patient",\
      "searchParams": ["birthdate"]\
    },\
    {\
      "resourceType": "Patient",\
      "searchParams": ["identifier"]\
    }\
  ],
  "candidateFilterSearchParams": [],
  "matchFields": [\
    {\
      "name": "mrn",\
      "resourceType": "Patient",\
      "resourcePath": "identifier",\
      "matcher": {\
        "algorithm": "IDENTIFIER",\
        "identifierSystem": "http://example.org/fhir/mrn"\
      }\
    },\
    {\
      "name": "family-name",\
      "resourceType": "Patient",\
      "resourcePath": "name.family",\
      "similarity": {\
        "algorithm": "JARO_WINKLER",\
        "matchThreshold": 0.88\
      }\
    },\
    {\
      "name": "given-name",\
      "resourceType": "Patient",\
      "resourcePath": "name.given",\
      "matcher": {\
        "algorithm": "METAPHONE"\
      }\
    },\
    {\
      "name": "birthdate",\
      "resourceType": "Patient",\
      "resourcePath": "birthDate",\
      "matcher": {\
        "algorithm": "DATE"\
      }\
    }\
  ],
  "matchResultMap": {
    "mrn": "MATCH",
    "family-name,given-name,birthdate": "MATCH",
    "family-name,birthdate": "POSSIBLE_MATCH"
  }
}
```

Copy

**Changes from Example 4:**

**`family-name` now uses `similarity` instead of `matcher`** — The `similarity` property is used for algorithms that return a score. The `matcher` property is used for algorithms that return a pass/fail result. Only one of the two may be active at a time per matchField.

`JARO_WINKLER` is particularly well-suited to name matching because it gives extra weight to shared characters at the beginning of a string. Typographic errors at the end of a name — a transposed letter, a dropped final consonant — produce a small penalty, while errors at the beginning produce a larger one. A threshold of `0.88` is a starting point for family names in English; you should adjust it using representative test data from your own population.

**New `matchResultMap` entry** — `"family-name,birthdate": "POSSIBLE_MATCH"` captures candidates where the family name score meets the threshold and the birth date matches, but the given name comparison fails (perhaps because the given name is missing from one record, or is entered very differently). These cases are worth a data steward's attention.

**Choosing a threshold:**

Start with a value in the 0.85–0.92 range for family names and test it against real data using the `$mdm-evaluate` operation, which lets you run a single pair of resources through the matching rules without creating any links. Lower values increase the number of matches found (higher recall) at the cost of more false positives. Higher values reduce false positives (higher precision) but risk missing real matches where names are entered inconsistently.

* * *

# Example 6: A Production-Grade Rule Set

This example combines all the patterns shown so far — multiple candidate searches, candidate filters, several different field algorithms, a two-tier result map, and an enterprise identifier — into a rule set representative of a production deployment.

```json
{
  "version": "prod-v1",
  "mdmTypes": ["Patient"],
  "eidSystems": {
    "Patient": "http://example.org/fhir/national-patient-id"
  },
  "candidateSearchParams": [\
    {\
      "resourceType": "Patient",\
      "searchParams": ["birthdate"]\
    },\
    {\
      "resourceType": "Patient",\
      "searchParams": ["identifier"]\
    },\
    {\
      "resourceType": "Patient",\
      "searchParams": ["phone"]\
    }\
  ],
  "candidateFilterSearchParams": [\
    {\
      "resourceType": "Patient",\
      "searchParam": "active",\
      "fixedValue": "true"\
    }\
  ],
  "matchFields": [\
    {\
      "name": "national-id",\
      "resourceType": "Patient",\
      "resourcePath": "identifier",\
      "matcher": {\
        "algorithm": "IDENTIFIER",\
        "identifierSystem": "http://example.org/fhir/national-patient-id"\
      }\
    },\
    {\
      "name": "mrn",\
      "resourceType": "Patient",\
      "resourcePath": "identifier",\
      "matcher": {\
        "algorithm": "IDENTIFIER",\
        "identifierSystem": "http://example.org/fhir/mrn"\
      }\
    },\
    {\
      "name": "family-name",\
      "resourceType": "Patient",\
      "resourcePath": "name.family",\
      "similarity": {\
        "algorithm": "JARO_WINKLER",\
        "matchThreshold": 0.88\
      }\
    },\
    {\
      "name": "given-name",\
      "resourceType": "Patient",\
      "resourcePath": "name.given",\
      "matcher": {\
        "algorithm": "DOUBLE_METAPHONE"\
      }\
    },\
    {\
      "name": "birthdate",\
      "resourceType": "Patient",\
      "resourcePath": "birthDate",\
      "matcher": {\
        "algorithm": "DATE"\
      }\
    },\
    {\
      "name": "gender",\
      "resourceType": "Patient",\
      "resourcePath": "gender",\
      "matcher": {\
        "algorithm": "STRING"\
      }\
    },\
    {\
      "name": "phone",\
      "resourceType": "Patient",\
      "fhirPath": "telecom.where(system='phone').value",\
      "matcher": {\
        "algorithm": "NUMERIC"\
      }\
    }\
  ],
  "matchResultMap": {
    "national-id": "MATCH",
    "mrn,birthdate": "MATCH",
    "family-name,given-name,birthdate,gender": "MATCH",
    "family-name,given-name,birthdate": "POSSIBLE_MATCH",
    "family-name,birthdate,phone": "POSSIBLE_MATCH"
  }
}
```

Copy

**Section-by-section explanation:**

## `eidSystems`

```json
"eidSystems": {
  "Patient": "http://example.org/fhir/national-patient-id"
}
```

Copy

Declaring an EID (Enterprise Identifier) system tells MDM that identifiers with this system URI are authoritative unique identifiers. When an incoming Patient carries a national patient ID, MDM first searches for an existing Golden Resource that already holds that identifier. If one is found, MDM links the incoming Patient directly to it without running any further candidate searches or field comparisons. This shortcut makes EID-based matching faster and more reliable than demographic matching alone.

If no Golden Resource with that EID exists, MDM proceeds with the normal candidate search phases.

Where a resource type has more than one authoritative identifier, give an array of systems instead. The incoming resource is then linked directly if **any** of its EIDs match:

```json
"eidSystems": {
  "Patient": [\
    "http://example.org/fhir/national-patient-id",\
    "http://example.org/fhir/regional-mrn"\
  ]
}
```

Copy

See [Using several EID systems for one resource type](/content/docs/mdm/mdm_rule_definition_eid.html#multiple-eid-systems/index.html).

Replace `http://example.org/fhir/national-patient-id` with the system URI of the national or enterprise patient identifier used in your jurisdiction.

## `candidateSearchParams`

Three parameters are declared. A candidate is retrieved if the incoming Patient matches an existing Patient on _any one_ of them:

- `birthdate` — Retrieves patients who share the same date of birth.
- `identifier` — Retrieves patients who share at least one identifier value, regardless of system. This catches cases where a birth date is entered incorrectly in one source system.
- `phone` — Retrieves patients who share the same phone number. This provides a third independent retrieval path that is resilient to errors in both date and identifier fields.

## `candidateFilterSearchParams`

```json
{
  "resourceType": "Patient",
  "searchParam": "active",
  "fixedValue": "true"
}
```

Copy

This filter is applied to _all three_ candidate searches. Only active Patients are ever considered as candidates. Inactive patients — those that have been merged, deactivated, or marked as test data — are excluded before Phase 2 begins, regardless of how closely they match the incoming resource.

## `matchFields` — `national-id` and `mrn`

Two separate `IDENTIFIER` matchFields cover the national patient ID and the MRN. Each restricts comparison to its own `identifierSystem`, so each type of identifier can be used independently in the `matchResultMap`. This also prevents identifiers from any other namespace, which may not be reliable enough to establish patient identity, from contributing to the match.

## `matchFields` — `given-name` with `DOUBLE_METAPHONE`

`DOUBLE_METAPHONE` is used here instead of `METAPHONE` because this organization's patient population includes names from multiple linguistic backgrounds. Double Metaphone computes two phonetic codes per value — a primary and an alternate — to handle ambiguous pronunciations and non-English name origins. It matches a broader set of spelling variants, including _Smith_ and _Schmidt_, which standard Metaphone would not match.

## `matchFields` — `gender` with `STRING`

Gender is a coded administrative value (`male`, `female`, `other`, `unknown`) that should match character-for-character. The `STRING` algorithm compares values as text after normalizing to uppercase, so `Male` and `male` are treated as equal. It is appropriate for coded fields where phonetic encoding is meaningless.

Including gender in a `MATCH` rule alongside three other fields significantly reduces false positives, at the cost of missing a match if one source system records gender differently from another.

## `matchFields` — `phone` with `fhirPath` and `NUMERIC`

```json
{
  "name": "phone",
  "resourceType": "Patient",
  "fhirPath": "telecom.where(system='phone').value",
  "matcher": {
    "algorithm": "NUMERIC"
  }
}
```

Copy

This matchField uses `fhirPath` rather than `resourcePath`. The `fhirPath` expression `telecom.where(system='phone').value` selects only telecom entries whose `system` is `phone`, avoiding false matches between a phone number on one record and an email address or fax number on another.

The `NUMERIC` algorithm strips all non-digit characters (such as parentheses, hyphens, and spaces) before comparing, so `(416) 967-1111` and `416-967-1111` are treated as the same number.

## `matchResultMap`

```json
"matchResultMap": {
  "national-id": "MATCH",
  "mrn,birthdate": "MATCH",
  "family-name,given-name,birthdate,gender": "MATCH",
  "family-name,given-name,birthdate": "POSSIBLE_MATCH",
  "family-name,birthdate,phone": "POSSIBLE_MATCH"
}
```

Copy

The five entries express a deliberate hierarchy of confidence:

1. **`national-id`** — A national patient ID match is conclusive on its own. No additional evidence is required.
2. **`mrn,birthdate`** — A shared MRN combined with a matching birth date is strong evidence. An MRN alone would not be sufficient because MRN namespaces sometimes overlap across unrelated systems; requiring the birth date to agree as well guards against this.
3. **`family-name,given-name,birthdate,gender`** — Four demographic fields all agreeing constitutes an automatic match.
4. **`family-name,given-name,birthdate`** — Three demographic fields agreeing is treated as possible but not certain. It may reflect a real match where gender was recorded differently, or two different patients who happen to have similar names and share a birth date. A data steward review is appropriate.
5. **`family-name,birthdate,phone`** — A matching phone number combined with a matching family name and birth date is suggestive but not conclusive. Phone numbers are shared and change hands, so this combination alone should not produce an automatic link.

* * *

# Design Considerations

## Choosing Candidate Search Parameters

The candidate search is the most consequential decision in your rule set. A candidate that is never retrieved can never be matched. Choose search parameters that divide your population into small groups and that are reliably populated:

- **Good candidates:**`birthdate`, `identifier`, `phone`. These are specific enough to return small result sets.
- **Avoid:**`gender`, `address-state`, `family`. These may match a very large number of records and create performance problems.
- **Use at least two independent parameters** so that a data entry error in one field does not cause a real match to be missed entirely.

## MATCH vs POSSIBLE\_MATCH

Use `MATCH` only when the evidence is strong enough that you are willing to accept occasional false positives without manual review. Use `POSSIBLE_MATCH` for cases where the evidence is meaningful, but you want a human to confirm.

A practical pattern is: use your strongest available evidence (such as a unique identifier or four demographic fields) for `MATCH`, and use one or two weaker combinations for `POSSIBLE_MATCH`. Do not create `POSSIBLE_MATCH` entries for combinations that will produce large volumes of links, since each one requires a data steward's time to resolve.

## Versioning

Change the `version` string every time you modify the rule definition script, even for small adjustments. Each MDM link stores the version that was active when it was created. Consistent versioning lets you identify which links an older rule set created and understand why, if you need to investigate a suspicious link later.

Use a meaningful value rather than a sequential number — for example, a date string such as `v2025-01-15` is easier to reason about than `v3` when reviewing a link created months earlier.

## Keeping the matchResultMap Clean

Each entry in `matchResultMap` causes MDM to evaluate the listed fields for every candidate. Entries that are logically redundant slow down matching without producing any additional links. Two common patterns to avoid:

```json
{
  "fieldA,fieldB": "MATCH",
  "fieldA,fieldB,fieldC": "MATCH"
}
```

Copy

The second entry is redundant: whenever `fieldA` and `fieldB` both pass, a `MATCH` link is already created by the first entry regardless of whether `fieldC` passes.

```json
{
  "fieldA": "MATCH",
  "fieldA,fieldB": "POSSIBLE_MATCH"
}
```

Copy

The second entry is also redundant: whenever `fieldA` passes, a `MATCH` is created, and `MATCH` takes precedence over `POSSIBLE_MATCH`. A `POSSIBLE_MATCH` would never actually be created for this combination.

* * *

# Next Steps

- **Test individual field comparisons** before deploying a new rule set using the [$mdm-evaluate](/content/docs/mdm/mdm_operations.html) operation. It runs a pair of resources through the matching rules and returns the comparison result for each matchField without creating any links.
- **Understand the full algorithm reference** — the complete list of matchers and similarity algorithms with guidance on when to use each is documented in [Supported Match Algorithms](/content/docs/mdm/supported_match_algorithms.html).
- **Configure enterprise identifiers** — if your organization uses a national patient ID or another authoritative unique identifier, see [Using Enterprise Identifiers (EIDs) in MDM Rule Definition](/content/docs/mdm/mdm_rule_definition_eid.html) for a detailed guide.
- **Review the full schema** — every field and its allowed values are documented in the [MDM Rule Schema](/content/docs/mdm/mdm_rule_schema.html) reference.
- **Resolve POSSIBLE\_MATCH links** — once your rules are in place and generating links, the MDM data stewardship interface lets reviewers confirm or dismiss each `POSSIBLE_MATCH` link. See the [MDM UI Tutorial](/content/docs/guide_tutorials/tutorial_mdm_ui.html) for a walkthrough.

[MDM First Golden Resource Tutorial](/content/docs/guide_tutorials/first_golden_resource.html)

MDM Advanced Rule Authoring Tutorial

Tutorials

[Introduction](/content/docs/guide_tutorials/tutorial_intro.html)
[Integration Testing Tutorial](/content/docs/guide_tutorials/tutorial_integration_test.html)
[Custom Operations Tutorial](/content/docs/guide_tutorials/tutorial_custom_operations.html)
[HL7 v2.x Ingestion Tutorial](/content/docs/guide_tutorials/tutorial_processing_hl7_v2x_feeds.html)
[Federated OAuth2/OIDC Tutorial](/content/docs/guide_tutorials/tutorial_federated_oauth2.html)
[MDM Tutorial](/content/docs/guide_tutorials/tutorial_mdm.html)
[MDM UI Tutorial](/content/docs/guide_tutorials/tutorial_mdm_ui.html)
[MDM First Golden Resource Tutorial](/content/docs/guide_tutorials/first_golden_resource.html)
[MDM Advanced Rule Authoring Tutorial](/content/docs/guide_tutorials/advanced_rule_authoring.html)
[MDM Web UI Admin Setup Tutorial](/content/docs/guide_tutorials/mdm_web_ui_admin_setup.html)
[CDA Import Tutorial](/content/docs/guide_tutorials/tutorial_cda_import.html)
[CDA Export Tutorial](/content/docs/guide_tutorials/tutorial_cda_export.html)
[CDA Export - Custom Narrative Generation Tutorial](/content/docs/guide_tutorials/tutorial_cda_export_narrative_generation.html)
[CDA Import JSON example](/content/docs/guide_tutorials/tutorial_cda_import_json.html)
[CDA Export Template JSON example](/content/docs/guide_tutorials/tutorial_cda_export_json.html)
[Consent Tutorial](/content/docs/guide_tutorials/tutorial_consent.html)
[CMS-0057-F IG Setup Tutorial](/content/docs/guide_tutorials/tutorial_terminology_cms57f.html)

[MDM Web UI Admin Setup Tutorial](/content/docs/guide_tutorials/mdm_web_ui_admin_setup.html)
