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_id | name | dob | ssn_last4 | phone | address | insurance_id | last_visit |
|---|---|---|---|---|---|---|---|
CC-10042 | Maria Garcia | 1985-03-22 | 4589 | +1-555-0401 | 742 Elm Street, Springfield | null | 2026-01-05 |
CC-10043 | James Liu | 1972-08-14 | 7823 | +1-555-0402 | 118 Oak Ave, Springfield | INS-882341 | 2026-01-06 |
CC-10044 | Fatima Al-Hassan | 1990-11-30 | 3156 | +1-555-0403 | 203 Pine Rd, Shelbyville | INS-774520 | 2026-01-08 |
CC-10045 | Robert Johnson | 1968-05-17 | 9012 | +1-555-0404 | 55 Maple Dr, Springfield | INS-661293 | 2026-01-10 |
CC-10046 | Elena Petrov | 1995-01-09 | 6734 | null | 891 Cedar Ln, Springfield | null | 2026-01-11 |
CC-10047 | David Kim | 1983-07-25 | 2198 | +1-555-0406 | 320 Birch St, Capital City | INS-559871 | 2026-01-12 |
General Hospital
| mrn | full_name | date_of_birth | ssn_last4 | insurance_id | department | admission_date | |
|---|---|---|---|---|---|---|---|
GH-MRN-88401 | Maria L. Garcia | 1985-03-22 | 4589 | [email protected] | INS-993847 | Emergency | 2026-01-12 |
GH-MRN-88402 | James W. Liu | 1972-08-14 | 7823 | [email protected] | INS-882341 | Cardiology | 2026-01-09 |
GH-MRN-88403 | Fatima Al Hassan | 1990-11-30 | 3156 | [email protected] | INS-774520 | OB/GYN | 2026-01-14 |
GH-MRN-88404 | R. Johnson | 1968-05-17 | 9012 | null | INS-661293 | Orthopedics | 2026-01-15 |
GH-MRN-88405 | Elena V. Petrov | 1995-01-09 | 6734 | [email protected] | null | Dermatology | 2026-01-16 |
GH-MRN-88406 | Tomoko Sato | 1978-12-03 | 5540 | [email protected] | INS-448762 | Neurology | 2026-01-17 |
GH-MRN-88407 | David K. Kim | 1983-07-25 | 2198 | [email protected] | INS-559871 | Internal Med | 2026-01-18 |
Corner Pharmacy
| rx_id | customer_name | birth_date | phone_number | prescriber | rx_date | medication |
|---|---|---|---|---|---|---|
RX-220401 | M. Garcia | 1985-03-22 | +1-555-0401 | Dr. Patel | 2026-01-13 | Lisinopril 10mg |
RX-220402 | James Liu | 1972-08-14 | +1-555-0402 | Dr. Nakamura | 2026-01-10 | Atorvastatin 20mg |
RX-220403 | F. Al-Hassan | 1990-11-30 | +1-555-0403 | Dr. Rivera | 2026-01-15 | Prenatal vitamins |
RX-220404 | Robert Johnson | 1968-05-17 | +1-555-0404 | Dr. Patel | 2026-01-16 | Ibuprofen 800mg |
RX-220405 | Elena Petrov | 1995-01-09 | +1-555-0450 | Dr. Chang | 2026-01-17 | Tretinoin 0.025% |
RX-220406 | Tomoko Sato | 1978-12-03 | +1-555-0407 | Dr. Kim | 2026-01-18 | Sumatriptan 50mg |
RX-220407 | D. Kim | 1983-07-25 | +1-555-0406 | Dr. Rivera | 2026-01-19 | Metformin 500mg |
Data Challenges by Patient
| Patient | Clinic Name | Hospital Name | Pharmacy Name | Shared Identifiers |
|---|---|---|---|---|
| Maria Garcia | Maria Garcia | Maria L. Garcia | M. Garcia | ssn_last4 (4589), dob (1985-03-22), phone (+1-555-0401) |
| James Liu | James Liu | James W. Liu | James Liu | ssn_last4 (7823), dob (1972-08-14), phone (+1-555-0402), insurance (INS-882341) |
| Fatima Al-Hassan | Fatima Al-Hassan | Fatima Al Hassan | F. Al-Hassan | ssn_last4 (3156), dob (1990-11-30), phone (+1-555-0403), insurance (INS-774520) |
| Robert Johnson | Robert Johnson | R. Johnson | Robert Johnson | ssn_last4 (9012), dob (1968-05-17), phone (+1-555-0404), insurance (INS-661293) |
| Elena Petrov | Elena Petrov | Elena V. Petrov | Elena Petrov | ssn_last4 (6734), dob (1995-01-09), phone differs (null / null / 0450) |
| Tomoko Sato | not in clinic | Tomoko Sato | Tomoko Sato | ssn_last4 (5540), dob (1978-12-03) |
| David Kim | David Kim | David K. Kim | D. Kim | ssn_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
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
| Pair | ssn_last4 | name_and_dob | phone_exact | insurance_exact | Total | Decision |
|---|---|---|---|---|---|---|
| Clinic / Hospital | 0.9 | 0.7 (0.92 JW + dob) | 0.0 (no field) | 0.0 (diff IDs) | 1.6 | Match |
| Clinic / Pharmacy | 0.0 (no field) | 0.7 (0.71 JW? No: "Maria Garcia" vs "M. Garcia" = 0.83, below 0.85) | 0.5 | 0.0 (no field) | 0.5 | Reject |
| Hospital / Pharmacy | 0.0 (no field) | 0.0 (0.79 JW, below threshold) | 0.0 (no field) | 0.0 (no field) | 0.0 | Reject |
Wait, the clinic and pharmacy both share ssn_last4 = 4589. Let us recalculate:
| Pair | ssn_last4 | name_and_dob | phone_exact | insurance_exact | Total | Decision |
|---|---|---|---|---|---|---|
| Clinic / Hospital | 0.9 | 0.7 (0.92 JW + dob match) | 0.0 | 0.0 | 1.6 | Match |
| Clinic / Pharmacy | 0.0 (pharmacy has no ssn_last4) | 0.0 (0.83 JW, below 0.85) | 0.5 | 0.0 | 0.5 | Reject |
| Hospital / Pharmacy | 0.0 (pharmacy has no ssn_last4) | 0.0 (0.79 JW, below 0.85) | 0.0 | 0.0 | 0.0 | Reject |
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:
| Pair | ssn_last4 | name_and_dob | phone_exact | insurance_exact | Total | Decision |
|---|---|---|---|---|---|---|
| Clinic / Hospital | 0.9 | 0.7 (0.91 JW + dob) | 0.0 | 0.4 | 2.0 | Match |
| Clinic / Pharmacy | 0.0 | 0.7 (1.0 JW + dob) | 0.5 | 0.0 | 1.2 | Match |
| Hospital / Pharmacy | 0.0 | 0.7 (0.91 JW + dob) | 0.5 | 0.0 | 1.2 | Match |
Fatima Al-Hassan, hyphen variation:
| Pair | ssn_last4 | name_and_dob | phone_exact | insurance_exact | Total | Decision |
|---|---|---|---|---|---|---|
| Clinic / Hospital | 0.9 | 0.7 (0.96 JW + dob) | 0.0 | 0.4 | 2.0 | Match |
| Clinic / Pharmacy | 0.0 | 0.7 (0.87 JW + dob) | 0.5 | 0.0 | 1.2 | Match |
| Hospital / Pharmacy | 0.0 | 0.7 (0.89 JW + dob) | 0.5 | 0.0 | 1.2 | Match |
"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:
| Pair | ssn_last4 | name_and_dob | phone_exact | insurance_exact | Total | Decision |
|---|---|---|---|---|---|---|
| Clinic / Hospital | 0.9 | 0.7 (0.89 JW + dob) | 0.0 (null) | 0.0 | 1.6 | Match |
| Clinic / Pharmacy | 0.0 | 0.7 (1.0 JW + dob) | 0.0 (null clinic) | 0.0 | 0.7 | Review |
| Hospital / Pharmacy | 0.0 | 0.7 (0.89 JW + dob) | 0.0 (diff phones) | 0.0 | 0.7 | Review |
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: 2Golden Record: Maria Garcia
After survivorship with hospital as the highest priority source:
{
"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:
{
"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"
}
}{
"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):
{
"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:
{
"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:
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: medicationWith 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):
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 requirementIf 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:
audit:
log_all_comparisons: true
log_decisions: true
log_conflicts: trueRelated
- Cloud: Compliance & PII Masking: Full compliance configuration reference
- Cloud: Audit Logs: Audit trail configuration and retention
- Spec Reference: Entity: Entity, compliance, and hierarchy configuration
- Tuning Match Quality: Diagnose and improve match quality
