Replace detected PHI with realistic, type-matched fake values in OpenMed so clinical notes stay readable and parseable instead of full of [REDACTED] markers. Use when the user wants surrogate names, MRNs, addresses, or dates rather than opaque masks, needs consistent fake identities across a document, must keep notes natural for downstream NLP, or wants to register a custom surrogate generator or provider. Covers deidentify(method="replace", consistent=True, seed=..., locale=...), register_label_generator, register_clinical_provider, and Anonymizer/AnonymizerConfig. Pairs with OpenMed deidentifying-clinical-text and configuring-privacy-policies.
69
87%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
Passed
No findings from the security scan
method="replace" swaps each detected identifier for a realistic, type-matched
fake — John Doe becomes Mark Lee, a phone becomes a plausible phone, a date
becomes a plausible date. Unlike opaque [REDACTED]/[NAME] masks, surrogate
text reads naturally and stays parseable by downstream NLP, while still
containing no real PHI. OpenMed generates surrogates on-device via Faker-backed
providers keyed to each canonical label.
Use surrogates when the de-identified text must remain readable or machine-
parseable: training data for clinical NLP, demos, QA, or notes a human still
needs to skim. If you only need the identifiers gone and don't care about
readability, plain method="mask" is simpler and more obviously redacted.
import openmed
note = (
"Patient John Doe (MRN 1234567) saw Dr. John Doe's colleague on 2024-03-02. "
"Reach John Doe at 617-555-0142."
)
result = openmed.deidentify(
note,
method="replace",
consistent=True, # every "John Doe" -> the SAME surrogate within this call
seed=42, # reproducible across runs
locale="en_US", # shapes the fakes; defaults from lang via LANG_TO_LOCALE
)
print(result.deidentified_text)
# Patient Mark Lee (MRN 8830127) saw Dr. Mark Lee's colleague on 2024-07-18. ...consistent=True is what makes the output coherent: the three mentions of
"John Doe" collapse to one fake identity instead of three different ones, so the
note still makes sense. seed= makes that mapping reproducible run to run.
method="mask" ([NAME]) | method="replace" (surrogate) | |
|---|---|---|
| Readability | low — placeholders | high — reads like a real note |
| Downstream NLP | tokenizers see [NAME] everywhere | natural distribution preserved |
| Co-reference | lost (all [NAME]) | preserved with consistent=True |
| Obvious it's de-identified | yes | no (must be tracked out-of-band) |
| Reversible | with keep_mapping=True | with keep_mapping=True |
When a built-in surrogate doesn't match your house format (e.g. your MRNs are
H + 7 digits), register a generator or a Faker provider.
from openmed import (
register_label_generator, register_clinical_provider,
Anonymizer, AnonymizerConfig,
)
# Override the surrogate for one canonical label. Signature: (faker, original, *, locale)
def hospital_mrn(faker, original, *, locale):
return f"H{faker.numerify('#######')}"
register_label_generator("ID_NUM", hospital_mrn) # global, all new Anonymizers
# Add a whole custom Faker provider (e.g. proprietary identifier formats):
register_clinical_provider(MyClinicalProvider) # a faker BaseProvider subclass
# Per-instance control (preferred for isolation): pass providers via config,
# and pull a single surrogate directly when you need one.
anon = Anonymizer(AnonymizerConfig(
lang="en", consistent=True, seed=7, custom_providers=[MyClinicalProvider],
))
fake = anon.surrogate("1234567", "ID_NUM")Use register_label_generator(canonical_label, fn) to swap one label's
surrogate; register_clinical_provider(provider) to add providers globally; or
AnonymizerConfig.custom_providers for per-run scoping. Validate any custom
label against openmed.CANONICAL_LABELS.
method="replace" (or a profile like gdpr_pseudonymization /
canada_pipeda that replaces by default — see configuring-privacy-policies).consistent=True and a seed= so repeated
mentions resolve to one identity and the result is reproducible.locale= so surrogates look native (pt_BR, de_DE, …); it
defaults from lang via LANG_TO_LOCALE
(deidentifying-multilingual-text).keep_mapping=True and store
result.mapping as a secret, separate from the output.auditing-deidentification-runs).deidentifying-clinical-text — method, thresholds,
keep_mapping, policies.configuring-privacy-policies
(gdpr_pseudonymization, canada_pipeda).deidentifying-multilingual-text (lang/locale).openmed.reidentify(text, mapping) when keep_mapping=True.openmed_deidentify / REST POST /pii/deidentify.AuditReport)
so surrogate notes are never mistaken for source records.keep_mapping=True, result.mapping
re-identifies everyone — encrypt it and store it apart from the output.register_label_generator is global and process-wide. It mutates a shared
registry; for isolation use AnonymizerConfig.custom_providers instead.consistent=True makes mentions
agree within a call; cross-document stability requires the same seed.openmed/core/pii.py (deidentify(method="replace")),
openmed/core/anonymizer/ (Anonymizer, AnonymizerConfig,
register_label_generator, register_clinical_provider, LANG_TO_LOCALE).maziyarpanahi/openmed
00e9efd
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.