From openmed-skills
Produces signed, PHI-free audit trails for OpenMed de-identification runs. Use for HIPAA/GDPR compliance evidence, verifying redactions, and reviewing de-id decisions without exposing plaintext.
How this skill is triggered — by the user, by Claude, or both
Slash command
/openmed-skills:auditing-deidentification-runsThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
`deidentify(..., audit=True)` returns an `AuditReport` instead of the rewritten
deidentify(..., audit=True) returns an AuditReport instead of the rewritten
text: a deterministic, PHI-free record of every redaction decision —
offsets, label, detector confidence and threshold, the action taken, content
hashes, model provenance, and projected residual risk. Sign it to make it
tamper-evident, retain it as compliance evidence, and verify it later without
ever touching the original text. Runs on-device.
Use it whenever a de-identification needs to be defensible: regulatory retention, internal review, reproducibility checks, or proving to an auditor that identifiers were detected and handled — all without storing or exposing the PHI itself.
import openmed
note = "Patient John Doe (MRN 1234567) seen 2024-03-02. SSN 123-45-6789."
# Returns an AuditReport, NOT a DeidentificationResult, when audit=True.
report = openmed.deidentify(note, policy="hipaa_safe_harbor", audit=True)
# Make it tamper-evident with a release HMAC key (keep the key in a vault).
report.sign(b"my-release-hmac-key", key_id="release-2026")
# Persist the no-PHI report (no plaintext identifiers inside).
import json
with open("deid_audit.json", "w") as fh:
json.dump(report.to_dict(), fh, indent=2)
# Later: verify integrity. Optionally bind to the exact texts via their hashes.
ok = report.verify(b"my-release-hmac-key", original_text=note)
assert ok
AuditReport (from openmed.core.audit) carries no plaintext PHI. Key fields:
| Field | Meaning |
|---|---|
policy | policy profile name in effect |
resolved_profile | the concrete settings applied (method, model, thresholds, language, sweep flags) |
detectors | list[DetectorInfo] — provenance of each detector |
safety_sweep | structured-ID sweep metadata (patterns version, source) |
spans | list[AuditSpan] — one entry per detected identifier |
thresholds | per-label confidence thresholds used |
residual_risk | projected leakage / re-identification risk summary |
openmed_version | library version that produced the report |
manifest_hash | hash of the model manifest used |
document_length | character length of the input |
input_hash | sha256: hash of the original text |
deidentified_text_hash | sha256: hash of the de-identified output |
repro_hash | deterministic hash over the canonical payload |
signature | AuditSignature once .sign() is called (else None) |
AuditSpan (per identifier, no plaintext): start, end, label,
canonical_label, sources, confidence, threshold, action, surrogate,
text_hash (hash of the span text), plus evidence and a redacted context.
DetectorInfo: source, model_id, model_format, commit, metadata.
AuditSignature: key_id, algorithm (HMAC-SHA256), value (hex digest).
The whole point of de-identification is to not retain identifiers. An audit log full of raw names and MRNs would itself be a PHI breach. So OpenMed records:
start/end) — where an identifier was, not what it was;input_hash, deidentified_text_hash, per-span text_hash) —
prove what was processed without revealing it;detectors, manifest_hash, openmed_version) — which
model/version made each call, for reproducibility;residual_risk) — projected leakage so a reviewer can
judge whether the output is safe to release.This lets an auditor confirm coverage and integrity while the report stays shareable.
audit=True and a policy= to get an AuditReport.residual_risk before releasing the output — non-zero projected
leakage means review (cross-check with auditing-safe-harbor-checklist).report.sign(key, key_id=...) using a secret release HMAC key
stored in a vault (never in the repo).report.to_dict() (JSON). For a reviewer view without full
context, use report.export_review_bundle().report.verify(key). Pass original_text= /
deidentified_text= to also confirm the texts match the recorded hashes.deidentifying-clinical-text (deidentify); add
audit=True to get the report from the same call.auditing-safe-harbor-checklist maps span labels to the
18 HIPAA identifier categories and flags gaps.configuring-privacy-policies — resolved_profile reflects the
chosen policy=.openmed_deidentify and REST POST /pii/deidentify
drive the same engine; audit output is available through them.audit=True changes the return type to AuditReport. You get the report,
not .deidentified_text. Run a separate normal call if you also need the text..sign() is a deliberate second
step; signature is None until you call it. Empty/None keys are rejected.verify needs the same key. It checks the HMAC and the repro_hash;
optionally it re-hashes texts you pass to confirm they are the audited ones.metadata/evidence; the design is hash-and-offset only.openmed/core/audit.py (AuditReport, AuditSpan,
AuditSignature, DetectorInfo, sign, verify), openmed/core/pii.py
(deidentify(audit=True)).npx claudepluginhub maziyarpanahi/openmed --plugin openmed-skillsGenerates and verifies 21 CFR Part 11 audit trails for OpenMed pipelines in GxP/clinical-trial settings, with electronic signatures and tamper-evidence.
Guides PHI data handling per HIPAA: 18 identifiers, Safe Harbor/Expert Determination de-identification, minimum necessary principle, RBAC access controls, audit logging, encryption at rest/transit, secure disposal.
De-identifies PHI via HIPAA safe harbor (removes 18 identifiers) and expert determination methods. Assesses re-identification risks, limited datasets, and data agreements.