generating-synthetic-surrogates
GitHub将临床文本中的受保护健康信息替换为逼真的伪造值,保持文本可读性和NLP兼容性。支持一致性替换、自定义生成器及本地化配置,适用于训练数据准备和演示场景。
Trigger Scenarios
Install
npx skills add maziyarpanahi/openmed --skill generating-synthetic-surrogates -g -y
SKILL.md
Frontmatter
{
"name": "generating-synthetic-surrogates",
"license": "Apache-2.0",
"metadata": {
"pairs": "after",
"project": "OpenMed",
"version": "1.0",
"category": "de-identification"
},
"description": "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."
}
Generating synthetic surrogates
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.
When to use this skill
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.
Quick start
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.
Surrogates vs opaque redaction
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 |
Custom providers and label generators
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.
Workflow
- Choose
method="replace"(or a profile likegdpr_pseudonymization/canada_pipedathat replaces by default — seeconfiguring-privacy-policies). - Enable consistency with
consistent=Trueand aseed=so repeated mentions resolve to one identity and the result is reproducible. - Set
locale=so surrogates look native (pt_BR,de_DE, …); it defaults fromlangviaLANG_TO_LOCALE(deidentifying-multilingual-text). - Register custom generators for any house-specific formats (MRN, account, address) before the run.
- If reversibility is needed, add
keep_mapping=Trueand storeresult.mappingas a secret, separate from the output. - Verify no surrogate collides with a real value and residual risk is low
(
auditing-deidentification-runs).
Hand-off to / from OpenMed
- Core de-id:
deidentifying-clinical-text—method, thresholds,keep_mapping, policies. - Policies that replace:
configuring-privacy-policies(gdpr_pseudonymization,canada_pipeda). - Multilingual surrogates:
deidentifying-multilingual-text(lang/locale). - Restore:
openmed.reidentify(text, mapping)whenkeep_mapping=True. - Other surfaces: MCP
openmed_deidentify/ RESTPOST /pii/deidentify.
Edge cases & gotchas
- Surrogates must not collide with real values. A fake MRN that happens to be a real patient's MRN re-identifies them. Keep generated identifiers out of the real ID space (dedicated prefix/range) and check against your live keys.
- Surrogates look real but are not labeled. Anyone reading the output cannot
tell it's de-identified. Track provenance out-of-band (e.g. an
AuditReport) so surrogate notes are never mistaken for source records. - Keep the mapping secret. With
keep_mapping=True,result.mappingre-identifies everyone — encrypt it and store it apart from the output. register_label_generatoris global and process-wide. It mutates a shared registry; for isolation useAnonymizerConfig.custom_providersinstead.- Consistency is per-document by default.
consistent=Truemakes mentions agree within a call; cross-document stability requires the sameseed. - Permissive licensing only. Don't build providers from UMLS/SNOMED/CPT/MIMIC/i2b2/n2c2; call restricted resources out-of-process.
Standards & references
- GDPR pseudonymization, Regulation (EU) 2016/679 Art. 4(5): https://eur-lex.europa.eu/eli/reg/2016/679/oj
- HIPAA de-identification, 45 CFR 164.514(b): https://www.hhs.gov/hipaa/for-professionals/privacy/special-topics/de-identification/index.html
- OpenMed source:
openmed/core/pii.py(deidentify(method="replace")),openmed/core/anonymizer/(Anonymizer,AnonymizerConfig,register_label_generator,register_clinical_provider,LANG_TO_LOCALE).
Version History
- f213557 Current 2026-07-23 00:45


