Configuring privacy policies
A policy profile is a named bundle of de-identification decisions: which
action (mask/redact/replace/keep) applies to each label, how aggressively
detectors arbitrate, whether the mandatory safety sweep runs, and whether a
reversible mapping is produced. OpenMed ships seven profiles. Pass one by name
to deidentify(policy=...) and you get a compliance-aligned default without
hand-wiring 50+ per-label actions. Everything runs on-device.
When to use this skill
Use it to pick the right policy= for a regulatory context, to understand what
a profile actually changes, or to go beyond the bundle — keeping quasi-
identifiers for research, or registering a custom surrogate generator (e.g. your
own MRN format).
Quick start
import openmed
note = "Jane Roe, DOB 1979-04-11, lives in Cambridge MA 02139. SSN 123-45-6789."
# HIPAA Safe Harbor: mask every identifier class.
safe = openmed.deidentify(note, policy="hipaa_safe_harbor")
# GDPR pseudonymization: replace with fakes AND keep a reversible mapping.
gdpr = openmed.deidentify(note, policy="gdpr_pseudonymization")
mapping = gdpr.mapping # present because the profile sets keep_mapping=True
# Research limited dataset: mask direct identifiers, KEEP quasi-identifiers
# (dates, age, ZIP, geography) so the data stays analytically useful.
lds = openmed.deidentify(note, policy="research_limited_dataset")
The seven bundled profiles
Each profile lives in openmed/core/policies/<name>.json. Summary of what each
actually configures:
| Profile |
Default action |
Quasi-identifiers |
Mapping |
Safety sweep |
Use case |
hipaa_safe_harbor |
mask all |
masked |
none |
mandatory |
HIPAA §164.514(b)(2) Safe Harbor — strip all 18 identifier classes |
hipaa_expert_review_assist |
redact |
redacted; clinical concepts kept |
none |
optional |
Assist Expert Determination (§164.514(b)(1)); keeps microbiology/clinical terms for a statistician to assess residual risk |
gdpr_pseudonymization |
replace |
replaced; clinical kept |
kept + reversible |
mandatory |
GDPR Art. 4(5) pseudonymization — reversible under controlled key |
canada_pipeda |
replace (IDs masked) |
replaced |
kept + reversible |
mandatory |
PIPEDA-aligned; like GDPR but masks ID_NUM/SSN outright |
research_limited_dataset |
mask direct ids |
keeps dates, age, ZIP, geography, org, job |
none |
mandatory |
HIPAA Limited Data Set (§164.514(e)) — usable for research with a DUA |
clinical_minimal_redaction |
mask direct ids |
keeps quasi-identifiers |
none |
optional |
Internal clinical use where readability matters; lighter cascade |
strict_no_leak |
mask everything |
masked; even clinical concepts masked |
none |
mandatory |
Maximum-recall, union arbitration, all cascade tiers — zero-leakage posture |
Key dimensions to reason about:
default_action — mask ([NAME]), redact, replace (fake value), or
keep. Set per label in the profile's actions map.
policy_label_actions — coarse action by class:
DIRECT_IDENTIFIER / QUASI_IDENTIFIER / CLINICAL_CONCEPT. Research and
minimal-redaction profiles keep quasi-identifiers; strict-no-leak masks
even clinical concepts.
keep_mapping / reversible_id — only GDPR and PIPEDA produce a
reversible mapping. Treat that mapping as PHI.
safety_sweep_mandatory — deterministic structured-ID sweep (SSN, MRN-
like, emails) that runs regardless of model confidence. Off only for the two
"minimal/assist" profiles.
arbitration_mode / forced_cascade_tiers — strict_no_leak uses
high_recall_union across tiers R0–R3 (most aggressive); minimal redaction
uses only R0–R1.
Choosing: map regulation → profile
- Publish or share data with no DUA, US →
hipaa_safe_harbor.
- Statistician will certify low risk (keep clinical signal) →
hipaa_expert_review_assist, then human Expert Determination.
- EU subjects, need reversibility under a key →
gdpr_pseudonymization.
- Canadian subjects →
canada_pipeda.
- Research cohort needing dates/age/geography →
research_limited_dataset
(requires a Data Use Agreement).
- Internal clinical workflow, readability first →
clinical_minimal_redaction.
- Adversarial / zero-tolerance leakage →
strict_no_leak.
Customizing beyond the bundle
When a profile is close but not exact, drive the engine directly with
Anonymizer / AnonymizerConfig, or register custom generators.
from openmed import (
Anonymizer, AnonymizerConfig,
register_label_generator, register_clinical_provider,
)
# 1) Per-instance config (language, locale, deterministic surrogates):
anon = Anonymizer(AnonymizerConfig(lang="en", consistent=True, seed=7))
fake_name = anon.surrogate("John Doe", "PERSON") # type-matched surrogate
# 2) Override the surrogate for one canonical label (e.g. your MRN format).
# Generators take (faker, original, *, locale) and return a string.
def hospital_mrn(faker, original, *, locale):
return f"H{faker.numerify('#######')}"
register_label_generator("ID_NUM", hospital_mrn) # global, all new Anonymizers
# 3) Add a custom Faker provider (e.g. proprietary identifier formats).
register_clinical_provider(MyClinicalProvider) # a faker BaseProvider class
Use register_label_generator(canonical_label, fn) to swap one label's
surrogate; use register_clinical_provider(provider) to add whole Faker
providers. For per-call scoping, pass providers via
AnonymizerConfig.custom_providers instead of the global registry. Validate
custom labels against openmed.CANONICAL_LABELS.
Hand-off to / from OpenMed
- Apply a policy:
openmed.deidentify(text, policy="<name>") — see
deidentifying-clinical-text.
- Surrogate strategy:
generating-synthetic-surrogates for method="replace"
with consistent/seed/locale and custom providers.
- Verify coverage:
auditing-deidentification-runs (audit=True) and
auditing-safe-harbor-checklist (18 identifier categories).
- Other surfaces: MCP
openmed_deidentify and REST POST /pii/deidentify
accept the same policy argument.
Edge cases & gotchas
- Profiles are configuration, not a guarantee. A profile that keeps quasi-
identifiers (research/minimal) does not meet Safe Harbor — pair it with a
Data Use Agreement or Expert Determination.
- Reversible profiles produce a re-identifying mapping. GDPR/PIPEDA mappings
are as sensitive as the raw PHI; store them encrypted and separately.
register_label_generator is global and persists for the process. It
mutates a shared registry; prefer AnonymizerConfig.custom_providers for
isolated, per-run behavior.
- Surrogates must not collide with real values. Keep generated identifiers
out of the real ID space; see
generating-synthetic-surrogates.
- Permissive licensing only. Do not bundle UMLS/SNOMED/CPT/MIMIC/i2b2/n2c2
into custom providers; call restricted terminologies out-of-process.
Standards & references
1---2name: configuring-privacy-policies3description: Select and customize OpenMed's seven bundled privacy policy profiles for de-identification, and build custom surrogate generators. Use when the user asks which policy fits HIPAA Safe Harbor vs Expert Determination vs GDPR vs PIPEDA vs a research limited dataset vs strict no-leak, wants to pass policy= to deidentify(), needs to keep quasi-identifiers for research, or must register a custom MRN/name/address surrogate provider. Covers the profile-to-use-case map, AnonymizerConfig/Anonymizer for fine control, and register_clinical_provider / register_label_generator. Pairs with OpenMed deidentifying-clinical-text and generating-synthetic-surrogates.4license: Apache-2.05---67# Configuring privacy policies89A **policy profile** is a named bundle of de-identification decisions: which10action (mask/redact/replace/keep) applies to each label, how aggressively11detectors arbitrate, whether the mandatory safety sweep runs, and whether a12reversible mapping is produced. OpenMed ships seven profiles. Pass one by name13to `deidentify(policy=...)` and you get a compliance-aligned default without14hand-wiring 50+ per-label actions. Everything runs **on-device**.1516## When to use this skill1718Use it to pick the right `policy=` for a regulatory context, to understand what19a profile actually changes, or to go beyond the bundle — keeping quasi-20identifiers for research, or registering a custom surrogate generator (e.g. your21own MRN format).2223## Quick start2425```python26import openmed2728note = "Jane Roe, DOB 1979-04-11, lives in Cambridge MA 02139. SSN 123-45-6789."2930# HIPAA Safe Harbor: mask every identifier class.31safe = openmed.deidentify(note, policy="hipaa_safe_harbor")3233# GDPR pseudonymization: replace with fakes AND keep a reversible mapping.34gdpr = openmed.deidentify(note, policy="gdpr_pseudonymization")35mapping = gdpr.mapping # present because the profile sets keep_mapping=True3637# Research limited dataset: mask direct identifiers, KEEP quasi-identifiers38# (dates, age, ZIP, geography) so the data stays analytically useful.39lds = openmed.deidentify(note, policy="research_limited_dataset")40```4142## The seven bundled profiles4344Each profile lives in `openmed/core/policies/<name>.json`. Summary of what each45actually configures:4647| Profile | Default action | Quasi-identifiers | Mapping | Safety sweep | Use case |48| --- | --- | --- | --- | --- | --- |49| `hipaa_safe_harbor` | **mask** all | masked | none | **mandatory** | HIPAA §164.514(b)(2) Safe Harbor — strip all 18 identifier classes |50| `hipaa_expert_review_assist` | **redact** | redacted; clinical concepts **kept** | none | optional | Assist Expert Determination (§164.514(b)(1)); keeps microbiology/clinical terms for a statistician to assess residual risk |51| `gdpr_pseudonymization` | **replace** | replaced; clinical kept | **kept + reversible** | mandatory | GDPR Art. 4(5) pseudonymization — reversible under controlled key |52| `canada_pipeda` | **replace** (IDs **masked**) | replaced | **kept + reversible** | mandatory | PIPEDA-aligned; like GDPR but masks ID_NUM/SSN outright |53| `research_limited_dataset` | mask direct ids | **keeps** dates, age, ZIP, geography, org, job | none | mandatory | HIPAA Limited Data Set (§164.514(e)) — usable for research with a DUA |54| `clinical_minimal_redaction` | mask direct ids | **keeps** quasi-identifiers | none | **optional** | Internal clinical use where readability matters; lighter cascade |55| `strict_no_leak` | **mask everything** | masked; even clinical concepts masked | none | mandatory | Maximum-recall, union arbitration, all cascade tiers — zero-leakage posture |5657Key dimensions to reason about:5859- **`default_action`** — `mask` (`[NAME]`), `redact`, `replace` (fake value), or60 `keep`. Set per label in the profile's `actions` map.61- **`policy_label_actions`** — coarse action by class:62 `DIRECT_IDENTIFIER` / `QUASI_IDENTIFIER` / `CLINICAL_CONCEPT`. Research and63 minimal-redaction profiles **keep** quasi-identifiers; strict-no-leak masks64 even clinical concepts.65- **`keep_mapping` / `reversible_id`** — only GDPR and PIPEDA produce a66 reversible mapping. Treat that mapping as PHI.67- **`safety_sweep_mandatory`** — deterministic structured-ID sweep (SSN, MRN-68 like, emails) that runs regardless of model confidence. Off only for the two69 "minimal/assist" profiles.70- **`arbitration_mode` / `forced_cascade_tiers`** — `strict_no_leak` uses71 `high_recall_union` across tiers R0–R3 (most aggressive); minimal redaction72 uses only R0–R1.7374## Choosing: map regulation → profile7576- **Publish or share data with no DUA, US** → `hipaa_safe_harbor`.77- **Statistician will certify low risk (keep clinical signal)** →78 `hipaa_expert_review_assist`, then human Expert Determination.79- **EU subjects, need reversibility under a key** → `gdpr_pseudonymization`.80- **Canadian subjects** → `canada_pipeda`.81- **Research cohort needing dates/age/geography** → `research_limited_dataset`82 (requires a Data Use Agreement).83- **Internal clinical workflow, readability first** → `clinical_minimal_redaction`.84- **Adversarial / zero-tolerance leakage** → `strict_no_leak`.8586## Customizing beyond the bundle8788When a profile is close but not exact, drive the engine directly with89`Anonymizer` / `AnonymizerConfig`, or register custom generators.9091```python92from openmed import (93 Anonymizer, AnonymizerConfig,94 register_label_generator, register_clinical_provider,95)9697# 1) Per-instance config (language, locale, deterministic surrogates):98anon = Anonymizer(AnonymizerConfig(lang="en", consistent=True, seed=7))99fake_name = anon.surrogate("John Doe", "PERSON") # type-matched surrogate100101# 2) Override the surrogate for one canonical label (e.g. your MRN format).102# Generators take (faker, original, *, locale) and return a string.103def hospital_mrn(faker, original, *, locale):104 return f"H{faker.numerify('#######')}"105106register_label_generator("ID_NUM", hospital_mrn) # global, all new Anonymizers107108# 3) Add a custom Faker provider (e.g. proprietary identifier formats).109register_clinical_provider(MyClinicalProvider) # a faker BaseProvider class110```111112Use `register_label_generator(canonical_label, fn)` to swap one label's113surrogate; use `register_clinical_provider(provider)` to add whole Faker114providers. For per-call scoping, pass providers via115`AnonymizerConfig.custom_providers` instead of the global registry. Validate116custom labels against `openmed.CANONICAL_LABELS`.117118## Hand-off to / from OpenMed119120- **Apply a policy:** `openmed.deidentify(text, policy="<name>")` — see121 `deidentifying-clinical-text`.122- **Surrogate strategy:** `generating-synthetic-surrogates` for `method="replace"`123 with `consistent`/`seed`/`locale` and custom providers.124- **Verify coverage:** `auditing-deidentification-runs` (`audit=True`) and125 `auditing-safe-harbor-checklist` (18 identifier categories).126- **Other surfaces:** MCP `openmed_deidentify` and REST `POST /pii/deidentify`127 accept the same `policy` argument.128129## Edge cases & gotchas130131- **Profiles are configuration, not a guarantee.** A profile that *keeps* quasi-132 identifiers (research/minimal) does **not** meet Safe Harbor — pair it with a133 Data Use Agreement or Expert Determination.134- **Reversible profiles produce a re-identifying mapping.** GDPR/PIPEDA mappings135 are as sensitive as the raw PHI; store them encrypted and separately.136- **`register_label_generator` is global and persists for the process.** It137 mutates a shared registry; prefer `AnonymizerConfig.custom_providers` for138 isolated, per-run behavior.139- **Surrogates must not collide with real values.** Keep generated identifiers140 out of the real ID space; see `generating-synthetic-surrogates`.141- **Permissive licensing only.** Do not bundle UMLS/SNOMED/CPT/MIMIC/i2b2/n2c2142 into custom providers; call restricted terminologies out-of-process.143144## Standards & references145146- HIPAA de-identification, 45 CFR 164.514(b): https://www.hhs.gov/hipaa/for-professionals/privacy/special-topics/de-identification/index.html147- HIPAA Limited Data Set, 45 CFR 164.514(e).148- GDPR pseudonymization, Regulation (EU) 2016/679 Art. 4(5) & Art. 25.149- PIPEDA (Canada): https://www.priv.gc.ca/en/privacy-topics/privacy-laws-in-canada/the-personal-information-protection-and-electronic-documents-act-pipeda/150- OpenMed source: `openmed/core/policies/*.json`, `openmed/core/anonymizer/`.