CtrlK
BlogDocsLog inGet started
Tessl Logo

reidentifying-text

Reversibly de-identify clinical text with OpenMed and later restore the original PHI from a saved mapping. Use when the user needs pseudonymization rather than permanent anonymization, wants to mask PHI now and re-link it later under authorization (e.g. recontact, adjudication, GDPR pseudonymization), asks about deidentify keep_mapping, reidentify, or how to store and protect the re-identification mapping. Covers when reversibility is and is not appropriate (pseudonymization vs HIPAA Safe Harbor anonymization). Pairs after extracting-pii-entities and deidentifying-clinical-text.

75

Quality

93%

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide

SecuritybySnyk

Passed

No findings from the security scan

SKILL.md
Quality
Evals
Security

Reidentifying Text

Some workflows need to remove PHI for processing but keep the ability to restore it later under authorization — adjudication, patient recontact, linking results back to a record. That is pseudonymization (reversible), not anonymization (irreversible). OpenMed supports it with deidentify(..., keep_mapping=True) to capture a mapping, and reidentify to restore. Everything runs on-device.

When to use

  • You need to re-link redacted output to the original record later.
  • You are doing GDPR pseudonymization (Art. 4(5)): identifiers held separately, reversible under controlled conditions.
  • A reviewer must spot-check redactions against originals.

Do NOT use reversibility when:

  • The goal is HIPAA Safe Harbor anonymization or a true anonymous release — a re-identification mapping defeats anonymization. Use method="remove" and keep no mapping.
  • The redacted text leaves your trust boundary and the mapping might travel with it. The mapping is the secret; never co-locate it with the de-identified output.

Install

pip install "openmed[hf]"

Quick start: reversible round-trip

import openmed

note = "Patient John Doe (MRN 00481726) seen on 2024-03-02 by Dr. Alice Smith."

# 1) De-identify AND capture the reversal mapping
deid = openmed.deidentify(
    note,
    method="mask",          # or "replace" for realistic surrogates
    keep_mapping=True,       # <-- required to enable reidentify()
    policy="gdpr_pseudonymization",
)

safe_text = deid.deidentified_text       # ship/process this
mapping   = deid.mapping                  # SECRET: store separately, encrypted

# 2) Later, under authorization, restore the original
restored = openmed.reidentify(safe_text, mapping)
assert restored == note

reidentify(deidentified_text, mapping) performs the inverse substitution. The mapping is a dict[str, str] of redacted → original text, produced only when keep_mapping=True.

Use consistent surrogates for stable pseudonyms

For replacement that maps the same identifier to the same surrogate across a document (and reproducibly across runs with a seed):

import openmed

deid = openmed.deidentify(
    "Mr. John Doe called. John Doe's MRN is 00481726.",
    method="replace",
    consistent=True,    # same input value -> same surrogate within the run
    seed=42,            # reproducible across runs (implies consistent=True)
    keep_mapping=True,
)
print(deid.deidentified_text)
restored = openmed.reidentify(deid.deidentified_text, deid.mapping)

consistent=True keeps surrogates stable so analytics on the pseudonymized text stay coherent; seed makes them reproducible. Either way, reversal still requires the saved mapping.

Store the mapping securely — separate from the text

The mapping is the re-identification key. Treat it like a secret:

  • Never write it to the same store/file/log as the de-identified text.
  • Encrypt at rest; restrict access; audit every reversal.
  • Key the store by an opaque document id, not by any patient identifier.
import json, os
import openmed

note = "Patient John Doe (MRN 00481726), DOB 1970-01-15."
deid = openmed.deidentify(note, method="mask", keep_mapping=True, seed=7)

doc_id = "doc-7f3a"   # opaque id, no PHI

# De-identified text -> general processing store (safe to share downstream)
with open(f"deid/{doc_id}.txt", "w", encoding="utf-8") as fh:
    fh.write(deid.deidentified_text)

# Mapping -> SEPARATE, access-controlled, encrypted vault (illustrative path)
os.makedirs("vault", exist_ok=True)
with open(f"vault/{doc_id}.map.json", "w", encoding="utf-8") as fh:
    json.dump(deid.mapping, fh)   # encrypt this store in production

To re-identify later, load only the mapping for the authorized doc_id:

import json, openmed
with open("vault/doc-7f3a.map.json", encoding="utf-8") as fh:
    mapping = json.load(fh)
with open("deid/doc-7f3a.txt", encoding="utf-8") as fh:
    safe_text = fh.read()
original = openmed.reidentify(safe_text, mapping)

Reversible vs irreversible: pick deliberately

GoalCallMapping
GDPR pseudonymization (reversible)deidentify(..., keep_mapping=True, policy="gdpr_pseudonymization")keep, encrypted, separate
HIPAA Safe Harbor anonymizationdeidentify(..., method="remove", policy="hipaa_safe_harbor")none
Irreversible token linkingdeidentify(..., method="hash")none (one-way)

method="hash" yields consistent, one-way tokens — good for joining records without ever restoring the original. That is not reversible and needs no mapping.

Hand-off to / from OpenMed

  • From extracting-pii-entities: preview the spans first if you want to confirm what will be masked before committing to a reversible run.
  • From deidentifying-clinical-text: that skill covers methods, policies, and the safety sweep; this one adds the keep_mapping + reidentify round-trip.
  • To downstream NER: run openmed.analyze_text on deid.deidentified_text; re-identify only the final, authorized output — never intermediate logs.

Edge cases & gotchas

  • keep_mapping=True is mandatory for reidentify to work; without it deid.mapping is None.
  • Result field is .deidentified_text (and .pii_entities, .mapping), not .text/.entities.
  • Mapping direction is redacted → original. reidentify substitutes those keys back into the text.
  • Mask collisions: with method="mask", identical placeholders (e.g. two [NAME]) cannot be distinguished on reversal. For lossless round-trips use method="replace" with consistent=True/seed, which produces distinct, reversible surrogates.
  • Never anonymize-and-keep-mapping. If the release must be anonymous, keep no mapping — a stored mapping makes it pseudonymous, not anonymous.
  • Authorization & audit. Re-identification is privileged; log who/when/why and keep the mapping out of general PHI logs.

Standards & references

  • GDPR pseudonymization: Regulation (EU) 2016/679, Art. 4(5) & Recital 26 — https://gdpr-info.eu/art-4-gdpr/
  • HIPAA de-identification (Safe Harbor / Expert Determination): https://www.hhs.gov/hipaa/for-professionals/privacy/special-topics/de-identification/
  • See also OpenMed policy profiles gdpr_pseudonymization and hipaa_safe_harbor (configuring-privacy-policies).
Repository
maziyarpanahi/openmed
Last updated
First committed

Is this your skill?

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.