Skip to content
Security
Skill

/generating-synthetic-surrogates

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

BOOST
From plugin
openmed
5.7k74 skills
Install
$ npx -y skills add maziyarpanahi/openmed --skill generating-synthetic-surrogates --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/generating-synthetic-surrogates

Context preview

The summary Claude sees to decide when to auto-load this skill.

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

SKILL.md

generating-synthetic-surrogates.SKILL.md
name: generating-synthetic-surrogates
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."
license: Apache-2.0
metadata:
  project: OpenMed
  category: de-identification
  pairs: after
  version: "1.0"

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

1. **Choose `method="replace"`** (or a profile like `gdpr_pseudonymization` / `canada_pipeda` that replaces by default — see `configuring-privacy-policies`). 2. **Enable consistency** with `consistent=True` and a `seed=` so repeated mentions resolve to one identity and the result is reproducible. 3. **Set `locale=`** so surrogates look native (`pt_BR`, `de_DE`, …); it defaults from `lang` via `LANG_TO_LOCALE` (`deidentifying-multilingual-text`). 4. **Register custom generators** for any house-specific formats (MRN, account, address) before the run. 5. **If reversibility is needed**, add `keep_mapping=True` and store `result.mapping` as a secret, separate from the output. 6. **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)` when `keep_mapping=True`.
  • **Other surfaces:** MCP `openmed_deidentify` / REST `POST /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 m

Read more
Ships withopenmed

Local-first healthcare AI: clinical NER and HIPAA PII de-identification on hardware you control. 2,200+ medical models, 35 model-backed PII languages, and Python, MLX, Android and browser runtimes. Apache-2.0 SDK.

Get the whole plugin

Other skills on openmed.