@cosyte/deid
Public entry point for @cosyte/deid - a healthcare de-identification engine.
@cosyte/deid is not a parser. It is a consumer-tier library: it applies a HIPAA-grounded
de-identification policy (Safe Harbor by default) to a structurally-located model of a healthcare
document and returns a transformed model plus a value-free manifest of what it acted on. It
borrows the parser archetype's disciplines (typed diagnostics, immutable output, a policy/profile
system) but inverts the parser's reflex: it fails closed - an unrecognized structure or an
un-locatable identifier is blocked, never passed through as safe.
Honesty line (governs the whole library). Results are "Safe-Harbor-transformed per the configured policy" - never "de-identified" and never "HIPAA-compliant". Safe Harbor is implemented mechanically; the §164.514(b)(2)(ii) actual-knowledge condition is the consumer's; Expert Determination (§164.514(b)(1)) is supported, never rendered or certified here.
The root entry ships the format-agnostic core: the policy engine, the five transforms, the 18-category Safe Harbor model, the fail-closed rule, and the value-free manifest - tested against a generic locus model. The per-format locus maps (HL7 v2, C-CDA, FHIR, X12, NCPDP, DICOM) live behind the matching subpath exports.
Classes
DeidContext
An opaque handle to the consumer's key material. Construct it with createDeidContext; it exposes no secret field and redacts itself through every stringify channel.
Example
import { createDeidContext } from "@cosyte/deid";
const ctx = createDeidContext({ key: "secret" });
JSON.stringify({ ctx }); // => '{"ctx":"[DeidContext:redacted]"}'
Constructors
Constructor
new DeidContext():
DeidContext
Internal
Use createDeidContext.
Returns
Methods
forPatient()
forPatient(
patientId):DeidContext
Derive a sibling context scoped to a different patient, reusing the same key and seed. Used to de-identify a multi-patient corpus with one key while keeping each patient's date-shift offset distinct.
Parameters
patientId
string
The patient scope for the derived context.
Returns
A new DeidContext sharing this one's key/seed, scoped to patientId.
Example
import { createDeidContext } from "@cosyte/deid";
const base = createDeidContext({ key: "secret" });
const forPatient = base.forPatient("patient-2");
toJSON()
toJSON():
string
Redacts through JSON.stringify.
Returns
string
toString()
toString():
string
Redacts through String(...) and template interpolation.
Returns
string
DeidError
The error thrown on a FATAL_CODES condition. Carries a stable code; its message is
safe to log - it never contains PHI (no value, no key, no offset).
Example
import { deidentify, DeidError, FATAL_CODES } from "@cosyte/deid";
try {
deidentify(null as never, {});
} catch (err) {
if (err instanceof DeidError && err.code === FATAL_CODES.EMPTY_INPUT) {
// handle empty input
}
}
Extends
Error
Constructors
Constructor
new DeidError(
code,message):DeidError
Parameters
code
The FatalCode classifying this fatal.
message
string
A PHI-free explanation safe to log.
Returns
Overrides
Error.constructor
Properties
code
readonlycode:FatalCode
The stable fatal code.
DeidRegistry
The corpus-level consistency handle. Construct it with createDeidRegistry. It holds the consumer's key in a module-private registry - the handle exposes no secret field and redacts itself through every stringify channel.
Example
import { createDeidRegistry } from "@cosyte/deid";
const registry = createDeidRegistry({ key: "secret" });
// Same patient across documents shifts by the same offset:
const a = registry.forPatient("patient-1");
const b = registry.forPatient("patient-1");
a === b; // => true (memoized - same handle, same offset)
Constructors
Constructor
new DeidRegistry():
DeidRegistry
Internal
Use createDeidRegistry.
Returns
Methods
forPatient()
forPatient(
patientKey):DeidContext
Return the DeidContext scoped to patientKey, minting and memoizing it on first use. The
same key always yields the same context - hence the same deterministic date-shift offset - so a
patient's dates shift consistently across every document in the corpus. Pass this context as
DeidOptions.context to deidentify (or a per-format adapter) for that patient's documents.
Parameters
patientKey
string
A stable per-patient key (an MRN, an enterprise patient id - the consumer's choice, provided it is the same across that patient's documents).
Returns
The patient-scoped, self-redacting context.
Example
import { createDeidRegistry } from "@cosyte/deid";
const registry = createDeidRegistry({ key: "secret" });
const ctx = registry.forPatient("patient-1");
pseudonym()
pseudonym(
id):string
Map an identifier to its corpus-wide consistent pseudonym - a keyed-HMAC surrogate that is the same for the same input everywhere and not reversible without the key. Patient-independent: the same MRN maps to the same token regardless of which patient scope processes it, so records link.
Parameters
id
string
The identifier to pseudonymize (MRN, beneficiary number, account number).
Returns
string
The consistent lowercase-hex surrogate.
Example
import { createDeidRegistry } from "@cosyte/deid";
const registry = createDeidRegistry({ key: "secret" });
registry.pseudonym("MRN-1") === registry.pseudonym("MRN-1"); // => true (consistent)
remapUid()
remapUid(
uid):string
Map an opaque unique identifier (a DICOM study/series/instance UID, a GUID) to its corpus-wide
consistent surrogate, so cross-document UID linkage survives de-identification. Domain-separated
from pseudonym, so a UID and an identifier with the same text never share a surrogate. This
is the format-agnostic linkage primitive; a format that owns UID validity (DICOM UIDs must be a
valid 0.… OID) handles that in its own adapter.
Parameters
uid
string
The unique identifier to remap.
Returns
string
The consistent lowercase-hex surrogate.
Example
import { createDeidRegistry } from "@cosyte/deid";
const registry = createDeidRegistry({ key: "secret" });
registry.remapUid("1.2.840.113619.2.55") === registry.remapUid("1.2.840.113619.2.55"); // => true
toJSON()
toJSON():
string
Redacts through JSON.stringify.
Returns
string
toString()
toString():
string
Redacts through String(...) and template interpolation.
Returns
string
Interfaces
CategoryCoverage
Per-category coverage - for one of the 18 Safe Harbor categories (45 CFR §164.514(b)(2)(i)(A)–(R)), whether the pass acted on it and how. Present for all 18 categories in the report (in regulatory order A→R), so a reader sees the categories not acted on as plainly as those that were.
Example
import { buildExpertDeterminationSupportReport, SAFE_HARBOR_CATEGORIES } from "@cosyte/deid";
const report = buildExpertDeterminationSupportReport([
{ category: SAFE_HARBOR_CATEGORIES.SSN, transform: "redact", locus: "PID-19", count: 1,
disposition: "removed", code: "DEID_CATEGORY_REMOVED" },
]);
const ssn = report.categoryCoverage.find((c) => c.category === SAFE_HARBOR_CATEGORIES.SSN);
ssn?.actedOn; // => true
Properties
actedOn
readonlyactedOn:boolean
true when at least one manifest entry acted on this category.
category
readonlycategory:SafeHarborCategory
The Safe Harbor category.
codes
readonlycodes: readonlyDeidDispositionCode[]
The distinct disposition codes recorded for this category, sorted.
dispositions
readonlydispositions:Readonly<Record<ReportDisposition,number>>
Count of acted-on values by disposition.
letter
readonlyletter:string
The §164.514(b)(2)(i) sub-paragraph letter (A–R).
number
readonlynumber:number
The category ordinal (1–18).
residualRetained
readonlyresidualRetained:boolean
true when a coarse identifying residual was retained for this category.
title
readonlytitle:string
A short human title (no PHI).
totalCount
readonlytotalCount:number
Total count of values acted on across every locus of this category.
transforms
readonlytransforms: readonlyTransformName[]
The distinct transforms applied to this category, sorted.
DeidContextSpec
Specification for createDeidContext.
Example
import { createDeidContext } from "@cosyte/deid";
const ctx = createDeidContext({ key: "consumer-held-secret", patientId: "patient-1" });
Properties
dateShiftSeed?
readonlyoptionaldateShiftSeed?:string|Uint8Array<ArrayBufferLike>
A separate seed for deriving per-patient date-shift offsets. Defaults to the key when omitted,
so date-shifting works out of the box; supply a distinct seed to decouple the two secrets.
key
readonlykey:string|Uint8Array<ArrayBufferLike>
The HMAC key for keyed transforms. Consumer-held; never emitted. Must be non-empty.
maxShiftDays?
readonlyoptionalmaxShiftDays?:number
Absolute bound (days) on the per-patient date-shift offset. Defaults to 365.
patientId?
readonlyoptionalpatientId?:string
The patient scope this context de-identifies. Required for the deterministic date-shift offset.
DeidDocument
The transformed document - the same loci with de-identified values. Format-specific documents
replace this unknown-shaped placeholder; the core returns this generic shape.
Example
import { type DeidDocument } from "@cosyte/deid";
const doc: DeidDocument = { loci: [] };
Properties
loci
readonlyloci: readonlyTransformedLocus[]
The transformed loci.
DeidManifestEntry
A single value-free manifest entry.
Example
import { type DeidManifestEntry, SAFE_HARBOR_CATEGORIES, DEID_DISPOSITION_CODES } from "@cosyte/deid";
const entry: DeidManifestEntry = {
category: SAFE_HARBOR_CATEGORIES.SSN,
transform: "redact",
locus: "PID-19",
count: 1,
disposition: "removed",
code: DEID_DISPOSITION_CODES.DEID_CATEGORY_REMOVED,
};
Properties
category
readonlycategory:SafeHarborCategory
The Safe Harbor category acted on.
code
readonlycode:DeidDispositionCode
The stable disposition code.
count
readonlycount:number
How many values at this locus/category/disposition were acted on.
disposition
readonlydisposition:"transformed"|"removed"|"blocked"
The disposition.
locus
readonlylocus:string
The format-neutral locus (segment/field index · path · tag) - never a value.
transform
readonlytransform:TransformName
The transform applied.
DeidOptions
Options for deidentify.
Example
import { type DeidOptions, createDeidContext } from "@cosyte/deid";
const opts: DeidOptions = { policy: "safe-harbor", context: createDeidContext({ key: "secret" }) };
Properties
context?
readonlyoptionalcontext?:DeidContext
The context carrying the consumer's key, required only when the policy uses a keyed transform.
policy?
readonlyoptionalpolicy?:DeidPolicy|"safe-harbor"
The policy to apply. Defaults to the built-in Safe Harbor policy.
redactor?
readonlyoptionalredactor?:FreeTextRedactor
A consumer-supplied free-text redactor. When present, the engine invokes it
at each free-text locus instead of blocking, and records its output as consumer-asserted
(DEID_FREETEXT_CONSUMER_REDACTED). The library bundles no redactor. Fail-closed contract:
when this is omitted - or when the redactor throws or returns nothing - the free-text locus is
blocked (the safe default), never emitted un-redacted. A returned redaction is trusted as the
consumer's; the engine does not re-verify it and does not touch the structural PHI the adapters
remove. See FreeTextRedactor.
DeidPolicy
A de-identification policy: a name plus a per-category transform assignment covering all 18 categories.
Example
import { SAFE_HARBOR_POLICY } from "@cosyte/deid";
SAFE_HARBOR_POLICY.name; // => "safe-harbor"
Properties
name
readonlyname:string
The policy name - surfaced in output labelling ("Safe-Harbor-transformed per the configured policy").
transforms
readonlytransforms:Readonly<Record<SafeHarborCategory,TransformName>>
The transform applied to each Safe Harbor category.
DeidPolicySpec
The spec accepted by defineDeidPolicy: a name and a partial transform map that overrides the Safe Harbor defaults for the categories it names.
Example
import { type DeidPolicySpec, SAFE_HARBOR_CATEGORIES } from "@cosyte/deid";
const spec: DeidPolicySpec = {
name: "research",
transforms: { [SAFE_HARBOR_CATEGORIES.DATES]: "date-shift" },
};
Properties
name
readonlyname:string
The policy name.
transforms?
readonlyoptionaltransforms?:Partial<Readonly<Record<SafeHarborCategory,TransformName>>>
Per-category transform overrides; unlisted categories keep their Safe Harbor default.
DeidProfile
A reusable, named de-identification preset: a policy plus its honest usage posture and an optional default free-text redactor.
Example
import { SAFE_HARBOR_PROFILE } from "@cosyte/deid";
SAFE_HARBOR_PROFILE.name; // => "safe-harbor"
Properties
description
readonlydescription:string
A one-line honest description of what the profile does and does not guarantee - surfaced in docs and tooling so a preset is never adopted without its caveats.
name
readonlyname:string
The profile name (also the policy name).
policy
readonlypolicy:DeidPolicy
The concrete policy the engine applies.
redactor?
readonlyoptionalredactor?:FreeTextRedactor
An optional default free-text redactor the profile carries into profileOptions.
requiresContext
readonlyrequiresContext:boolean
Whether the profile requires a keyed per-patient DeidContext to run at all (true when
any category uses a keyed transform such as date-shift on a category that is always present).
standard
readonlystandard:DeidStandard
The standard this profile targets - governs how its output may honestly be described.
DeidProfileSpec
The spec accepted by defineDeidProfile: a name, an optional base profile (default SAFE_HARBOR_PROFILE), per-category transform overrides, and an optional default redactor.
Example
import { type DeidProfileSpec, SAFE_HARBOR_CATEGORIES } from "@cosyte/deid";
const spec: DeidProfileSpec = {
name: "site-a",
transforms: { [SAFE_HARBOR_CATEGORIES.MRN]: "redact" }, // tighten MRN from pseudonymize to redact
};
Properties
base?
readonlyoptionalbase?:DeidProfile
The base profile to derive from. Defaults to SAFE_HARBOR_PROFILE.
description?
readonlyoptionaldescription?:string
An optional human description; a default is synthesized from the base when omitted.
name
readonlyname:string
The profile name. Must not be a reserved standard label unless it genuinely matches it.
redactor?
readonlyoptionalredactor?:FreeTextRedactor
An optional default free-text redactor the derived profile carries.
transforms?
readonlyoptionaltransforms?:Partial<Readonly<Record<SafeHarborCategory,TransformName>>>
Per-category transform overrides. Each may only move a category to an equal-or-stronger transform than the base (widen-never-narrow); a weakening override is rejected.
DeidRegistrySpec
Specification for createDeidRegistry. Mirrors DeidContextSpec minus the per-patient scope - the registry mints patient scopes itself via DeidRegistry.forPatient.
Example
import { createDeidRegistry } from "@cosyte/deid";
const registry = createDeidRegistry({ key: process.env.DEID_KEY! });
Properties
dateShiftSeed?
readonlyoptionaldateShiftSeed?:string|Uint8Array<ArrayBufferLike>
A separate seed for deriving per-patient date-shift offsets. Defaults to the key when omitted;
supply a distinct seed to decouple pseudonymization from date-shifting.
key
readonlykey:string|Uint8Array<ArrayBufferLike>
The HMAC key for keyed transforms. Consumer-held; never emitted. Must be non-empty.
maxShiftDays?
readonlyoptionalmaxShiftDays?:number
Absolute bound (days) on each derived per-patient date-shift offset. Defaults to 365.
DeidResult
The immutable result of a de-id pass: the transformed document plus the value-free manifest.
Example
import { deidentify } from "@cosyte/deid";
const result = deidentify({ loci: [] }, {});
result.manifest; // => [] (nothing acted on)
Properties
document
readonlydocument:DeidDocument
The transformed document (the generic locus document from the core).
manifest
readonlymanifest: readonlyDeidManifestEntry[]
The value-free audit of every action, in locus order.
DispositionSummary
A roll-up of how many values landed in each disposition across the whole report - the one-glance posture of the pass. Every field is a count; none is a value.
Example
import { buildExpertDeterminationSupportReport, SAFE_HARBOR_CATEGORIES } from "@cosyte/deid";
const report = buildExpertDeterminationSupportReport([
{ category: SAFE_HARBOR_CATEGORIES.SSN, transform: "redact", locus: "PID-19", count: 2,
disposition: "removed", code: "DEID_CATEGORY_REMOVED" },
]);
report.dispositionSummary.removed; // => 2
Properties
blocked
readonlyblocked:number
Loci failed closed (blocked; value withheld).
freeTextBlocked
readonlyfreeTextBlocked:number
Free-text loci blocked by default (DEID_FREETEXT_BLOCKED).
freeTextConsumerRedacted
readonlyfreeTextConsumerRedacted:number
Free-text loci redacted by a consumer-supplied BYO redactor (DEID_FREETEXT_CONSUMER_REDACTED).
removed
readonlyremoved:number
Values removed outright.
residualRetained
readonlyresidualRetained:number
Of the transformed values, how many retained a coarse residual (DEID_RESIDUAL_RETAINED).
transformed
readonlytransformed:number
Values replaced with a surrogate / generalized / shifted / hashed / BYO-redacted.
ExpertDeterminationReportOptions
Options for buildExpertDeterminationSupportReport.
Properties
policy?
readonlyoptionalpolicy?:string|DeidPolicy
The policy applied (or its name) - surfaced as the report's policy label.
quasiIdentifiers?
readonlyoptionalquasiIdentifiers?:QuasiIdentifierClassInput
Consumer-supplied quasi-identifier equivalence-class sizes for the descriptive k-indicator.
ExpertDeterminationSupportReport
The structured, value-free Expert-Determination support report. Machine-readable (this object) and
human-readable (via formatExpertDeterminationSupportReport). It supports a determination
and is never one: determination is always null and disclaimer leads.
Example
import { buildExpertDeterminationSupportReport, SAFE_HARBOR_CATEGORIES } from "@cosyte/deid";
const report = buildExpertDeterminationSupportReport([
{ category: SAFE_HARBOR_CATEGORIES.NAMES, transform: "redact", locus: "PID-5", count: 1,
disposition: "removed", code: "DEID_CATEGORY_REMOVED" },
]);
report.determination; // => null (the library never renders one)
report.totals.categoriesActedOn; // => 1
Properties
categoryCoverage
readonlycategoryCoverage: readonlyCategoryCoverage[]
Coverage for all 18 Safe Harbor categories, in regulatory order (A→R).
determination
readonlydetermination:null
Always null: the library renders no determination.
disclaimer
readonlydisclaimer:string
The prominent non-certification statement (EXPERT_DETERMINATION_DISCLAIMER).
dispositionSummary
readonlydispositionSummary:DispositionSummary
The disposition roll-up.
documentCount
readonlydocumentCount:number
How many documents' manifests this report summarizes (1 for a single manifest).
kind
readonlykind:"expert-determination-support"
A stable discriminant for the report shape.
outputLabel
readonlyoutputLabel:string
The output label the pass applied - "Safe-Harbor-transformed per the configured policy".
perLocus
readonlyperLocus: readonlyDeidManifestEntry[]
Every acted-on locus, aggregated and in a deterministic order - the value-free manifest, structured.
policy
readonlypolicy:string|null
The policy name applied, or null if not supplied.
quasiIdentifierStatistics
readonlyquasiIdentifierStatistics:QuasiIdentifierStatistics|null
Descriptive quasi-identifier statistics, only when the consumer supplied class sizes; else null.
retainedQuasiIdentifiers
readonlyretainedQuasiIdentifiers: readonlyRetainedQuasiIdentifier[]
The retained-quasi-identifier residual inventory (coarse residuals the pass recorded as retained).
totals
readonlytotals:object
Headline totals.
categoriesActedOn
readonlycategoriesActedOn:number
How many of the 18 Safe Harbor categories were acted on.
loci
readonlyloci:number
Count of distinct acted-on loci (distinct locus paths across the whole report).
rows
readonlyrows:number
Count of aggregated perLocus rows (distinct category·transform·locus·disposition·code tuples).
FreeTextRedactionRequest
The value-bearing request the engine hands a FreeTextRedactor at each free-text locus. This is the one place a value crosses into consumer code by design - it never reaches the manifest.
Example
import { type FreeTextRedactionRequest } from "@cosyte/deid";
const request: FreeTextRedactionRequest = { text: "patient prose…", locus: "OBX-5" };
Properties
category?
readonlyoptionalcategory?:SafeHarborCategory
The Safe Harbor category associated with the locus, when the adapter could classify it.
locus
readonlylocus:string
The format-neutral locus path (e.g. "OBX-5", "section/text") - value-free, for routing/logging.
text
readonlytext:string
The free-text prose at the locus, for the consumer's redactor to de-identify.
FreeTextRedactionResult
The successful result of a FreeTextRedactor: the redacted prose to write back in place. To
decline a locus (so the engine fails closed and blocks it), a redactor returns null /
undefined or throws - it never returns un-redacted text expecting the engine to catch a miss.
Example
import { type FreeTextRedactionResult } from "@cosyte/deid";
const result: FreeTextRedactionResult = { text: "patient [REDACTED]…" };
Properties
text
readonlytext:string
The redacted prose. May be an empty string (all prose removed); that is a valid redaction.
GeneralizeOutcome
The outcome of a generalization: the reduced value plus whether it retains a coarse residual
(a kept year, a retained safe 3-digit ZIP prefix) that the manifest must surface for the
actual-knowledge test, versus a fully non-identifying result (000, 90+).
Properties
residual
readonlyresidual:boolean
true when a coarse identifying residual was retained; false when fully suppressed.
value
readonlyvalue:string
The generalized value.
GenericLocus
A single structurally-located candidate value. category is omitted when the caller cannot classify
the locus - an unclassified PHI-bearing locus is treated as catch-all (R) and fails closed.
Example
import { type GenericLocus, SAFE_HARBOR_CATEGORIES } from "@cosyte/deid";
const locus: GenericLocus = {
path: "PID-5",
kind: "identifier",
category: SAFE_HARBOR_CATEGORIES.NAMES,
value: "SENTINEL_NAME_01",
};
Properties
category?
readonlyoptionalcategory?:SafeHarborCategory
The Safe Harbor category, when known. Omit to force fail-closed handling as category (R).
kind
readonlykind:LocusKind
The kind of value at this locus.
path
readonlypath:string
The format-neutral path to the value. Recorded in the manifest; never a value.
value
readonlyvalue:string
The value at the locus. Consumed by the engine, never copied into the manifest.
LocusModel
The format-agnostic input model: a flat list of located candidate values. A per-format adapter produces this from a parsed HL7 / C-CDA / FHIR / X12 / NCPDP / DICOM model.
Example
import { type LocusModel } from "@cosyte/deid";
const model: LocusModel = { loci: [{ path: "PID-3", kind: "identifier", value: "X" }] };
Properties
loci
readonlyloci: readonlyGenericLocus[]
The located candidate values to de-identify.
QuasiIdentifierClassInput
Consumer-supplied quasi-identifier equivalence-class data. The library never derives this - it has no view of the quasi-identifier values. The consumer, who holds the values, groups their records by the chosen quasi-identifier set (e.g. 3-digit ZIP × birth year × sex) and supplies the size of each distinct group; the report echoes descriptive counts over those sizes (see QuasiIdentifierStatistics).
Example
import { type QuasiIdentifierClassInput } from "@cosyte/deid";
// Consumer grouped 100 records into classes of these sizes over their chosen quasi-identifier set:
const qi: QuasiIdentifierClassInput = {
quasiIdentifierSet: "3-digit ZIP × birth year × sex",
equivalenceClassSizes: [40, 33, 20, 5, 1, 1],
};
Properties
equivalenceClassSizes
readonlyequivalenceClassSizes: readonlynumber[]
One size per distinct quasi-identifier combination - the record count in that equivalence class.
quasiIdentifierSet?
readonlyoptionalquasiIdentifierSet?:string
A human label for the quasi-identifier set the sizes were computed over. No PHI.
QuasiIdentifierStatistics
Descriptive statistics over consumer-supplied equivalence-class sizes - including the smallest class size, the widely-used k-anonymity indicator.
This is not a risk score and not a determination. It is arithmetic over sizes the consumer
supplied: the library counts what it is given, applies no threshold, draws no k ≥ n ⇒ safe
conclusion, and emits no verdict. A statistician documents an indicator like this as one input to a
§164.514(b)(1) determination - the determination remains theirs. See note.
Example
import { buildExpertDeterminationSupportReport } from "@cosyte/deid";
const report = buildExpertDeterminationSupportReport([], {
quasiIdentifiers: { equivalenceClassSizes: [40, 20, 5, 1, 1] },
});
report.quasiIdentifierStatistics?.minimumEquivalenceClassSize; // => 1
report.quasiIdentifierStatistics?.uniqueRecords; // => 2
Properties
distinctCombinations
readonlydistinctCombinations:number
Number of distinct quasi-identifier combinations = the number of equivalence classes supplied.
minimumEquivalenceClassSize
readonlyminimumEquivalenceClassSize:number
The smallest equivalence-class size - the k-anonymity indicator. Descriptive only.
note
readonlynote:string
The honesty note: this is a descriptive input, never a risk score or determination.
quasiIdentifierSet
readonlyquasiIdentifierSet:string|null
The label the consumer gave the quasi-identifier set, or null if unlabelled.
totalRecords
readonlytotalRecords:number
Total records across all classes = the sum of the supplied sizes.
uniqueRecords
readonlyuniqueRecords:number
How many records fall in a class of size 1 (sample-uniques on the chosen set). Descriptive only.
RetainedQuasiIdentifier
One entry in the retained-quasi-identifier residual inventory - a coarse identifying element the
pass deliberately kept for analytic utility and recorded as DEID_RESIDUAL_RETAINED: a
year-only date, a retained safe 3-digit ZIP prefix, an exact age ≤ 89. These are exactly the residuals
an expert reasons about under the §164.514(b)(2)(ii) actual-knowledge test.
Example
import { buildExpertDeterminationSupportReport, SAFE_HARBOR_CATEGORIES } from "@cosyte/deid";
const report = buildExpertDeterminationSupportReport([
{ category: SAFE_HARBOR_CATEGORIES.DATES, transform: "generalize", locus: "PID-7", count: 1,
disposition: "transformed", code: "DEID_RESIDUAL_RETAINED" },
]);
report.retainedQuasiIdentifiers[0]?.locus; // => "PID-7"
Properties
category
readonlycategory:SafeHarborCategory
The Safe Harbor category of the retained residual.
count
readonlycount:number
How many values at this locus retained a residual.
locus
readonlylocus:string
The format-neutral locus (segment/field index · path · tag) - never a value.
TransformedLocus
The transformed value of a locus after a de-id pass: the reduced/surrogate value, or null when
the value was removed or blocked (fail-closed). Carries no secret and no original value.
Example
import { type TransformedLocus } from "@cosyte/deid";
const t: TransformedLocus = { path: "PID-3", kind: "identifier", value: null, disposition: "removed" };
Properties
disposition
readonlydisposition:"transformed"|"removed"|"blocked"|"retained"
What happened to the value.
kind
readonlykind:LocusKind
The kind of value at this locus (unchanged from input).
path
readonlypath:string
The format-neutral path (unchanged from input).
value
readonlyvalue:string|null
The transformed value, or null when removed / blocked.
Type Aliases
DeidDispositionCode
DeidDispositionCode = typeof
DEID_DISPOSITION_CODES[keyof typeofDEID_DISPOSITION_CODES]
A value from DEID_DISPOSITION_CODES - the code every manifest entry carries.
Example
import { DEID_DISPOSITION_CODES, type DeidDispositionCode } from "@cosyte/deid";
const code: DeidDispositionCode = DEID_DISPOSITION_CODES.DEID_CATEGORY_REMOVED;
DeidStandard
DeidStandard =
"safe-harbor"|"limited-data-set"|"custom"
The named standard a profile targets, surfaced so output labelling can never overclaim.
FatalCode
FatalCode = typeof
FATAL_CODES[keyof typeofFATAL_CODES]
A value from FATAL_CODES - the code carried by a thrown DeidError.
Example
import { FATAL_CODES, type FatalCode } from "@cosyte/deid";
const code: FatalCode = FATAL_CODES.EMPTY_INPUT;
FreeTextRedactor
FreeTextRedactor = (
request) =>FreeTextRedactionResult|null|undefined
A consumer-supplied free-text redactor. The engine invokes it at each free-text locus and treats a
returned FreeTextRedactionResult as consumer-asserted (recorded as
DEID_FREETEXT_CONSUMER_REDACTED). Returning null / undefined - or throwing - makes the engine
fail closed and block the locus. The library never inspects the returned text for residual PHI;
completeness is the consumer's responsibility.
Parameters
request
The free-text value, its value-free locus path, and its category when known.
Returns
FreeTextRedactionResult | null | undefined
The redacted prose as { text }, or null / undefined to decline (engine blocks).
Example
import { deidentify, type FreeTextRedactor } from "@cosyte/deid";
// A consumer plugs in their own detector; the library bundles none.
const redactor: FreeTextRedactor = ({ text }) => ({ text: myNerModel.scrub(text) });
const result = deidentify(model, { redactor });
LocusKind
LocusKind =
"identifier"|"date"|"age"|"zip"|"freetext"|"clinical"|"unknown"
The kind of value at a locus - drives which generalization applies and whether the engine must
fail closed. clinical is the over-scrub guard: a clinical value (a lab result, a dose, a code, a
status) is not an identifier and must survive untouched.
Example
import { type LocusKind } from "@cosyte/deid";
const kind: LocusKind = "identifier";
ReportDisposition
ReportDisposition =
"transformed"|"removed"|"blocked"
A manifest disposition - the three outcomes a locus can have.
SafeHarborCategory
SafeHarborCategory = typeof
SAFE_HARBOR_CATEGORIES[keyof typeofSAFE_HARBOR_CATEGORIES]
A value from SAFE_HARBOR_CATEGORIES - the type a policy and a manifest entry carry.
Example
import { SAFE_HARBOR_CATEGORIES, type SafeHarborCategory } from "@cosyte/deid";
const c: SafeHarborCategory = SAFE_HARBOR_CATEGORIES.SSN;
TransformName
TransformName =
"redact"|"generalize"|"date-shift"|"pseudonymize"|"hash"|"block"|"byo-redact"
The name of a transform a policy can assign to a category. block is the fail-closed action
(withhold the value); generalize selects the correct generalization from the locus kind (date /
ZIP / age).
byo-redact is not a policy-assignable Safe Harbor transform and not something the library
performs - it is the manifest marker the engine records when a consumer-supplied free-text
redactor redacts a free-text locus. The library bundles no redactor; the
consumer brings the detector. Assigning byo-redact to a category in a policy has no effect beyond
the fail-closed default (the engine blocks it), because free-text redaction is driven by the
redactor option, not by the per-category policy map.
Example
import { type TransformName } from "@cosyte/deid";
const t: TransformName = "pseudonymize";
Variables
DEID_DISPOSITION_CODES
constDEID_DISPOSITION_CODES:object
Disposition codes - the value-free record of what the engine did at a locus. Every manifest entry carries exactly one. They describe the action and its residual, never the value acted on.
Type Declaration
DEID_CATEGORY_DATE_SHIFTED
readonlyDEID_CATEGORY_DATE_SHIFTED:"DEID_CATEGORY_DATE_SHIFTED"="DEID_CATEGORY_DATE_SHIFTED"
A date was shifted by a deterministic per-patient offset (interval-preserving).
DEID_CATEGORY_GENERALIZED
readonlyDEID_CATEGORY_GENERALIZED:"DEID_CATEGORY_GENERALIZED"="DEID_CATEGORY_GENERALIZED"
A category was generalized to a fully non-identifying form (ZIP → 000, age → 90+).
DEID_CATEGORY_HASHED
readonlyDEID_CATEGORY_HASHED:"DEID_CATEGORY_HASHED"="DEID_CATEGORY_HASHED"
A value was replaced by a keyed one-way digest (keyed hash).
DEID_CATEGORY_PSEUDONYMIZED
readonlyDEID_CATEGORY_PSEUDONYMIZED:"DEID_CATEGORY_PSEUDONYMIZED"="DEID_CATEGORY_PSEUDONYMIZED"
A category was replaced by a consistent keyed-HMAC surrogate (pseudonymization).
DEID_CATEGORY_REMOVED
readonlyDEID_CATEGORY_REMOVED:"DEID_CATEGORY_REMOVED"="DEID_CATEGORY_REMOVED"
A category was removed outright (redaction).
DEID_FREETEXT_BLOCKED
readonlyDEID_FREETEXT_BLOCKED:"DEID_FREETEXT_BLOCKED"="DEID_FREETEXT_BLOCKED"
Fail-closed: a free-text locus was blocked by default (no naive regex scrub).
DEID_FREETEXT_CONSUMER_REDACTED
readonlyDEID_FREETEXT_CONSUMER_REDACTED:"DEID_FREETEXT_CONSUMER_REDACTED"="DEID_FREETEXT_CONSUMER_REDACTED"
A free-text locus was redacted by a consumer-supplied BYO redactor, not by the library. The library ships no NLP/PHI-detection engine; it orchestrates the consumer's redactor at free-text loci and records the outcome here. This code is consumer-asserted, never a library guarantee: "no findings" from a BYO redactor is not an attestation, and a redactor's completeness is the consumer's responsibility (Expert-Determination territory). The structural PHI removal the format adapters perform is unaffected - this covers only the free prose.
DEID_LOCUS_BLOCKED
readonlyDEID_LOCUS_BLOCKED:"DEID_LOCUS_BLOCKED"="DEID_LOCUS_BLOCKED"
Fail-closed: an unrecognized / un-locatable / uncertain locus was blocked (value withheld).
DEID_RESIDUAL_RETAINED
readonlyDEID_RESIDUAL_RETAINED:"DEID_RESIDUAL_RETAINED"="DEID_RESIDUAL_RETAINED"
A generalization retained a coarse residual (a kept year, a retained safe 3-digit ZIP prefix). Surfaced so a human can apply the §164.514(b)(2)(ii) actual-knowledge test with the facts present.
Example
import { DEID_DISPOSITION_CODES } from "@cosyte/deid";
DEID_DISPOSITION_CODES.DEID_LOCUS_BLOCKED; // => "DEID_LOCUS_BLOCKED"
EXPERT_DETERMINATION_DISCLAIMER
constEXPERT_DETERMINATION_DISCLAIMER:string
The prominent non-certification statement. It is the first field a reader sees and is repeated at the head of the human-readable rendering. Its job is to make over-claiming impossible to do by accident: the report supports a determination, it is never the determination.
Example
import { EXPERT_DETERMINATION_DISCLAIMER } from "@cosyte/deid";
EXPERT_DETERMINATION_DISCLAIMER.includes("NOT a determination"); // => true
FATAL_CODES
constFATAL_CODES:object
Fatal codes - conditions that abort a de-identification pass by throwing a DeidError. The engine fails closed: it never silently degrades a fatal into a pass-through of PHI.
Type Declaration
DEID_CONTEXT_INVALID
readonlyDEID_CONTEXT_INVALID:"DEID_CONTEXT_INVALID"="DEID_CONTEXT_INVALID"
A DeidContext was configured with an invalid parameter that would silently weaken
de-identification - most importantly a maxShiftDays that floors to 0, which pins every
per-patient date-shift offset to zero, so a date-shift policy would emit the original real
dates under a research label. A no-op shift is a leak, so the engine rejects the degenerate
configuration at construction rather than silently shipping unshifted dates. The fatal set is
additions-only.
DEID_NO_KEY
readonlyDEID_NO_KEY:"DEID_NO_KEY"="DEID_NO_KEY"
A keyed transform (pseudonymize / keyed-hash / date-shift) was required for a category present in the model, but no key (or, for date-shift, no per-patient scope) was supplied. The engine never falls back to an unkeyed transform - an unkeyed hash of an identifier is re-identifiable.
DEID_POLICY_INVALID
readonlyDEID_POLICY_INVALID:"DEID_POLICY_INVALID"="DEID_POLICY_INVALID"
A policy violates the key/label contract - most importantly, it applies the interval-preserving
date-shift transform while carrying the reserved safe-harbor label. A shifted-but-real
date is still "an element of a date" under §164.514(b)(2)(i)(C), so date-shift is an
Expert-Determination technique, not Safe Harbor; labelling it safe-harbor would misrepresent
the residual risk. The engine rejects it at point of use rather than silently emit shifted real
dates under a Safe Harbor claim. The fatal set is additions-only.
DEID_PROFILE_INVALID
readonlyDEID_PROFILE_INVALID:"DEID_PROFILE_INVALID"="DEID_PROFILE_INVALID"
A DeidProfile spec violates the widen-never-narrow contract: a per-site profile derived from a base profile may only move a category to an equal-or-stronger transform (more removal, never less), and may never re-weaken a category the base scrubs. A profile that would reduce the de-identification strength of any category is rejected, so a site preset can only ever tighten - not quietly loosen - the base standard's protection. The fatal set is additions-only.
EMPTY_INPUT
readonlyEMPTY_INPUT:"EMPTY_INPUT"="EMPTY_INPUT"
The input model was null/undefined or carried no locus list - nothing to de-identify.
Example
import { FATAL_CODES } from "@cosyte/deid";
FATAL_CODES.DEID_NO_KEY; // => "DEID_NO_KEY"
KEYED_TRANSFORMS
constKEYED_TRANSFORMS:ReadonlySet<TransformName>
The transforms that require the consumer's key (and, for date-shift, a per-patient scope).
LIMITED_DATA_SET_PROFILE
constLIMITED_DATA_SET_PROFILE:DeidProfile
The Limited Data Set / longitudinal research profile. Identical to Safe Harbor except dates are date-shifted (a single consistent per-patient offset, intervals preserved) rather than generalized to year - so time-series analysis survives.
This is deliberately less protective than Safe Harbor and is NOT Safe Harbor. A shifted-but-real date is still "an element of a date" (§164.514(b)(2)(i)(C)), so this profile:
- is not labelled
safe-harbor(the reserved-label guard would reject it); - requires a keyed per-patient DeidContext (an absent key is a fatal
DEID_NO_KEY); - produces an Expert-Determination-supporting dataset, not a certified de-identification, and not, on its own, a HIPAA §164.514(e) Limited Data Set - disclosing an actual Limited Data Set additionally requires a Data Use Agreement, which is the consumer's responsibility.
Example
import { LIMITED_DATA_SET_PROFILE } from "@cosyte/deid";
LIMITED_DATA_SET_PROFILE.requiresContext; // => true (date-shift needs a per-patient key)
OUTPUT_LABEL
constOUTPUT_LABEL:"Safe-Harbor-transformed per the configured policy"="Safe-Harbor-transformed per the configured policy"
The label the library applies to its output. Deliberately not "de-identified" / "HIPAA-compliant" — the certification is always the consumer's.
Example
import { OUTPUT_LABEL } from "@cosyte/deid";
OUTPUT_LABEL; // => "Safe-Harbor-transformed per the configured policy"
RESTRICTED_ZIP3
constRESTRICTED_ZIP3:ReadonlySet<string>
The 17 restricted three-digit ZIP prefixes (population ≤ 20,000 per 2000 Census). A ZIP whose first
three digits are in this set is generalized to 000; any other prefix retains its three digits.
Example
import { RESTRICTED_ZIP3 } from "@cosyte/deid";
RESTRICTED_ZIP3.has("036"); // => true
RESTRICTED_ZIP3.has("902"); // => false
RESTRICTED_ZIP3_SOURCE
constRESTRICTED_ZIP3_SOURCE:Readonly<{census:"2000";citation:"HHS OCR, Guidance Regarding Methods for De-identification of PHI (2012); 45 CFR §164.514(b)(2)(i)(B)"; }>
The Census vintage this list is grounded in, surfaced so a consumer knows exactly which published
artifact the 000 rule is applied from.
Example
import { RESTRICTED_ZIP3_SOURCE } from "@cosyte/deid";
RESTRICTED_ZIP3_SOURCE.census; // => "2000"
SAFE_HARBOR_CATEGORIES
constSAFE_HARBOR_CATEGORIES:object
The stable registry of the 18 Safe Harbor identifier categories. key === value so the set
survives an Object.values(...) snapshot into a stability tripwire. Renaming a category is a
breaking change - consumers branch on these in policies and manifests.
Type Declaration
ACCOUNT
readonlyACCOUNT:"ACCOUNT"="ACCOUNT"
(J) Account numbers.
BIOMETRIC
readonlyBIOMETRIC:"BIOMETRIC"="BIOMETRIC"
(P) Biometric identifiers, including finger and voice prints.
CERTIFICATE_LICENSE
readonlyCERTIFICATE_LICENSE:"CERTIFICATE_LICENSE"="CERTIFICATE_LICENSE"
(K) Certificate / license numbers.
DATES
readonlyDATES:"DATES"="DATES"
(C) All elements of dates (except year) directly related to the individual, and all ages > 89.
DEVICE
readonlyDEVICE:"DEVICE"="DEVICE"
(M) Device identifiers and serial numbers.
EMAIL
readonlyEMAIL:"EMAIL"="EMAIL"
(F) Email addresses.
FAX
readonlyFAX:"FAX"="FAX"
(E) Fax numbers.
FULL_FACE_PHOTO
readonlyFULL_FACE_PHOTO:"FULL_FACE_PHOTO"="FULL_FACE_PHOTO"
(Q) Full-face photographs and any comparable images.
GEOGRAPHIC
readonlyGEOGRAPHIC:"GEOGRAPHIC"="GEOGRAPHIC"
(B) All geographic subdivisions smaller than a state - street, city, county, precinct, ZIP, geocodes.
HEALTH_PLAN_BENEFICIARY
readonlyHEALTH_PLAN_BENEFICIARY:"HEALTH_PLAN_BENEFICIARY"="HEALTH_PLAN_BENEFICIARY"
(I) Health plan beneficiary numbers.
IP_ADDRESS
readonlyIP_ADDRESS:"IP_ADDRESS"="IP_ADDRESS"
(O) IP addresses.
MRN
readonlyMRN:"MRN"="MRN"
(H) Medical record numbers.
NAMES
readonlyNAMES:"NAMES"="NAMES"
(A) Names - patient, relatives, employers, household members.
OTHER_UNIQUE_ID
readonlyOTHER_UNIQUE_ID:"OTHER_UNIQUE_ID"="OTHER_UNIQUE_ID"
(R) Any other unique identifying number, characteristic, or code - the open-ended catch-all.
PHONE
readonlyPHONE:"PHONE"="PHONE"
(D) Telephone numbers.
SSN
readonlySSN:"SSN"="SSN"
(G) Social Security numbers.
URL
readonlyURL:"URL"="URL"
(N) Web URLs.
VEHICLE
readonlyVEHICLE:"VEHICLE"="VEHICLE"
(L) Vehicle identifiers and serial numbers, including license plates.
Example
import { SAFE_HARBOR_CATEGORIES } from "@cosyte/deid";
SAFE_HARBOR_CATEGORIES.MRN; // => "MRN"
SAFE_HARBOR_CATEGORY_META
constSAFE_HARBOR_CATEGORY_META:Readonly<Record<SafeHarborCategory, {letter:string;number:number;title:string; }>>
Per-category regulatory metadata: the §164.514(b)(2)(i) sub-paragraph letter (A–R), the ordinal number (1–18), and a short human title. Grounded firsthand in the regulation text; contains no PHI.
Example
import { SAFE_HARBOR_CATEGORY_META, SAFE_HARBOR_CATEGORIES } from "@cosyte/deid";
SAFE_HARBOR_CATEGORY_META[SAFE_HARBOR_CATEGORIES.GEOGRAPHIC].letter; // => "B"
SAFE_HARBOR_POLICY
constSAFE_HARBOR_POLICY:DeidPolicy
The built-in Safe Harbor policy. Direct identifiers with no analytic value are redacted; MRN / beneficiary / account numbers are pseudonymized (consistent surrogates); geography and dates are generalized; the open-ended catch-all (R) is blocked (fail-closed). Dates generalize to year - date-shift is an Expert-Determination mode, not Safe Harbor.
Example
import { SAFE_HARBOR_POLICY, SAFE_HARBOR_CATEGORIES } from "@cosyte/deid";
SAFE_HARBOR_POLICY.transforms[SAFE_HARBOR_CATEGORIES.MRN]; // => "pseudonymize"
SAFE_HARBOR_PROFILE
constSAFE_HARBOR_PROFILE:DeidProfile
The Safe Harbor profile - the fail-closed default. Wraps SAFE_HARBOR_POLICY: direct identifiers removed, MRN/beneficiary/account pseudonymized, geography and dates generalized, the catch-all (R) blocked. Output is "Safe-Harbor-transformed per the configured policy", never "de-identified".
Example
import { SAFE_HARBOR_PROFILE } from "@cosyte/deid";
SAFE_HARBOR_PROFILE.standard; // => "safe-harbor"
VERSION
constVERSION:"0.0.0"="0.0.0"
Library version string, synced with package.json#version at release time.
Example
import { VERSION } from "@cosyte/deid";
typeof VERSION; // => "string"
Functions
buildExpertDeterminationSupportReport()
buildExpertDeterminationSupportReport(
manifests,options?):ExpertDeterminationSupportReport
Build the ExpertDeterminationSupportReport from the value-free manifest(s) of one or more de-identification passes. Deterministic; the input is never mutated; the result is deeply frozen.
Accepts either a single manifest (readonly DeidManifestEntry[]) or a corpus (an array of
manifests). Counts for identical loci are summed across the corpus. The report is value-free - it
carries categories, dispositions, loci, and counts, never a PHI value - and it renders no
determination: determination is null and the disclaimer leads.
Parameters
manifests
readonly DeidManifestEntry[] | readonly readonly DeidManifestEntry[][]
A single manifest, or an array of manifests (a corpus).
options?
ExpertDeterminationReportOptions = {}
The policy label and optional consumer-supplied quasi-identifier class sizes.
Returns
ExpertDeterminationSupportReport
The frozen, value-free support report.
Example
import { deidentify, buildExpertDeterminationSupportReport, SAFE_HARBOR_CATEGORIES } from "@cosyte/deid";
const { manifest } = deidentify(
{ loci: [{ path: "PID-5", kind: "name", category: SAFE_HARBOR_CATEGORIES.NAMES, value: "X" }] },
{},
);
const report = buildExpertDeterminationSupportReport(manifest, { policy: "safe-harbor" });
report.determination; // => null
report.policy; // => "safe-harbor"
createDeidContext()
createDeidContext(
spec):DeidContext
Create a DeidContext from consumer-held key material. The key is coerced to bytes and stored only in the module-private registry - never on the returned handle.
Parameters
spec
The key, optional date-shift seed, optional patient scope, and offset bound.
Returns
An opaque, self-redacting context.
Throws
DeidError with code DEID_NO_KEY if the key (or an explicit seed) is empty.
Example
import { createDeidContext } from "@cosyte/deid";
const ctx = createDeidContext({ key: "consumer-secret", patientId: "patient-1" });
createDeidRegistry()
createDeidRegistry(
spec):DeidRegistry
Create a DeidRegistry from consumer-held key material. Fails closed on an absent/empty
key - there is no default or weak key (the underlying createDeidContext throws DEID_NO_KEY).
The key is stored only in the module-private state, never on the returned handle.
Parameters
spec
The key, optional date-shift seed, and optional offset bound.
Returns
An opaque, self-redacting corpus consistency handle.
Throws
DeidError with code DEID_NO_KEY if the key (or an explicit seed) is empty.
Example
import { createDeidRegistry } from "@cosyte/deid";
const registry = createDeidRegistry({ key: "consumer-secret" });
dateShift()
dateShift(
value,ctx):string|null
Shift a date by the context's deterministic per-patient offset, preserving intervals. Supports ISO
YYYY-MM-DD, HL7 YYYYMMDD, and an ISO datetime (YYYY-MM-DDThh:mm…). Fails closed (null) on an
unparseable or invalid date.
Timezone-independent. Only the calendar-date portion is shifted (via UTC calendar math); a
datetime's time-of-day and zone designator are preserved verbatim, so the same input yields the
same output on every host regardless of the machine's TZ. Because the offset is a whole number of
days and the clock/zone are untouched, intervals are preserved exactly.
The offset is not returned - only the shifted value is. Two dates for the same patient move by the same amount, so the number of days between them is unchanged.
Parameters
value
string
The date value to shift.
ctx
The de-identification context (must carry a patientId scope for the offset).
Returns
string | null
The shifted date at the original precision, or null if it could not be parsed.
Throws
DeidError with code DEID_NO_KEY if the context has no per-patient scope.
Example
import { dateShift, createDeidContext } from "@cosyte/deid";
const ctx = createDeidContext({ key: "secret", patientId: "patient-1" });
const a = dateShift("2020-01-01", ctx);
const b = dateShift("2020-01-11", ctx);
// a and b are shifted, but remain exactly 10 days apart.
defineDeidPolicy()
defineDeidPolicy(
spec):DeidPolicy
Derive a custom policy from the Safe Harbor defaults. Unlisted categories keep the safe default, so a custom policy can only ever be built by deviating from Safe Harbor deliberately, never by forgetting a category. The result is frozen.
Parameters
spec
The policy name and per-category overrides.
Returns
A frozen DeidPolicy covering all 18 categories.
Example
import { defineDeidPolicy, SAFE_HARBOR_CATEGORIES } from "@cosyte/deid";
const research = defineDeidPolicy({
name: "research",
transforms: { [SAFE_HARBOR_CATEGORIES.DATES]: "date-shift" },
});
research.transforms[SAFE_HARBOR_CATEGORIES.NAMES]; // => "redact" (kept from Safe Harbor)
defineDeidProfile()
defineDeidProfile(
spec):DeidProfile
Derive a per-site DeidProfile from a base profile (Safe Harbor by default), enforcing the widen-never-narrow contract: every override must move its category to an equal-or-stronger transform than the base's. A weakening override - or reclaiming a reserved standard label - is rejected (FATAL_CODES.DEID_PROFILE_INVALID), so a site preset can only tighten, never loosen, the base standard's protection.
Parameters
spec
The profile name, base, per-category overrides, and optional redactor.
Returns
A frozen DeidProfile.
Throws
DeidError DEID_PROFILE_INVALID if an override weakens a category or the name
reclaims a reserved standard label; DEID_POLICY_INVALID if the derived policy violates the
key/label contract (e.g. a safe-harbor-labelled policy that date-shifts).
Example
import { defineDeidProfile, SAFE_HARBOR_CATEGORIES } from "@cosyte/deid";
const strict = defineDeidProfile({
name: "site-strict",
transforms: { [SAFE_HARBOR_CATEGORIES.MRN]: "redact" }, // pseudonymize -> redact (stronger): OK
});
strict.policy.transforms[SAFE_HARBOR_CATEGORIES.MRN]; // => "redact"
deidentify()
deidentify(
model,options):DeidResult
De-identify a format-agnostic LocusModel under a policy. Returns the transformed document and a value-free manifest. The input model is never mutated; the result is deeply frozen.
The output is "Safe-Harbor-transformed per the configured policy" - it is not certified de-identified, and Expert Determination is not rendered.
Parameters
model
The located candidate values to de-identify.
loci?
readonly GenericLocus[]
options
The policy and (for keyed transforms) the key context.
Returns
The frozen DeidResult: transformed document + value-free manifest.
Throws
DeidError EMPTY_INPUT if the model is null or carries no locus list; DEID_NO_KEY
if a keyed transform is required but no key context was supplied.
Example
import { deidentify, SAFE_HARBOR_CATEGORIES } from "@cosyte/deid";
const result = deidentify(
{ loci: [{ path: "PID-19", kind: "identifier", category: SAFE_HARBOR_CATEGORIES.SSN, value: "SENTINEL" }] },
{},
);
result.document.loci[0]?.value; // => null (removed)
result.manifest[0]?.disposition; // => "removed"
formatExpertDeterminationSupportReport()
formatExpertDeterminationSupportReport(
report):string
Render an ExpertDeterminationSupportReport as a human-readable Markdown document - the same value-free facts as the structured object, led by the non-certification disclaimer. Suitable to hand to a statistician alongside the machine-readable report.
Parameters
report
ExpertDeterminationSupportReport
A report from buildExpertDeterminationSupportReport.
Returns
string
A Markdown string (value-free: categories, dispositions, loci, counts - never a value).
Example
import { buildExpertDeterminationSupportReport, formatExpertDeterminationSupportReport } from "@cosyte/deid";
const md = formatExpertDeterminationSupportReport(buildExpertDeterminationSupportReport([]));
md.startsWith("# Expert-Determination support report"); // => true
generalizeAge()
generalizeAge(
age):GeneralizeOutcome|null
Generalize an age (§164.514(b)(2)(i)(C)): any age over 89 aggregates to "90+"; ages 0–89 are
retained (a residual). Fails closed (null) for a non-finite or negative age.
Parameters
age
number
The age in years.
Returns
GeneralizeOutcome | null
The { value, residual } outcome - residual: false for "90+", true for a kept age -
or null if the age is not a finite non-negative number.
Example
import { generalizeAge } from "@cosyte/deid";
generalizeAge(92)?.value; // => "90+"
generalizeAge(89)?.value; // => "89"
generalizeDate()
generalizeDate(
value):GeneralizeOutcome|null
Generalize a date to its year (§164.514(b)(2)(i)(C)). Accepts common encodings - ISO
YYYY-MM-DD / YYYY-MM-DDThh:mm:ss, HL7 YYYYMMDD - anything beginning with a plausible
four-digit year. Fails closed (null) when no year can be extracted; the retained year is a
residual.
Parameters
value
string
The date value to generalize.
Returns
GeneralizeOutcome | null
The { value: year, residual: true } outcome, or null if no year is present.
Example
import { generalizeDate } from "@cosyte/deid";
generalizeDate("2019-03-14")?.value; // => "2019"
generalizeDate("not-a-date"); // => null
generalizeZip()
generalizeZip(
zip):GeneralizeOutcome|null
Generalize a ZIP code to its safe form (§164.514(b)(2)(i)(B)): the initial three digits, or 000
when those three digits name an area with ≤ 20,000 people (the cited restricted list). Fails closed
(null) when three leading digits cannot be read.
Parameters
zip
string
The ZIP code (5-digit, ZIP+4, or any form beginning with digits).
Returns
GeneralizeOutcome | null
The { value, residual } outcome - residual: false for 000, true for a kept prefix -
or null if fewer than three leading digits are present.
Example
import { generalizeZip } from "@cosyte/deid";
generalizeZip("90210")?.value; // => "902"
generalizeZip("03601")?.value; // => "000" (036 is a restricted prefix)
keyedHash()
keyedHash(
value,ctx):string
Replace a value with a keyed one-way digest (HMAC-SHA-256). Like pseudonymize it is consistent and non-reversible without the key, but domain-separated so it is a distinct surrogate space from pseudonyms.
Parameters
value
string
The value to hash.
ctx
The de-identification context holding the consumer's key.
Returns
string
A lowercase-hex keyed digest, consistent per (key, value).
Example
import { keyedHash, createDeidContext } from "@cosyte/deid";
const ctx = createDeidContext({ key: "secret" });
typeof keyedHash("value", ctx); // => "string"
profileOptions()
profileOptions(
profile,context?,overrides?):DeidOptions
Build the DeidOptions to pass to any adapter (deidentifyHl7, deidentifyFhir, …) from a
profile: its policy, the supplied key context, and the profile's default redactor (unless overridden).
Parameters
profile
The profile to apply.
context?
The keyed per-patient context (required by profiles whose requiresContext is true).
overrides?
Optional context/redactor overrides merged over the profile's defaults.
context?
redactor?
Returns
The DeidOptions for an adapter call.
Example
import { SAFE_HARBOR_PROFILE, profileOptions, createDeidContext } from "@cosyte/deid";
const opts = profileOptions(SAFE_HARBOR_PROFILE, createDeidContext({ key: "k" }));
opts.policy === SAFE_HARBOR_PROFILE.policy; // => true
pseudonymize()
pseudonymize(
id,ctx):string
Replace an identifier with a consistent keyed-HMAC surrogate. The same input under the same key always yields the same surrogate (linkage preserved); the surrogate is not reversible without the key (collision-resistant, key-dependent). Domain-separated from keyedHash.
Parameters
id
string
The identifier to pseudonymize (MRN, beneficiary number, account number).
ctx
The de-identification context holding the consumer's key.
Returns
string
A lowercase-hex surrogate, consistent per (key, id).
Example
import { pseudonymize, createDeidContext } from "@cosyte/deid";
const ctx = createDeidContext({ key: "secret" });
pseudonymize("MRN-123", ctx) === pseudonymize("MRN-123", ctx); // => true (consistent)
redact()
redact():
null
Redact a value. Returns null - the removed-value sentinel used throughout the transformed
document. Pure and keyless.
Returns
null
null, signalling the value has been removed.
Example
import { redact } from "@cosyte/deid";
redact(); // => null
resolvePolicy()
resolvePolicy(
policy):DeidPolicy
Resolve the policy argument accepted by the engine: the string "safe-harbor" (or undefined)
yields the built-in policy; a DeidPolicy object is returned as-is.
Parameters
policy
DeidPolicy | "safe-harbor" | undefined
"safe-harbor", a DeidPolicy, or undefined.
Returns
The concrete policy to apply.
Example
import { resolvePolicy, SAFE_HARBOR_POLICY } from "@cosyte/deid";
resolvePolicy("safe-harbor") === SAFE_HARBOR_POLICY; // => true
unkeyedHash()
unkeyedHash(
value):string
A plain, unsalted SHA-256 digest - NON-CONFORMING to §164.514(c) and never used by the engine. It is re-identifiable for a small, enumerable identifier space (an attacker hashes every candidate and matches). Exported solely so the test suite can prove the reversibility hazard the keyed path avoids. Do not use this to de-identify anything.
Parameters
value
string
The value to hash.
Returns
string
The lowercase-hex SHA-256 of value - reversible for a small input space.
Example
import { unkeyedHash } from "@cosyte/deid";
// Deterministic and unsalted - this is the footgun, shown so tests can assert against it.
unkeyedHash("a") === unkeyedHash("a"); // => true