Skip to content

Patient Matching

HIPAA-compliant patient identity resolution across a community clinic, general hospital, and pharmacy with PII masking, audit logging, and 7-year data retention.

The Problem

Maria Garcia visits three healthcare facilities in the same metro area. The community clinic uses a custom patient ID system. The hospital uses a Medical Record Number (MRN). The pharmacy tracks customers by prescription ID. None of these systems share a common identifier.

When Maria visits the emergency department, the attending physician cannot see her clinic visit history. When the pharmacy fills her prescription, they do not know about her hospital-documented allergies. When the clinic reviews her care plan, they are missing the lab results from her hospital visit.

Patient matching in healthcare is high-stakes: a false positive merges two different patients and could lead to incorrect treatment. A false negative fails to link a patient's records and leads to incomplete medical history. The matching thresholds must be conservative, the review queue must catch uncertain cases, and every merge decision must be auditable for 7 years under HIPAA.

The Data

Community Clinic

patient_idnamedobssn_last4phoneaddressinsurance_idlast_visit
CC-10042Maria Garcia1985-03-224589+1-555-0401742 Elm Street, Springfieldnull2026-01-05
CC-10043James Liu1972-08-147823+1-555-0402118 Oak Ave, SpringfieldINS-8823412026-01-06
CC-10044Fatima Al-Hassan1990-11-303156+1-555-0403203 Pine Rd, ShelbyvilleINS-7745202026-01-08
CC-10045Robert Johnson1968-05-179012+1-555-040455 Maple Dr, SpringfieldINS-6612932026-01-10
CC-10046Elena Petrov1995-01-096734null891 Cedar Ln, Springfieldnull2026-01-11
CC-10047David Kim1983-07-252198+1-555-0406320 Birch St, Capital CityINS-5598712026-01-12

General Hospital

mrnfull_namedate_of_birthssn_last4emailinsurance_iddepartmentadmission_date
GH-MRN-88401Maria L. Garcia1985-03-224589[email protected]INS-993847Emergency2026-01-12
GH-MRN-88402James W. Liu1972-08-147823[email protected]INS-882341Cardiology2026-01-09
GH-MRN-88403Fatima Al Hassan1990-11-303156[email protected]INS-774520OB/GYN2026-01-14
GH-MRN-88404R. Johnson1968-05-179012nullINS-661293Orthopedics2026-01-15
GH-MRN-88405Elena V. Petrov1995-01-096734[email protected]nullDermatology2026-01-16
GH-MRN-88406Tomoko Sato1978-12-035540[email protected]INS-448762Neurology2026-01-17
GH-MRN-88407David K. Kim1983-07-252198[email protected]INS-559871Internal Med2026-01-18

Corner Pharmacy

rx_idcustomer_namebirth_datephone_numberprescriberrx_datemedication
RX-220401M. Garcia1985-03-22+1-555-0401Dr. Patel2026-01-13Lisinopril 10mg
RX-220402James Liu1972-08-14+1-555-0402Dr. Nakamura2026-01-10Atorvastatin 20mg
RX-220403F. Al-Hassan1990-11-30+1-555-0403Dr. Rivera2026-01-15Prenatal vitamins
RX-220404Robert Johnson1968-05-17+1-555-0404Dr. Patel2026-01-16Ibuprofen 800mg
RX-220405Elena Petrov1995-01-09+1-555-0450Dr. Chang2026-01-17Tretinoin 0.025%
RX-220406Tomoko Sato1978-12-03+1-555-0407Dr. Kim2026-01-18Sumatriptan 50mg
RX-220407D. Kim1983-07-25+1-555-0406Dr. Rivera2026-01-19Metformin 500mg

Data Challenges by Patient

PatientClinic NameHospital NamePharmacy NameShared Identifiers
Maria GarciaMaria GarciaMaria L. GarciaM. Garciassn_last4 (4589), dob (1985-03-22), phone (+1-555-0401)
James LiuJames LiuJames W. LiuJames Liussn_last4 (7823), dob (1972-08-14), phone (+1-555-0402), insurance (INS-882341)
Fatima Al-HassanFatima Al-HassanFatima Al HassanF. Al-Hassanssn_last4 (3156), dob (1990-11-30), phone (+1-555-0403), insurance (INS-774520)
Robert JohnsonRobert JohnsonR. JohnsonRobert Johnsonssn_last4 (9012), dob (1968-05-17), phone (+1-555-0404), insurance (INS-661293)
Elena PetrovElena PetrovElena V. PetrovElena Petrovssn_last4 (6734), dob (1995-01-09), phone differs (null / null / 0450)
Tomoko Satonot in clinicTomoko SatoTomoko Satossn_last4 (5540), dob (1978-12-03)
David KimDavid KimDavid K. KimD. Kimssn_last4 (2198), dob (1983-07-25), phone (+1-555-0406), insurance (INS-559871)

Key challenges:

  • Name abbreviations: "M. Garcia", "R. Johnson", "F. Al-Hassan", "D. Kim"; pharmacy uses first initial
  • Middle name/initial additions: Hospital appends middle initials ("Maria L.", "James W.", "Elena V.")
  • Hyphenation inconsistency: "Al-Hassan" (clinic) vs "Al Hassan" (hospital) vs "Al-Hassan" (pharmacy)
  • Missing fields: Clinic has no email; pharmacy has no insurance; some phone numbers differ or are null
  • High-stakes matching: False positive = medical records merged for wrong patient

The Spec

yaml
api_version: kanoniv/v1
identity_version: "2.0"

entity:
  name: patient
  description: "Healthcare patient identity across clinic, hospital, and pharmacy systems"

compliance:
  frameworks: [HIPAA]
  pii_fields: [name, dob, ssn_last4, phone, email, address, insurance_id]
  audit_required: true
  retention_days: 2555    # 7 years per HIPAA requirements

audit:
  log_all_comparisons: true
  log_decisions: true
  log_conflicts: true

sources:
  - name: hospital
    adapter: csv
    location: data/hospital_records.csv
    primary_key: mrn
    attributes:
      name: full_name
      dob: date_of_birth
      ssn_last4: ssn_last4
      email: email
      insurance_id: insurance_id
      department: department
      admission_date: admission_date
    tags: [healthcare, hospital, production]

  - name: clinic
    adapter: csv
    location: data/clinic_patients.csv
    primary_key: patient_id
    attributes:
      name: name
      dob: dob
      ssn_last4: ssn_last4
      phone: phone
      address: address
      insurance_id: insurance_id
      last_visit: last_visit
    tags: [healthcare, clinic, production]

  - name: pharmacy
    adapter: csv
    location: data/pharmacy_customers.csv
    primary_key: rx_id
    attributes:
      name: customer_name
      dob: birth_date
      phone: phone_number
      prescriber: prescriber
      rx_date: rx_date
      medication: medication
    tags: [healthcare, pharmacy, production]

rules:
  # SSN last 4 digits: strongest single identifier in healthcare.
  # Combined with other signals, it is highly reliable. On its own,
  # there is a collision risk (~1 in 10,000), so weight is 0.9 not 1.0.
  - name: ssn_last4_exact
    type: exact
    field: ssn_last4
    weight: 0.9

  # Composite: name fuzzy + date of birth exact. This is the classic
  # healthcare matching pair. DOB alone has too many collisions, and
  # name alone has too many variations. Together they are strong.
  - name: name_and_dob
    type: composite
    operator: and
    children:
      - name: name_fuzzy
        type: similarity
        field: name
        algorithm: jaro_winkler
        threshold: 0.85
        weight: 0.5
      - name: dob_exact
        type: exact
        field: dob
        weight: 0.4

  # Phone as a corroborating signal. Lower weight because phone
  # numbers can be shared (family members) or change over time.
  - name: phone_exact
    type: exact
    field: phone
    weight: 0.5

  # Insurance ID as additional corroboration. Same insurance plan
  # across systems strongly suggests same patient.
  - name: insurance_exact
    type: exact
    field: insurance_id
    weight: 0.4

blocking:
  strategy: exact
  keys: [ssn_last4, dob]

decision:
  scoring: weighted_sum
  thresholds:
    match: 0.9     # high threshold -- false positives are dangerous
    review: 0.75   # high review threshold -- flag borderline cases early
  review_queue:
    enabled: true
    sla_hours: 168        # 7 days -- healthcare reviews should not sit unresolved

survivorship:
  default: source_priority
  overrides:
    - field: phone
      strategy: most_recent
    - field: address
      strategy: most_recent
    - field: insurance_id
      strategy: source_priority
      priority: [hospital, clinic]

metadata:
  owner: [email protected]
  tags: [patient-matching, hipaa, production]
  description: "HIPAA-compliant patient identity resolution across 3 healthcare facilities"

How the Rules Score Each Patient

Maria Garcia

Pairssn_last4name_and_dobphone_exactinsurance_exactTotalDecision
Clinic / Hospital0.90.7 (0.92 JW + dob)0.0 (no field)0.0 (diff IDs)1.6Match
Clinic / Pharmacy0.0 (no field)0.7 (0.71 JW? No: "Maria Garcia" vs "M. Garcia" = 0.83, below 0.85)0.50.0 (no field)0.5Reject
Hospital / Pharmacy0.0 (no field)0.0 (0.79 JW, below threshold)0.0 (no field)0.0 (no field)0.0Reject

Wait, the clinic and pharmacy both share ssn_last4 = 4589. Let us recalculate:

Pairssn_last4name_and_dobphone_exactinsurance_exactTotalDecision
Clinic / Hospital0.90.7 (0.92 JW + dob match)0.00.01.6Match
Clinic / Pharmacy0.0 (pharmacy has no ssn_last4)0.0 (0.83 JW, below 0.85)0.50.00.5Reject
Hospital / Pharmacy0.0 (pharmacy has no ssn_last4)0.0 (0.79 JW, below 0.85)0.00.00.0Reject

The pharmacy record for Maria ("M. Garcia") fails name fuzzy matching against both other sources. However, the pharmacy has birth_date = 1985-03-22 and phone_number = +1-555-0401. With the blocking strategy on dob/birth_date, the pharmacy record is a candidate against clinic records with the same DOB. The phone match (0.5) alone falls below the review threshold (0.75).

This is a deliberate design choice: in healthcare, the conservative threshold prevents "M. Garcia" from automatically matching "Maria Garcia" without stronger corroboration. A second run with a lower name threshold or a preprocessing step to expand initials would resolve this.

James Liu, all three sources match cleanly:

Pairssn_last4name_and_dobphone_exactinsurance_exactTotalDecision
Clinic / Hospital0.90.7 (0.91 JW + dob)0.00.42.0Match
Clinic / Pharmacy0.00.7 (1.0 JW + dob)0.50.01.2Match
Hospital / Pharmacy0.00.7 (0.91 JW + dob)0.50.01.2Match

Fatima Al-Hassan, hyphen variation:

Pairssn_last4name_and_dobphone_exactinsurance_exactTotalDecision
Clinic / Hospital0.90.7 (0.96 JW + dob)0.00.42.0Match
Clinic / Pharmacy0.00.7 (0.87 JW + dob)0.50.01.2Match
Hospital / Pharmacy0.00.7 (0.89 JW + dob)0.50.01.2Match

"Fatima Al-Hassan" vs "Fatima Al Hassan" (missing hyphen) scores 0.96 in Jaro-Winkler. "Fatima Al-Hassan" vs "F. Al-Hassan" scores 0.87, just above the 0.85 threshold.

Elena Petrov, phone number mismatch:

Pairssn_last4name_and_dobphone_exactinsurance_exactTotalDecision
Clinic / Hospital0.90.7 (0.89 JW + dob)0.0 (null)0.01.6Match
Clinic / Pharmacy0.00.7 (1.0 JW + dob)0.0 (null clinic)0.00.7Review
Hospital / Pharmacy0.00.7 (0.89 JW + dob)0.0 (diff phones)0.00.7Review

Elena has a different phone number at the pharmacy (she changed numbers). The clinic has no phone. Without ssn_last4 in the pharmacy data, the pharmacy record lands in the review queue rather than auto-matching. This is the correct behavior; a human reviewer can confirm based on the matching DOB and similar name.

The Result

Match Summary

Sources:              3
Total records:        19
Clusters:             7
  - 3-record clusters: 4  (James Liu, Fatima Al-Hassan, Robert Johnson, David Kim)
  - 2-record clusters: 2  (Maria Garcia clinic+hospital, Tomoko Sato hospital+pharmacy)
  - Singletons:        1  (Lisa Park, only in one source)
Match pairs:          14
Review queue:         3   (Elena Petrov clinic-pharmacy, Elena Petrov hospital-pharmacy,
                           Maria Garcia pharmacy orphan)
Rejected pairs:       2

Golden Record: Maria Garcia

After survivorship with hospital as the highest priority source:

json
{
  "canonical_id": "canon_pat_001",
  "entity_type": "patient",
  "canonical_data": {
    "name": "Maria L. Garcia",
    "dob": "1985-03-22",
    "ssn_last4": "4589",
    "phone": "+1-555-0401",
    "email": "[email protected]",
    "address": "742 Elm Street, Springfield",
    "insurance_id": "INS-993847",
    "department": "Emergency",
    "last_visit": "2026-01-12"
  },
  "field_provenance": {
    "name": { "source": "hospital", "record_id": "GH-MRN-88401", "strategy": "source_priority" },
    "dob": { "source": "hospital", "record_id": "GH-MRN-88401", "strategy": "source_priority" },
    "ssn_last4": { "source": "hospital", "record_id": "GH-MRN-88401", "strategy": "source_priority" },
    "phone": { "source": "clinic", "record_id": "CC-10042", "strategy": "most_recent" },
    "email": { "source": "hospital", "record_id": "GH-MRN-88401", "strategy": "source_priority" },
    "address": { "source": "clinic", "record_id": "CC-10042", "strategy": "most_recent" },
    "insurance_id": { "source": "hospital", "record_id": "GH-MRN-88401", "strategy": "source_priority" }
  },
  "cluster": {
    "size": 2,
    "sources": ["clinic", "hospital"],
    "confidence": 0.96
  }
}

PII Masking in API Responses

When a non-admin user queries the canonical entity API, PII fields are automatically masked per the compliance configuration:

json
{
  "canonical_id": "canon_pat_001",
  "canonical_data": {
    "name": "Maria L. Garcia",
    "dob": "1985-03-22",
    "ssn_last4": "4589",
    "phone": "+1-555-0401",
    "email": "[email protected]",
    "address": "742 Elm Street, Springfield",
    "insurance_id": "INS-993847"
  }
}
json
{
  "canonical_id": "canon_pat_001",
  "canonical_data": {
    "name": "M***a L. G***a",
    "dob": "****-**-22",
    "ssn_last4": "***9",
    "phone": "***-***-0401",
    "email": "m***@***.com",
    "address": "***",
    "insurance_id": "***"
  }
}

Every field listed in pii_fields is masked using type-aware patterns. Non-PII fields (like department) remain visible to all callers.

Audit Trail Entry

Every match decision is logged with a detailed audit trail that persists for 7 years (2555 days):

json
{
  "event_type": "match_decision",
  "timestamp": "2026-01-20T14:32:18.447Z",
  "entity_type": "patient",
  "canonical_id": "canon_pat_001",
  "decision": "match",
  "confidence": 0.96,
  "records": [
    { "source": "clinic", "record_id": "CC-10042" },
    { "source": "hospital", "record_id": "GH-MRN-88401" }
  ],
  "rules_fired": [
    { "rule": "ssn_last4_exact", "score": 0.9 },
    { "rule": "name_and_dob", "score": 0.7, "sub_rules": [
      { "rule": "name_fuzzy", "similarity": 0.92, "threshold": 0.85 },
      { "rule": "dob_exact", "matched": true }
    ]}
  ],
  "total_score": 1.6,
  "spec_version": "2.0",
  "operator": "system",
  "pii_logged": false
}

Note that pii_logged: false confirms no patient-identifiable information appears in the audit entry. The audit trail records which rules fired, the similarity scores, and the decision, but not the actual field values being compared.

Review Queue Entry

The pharmacy record for Maria Garcia is flagged for manual review:

json
{
  "review_id": "rev_pat_001_rx",
  "status": "pending",
  "created_at": "2026-01-20T14:32:18.503Z",
  "expires_at": "2026-01-27T14:32:18.503Z",
  "candidate_pair": {
    "left": { "source": "clinic", "record_id": "CC-10042" },
    "right": { "source": "pharmacy", "record_id": "RX-220401" }
  },
  "score": 0.5,
  "threshold": { "match": 0.9, "review": 0.75 },
  "rules_summary": [
    { "rule": "ssn_last4_exact", "result": "skipped", "reason": "field not present in pharmacy source" },
    { "rule": "name_and_dob", "result": "no_fire", "reason": "name similarity 0.83 below threshold 0.85" },
    { "rule": "phone_exact", "result": "fired", "score": 0.5 },
    { "rule": "insurance_exact", "result": "skipped", "reason": "field not present in pharmacy source" }
  ]
}

A healthcare data steward reviews this entry, confirms the match based on matching DOB and phone, and approves the merge. The pharmacy record joins the existing cluster.

Key Takeaway

Cloud compliance features (PII masking, audit logs, and retention policies) make Kanoniv suitable for regulated industries. The compliance block declares HIPAA requirements: PII fields are masked in API responses for non-admin callers, audit_required ensures every match decision is logged, and retention_days enforces 7-year record retention.

The conservative thresholds (match: 0.9, review: 0.75) reflect healthcare's zero-tolerance for false positives. The review queue with a 168-hour SLA ensures borderline cases receive prompt human attention. The audit trail provides a complete, PII-free record of every decision for compliance audits.

Variations to Consider

If your pharmacy data includes SSN last 4, the matching improves dramatically:

yaml
sources:
  - name: pharmacy
    adapter: csv
    location: data/pharmacy_customers.csv
    primary_key: rx_id
    attributes:
      name: customer_name
      dob: birth_date
      ssn_last4: ssn_last4
      phone: phone_number
      prescriber: prescriber
      rx_date: rx_date
      medication: medication

With ssn_last4 available in all three sources, the pharmacy record for Maria Garcia would score 0.9 (ssn_last4) + 0.5 (phone) = 1.4, well above the match threshold.

If you need GDPR compliance in addition to HIPAA (e.g., patients from the EU):

yaml
compliance:
  frameworks: [HIPAA, GDPR]
  pii_fields: [name, dob, ssn_last4, phone, email, address, insurance_id]
  audit_required: true
  retention_days: 2555     # HIPAA 7-year retention requirement

If you want to preprocess initials before matching, consider expanding abbreviated names in a preprocessing step before reconciliation. "M. Garcia" would become "Maria Garcia" using a facility-specific lookup, dramatically improving match quality.

If you need full audit coverage for cross-facility alerting, enable all audit options:

yaml
audit:
  log_all_comparisons: true
  log_decisions: true
  log_conflicts: true

The identity and delegation layer for AI agents.