Skip to main content

patient_matching

An open-source Python implementation of the CMS Patient Matching Proposal v3.2.2, providing deterministic patient matching using all 26 approved Table 2 field combination rules with support for IAL2 identity-proofed tokens, FHIR R4 Patient resources, and configurable fuzzy matching.

Overview

The CMS Patient Matching Proposal defines a standardized approach to matching patients across healthcare systems. This library implements:

  • 26 Table 2 matching rules with exact and fuzzy field comparisons
  • IAL2 token extraction — verify JWT tokens from Credential Service Providers (CSPs) and convert to FHIR Patient resources
  • Demographic normalization — text normalization, nickname expansion, E.164 phone formatting, USPS address standardization, placeholder detection
  • FHIR R4 integration — fetch patients from FHIR servers with OAuth2, paginate through Bundles
  • Patient cache — DuckDB-backed cache with field-level indexing and Damerau-Levenshtein fuzzy search
  • HTTP API — FastAPI application with FHIR $match endpoint
  • Confidence scoring — based on P(collision) values from Table 2 rules

Architecture

                          ┌─────────────────┐
                          │   FastAPI API    │
                          │  POST /$match   │
                          │  POST /match/ial2│
                          └────────┬────────┘
                                   │
                          ┌────────▼────────┐
                          │ PatientMatcher   │
                          │    Service       │
                          └──┬─────┬─────┬──┘
                             │     │     │
                ┌────────────┘     │     └────────────┐
                │                  │                   │
       ┌────────▼───────┐  ┌──────▼──────┐  ┌────────▼────────┐
       │ IAL2 Extractor  │  │ Normalizer  │  │ Matching Engine │
       │ (JWT → FHIR)   │  │ (A.1–D.6)  │  │ (26 Rules)      │
       └────────────────┘  └─────────────┘  └────────┬────────┘
                                                      │
                                             ┌────────▼────────┐
                                             │  Cache Backend   │
                                             │  (DuckDB)        │
                                             └────────┬────────┘
                                                      │
                                             ┌────────▼────────┐
                                             │  FHIR Client     │
                                             │  (fetch + OAuth) │
                                             └─────────────────┘

Installation

pip install cms-hte-patient-matching

Or install from source:

git clone https://github.com/icanbwell/cms-hte-patient-matching.git
cd cms-hte-patient-matching
pip install -e .

Optional Dependencies

The core package has minimal dependencies. Install extras for specific features:

# FHIR client with OAuth
pip install cms-hte-patient-matching[fhir]    # httpx, fhirschemapy

# DuckDB patient cache
pip install cms-hte-patient-matching[cache]   # duckdb, rapidfuzz

# FastAPI HTTP API
pip install cms-hte-patient-matching[api]     # fastapi, uvicorn

# Scheduled cache refresh
pip install cms-hte-patient-matching[scheduler]  # apscheduler

# All features
pip install cms-hte-patient-matching[all]

Quick Start

Match a FHIR Patient Against a Cache

from patient_matching.cache import DuckDBCache, CacheMatchingBackend, CacheManager
from patient_matching.fhir_client import FhirClient, FhirClientConfig
from patient_matching.matching import MatchingEngine
from patient_matching.normalization import NormalizationManager

# 1. Set up the patient cache
cache = DuckDBCache()  # in-memory DuckDB

# 2. Connect to a FHIR server and build the cache
fhir_client = FhirClient(FhirClientConfig(base_url="https://fhir.example.com/r4"))
normalizer = NormalizationManager()
cache_manager = CacheManager(
    fhir_client=fhir_client,
    cache=cache,
    normalizer=normalizer,
)
cache_manager.build_cache()

# 3. Set up the matching engine
backend = CacheMatchingBackend(cache)
engine = MatchingEngine(backend=backend)

# 4. Match a patient
query_patient = {
    "resourceType": "Patient",
    "name": [{"family": "Smith", "given": ["John"]}],
    "birthDate": "1990-01-15",
    "telecom": [{"system": "phone", "value": "+12125551234"}],
}

normalized = normalizer.normalize(query_patient)
result = engine.match(normalized)

print(result.outcome)           # MatchOutcome.MATCH
print(result.matched_rule_id)   # e.g., "rule_02"
print(result.match_type)        # "exact" or "fuzzy"

Use the Service Layer (Simplest API)

from patient_matching.api import PatientMatcherService

service = PatientMatcherService(
    engine=engine,
    normalizer=normalizer,
)

response = service.match_patient(query_patient)

print(response.outcome)           # "match", "no_match", or "ambiguous"
print(response.confidence_score)  # 0.0 - 1.0
print(response.matched_patient_ids)

Match from an IAL2 Token

from patient_matching.ial2_extraction import IAL2Extractor, TokenVerifier

verifier = TokenVerifier(
    jwks_uri="https://idp.example.com/.well-known/jwks.json",
    audience="your-client-id",
)
extractor = IAL2Extractor(verifier=verifier)

service = PatientMatcherService(
    engine=engine,
    normalizer=normalizer,
    ial2_extractor=extractor,
)

# Full pipeline: verify JWT → extract demographics → normalize → match
response = service.match_from_token(jwt_token_string)

Run the HTTP API

from patient_matching.api import create_app

app = create_app(service=service)

# Run with uvicorn
# uvicorn patient_matching.api:app --host 0.0.0.0 --port 8000

Or from the command line:

uvicorn patient_matching.api.app:app --host 0.0.0.0 --port 8000

Endpoints:

Method Path Description
POST /Patient/$match FHIR $match operation — accepts a Parameters resource containing a Patient, returns a Bundle
POST /match/ial2 Match from an IAL2 JWT token
GET /health Health check

FHIR $match Example

curl -X POST http://localhost:8000/Patient/\$match \
  -H "Content-Type: application/fhir+json" \
  -d '{
    "resourceType": "Parameters",
    "parameter": [{
      "name": "resource",
      "resource": {
        "resourceType": "Patient",
        "name": [{"family": "Smith", "given": ["John"]}],
        "birthDate": "1990-01-15"
      }
    }]
  }'

Table 2 Matching Rules

The CMS proposal defines 26 approved field combinations. Each rule specifies which fields must match, whether fuzzy matching is allowed (marked with *), and the collision probability:

Rule Fields P(collision) exact P(collision) fuzzy
01 First Name* + Last Name* + DOB + Street Line* 2.86e-14 2.73e-11
02 First Name + Last Name* + DOB + Phone 2.86e-14 3.96e-12
03 First Name* + Last Name* + DOB + Email 1.72e-14 1.64e-11
04 First Name* + Last Name + DOB + SSN Last 4 1.00e-11 1.00e-09
05 First Name + Last Name* + DOB + SSN Last 4 1.00e-11 1.39e-09
06 First Name* + Last Name + DOB + ITIN Last 4 1.00e-11 1.00e-09
07 First Name + Last Name* + DOB + ITIN Last 4 1.00e-11 1.39e-09
08 First Name + DOB + MBI 3.60e-13
09 First Name + DOB + Legal ID 3.60e-13
10 Last Name* + DOB + Legal ID 3.60e-13 4.99e-11
11 First Name + DOB + Phone 3.60e-10
12 First Name + DOB + Email 2.16e-10
13 Last Name + Phone + SSN Last 4 1.00e-11
14 Last Name + Phone + ITIN Last 4 1.00e-11
15 Last Name* + Email + SSN Last 4 6.00e-12 8.31e-10
16 Last Name* + Email + ITIN Last 4 6.00e-12 8.31e-10
17 First Name + Phone + SSN Last 4 1.00e-11
18 First Name + Phone + ITIN Last 4 1.00e-11
19 First Name + Email + SSN Last 4 6.00e-12
20 First Name + Email + ITIN Last 4 6.00e-12
21 Phone + MBI 1.00e-12
22 Phone + Legal ID 1.00e-12
23 Email + MBI 6.00e-13
24 Email + Legal ID 6.00e-13
25 Legal ID + MBI 1.00e-14
26 Namespace-bound unique IDs (EMPI, FHIR ID, CSP UUID) 0.0

Fields marked with * are fuzzy-eligible. Fuzzy matching uses Damerau-Levenshtein distance <= 1 for strings of 5 or more characters (per CMS Appendix E.3).

Normalization Pipeline

Before matching, patient demographics are normalized following the CMS proposal sections:

Text (A.1-A.4)

  • Lowercase all text
  • Fold diacritics and accents to ASCII (e.g., Müller -> muller)
  • Remove punctuation
  • Collapse whitespace

Names (B.1-B.6)

  • Parse into components (given, family, suffix) using nominally
  • Expand nicknames from reference table (e.g., Bob -> {Bob, Robert, Bobby, ...})
  • Normalize suffixes (e.g., Jr. -> jr, III -> iii)
  • Preserve historical/maiden names
  • Detect and remove placeholder names (e.g., Baby Boy, Test Patient)
  • Suffix conflict rule (B.5): if both patients have suffixes and they differ, the match is negated

Phone (C.3-C.4)

  • Convert to E.164 format using phonenumbers (e.g., (212) 555-1234 -> +12125551234)
  • Detect and remove placeholder numbers (e.g., 000-000-0000, 555-555-5555)

Address (C.1-C.2)

  • Standardize to USPS format using usaddress-scourgify
  • Detect and remove placeholder addresses

Identifiers (D.1-D.5)

  • Extract SSN last 4, ITIN last 4, MBI, Legal IDs from FHIR identifier entries
  • Detect and remove placeholder values (e.g., 000-00-0000, 999-99-9999)

Dates (A.5, D.6)

  • Validate format (YYYY-MM-DD, YYYY-MM, or YYYY)
  • No imputation or zero-padding of partial dates
  • Reject dates outside valid range (today - 120 years to today)

Modules

patient_matching.matching

Core matching engine implementing the 26 Table 2 rules.

  • MatchingEngine — evaluates all rules against a query patient, returns match/no_match/ambiguous
  • FieldExtractor — extracts matching-relevant fields from FHIR Patient resources
  • FieldComparator — exact and fuzzy field comparison (Damerau-Levenshtein)
  • MatchingBackend — abstract interface for candidate retrieval

patient_matching.normalization

Demographic normalization per CMS proposal sections A-D.

  • NormalizationManager — high-level entry point
  • PatientNormalizer — orchestrates all sub-normalizers
  • NameNormalizer — name parsing, nicknames, suffix normalization
  • PhoneNormalizer — E.164 conversion
  • AddressNormalizer — USPS standardization
  • DateNormalizer — date validation
  • PlaceholderDetector — detects non-comparable placeholder values

patient_matching.cache

Patient cache with field-level indexing for efficient blocking.

  • DuckDBCache — in-memory DuckDB with two-table schema (patients + field_values)
  • CacheMatchingBackend — adapts cache to the MatchingBackend interface
  • CacheManager — ETL pipeline: fetch from FHIR server -> normalize -> extract fields -> store

patient_matching.fhir_client

FHIR R4 client with OAuth2 support.

  • FhirClient — paginated patient fetching with Bundle next link traversal
  • ClientCredentialsAuth — OAuth2 client credentials flow with token caching

patient_matching.ial2_extraction

IAL2 JWT token processing per CSP Payload Specification V7.1.

  • IAL2Extractor — verify token, extract claims, convert to FHIR Patient
  • TokenVerifier — JWKS-based JWT signature verification
  • IAL2Claims — structured claims model with alias resolution
  • IAL2ToFhirConverter — converts IAL2 claims to FHIR R4 Patient resource

patient_matching.api

FastAPI HTTP API with FHIR-compliant endpoints.

  • PatientMatcherService — end-to-end orchestrator with confidence scoring
  • create_app() — FastAPI application factory

patient_matching.fuzzy

Multi-backend fuzzy string search (DuckDB, PostgreSQL, MongoDB, Redis, Elasticsearch).

  • FuzzySearchManager — unified search interface
  • FuzzySearchFactory — backend creation from configuration
  • FuzzySearchBenchmark — compare performance across backends

Development

Prerequisites

  • Python 3.12+
  • Docker (for containerized development)
  • uv (Python package manager)

Setup

Private packages are hosted on JFrog. Set JFROG_READ_TOKEN in your environment before building:

export JFROG_READ_TOKEN="<your-jfrog-token>"

Add it to ~/.zshrc or ~/.bashrc to persist across sessions.

make init          # Install dependencies and set up pre-commit hooks
make up            # Start Docker dev stack

Running Tests

make tests         # Run tests in Docker container

# Or locally:
uv run pytest -v

295 tests cover all modules including matching rules, normalization, caching, FHIR client, API endpoints, and fuzzy backends.

Code Quality

make run-pre-commit   # Run all pre-commit hooks

Pre-commit hooks include:

Tool Purpose
Ruff Linting and formatting
mypy Strict type checking (Python 3.12)
Bandit Security vulnerability scanning
detect-secrets Secret detection
Standard hooks Trailing whitespace, CRLF, valid Python AST, etc.

Building

make dist          # Build distribution packages into dist/ (named `dist`, not `build`,
                    # since `make build` already means "build the dev Docker image")
make testpackage   # Upload to TestPyPI (token in TWINE_PASSWORD)
make package       # Upload to PyPI (token in TWINE_PASSWORD)

Real releases publish via GitHub Actions using PyPI Trusted Publishing (.github/workflows/python-publish.yml, triggered on a GitHub Release) rather than make package — Trusted Publishing only authenticates from within that CI run, so make package/make testpackage are for local TestPyPI dry runs only.

Project Structure

patient_matching/
├── patient_matching/
│   ├── api/                    # FastAPI HTTP endpoints
│   │   ├── app.py              # Application factory + routes
│   │   ├── service.py          # Orchestrator + confidence scoring
│   │   └── tests/
│   ├── cache/                  # Patient cache storage
│   │   ├── cache_backend.py    # Abstract cache interface
│   │   ├── duckdb_cache.py     # DuckDB implementation
│   │   ├── matching_adapter.py # Cache → MatchingBackend bridge
│   │   ├── cache_manager.py    # ETL pipeline + scheduled refresh
│   │   └── tests/
│   ├── fhir_client/            # FHIR R4 client
│   │   ├── auth.py             # OAuth2 client credentials
│   │   ├── client.py           # Patient fetcher with pagination
│   │   └── tests/
│   ├── ial2_extraction/        # IAL2 JWT processing
│   │   ├── token_verifier.py   # JWKS signature verification
│   │   ├── ial2_extractor.py   # Token → FHIR Patient
│   │   ├── claims_model.py     # CSP Payload V7.1 claims
│   │   ├── fhir_converter.py   # Claims → FHIR conversion
│   │   └── tests/
│   ├── matching/               # Core matching engine
│   │   ├── matching_engine.py  # Rule evaluation + deduplication
│   │   ├── table2_rules.py     # 26 CMS-approved rules
│   │   ├── field_extractor.py  # FHIR → matching fields
│   │   ├── field_comparator.py # Exact + fuzzy comparison
│   │   ├── backend.py          # Abstract candidate retrieval
│   │   ├── match_result.py     # Result types + outcomes
│   │   └── tests/
│   ├── normalization/          # Demographic normalization
│   │   ├── normalizer.py       # Orchestrator
│   │   ├── manager.py          # High-level manager
│   │   ├── name_normalizer.py  # Names, nicknames, suffixes
│   │   ├── phone_normalizer.py # E.164 phone formatting
│   │   ├── address_normalizer.py # USPS standardization
│   │   ├── date_normalizer.py  # Date validation
│   │   ├── placeholder_detector.py # Placeholder value detection
│   │   ├── text_utils.py       # Text normalization primitives
│   │   └── tests/
│   └── fuzzy/                  # Multi-backend fuzzy search
│       └── fuzzy_db/
│           ├── core.py         # Enums, config, base classes
│           ├── factory.py      # Backend factory
│           ├── manager.py      # Unified search interface
│           ├── config.py       # YAML/JSON/env config loading
│           ├── utils.py        # Benchmarking + recommendations
│           ├── backends/       # DuckDB, PostgreSQL, MongoDB,
│           │                   # Redis, Elasticsearch
│           └── tests/
├── pyproject.toml
├── uv.lock
├── Makefile
├── VERSION
└── docker-compose.yml

License

Apache License 2.0

Repository: https://github.com/icanbwell/cms-hte-patient-matching

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

cms_hte_patient_matching-0.0.2.tar.gz (83.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

cms_hte_patient_matching-0.0.2-py3-none-any.whl (99.7 kB view details)

Uploaded Python 3

File details

Details for the file cms_hte_patient_matching-0.0.2.tar.gz.

File metadata

  • Download URL: cms_hte_patient_matching-0.0.2.tar.gz
  • Upload date:
  • Size: 83.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for cms_hte_patient_matching-0.0.2.tar.gz
Algorithm Hash digest
SHA256 510c9995b0080716e902fa0ce2b135e7b9f593c9d0bc45a54f1648e305d899e7
MD5 3a72d20ea4a74dadf5990c4d9a6ceae0
BLAKE2b-256 eae204b2225f3ef97e9c6c5b2c39c689fd58fc237c61a2023ea6be6105e8a684

See more details on using hashes here.

Provenance

The following attestation bundles were made for cms_hte_patient_matching-0.0.2.tar.gz:

Publisher: python-publish.yml on icanbwell/cms-hte-patient-matching

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file cms_hte_patient_matching-0.0.2-py3-none-any.whl.

File metadata

File hashes

Hashes for cms_hte_patient_matching-0.0.2-py3-none-any.whl
Algorithm Hash digest
SHA256 a51e9ad088de98b1d070db605a147b06b56b8dcefd5cedb67be174e5cc0bb150
MD5 c883cb7b5efd3dc3d1440ff274ce3ca0
BLAKE2b-256 210261c0e34fd97209df214c134eb7b7adf907cf2be859250054a759820b3b4a

See more details on using hashes here.

Provenance

The following attestation bundles were made for cms_hte_patient_matching-0.0.2-py3-none-any.whl:

Publisher: python-publish.yml on icanbwell/cms-hte-patient-matching

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

0.0.6

2 files

0.0.5

2 files

0.0.4

2 files

0.0.3

2 files

This release

0.0.2 This release

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page