How to Implement Custom Clinical ID Providers for PII De-identification in OpenMed
You can extend OpenMed's de-identification engine by subclassing Faker's BaseProvider, registering it via register_clinical_provider(), and optionally mapping it to canonical labels in LABEL_GENERATORS to generate organization-specific surrogate identifiers.
OpenMed uses the Faker library to synthesize realistic surrogate values for protected health information (PHI/PII). When standard identifiers like national insurance numbers or medical record numbers do not match your organization's specific formats, you must implement custom clinical ID providers for PII de-identification to ensure the synthetic data passes your validation requirements while maintaining consistency across de-identification runs.
Architecture Overview
The de-identification pipeline in OpenMed separates provider definitions from the anonymization engine. Built-in clinical ID providers reside in openmed/core/anonymizer/providers/clinical_ids.py, which includes generators for Aadhaar numbers, German Steuer-IDs, and medical record numbers (MRNs).
The registration flow follows a three-stage pattern:
- Provider Definition: Subclass
faker.providers.BaseProviderto expose generation methods - Registration: Call
register_clinical_provider()fromopenmed/core/anonymizer/__init__.pyto append the class to the internal_extra_providerslist - Label Binding: Map canonical labels (e.g.,
"CUSTOM_HOSPITAL_ID") to generator functions inLABEL_GENERATORSlocated inopenmed/core/anonymizer/registry.py
Creating a Custom Faker Provider
Create a Python file that subclasses BaseProvider and implements a method returning your synthetic ID format. The method must generate strings that satisfy validators in openmed/core/pii_i18n.py if you intend to use the built-in validation pipeline.
# openmed/custom_providers/hospital_id.py
from faker.providers import BaseProvider
import random
class HospitalIdProvider(BaseProvider):
"""Generates IDs of the form HOSP-XXXX where X is a digit."""
def hospital_id(self) -> str:
# 4 random digits, zero-padded
suffix = f"{random.randint(0, 9999):04d}"
return f"HOSP-{suffix}"
Registering the Provider Globally
To make your provider available across all new Anonymizer instances, register it at application startup. The register_clinical_provider() function patches the internal registration routine that the engine invokes when building new Faker instances.
# Execute once at application startup
from openmed.core.anonymizer import register_clinical_provider
from openmed.custom_providers.hospital_id import HospitalIdProvider
register_clinical_provider(HospitalIdProvider)
Important: Already instantiated Anonymizer objects will not see the new provider. This design prevents side-effects in long-running processes.
Mapping Custom Labels to Generators
If you define a new canonical label (e.g., "CUSTOM_HOSPITAL_ID"), you must register a generator function in LABEL_GENERATORS. The surrogate generation logic in openmed/core/anonymizer/engine.py looks up generators from this registry.
# openmed/core/anonymizer/registry.py or a plugin module
from openmed.core.anonymizer.registry import LABEL_GENERATORS
def hospital_id_generator(faker, original, *, locale):
# Faker now has the hospital_id method thanks to registration
return faker.hospital_id()
# Register under a new canonical label
LABEL_GENERATORS["CUSTOM_HOSPITAL_ID"] = hospital_id_generator
If you are extending an existing label (e.g., "IDENTIFIER"), simply ensure your provider method name matches the expected generator pattern, and the engine will invoke it automatically.
Per-Instance Registration
For scenarios requiring provider isolation, pass the provider class via AnonymizerConfig. This scopes the provider to a single instance without affecting global registration.
from openmed.core.anonymizer import Anonymizer, AnonymizerConfig
from openmed.custom_providers.hospital_id import HospitalIdProvider
cfg = AnonymizerConfig(custom_providers=[HospitalIdProvider])
anon = Anonymizer(config=cfg)
# The provider is available only for this instance
synthetic = anon.surrogate("12345", "CUSTOM_HOSPITAL_ID")
Validation and Determinism Considerations
When implementing custom clinical ID providers for PII de-identification, ensure your solution handles validation and reproducibility:
- Locale Awareness: Providers attach to each Faker locale instance automatically, allowing locale-specific formatting (e.g., language-dependent prefixes).
- Determinism: When
consistent=Trueand aseedare provided, the Faker instance seeds after provider registration, guaranteeing reproducible IDs across runs. - Validation Loop: For complex IDs requiring check digits (like the German Steuer-ID), import the relevant validator from
openmed/core/pii_i18n.pyinside your provider and loop until a valid value is found. Always enforce aMAX_TRIESlimit to prevent infinite loops. - Performance: Trial-and-error generation adds computational overhead; implement caching for expensive validation routines when possible.
Summary
- Create a Faker provider by subclassing
BaseProviderin a new module with methods returning your ID format. - Register globally via
register_clinical_provider()fromopenmed/core/anonymizer/__init__.pyto populate the internal_extra_providerslist. - Map new canonical labels to generator functions in
LABEL_GENERATORSfromopenmed/core/anonymizer/registry.py. - Use
AnonymizerConfig(custom_providers=[...])for instance-scoped registration without side effects. - Validate generated IDs against functions in
openmed/core/pii_i18n.pyand respectMAX_TRIESlimits for complex formats.
Frequently Asked Questions
How do I ensure my custom clinical IDs pass OpenMed's validation?
Import the relevant validator function from openmed/core/pii_i18n.py inside your provider method and implement a retry loop. Generate candidate values until the validator returns True, but always enforce a MAX_TRIES ceiling to prevent infinite loops if the validation rules are too strict.
Can I register multiple custom providers for different locales?
Yes. Register each provider class using register_clinical_provider(). Since providers attach to locale-specific Faker instances, you can implement locale-aware logic inside your methods (e.g., checking self.generator.locale) or create separate provider classes for different regions.
Will custom providers affect existing Anonymizer instances?
No. The registration in openmed/core/anonymizer/__init__.py only affects subsequently created Anonymizer objects. Existing instances maintain their original Faker configuration to prevent side-effects in long-running applications or multi-tenant environments.
How do I handle trial-and-error generation for complex ID formats?
Implement a generation loop inside your provider method that generates candidates, validates them using functions from openmed/core/pii_i18n.py, and returns the first valid result. Set a reasonable MAX_TRIES constant (e.g., 1000) and raise an exception or return a fallback if exceeded, mirroring the pattern used in openmed/core/anonymizer/providers/clinical_ids.py for the German Steuer-ID provider.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →