Co-validate with @cosyte/deid
A synthetic-fixture generator and a de-identifier are natural partners: @cosyte/synth produces data
that looks like PHI but contains none, and @cosyte/deid removes PHI. The @cosyte/synth/deid
subpath closes the loop between them - a deterministic, seeded co-validation harness:
- Generate a spec-clean synthetic artifact through
synth's own generators. - Enumerate the distinctive synthetic PHI sentinels
synthplanted at the patient loci. - De-identify it through
@cosyte/deid. - Verify every sentinel is gone from the de-identified output - a surviving sentinel is a hard failure - and that the clinical payload survives (the over-scrub guard).
Honest scope. This proves the pair works on
synth's own output - it is not an independent audit of@cosyte/deidagainst real-world documents. A sentineldeidblocks rather than redacts still passes the removal check (blocked = gone). The loop covers the five formats both packages support; NCPDP SCRIPT, ASTM, and DICOM are skipped and named.
@cosyte/deid is an optional peer dependency, needed only for this subpath (alongside the parsers
for the formats you pair). The package core keeps zero third-party runtime dependencies.
One artifact through the loop
import { hl7DeidLoop } from "@cosyte/synth/deid";
const r = hl7DeidLoop({ seed: 42, kind: "ORU^R01" });
r.pass; // => true
r.survivors; // => []
r.clinicalScrubbed; // => []
r.planted lists the distinctive synthetic tokens synth placed at the patient loci (a fake-name-pool
name, a 900-range SSN, a 555-01xx phone, a synthetic-assigning-authority MRN); r.survivors is the
subset still present at those loci after de-identification - empty on a clean pass.
Every covered format, one summary
import {
hl7DeidLoop,
fhirDeidLoop,
x12DeidLoop,
ncpdpTelecomDeidLoop,
ccdaDeidLoop,
summarizeDeidCoverage,
} from "@cosyte/synth/deid";
const summary = summarizeDeidCoverage([
hl7DeidLoop({ seed: 1 }),
fhirDeidLoop({ seed: 1 }),
x12DeidLoop({ seed: 1, variant: "837P" }),
ncpdpTelecomDeidLoop({ seed: 1, transaction: "B1" }),
ccdaDeidLoop({ seed: 1 }),
]);
summary.allPass; // => true
summary.totalSurvivors; // => 0
summary.totalClinicalScrubbed; // => 0
The removal check is locus-scoped
synth draws patient and provider/organization names, addresses, and phones from the same synthetic
pools, and a de-identifier legitimately retains provider identity. So the removal check sweeps only
the de-identified values that remain at the former PHI loci - not the whole document - so a provider
name that a de-identifier keeps (and that happens to match a patient token) never reads as a false
survivor. The check still reads the de-identifier's actual serialized output, so a de-identifier that
locates a locus but fails to strip it is still caught.
Deterministic and seeded
The loop is a pure function of the seed: the same seed yields the byte-identical spec-clean artifact, the byte-identical de-identified output, and the same sentinel set.
import { fhirDeidLoop } from "@cosyte/synth/deid";
fhirDeidLoop({ seed: 7 }).deidentified === fhirDeidLoop({ seed: 7 }).deidentified; // => true
Skipped formats, named
A coverage gap is always named, never silent:
import { DEID_LOOP_SKIPPED } from "@cosyte/synth/deid";
DEID_LOOP_SKIPPED.map((s) => s.format); // => ["ncpdp-script", "astm", "dicom"]
@cosyte/deid ships no NCPDP SCRIPT or ASTM adapter, and synth does not generate DICOM -
so those pairings are deferred until the adapters exist. Optional Synthea clinical-content ingestion
(re-serializing Synthea's coherent records through the cosyte parsers) is a documented future concern.