Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -419,7 +419,7 @@ const { x12, manifest } = deidentifyX12(parseX12(raw), { context });

| Locus | Handling |
| ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **`NM1`** (subscriber / patient / dependent) | name (`03–07`) **removed**; id (`09`) routed by the `08` qualifier: SSN **removed**, member **removed** under Safe Harbor |
| **`NM1`** (subscriber / patient / dependent) | name (`03` to `07`) **removed**; id (`09`) routed by the `08` qualifier: SSN **removed**, member **removed** under Safe Harbor |
| **`NM1` / `N1`** (employer, entity code `36`) | name + id **removed** on the same footing as a patient-side party: §164.514(b)(2)(i) names the individual's **employers** |
| **`NM1`** (recognized provider / organization) | **retained** (provider identity is not the individual's PHI, mirroring the HL7 adapter), and the **role code** it was classified on is recorded at its locus |
| **`NM1`** (unknown entity code) | **fails closed**: name + id blocked |
Expand Down Expand Up @@ -461,6 +461,13 @@ the format and the parser-surface reason, and **no** transformed document, manif
of any kind, rather than whatever the Telecom parser would have made of those bytes. Telecom callers
are unaffected.

**One transaction per Telecom message in this release.** `deidentifyTelecom` handles a message that
carries one transaction. A message whose header declares more than one is not handled yet: with
`@cosyte/ncpdp` 0.1.x installed the call throws that parser's multi-transaction build error and
returns nothing, and an older `@cosyte/ncpdp` returns only the first transaction with the header count
unchanged. Split a multi-transaction message before you de-identify it. A later release de-identifies
every transaction or refuses the message with a typed `DEID_*` error.

## De-identify a DICOM study

The `@cosyte/deid/dicom` adapter **delegates rather than reimplements**: [`@cosyte/dicom`](https://github.com/cosyte/dicom)
Expand Down Expand Up @@ -642,7 +649,8 @@ const opts = profileOptions(strict, ctx); // pass straight to any adapter
`@cosyte/deid` **transforms per a policy and evidences what it did: it never certifies**. Read
[`docs-content/limitations.md`](docs-content/limitations.md) before relying on it for anything that
leaves your control: the fail-closed posture, structured-core-only (free text is block-by-default),
**DICOM metadata-only** (burned-in pixels flagged, not cleaned), **NCPDP SCRIPT refused**, the BYO
**DICOM metadata-only** (burned-in pixels flagged, not cleaned), **NCPDP SCRIPT refused**, **NCPDP
Telecom one transaction per message**, the BYO
free-text redactor is the consumer's responsibility, and the Expert-Determination report **makes no
determination**.

Expand All @@ -651,7 +659,7 @@ determination**.
- **Policy engine**: `safe-harbor` built in; `defineDeidPolicy()` to deviate deliberately.
- **Five transforms**: redact, generalize (date→year, ZIP→3-digit/`000`, age→`90+`), keyed date-shift,
keyed-HMAC pseudonymize, keyed hash.
- **18 Safe Harbor categories**: §164.514(b)(2)(i)(A)–(R), including the open-ended catch-all (R).
- **18 Safe Harbor categories**: §164.514(b)(2)(i)(A) to (R), including the open-ended catch-all (R).
- **Fail-closed rule**: anything uncertain is blocked, never passed through; clinical values are
retained untouched.
- **Value-free manifest**: category + transform + locus + count + disposition + code + a boolean
Expand Down
30 changes: 19 additions & 11 deletions docs-content/limitations.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,17 @@ Read this page before you rely on the library for anything that leaves your cont

## What it does NOT do

| It does **not**… | Because… |
| ----------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Certify HIPAA de-identification | The library **transforms and evidences**; it never certifies. Output is _"Safe-Harbor-transformed per the configured policy,"_ never _"de-identified."_ |
| Discharge the §164.514(b)(2)(ii) **actual-knowledge** clause | That is an organizational judgment about what a recipient knows: the library surfaces the residual (kept year, safe-3-digit ZIP) so a human can apply it, but cannot make it. |
| Render or certify **Expert Determination** (§164.514(b)(1)) | _"The risk is very small"_ is a qualified statistician's contextual judgment about a dataset **and its recipient**. The [ED support report](#expert-determination) emits value-free facts as **input**; `determination` is always `null` and it computes no risk score. |
| De-identify **free text / narrative** | Free-text loci (HL7 OBX-5/NTE, C-CDA narrative `<text>`, FHIR notes/`div`, X12 MSG/NTE, NCPDP free text) are **blocked by default**. A [BYO redactor](#free-text) is **consumer-asserted**, never the library's guarantee; a naive built-in regex scrub is deliberately **refused** as a false-safety hazard. |
| Clean **DICOM burned-in pixels** or full-face images (category Q) | v1 is **metadata-only** (delegated PS3.15 Annex E). Burned-in annotation raises `DICOM_BURNED_IN_ANNOTATION_NOT_REMOVED` and `burnedInAnnotationHazard`; pixel decode is a future `@cosyte/dicom-pixel`. The two pixel options of CID 7050 are declared **withheld** on the result and never written to `(0012,0064)`. **Do not release an image on metadata alone.** |
| Guarantee **DICOM replacement-UID consistency across calls** by default | Referential integrity of replacement UIDs is guaranteed **only within a single call** unless the caller supplies a shared `uidMap`, which then extends it to every call sharing that cache. The declared scope is on the result (`uidReferentialIntegrity.scope`), so it is read rather than inferred. |
| Handle **NCPDP SCRIPT** ePrescribing | **Refused, not deferred in prose.** The current parser surface (lossy serialize + an address-less `Patient` model) cannot support a faithful structural de-id, so an NCPDP entry point handed a SCRIPT document raises a typed `DEID_FORMAT_UNSUPPORTED` naming the format and the parser-surface reason and returns **no** document, manifest or partial output at all. NCPDP **Telecom** is supported and is unaffected. |
| Guarantee against a determined re-identification attack | De-identification reduces risk to the regulatory bar; it is not a cryptographic guarantee. **Key custody is the consumer's**: a leaked HMAC key or date-shift offset re-identifies. |
| Do anything the manifest does not record | If it is not in the manifest, the library did not do it. The manifest is the complete, value-free audit. |
| It does **not**… | Because… |
| ----------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Certify HIPAA de-identification | The library **transforms and evidences**; it never certifies. Output is _"Safe-Harbor-transformed per the configured policy,"_ never _"de-identified."_ |
| Discharge the §164.514(b)(2)(ii) **actual-knowledge** clause | That is an organizational judgment about what a recipient knows: the library surfaces the residual (kept year, safe-3-digit ZIP) so a human can apply it, but cannot make it. |
| Render or certify **Expert Determination** (§164.514(b)(1)) | _"The risk is very small"_ is a qualified statistician's contextual judgment about a dataset **and its recipient**. The [ED support report](#expert-determination) emits value-free facts as **input**; `determination` is always `null` and it computes no risk score. |
| De-identify **free text / narrative** | Free-text loci (HL7 OBX-5/NTE, C-CDA narrative `<text>`, FHIR notes/`div`, X12 MSG/NTE, NCPDP free text) are **blocked by default**. A [BYO redactor](#free-text) is **consumer-asserted**, never the library's guarantee; a naive built-in regex scrub is deliberately **refused** as a false-safety hazard. |
| Clean **DICOM burned-in pixels** or full-face images (category Q) | v1 is **metadata-only** (delegated PS3.15 Annex E). Burned-in annotation raises `DICOM_BURNED_IN_ANNOTATION_NOT_REMOVED` and `burnedInAnnotationHazard`; pixel decode is a future `@cosyte/dicom-pixel`. The two pixel options of CID 7050 are declared **withheld** on the result and never written to `(0012,0064)`. **Do not release an image on metadata alone.** |
| Guarantee **DICOM replacement-UID consistency across calls** by default | Referential integrity of replacement UIDs is guaranteed **only within a single call** unless the caller supplies a shared `uidMap`, which then extends it to every call sharing that cache. The declared scope is on the result (`uidReferentialIntegrity.scope`), so it is read rather than inferred. |
| Handle **NCPDP SCRIPT** ePrescribing | **Refused, not deferred in prose.** The current parser surface (lossy serialize + an address-less `Patient` model) cannot support a faithful structural de-id, so an NCPDP entry point handed a SCRIPT document raises a typed `DEID_FORMAT_UNSUPPORTED` naming the format and the parser-surface reason and returns **no** document, manifest or partial output at all. NCPDP **Telecom** is supported and is unaffected. |
| Guarantee against a determined re-identification attack | De-identification reduces risk to the regulatory bar; it is not a cryptographic guarantee. **Key custody is the consumer's**: a leaked HMAC key or date-shift offset re-identifies. |
| Do anything the manifest does not record | If it is not in the manifest, the library did not do it. The manifest is the complete, value-free audit. |

## Fail-closed posture

Expand Down Expand Up @@ -348,3 +348,11 @@ report.disclaimer.includes("NOT a determination"); // => true
The library's promise is narrow and honest: **structured-field, fail-closed, policy-driven
Safe-Harbor transformation with a value-free manifest, never a leaked patient, never a destroyed
clinical value, and never a claim of "de-identified."**

## NCPDP Telecom: one transaction per message

`deidentifyTelecom` handles a Telecom message that carries one transaction. A message whose header
declares more than one is not handled in this release: with `@cosyte/ncpdp` 0.1.x installed the call
throws that parser's multi-transaction build error and returns nothing, and an older `@cosyte/ncpdp`
returns only the first transaction with the header count unchanged. Split such a message first. A later
release de-identifies every transaction or refuses the message with a typed `DEID_*` error.
Loading