@cosyte/synth 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +414 -0
- package/LICENSE +21 -0
- package/README.md +325 -0
- package/dist/astm/index.cjs +847 -0
- package/dist/astm/index.cjs.map +1 -0
- package/dist/astm/index.d.cts +418 -0
- package/dist/astm/index.d.ts +418 -0
- package/dist/astm/index.mjs +828 -0
- package/dist/astm/index.mjs.map +1 -0
- package/dist/ccda/index.cjs +1103 -0
- package/dist/ccda/index.cjs.map +1 -0
- package/dist/ccda/index.d.cts +380 -0
- package/dist/ccda/index.d.ts +380 -0
- package/dist/ccda/index.mjs +1077 -0
- package/dist/ccda/index.mjs.map +1 -0
- package/dist/deid/index.cjs +2809 -0
- package/dist/deid/index.cjs.map +1 -0
- package/dist/deid/index.d.cts +464 -0
- package/dist/deid/index.d.ts +464 -0
- package/dist/deid/index.mjs +2793 -0
- package/dist/deid/index.mjs.map +1 -0
- package/dist/example-codes-DeXcnCSK.d.cts +105 -0
- package/dist/example-codes-DeXcnCSK.d.ts +105 -0
- package/dist/fhir/index.cjs +1429 -0
- package/dist/fhir/index.cjs.map +1 -0
- package/dist/fhir/index.d.cts +772 -0
- package/dist/fhir/index.d.ts +772 -0
- package/dist/fhir/index.mjs +1384 -0
- package/dist/fhir/index.mjs.map +1 -0
- package/dist/hl7/index.cjs +1012 -0
- package/dist/hl7/index.cjs.map +1 -0
- package/dist/hl7/index.d.cts +548 -0
- package/dist/hl7/index.d.ts +548 -0
- package/dist/hl7/index.mjs +990 -0
- package/dist/hl7/index.mjs.map +1 -0
- package/dist/index.cjs +535 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +407 -0
- package/dist/index.d.ts +407 -0
- package/dist/index.mjs +488 -0
- package/dist/index.mjs.map +1 -0
- package/dist/ncpdp/index.cjs +714 -0
- package/dist/ncpdp/index.cjs.map +1 -0
- package/dist/ncpdp/index.d.cts +432 -0
- package/dist/ncpdp/index.d.ts +432 -0
- package/dist/ncpdp/index.mjs +695 -0
- package/dist/ncpdp/index.mjs.map +1 -0
- package/dist/providers-OLz3zAc-.d.cts +343 -0
- package/dist/providers-OLz3zAc-.d.ts +343 -0
- package/dist/quirk-DmkgoZdh.d.cts +239 -0
- package/dist/quirk-JLyO1Ncj.d.ts +239 -0
- package/dist/x12/index.cjs +920 -0
- package/dist/x12/index.cjs.map +1 -0
- package/dist/x12/index.d.cts +484 -0
- package/dist/x12/index.d.ts +484 -0
- package/dist/x12/index.mjs +892 -0
- package/dist/x12/index.mjs.map +1 -0
- package/package.json +210 -0
|
@@ -0,0 +1,464 @@
|
|
|
1
|
+
import { Hl7MessageKind } from '../hl7/index.js';
|
|
2
|
+
import { DeidPolicy, GenericLocus } from '@cosyte/deid';
|
|
3
|
+
import '@cosyte/hl7';
|
|
4
|
+
import '../providers-OLz3zAc-.js';
|
|
5
|
+
import '../quirk-JLyO1Ncj.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The format-agnostic core of the **`@cosyte/deid` pairing loop** — a **closed-loop
|
|
9
|
+
* co-validation harness** for the `synth` ⇄ `deid` pair.
|
|
10
|
+
*
|
|
11
|
+
* The loop's shape is: **generate a spec-clean synthetic artifact → enumerate the distinctive synthetic
|
|
12
|
+
* PHI sentinels `@cosyte/synth` planted at its patient loci → run it through `@cosyte/deid` → verify
|
|
13
|
+
* every planted sentinel is gone from the de-identified output** (a surviving sentinel is a hard
|
|
14
|
+
* failure), **and that the non-PHI clinical content survived** (the over-scrub guard). It is
|
|
15
|
+
* deterministic and seeded, and it does **not** change what the generators emit — it consumes their
|
|
16
|
+
* output.
|
|
17
|
+
*
|
|
18
|
+
* **Honesty line (governs the whole module).** This is a **co-validation harness**, not an independent
|
|
19
|
+
* audit of `@cosyte/deid` against real-world data. It proves *the pair works on `synth`'s own output*:
|
|
20
|
+
* that the synthetic PHI `synth` plants at the patient loci is removed by `deid`, and that `deid` does
|
|
21
|
+
* not over-scrub `synth`'s clinical payload. A sentinel that `deid` **blocks** rather than redacts still
|
|
22
|
+
* passes the removal check (blocked = gone from the wire). Formats where `@cosyte/deid` ships no adapter
|
|
23
|
+
* (NCPDP **SCRIPT**, **ASTM**) — and DICOM, which `synth` does not generate — are **skipped and named**
|
|
24
|
+
* (see `DEID_LOOP_SKIPPED` in `./index`), never silently.
|
|
25
|
+
*
|
|
26
|
+
* This module holds only the pure, parser-free pieces (the policy, the sentinel model, the sweep, the
|
|
27
|
+
* over-scrub check, the verdict). The per-format loops that drive the generators and the `deid` adapters
|
|
28
|
+
* live in `./index`.
|
|
29
|
+
*
|
|
30
|
+
* @module
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* The set of cosyte formats the loop covers — every format for which **both** `@cosyte/synth` generates
|
|
35
|
+
* and `@cosyte/deid` ships an adapter.
|
|
36
|
+
*/
|
|
37
|
+
type DeidLoopFormat = "hl7" | "fhir" | "ccda" | "x12" | "ncpdp-telecom";
|
|
38
|
+
/**
|
|
39
|
+
* A single **planted sentinel**: one distinctive, synthetic-by-construction PHI token that
|
|
40
|
+
* `@cosyte/synth` placed at a patient locus of a generated artifact, tracked so the loop can prove
|
|
41
|
+
* `@cosyte/deid` removed it. The token is always drawn from a synthetic-safety provider —
|
|
42
|
+
* a fake-name-pool name, a `900`-range SSN, a `555-01xx` phone, a synthetic-assigning-authority
|
|
43
|
+
* identifier — so a sentinel can never be, or collide with, a real person's PHI.
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* ```ts
|
|
47
|
+
* const s: DeidSentinel = { token: "Examplewood", locus: "PID-5", category: "names" };
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
interface DeidSentinel {
|
|
51
|
+
/** The exact synthetic token planted at the locus — a literal substring of the spec-clean artifact. */
|
|
52
|
+
readonly token: string;
|
|
53
|
+
/** Where it lives — a format-neutral path (e.g. `"PID-5"`, `"recordTarget"`, `"NM1[0]-3"`). */
|
|
54
|
+
readonly locus: string;
|
|
55
|
+
/** The Safe Harbor category `@cosyte/deid` assigned to the locus, when known. */
|
|
56
|
+
readonly category?: string;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* The verdict of one pass through the pairing loop for one artifact.
|
|
60
|
+
*
|
|
61
|
+
* `pass` is `true` **iff** at least one sentinel was planted, **no** planted sentinel survived
|
|
62
|
+
* de-identification, and **no** probed clinical value was over-scrubbed.
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* ```ts
|
|
66
|
+
* import { hl7DeidLoop } from "@cosyte/synth/deid";
|
|
67
|
+
* const r = hl7DeidLoop({ seed: 42 });
|
|
68
|
+
* r.pass; // => true
|
|
69
|
+
* r.survivors.length; // => 0
|
|
70
|
+
* ```
|
|
71
|
+
*/
|
|
72
|
+
interface DeidLoopResult {
|
|
73
|
+
/** The covered format. */
|
|
74
|
+
readonly format: DeidLoopFormat;
|
|
75
|
+
/** The concrete artifact label (e.g. `"ORU^R01"`, `"837P"`, `"CCD"`). */
|
|
76
|
+
readonly artifact: string;
|
|
77
|
+
/** The seed — the same seed yields the same artifact and the same verdict. */
|
|
78
|
+
readonly seed: number;
|
|
79
|
+
/** Every distinctive synthetic PHI sentinel `synth` planted at a patient locus of the artifact. */
|
|
80
|
+
readonly planted: readonly DeidSentinel[];
|
|
81
|
+
/** Planted sentinels still present in the de-identified output — **must be empty**. */
|
|
82
|
+
readonly survivors: readonly DeidSentinel[];
|
|
83
|
+
/** The non-PHI clinical code tokens probed for over-scrub (present in the spec-clean artifact). */
|
|
84
|
+
readonly clinicalProbed: readonly string[];
|
|
85
|
+
/** Probed clinical tokens missing after de-identification (over-scrub) — **must be empty**. */
|
|
86
|
+
readonly clinicalScrubbed: readonly string[];
|
|
87
|
+
/** The spec-clean serialized artifact (before de-identification). */
|
|
88
|
+
readonly original: string;
|
|
89
|
+
/** The serialized de-identified artifact (`@cosyte/deid`'s output). */
|
|
90
|
+
readonly deidentified: string;
|
|
91
|
+
/** `true` iff sentinels were planted, none survived, and nothing clinical was over-scrubbed. */
|
|
92
|
+
readonly pass: boolean;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* The name of the removal-oriented policy the loop runs `@cosyte/deid` under.
|
|
96
|
+
*
|
|
97
|
+
* @example
|
|
98
|
+
* ```ts
|
|
99
|
+
* import { DEID_LOOP_POLICY_NAME } from "@cosyte/synth/deid";
|
|
100
|
+
* DEID_LOOP_POLICY_NAME; // => "synth-deid-loop-removal"
|
|
101
|
+
* ```
|
|
102
|
+
*/
|
|
103
|
+
declare const DEID_LOOP_POLICY_NAME = "synth-deid-loop-removal";
|
|
104
|
+
/**
|
|
105
|
+
* Build the **removal-oriented** de-identification policy the loop runs `@cosyte/deid` under: HIPAA Safe
|
|
106
|
+
* Harbor, but with the three keyed-by-default identifier categories (MRN, health-plan-beneficiary,
|
|
107
|
+
* account) switched from `pseudonymize` to `redact`.
|
|
108
|
+
*
|
|
109
|
+
* Two reasons this is the right policy for a co-validation loop: (1) it needs **no key context**, so the
|
|
110
|
+
* loop is a pure function of the seed (the default Safe Harbor policy pseudonymizes those three
|
|
111
|
+
* categories, which requires the consumer's HMAC key); and (2) it makes the removal contract crisp —
|
|
112
|
+
* every PHI locus is **removed or generalized away**, never replaced by a surrogate that a naive sweep
|
|
113
|
+
* might mistake for a survivor. `pseudonymize` also removes the original value, so this is a stricter,
|
|
114
|
+
* not weaker, check.
|
|
115
|
+
*
|
|
116
|
+
* @returns A frozen {@link DeidPolicy} suitable for the `policy` option of any `@cosyte/deid` adapter.
|
|
117
|
+
* @example
|
|
118
|
+
* ```ts
|
|
119
|
+
* import { deidLoopPolicy } from "@cosyte/synth/deid";
|
|
120
|
+
* import { deidentifyHl7 } from "@cosyte/deid/hl7";
|
|
121
|
+
* const { document } = deidentifyHl7(msg, { policy: deidLoopPolicy() });
|
|
122
|
+
* ```
|
|
123
|
+
*/
|
|
124
|
+
declare function deidLoopPolicy(): DeidPolicy;
|
|
125
|
+
/**
|
|
126
|
+
* Enumerate the distinctive synthetic PHI sentinels from a de-identifier's **patient-scoped**
|
|
127
|
+
* identifier loci, decomposed to **literal** tokens of the spec-clean artifact.
|
|
128
|
+
*
|
|
129
|
+
* The loci come from `@cosyte/deid`'s own per-format extractor (`extractHl7Loci` / `extractFhirLoci` /
|
|
130
|
+
* `extractX12Loci` / `extractTelecomLoci`), which locates PHI **structurally** — so provider and
|
|
131
|
+
* organization names, which a de-identifier legitimately retains, are never extracted and never become
|
|
132
|
+
* false sentinels. Only `identifier`-kind loci are used (names, SSNs, phones, emails, member/account
|
|
133
|
+
* ids); dates (generalized to a year, not removed) and ZIPs (generalized to three digits) are handled by
|
|
134
|
+
* the format's own contract, not asserted here.
|
|
135
|
+
*
|
|
136
|
+
* Requiring each token to be a **literal substring of `original`** keeps the removal check non-vacuous:
|
|
137
|
+
* a sentinel the sweep later looks for is one that is provably present before de-identification.
|
|
138
|
+
*
|
|
139
|
+
* @param loci - The located candidate values from a `@cosyte/deid` extractor.
|
|
140
|
+
* @param original - The spec-clean serialized artifact the loci were extracted from.
|
|
141
|
+
* @returns The distinctive, literal, synthetic PHI sentinels — de-duplicated by token.
|
|
142
|
+
* @example
|
|
143
|
+
* ```ts
|
|
144
|
+
* import { extractHl7Loci } from "@cosyte/deid/hl7";
|
|
145
|
+
* const sentinels = identifierSentinels(extractHl7Loci(msg).loci, msg.toString());
|
|
146
|
+
* ```
|
|
147
|
+
*/
|
|
148
|
+
declare function identifierSentinels(loci: readonly GenericLocus[], original: string): readonly DeidSentinel[];
|
|
149
|
+
/**
|
|
150
|
+
* Enumerate the patient sentinels of a **C-CDA** document by reading the `<recordTarget>` element of the
|
|
151
|
+
* serialized XML — the patient participation. Scoping to `recordTarget` (rather than `author` /
|
|
152
|
+
* `custodian`, which carry provider identity a de-identifier retains) is what keeps the sentinels
|
|
153
|
+
* patient-PHI only. Used for C-CDA because `@cosyte/deid`'s C-CDA extractor operates on a raw DOM, which
|
|
154
|
+
* this zero-dependency module does not construct.
|
|
155
|
+
*
|
|
156
|
+
* @param xml - The serialized spec-clean C-CDA document.
|
|
157
|
+
* @returns The distinctive synthetic patient sentinels (given/family names, the patient id extensions).
|
|
158
|
+
* @example
|
|
159
|
+
* ```ts
|
|
160
|
+
* import { serializeCcda } from "@cosyte/ccda";
|
|
161
|
+
* const sentinels = recordTargetSentinels(serializeCcda(generateCcd({ seed: 1 })));
|
|
162
|
+
* ```
|
|
163
|
+
*/
|
|
164
|
+
declare function recordTargetSentinels(xml: string): readonly DeidSentinel[];
|
|
165
|
+
/**
|
|
166
|
+
* Sweep the **PHI-locus residue** of a de-identified artifact for **surviving** planted sentinels — the
|
|
167
|
+
* heart of the removal check. A sentinel survives if its exact token still appears in the values that
|
|
168
|
+
* remain at the (former) PHI loci after de-identification; a non-empty result is a **hard failure** (real
|
|
169
|
+
* PHI-shaped data leaked through de-identification).
|
|
170
|
+
*
|
|
171
|
+
* **Why the PHI-locus residue and not the whole document.** `@cosyte/synth` draws patient *and*
|
|
172
|
+
* provider/organization names, addresses, and phones from the same synthetic pools, and a de-identifier
|
|
173
|
+
* legitimately **retains** provider identity. So the same synthetic token can sit at both a patient locus
|
|
174
|
+
* (removed) and a provider locus (retained); a whole-document sweep would read the retained provider copy
|
|
175
|
+
* as a false survivor. Sweeping only the residue at the loci that were PHI — re-read from the
|
|
176
|
+
* de-identifier's own output — is locus-scoped and collision-proof: a token still present *there* is a
|
|
177
|
+
* genuine leak. It remains independent of the de-identifier's manifest: it reads the actual serialized
|
|
178
|
+
* output, so a de-identifier that locates a locus but fails to strip it is still caught.
|
|
179
|
+
*
|
|
180
|
+
* @param phiResidue - The de-identified values that remain at the former PHI loci (for the model formats,
|
|
181
|
+
* the re-extracted identifier-locus values joined; for C-CDA, the de-identified `<recordTarget>` block).
|
|
182
|
+
* @param sentinels - The sentinels planted in the corresponding spec-clean artifact.
|
|
183
|
+
* @returns The subset of `sentinels` still present in `phiResidue` — empty on a clean pass.
|
|
184
|
+
* @example
|
|
185
|
+
* ```ts
|
|
186
|
+
* sweepSurvivors(deidPhiResidue, planted); // => [] (all removed)
|
|
187
|
+
* ```
|
|
188
|
+
*/
|
|
189
|
+
declare function sweepSurvivors(phiResidue: string, sentinels: readonly DeidSentinel[]): readonly DeidSentinel[];
|
|
190
|
+
/**
|
|
191
|
+
* The over-scrub side of the loop: of the clinical (non-PHI) code tokens that appear in the spec-clean
|
|
192
|
+
* artifact, which are **missing** after de-identification. A de-identifier must keep clinical content; a
|
|
193
|
+
* non-empty result means it over-scrubbed. Only structured code tokens present before de-identification
|
|
194
|
+
* are probed (a code that appears solely inside a free-text narrative a de-identifier legitimately blocks
|
|
195
|
+
* is not a structured-content loss).
|
|
196
|
+
*
|
|
197
|
+
* Only **distinctive** codes (four or more characters) are probed: a short 2–3 digit code (e.g. a CVX
|
|
198
|
+
* vaccine code) can appear by coincidence *inside* a PHI value a de-identifier removes — a timestamp, an
|
|
199
|
+
* SSN — and would read as a false over-scrub. Distinctive codes (LOINC `4548-4`, SNOMED `44054006`,
|
|
200
|
+
* 11-digit NDCs, CPT `99213`) do not collide this way. An artifact whose clinical codes are all short
|
|
201
|
+
* simply yields an empty probe (the guard is skipped, never falsely tripped).
|
|
202
|
+
*
|
|
203
|
+
* @param original - The spec-clean serialized artifact.
|
|
204
|
+
* @param deidentified - The serialized de-identified artifact.
|
|
205
|
+
* @param clinicalCodes - Candidate structured clinical code tokens (e.g. LOINC/ICD/NDC from the example
|
|
206
|
+
* pools) to probe.
|
|
207
|
+
* @returns `{ probed, scrubbed }` — the distinctive codes present before, and the subset absent after.
|
|
208
|
+
* @example
|
|
209
|
+
* ```ts
|
|
210
|
+
* const { probed, scrubbed } = clinicalRetention(before, after, ["4548-4", "44054006"]);
|
|
211
|
+
* scrubbed; // => [] (clinical content retained)
|
|
212
|
+
* ```
|
|
213
|
+
*/
|
|
214
|
+
declare function clinicalRetention(original: string, deidentified: string, clinicalCodes: readonly string[]): {
|
|
215
|
+
readonly probed: readonly string[];
|
|
216
|
+
readonly scrubbed: readonly string[];
|
|
217
|
+
};
|
|
218
|
+
/**
|
|
219
|
+
* Assemble a {@link DeidLoopResult} from the planted sentinels, the de-identified output, and the
|
|
220
|
+
* clinical probe — computing the `pass` verdict (sentinels planted, none survived, nothing over-scrubbed).
|
|
221
|
+
*
|
|
222
|
+
* @param parts - The pieces gathered by a per-format loop.
|
|
223
|
+
* @returns The immutable verdict.
|
|
224
|
+
* @example
|
|
225
|
+
* ```ts
|
|
226
|
+
* const result = assembleVerdict({
|
|
227
|
+
* format: "hl7", artifact: "ORU^R01", seed: 1,
|
|
228
|
+
* planted, original, deidentified, clinicalCodes,
|
|
229
|
+
* });
|
|
230
|
+
* ```
|
|
231
|
+
*/
|
|
232
|
+
declare function assembleVerdict(parts: {
|
|
233
|
+
readonly format: DeidLoopFormat;
|
|
234
|
+
readonly artifact: string;
|
|
235
|
+
readonly seed: number;
|
|
236
|
+
readonly planted: readonly DeidSentinel[];
|
|
237
|
+
readonly original: string;
|
|
238
|
+
readonly deidentified: string;
|
|
239
|
+
/**
|
|
240
|
+
* The de-identified values that remain at the former PHI loci — what the survivor sweep reads (see
|
|
241
|
+
* {@link sweepSurvivors}). Defaults to `deidentified` when omitted (a whole-document sweep), but the
|
|
242
|
+
* per-format loops supply the locus-scoped residue so provider/organization identity a de-identifier
|
|
243
|
+
* legitimately retains never reads as a false survivor.
|
|
244
|
+
*/
|
|
245
|
+
readonly phiResidue?: string;
|
|
246
|
+
readonly clinicalCodes: readonly string[];
|
|
247
|
+
}): DeidLoopResult;
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* `@cosyte/synth/deid` — the **`@cosyte/deid` pairing loop**: a deterministic,
|
|
251
|
+
* seeded **co-validation harness** for the `synth` ⇄ `deid` pair.
|
|
252
|
+
*
|
|
253
|
+
* For each covered format it **generates** a spec-clean synthetic artifact through `@cosyte/synth`'s own
|
|
254
|
+
* generators, **enumerates** the distinctive synthetic PHI sentinels planted at the patient loci,
|
|
255
|
+
* **de-identifies** it through `@cosyte/deid`, and **verifies** that every planted sentinel is gone from
|
|
256
|
+
* the de-identified output while the clinical payload survives. A surviving sentinel is a hard failure;
|
|
257
|
+
* an over-scrubbed clinical value is a hard failure.
|
|
258
|
+
*
|
|
259
|
+
* **Scope & honesty.** This proves *the pair works on `synth`'s own output* — it
|
|
260
|
+
* is **not** an independent audit of `@cosyte/deid` against real-world documents. The sentinels are
|
|
261
|
+
* synthetic-by-construction (never realistic); a sentinel `deid` **blocks** rather than redacts still
|
|
262
|
+
* passes (blocked = gone). The loop covers the five formats both packages support
|
|
263
|
+
* ({@link DEID_LOOP_COVERED_FORMATS}); NCPDP **SCRIPT**, **ASTM**, and **DICOM** are **skipped and
|
|
264
|
+
* named** ({@link DEID_LOOP_SKIPPED}), never silently. `@cosyte/deid` is an **optional peer dependency**
|
|
265
|
+
* (the `mllp`/`ncpdp` vendoring pattern); importing this subpath pulls it in.
|
|
266
|
+
*
|
|
267
|
+
* @module
|
|
268
|
+
*/
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* The cosyte formats the pairing loop covers — every format for which **both** `@cosyte/synth`
|
|
272
|
+
* generates and `@cosyte/deid` ships an adapter.
|
|
273
|
+
*
|
|
274
|
+
* @example
|
|
275
|
+
* ```ts
|
|
276
|
+
* import { DEID_LOOP_COVERED_FORMATS } from "@cosyte/synth/deid";
|
|
277
|
+
* DEID_LOOP_COVERED_FORMATS; // ["hl7","fhir","ccda","x12","ncpdp-telecom"]
|
|
278
|
+
* ```
|
|
279
|
+
*/
|
|
280
|
+
declare const DEID_LOOP_COVERED_FORMATS: readonly ["hl7", "fhir", "ccda", "x12", "ncpdp-telecom"];
|
|
281
|
+
/**
|
|
282
|
+
* The format paths the loop **deliberately skips**, each with the honest reason — so a coverage gap is
|
|
283
|
+
* named, never silent.
|
|
284
|
+
*
|
|
285
|
+
* @example
|
|
286
|
+
* ```ts
|
|
287
|
+
* import { DEID_LOOP_SKIPPED } from "@cosyte/synth/deid";
|
|
288
|
+
* DEID_LOOP_SKIPPED.map((s) => s.format); // ["ncpdp-script","astm","dicom"]
|
|
289
|
+
* ```
|
|
290
|
+
*/
|
|
291
|
+
declare const DEID_LOOP_SKIPPED: readonly [Readonly<{
|
|
292
|
+
format: "ncpdp-script";
|
|
293
|
+
reason: "@cosyte/deid ships no NCPDP SCRIPT locus map; synth generates SCRIPT but the loop cannot pair it.";
|
|
294
|
+
}>, Readonly<{
|
|
295
|
+
format: "astm";
|
|
296
|
+
reason: "@cosyte/deid ships no ASTM adapter; synth generates ASTM but the loop cannot pair it.";
|
|
297
|
+
}>, Readonly<{
|
|
298
|
+
format: "dicom";
|
|
299
|
+
reason: "@cosyte/synth does not generate DICOM, so there is nothing to pair.";
|
|
300
|
+
}>];
|
|
301
|
+
/** Options accepted by {@link hl7DeidLoop}. */
|
|
302
|
+
interface Hl7DeidLoopOptions {
|
|
303
|
+
/** The seed — the same seed yields the same artifact and the same verdict. Defaults to `0`. */
|
|
304
|
+
readonly seed?: number;
|
|
305
|
+
/** The HL7 v2 message family to generate. Defaults to `"ORU^R01"` (a PHI- and clinically-dense report). */
|
|
306
|
+
readonly kind?: Hl7MessageKind;
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* Run the pairing loop for **HL7 v2**: generate a spec-clean message through `@cosyte/hl7`'s builder,
|
|
310
|
+
* plant PHI sentinels at the `PID`, de-identify through `@cosyte/deid/hl7`, and verify removal +
|
|
311
|
+
* clinical retention.
|
|
312
|
+
*
|
|
313
|
+
* @param options - The seed and message family. See {@link Hl7DeidLoopOptions}.
|
|
314
|
+
* @returns The {@link DeidLoopResult} for the message.
|
|
315
|
+
* @example
|
|
316
|
+
* ```ts
|
|
317
|
+
* import { hl7DeidLoop } from "@cosyte/synth/deid";
|
|
318
|
+
* const { pass, survivors } = hl7DeidLoop({ seed: 42, kind: "ORU^R01" });
|
|
319
|
+
* // pass === true, survivors.length === 0
|
|
320
|
+
* ```
|
|
321
|
+
*/
|
|
322
|
+
declare function hl7DeidLoop(options?: Hl7DeidLoopOptions): DeidLoopResult;
|
|
323
|
+
/** Options accepted by {@link fhirDeidLoop}. */
|
|
324
|
+
interface FhirDeidLoopOptions {
|
|
325
|
+
/** The seed. Defaults to `0`. */
|
|
326
|
+
readonly seed?: number;
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Run the pairing loop for **FHIR R4**: generate a spec-clean `Bundle` (US-Core `Patient` + a clinical
|
|
330
|
+
* spine) through `@cosyte/fhir`'s model, plant PHI sentinels at the `Patient` demographics, de-identify
|
|
331
|
+
* through `@cosyte/deid/fhir`, and verify removal + clinical retention. A `Bundle` (not a bare
|
|
332
|
+
* `Patient`) is used so there is clinical content to prove is **not** over-scrubbed.
|
|
333
|
+
*
|
|
334
|
+
* @param options - The seed. See {@link FhirDeidLoopOptions}.
|
|
335
|
+
* @returns The {@link DeidLoopResult} for the bundle.
|
|
336
|
+
* @example
|
|
337
|
+
* ```ts
|
|
338
|
+
* import { fhirDeidLoop } from "@cosyte/synth/deid";
|
|
339
|
+
* fhirDeidLoop({ seed: 7 }).pass; // => true
|
|
340
|
+
* ```
|
|
341
|
+
*/
|
|
342
|
+
declare function fhirDeidLoop(options?: FhirDeidLoopOptions): DeidLoopResult;
|
|
343
|
+
/** The X12 transaction families the loop can pair. */
|
|
344
|
+
type X12DeidVariant = "837P" | "837I" | "837D" | "271" | "835";
|
|
345
|
+
/** Options accepted by {@link x12DeidLoop}. */
|
|
346
|
+
interface X12DeidLoopOptions {
|
|
347
|
+
/** The seed. Defaults to `0`. */
|
|
348
|
+
readonly seed?: number;
|
|
349
|
+
/** The transaction family. Defaults to `"837P"`. */
|
|
350
|
+
readonly variant?: X12DeidVariant;
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* Run the pairing loop for **X12 005010**: generate a spec-clean transaction through `@cosyte/x12`'s
|
|
354
|
+
* builders, plant PHI sentinels across the subscriber/patient loops, de-identify through
|
|
355
|
+
* `@cosyte/deid/x12`, and verify removal + clinical retention.
|
|
356
|
+
*
|
|
357
|
+
* @param options - The seed and transaction family. See {@link X12DeidLoopOptions}.
|
|
358
|
+
* @returns The {@link DeidLoopResult} for the transaction.
|
|
359
|
+
* @example
|
|
360
|
+
* ```ts
|
|
361
|
+
* import { x12DeidLoop } from "@cosyte/synth/deid";
|
|
362
|
+
* x12DeidLoop({ seed: 1, variant: "837P" }).survivors; // => []
|
|
363
|
+
* ```
|
|
364
|
+
*/
|
|
365
|
+
declare function x12DeidLoop(options?: X12DeidLoopOptions): DeidLoopResult;
|
|
366
|
+
/** The NCPDP Telecom transactions the loop can pair. */
|
|
367
|
+
type NcpdpTelecomVariant = "B1" | "B2" | "B3";
|
|
368
|
+
/** Options accepted by {@link ncpdpTelecomDeidLoop}. */
|
|
369
|
+
interface NcpdpTelecomDeidLoopOptions {
|
|
370
|
+
/** The seed. Defaults to `0`. */
|
|
371
|
+
readonly seed?: number;
|
|
372
|
+
/** The Telecom transaction. Defaults to `"B1"` (a claim, the PHI- and clinically-densest). */
|
|
373
|
+
readonly transaction?: NcpdpTelecomVariant;
|
|
374
|
+
}
|
|
375
|
+
/**
|
|
376
|
+
* Run the pairing loop for **NCPDP Telecom** (pharmacy claims): generate a spec-clean transaction
|
|
377
|
+
* through `@cosyte/ncpdp`, plant PHI sentinels at the patient/cardholder segments, de-identify through
|
|
378
|
+
* `@cosyte/deid/ncpdp`, and verify removal + clinical retention. **NCPDP SCRIPT is out of scope** —
|
|
379
|
+
* `@cosyte/deid` ships no SCRIPT adapter ({@link DEID_LOOP_SKIPPED}).
|
|
380
|
+
*
|
|
381
|
+
* @param options - The seed and transaction. See {@link NcpdpTelecomDeidLoopOptions}.
|
|
382
|
+
* @returns The {@link DeidLoopResult} for the transaction.
|
|
383
|
+
* @example
|
|
384
|
+
* ```ts
|
|
385
|
+
* import { ncpdpTelecomDeidLoop } from "@cosyte/synth/deid";
|
|
386
|
+
* ncpdpTelecomDeidLoop({ seed: 3, transaction: "B1" }).pass; // => true
|
|
387
|
+
* ```
|
|
388
|
+
*/
|
|
389
|
+
declare function ncpdpTelecomDeidLoop(options?: NcpdpTelecomDeidLoopOptions): DeidLoopResult;
|
|
390
|
+
/** The C-CDA document types the loop can pair. */
|
|
391
|
+
type CcdaDeidDocumentType = "ccd" | "referral";
|
|
392
|
+
/** Options accepted by {@link ccdaDeidLoop}. */
|
|
393
|
+
interface CcdaDeidLoopOptions {
|
|
394
|
+
/** The seed. Defaults to `0`. */
|
|
395
|
+
readonly seed?: number;
|
|
396
|
+
/** The document type. Defaults to `"ccd"`. */
|
|
397
|
+
readonly documentType?: CcdaDeidDocumentType;
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
* Run the pairing loop for **C-CDA R2.1**: generate a spec-clean document through `@cosyte/ccda`'s
|
|
401
|
+
* `buildCcda`, plant PHI sentinels at the `recordTarget` patient participation, de-identify through
|
|
402
|
+
* `@cosyte/deid/ccda`, and verify removal + clinical retention.
|
|
403
|
+
*
|
|
404
|
+
* Sentinels are read from the `<recordTarget>` element of the serialized XML (not `@cosyte/deid`'s DOM
|
|
405
|
+
* extractor) so that this zero-dependency subpath constructs no XML DOM; scoping to `recordTarget` keeps
|
|
406
|
+
* the sentinels patient-PHI only (`author`/`custodian` provider identity is legitimately retained).
|
|
407
|
+
*
|
|
408
|
+
* @param options - The seed and document type. See {@link CcdaDeidLoopOptions}.
|
|
409
|
+
* @returns The {@link DeidLoopResult} for the document.
|
|
410
|
+
* @example
|
|
411
|
+
* ```ts
|
|
412
|
+
* import { ccdaDeidLoop } from "@cosyte/synth/deid";
|
|
413
|
+
* ccdaDeidLoop({ seed: 5, documentType: "ccd" }).survivors; // => []
|
|
414
|
+
* ```
|
|
415
|
+
*/
|
|
416
|
+
declare function ccdaDeidLoop(options?: CcdaDeidLoopOptions): DeidLoopResult;
|
|
417
|
+
/** A per-format row of a {@link DeidCoverageSummary}. */
|
|
418
|
+
interface DeidCoverageRow {
|
|
419
|
+
/** The covered format. */
|
|
420
|
+
readonly format: string;
|
|
421
|
+
/** The concrete artifacts run for this format. */
|
|
422
|
+
readonly artifacts: readonly string[];
|
|
423
|
+
/** Total distinctive PHI sentinels planted across the runs. */
|
|
424
|
+
readonly planted: number;
|
|
425
|
+
/** Total surviving sentinels — **0** on a clean pass. */
|
|
426
|
+
readonly survivors: number;
|
|
427
|
+
/** Total clinical tokens probed for over-scrub. */
|
|
428
|
+
readonly clinicalProbed: number;
|
|
429
|
+
/** Total clinical tokens over-scrubbed — **0** on a clean pass. */
|
|
430
|
+
readonly clinicalScrubbed: number;
|
|
431
|
+
/** `true` iff every run for this format passed. */
|
|
432
|
+
readonly pass: boolean;
|
|
433
|
+
}
|
|
434
|
+
/** The aggregate coverage summary across a set of loop runs. */
|
|
435
|
+
interface DeidCoverageSummary {
|
|
436
|
+
/** One row per format. */
|
|
437
|
+
readonly byFormat: readonly DeidCoverageRow[];
|
|
438
|
+
/** Total sentinels planted across every run. */
|
|
439
|
+
readonly totalPlanted: number;
|
|
440
|
+
/** Total surviving sentinels across every run — **0** on a clean pass. */
|
|
441
|
+
readonly totalSurvivors: number;
|
|
442
|
+
/** Total clinical over-scrubs across every run — **0** on a clean pass. */
|
|
443
|
+
readonly totalClinicalScrubbed: number;
|
|
444
|
+
/** `true` iff every run passed. */
|
|
445
|
+
readonly allPass: boolean;
|
|
446
|
+
/** The named, deliberately-skipped format paths (from {@link DEID_LOOP_SKIPPED}). */
|
|
447
|
+
readonly skipped: typeof DEID_LOOP_SKIPPED;
|
|
448
|
+
}
|
|
449
|
+
/**
|
|
450
|
+
* Summarize a set of loop runs into a per-format coverage report — the "coverage summary per format" the
|
|
451
|
+
* harness reports.
|
|
452
|
+
*
|
|
453
|
+
* @param results - The loop results to aggregate.
|
|
454
|
+
* @returns The {@link DeidCoverageSummary}.
|
|
455
|
+
* @example
|
|
456
|
+
* ```ts
|
|
457
|
+
* import { hl7DeidLoop, fhirDeidLoop, summarizeDeidCoverage } from "@cosyte/synth/deid";
|
|
458
|
+
* const summary = summarizeDeidCoverage([hl7DeidLoop(), fhirDeidLoop()]);
|
|
459
|
+
* summary.allPass; // => true
|
|
460
|
+
* ```
|
|
461
|
+
*/
|
|
462
|
+
declare function summarizeDeidCoverage(results: readonly DeidLoopResult[]): DeidCoverageSummary;
|
|
463
|
+
|
|
464
|
+
export { type CcdaDeidDocumentType, type CcdaDeidLoopOptions, DEID_LOOP_COVERED_FORMATS, DEID_LOOP_POLICY_NAME, DEID_LOOP_SKIPPED, type DeidCoverageRow, type DeidCoverageSummary, type DeidLoopFormat, type DeidLoopResult, type DeidSentinel, type FhirDeidLoopOptions, type Hl7DeidLoopOptions, type NcpdpTelecomDeidLoopOptions, type NcpdpTelecomVariant, type X12DeidLoopOptions, type X12DeidVariant, assembleVerdict, ccdaDeidLoop, clinicalRetention, deidLoopPolicy, fhirDeidLoop, hl7DeidLoop, identifierSentinels, ncpdpTelecomDeidLoop, recordTargetSentinels, summarizeDeidCoverage, sweepSurvivors, x12DeidLoop };
|