@cosyte/synth 0.1.9 → 0.1.11
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 +39 -0
- package/README.md +39 -19
- package/dist/animal-code-Bdl_9Jma.d.cts +30 -0
- package/dist/animal-code-Bdl_9Jma.d.ts +30 -0
- package/dist/astm/index.cjs +147 -17
- package/dist/astm/index.cjs.map +1 -1
- package/dist/astm/index.d.cts +22 -9
- package/dist/astm/index.d.ts +22 -9
- package/dist/astm/index.mjs +147 -17
- package/dist/astm/index.mjs.map +1 -1
- package/dist/ccda/index.cjs +147 -17
- package/dist/ccda/index.cjs.map +1 -1
- package/dist/ccda/index.d.cts +12 -3
- package/dist/ccda/index.d.ts +12 -3
- package/dist/ccda/index.mjs +147 -17
- package/dist/ccda/index.mjs.map +1 -1
- package/dist/deid/index.cjs +537 -30
- package/dist/deid/index.cjs.map +1 -1
- package/dist/deid/index.d.cts +2 -1
- package/dist/deid/index.d.ts +2 -1
- package/dist/deid/index.mjs +537 -30
- package/dist/deid/index.mjs.map +1 -1
- package/dist/fhir/index.cjs +142 -14
- package/dist/fhir/index.cjs.map +1 -1
- package/dist/fhir/index.d.cts +4 -1
- package/dist/fhir/index.d.ts +4 -1
- package/dist/fhir/index.mjs +142 -14
- package/dist/fhir/index.mjs.map +1 -1
- package/dist/hl7/index.cjs +566 -39
- package/dist/hl7/index.cjs.map +1 -1
- package/dist/hl7/index.d.cts +416 -116
- package/dist/hl7/index.d.ts +416 -116
- package/dist/hl7/index.mjs +557 -40
- package/dist/hl7/index.mjs.map +1 -1
- package/dist/index.cjs +124 -13
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +183 -10
- package/dist/index.d.ts +183 -10
- package/dist/index.mjs +115 -14
- package/dist/index.mjs.map +1 -1
- package/dist/ncpdp/index.cjs +142 -14
- package/dist/ncpdp/index.cjs.map +1 -1
- package/dist/ncpdp/index.d.cts +4 -1
- package/dist/ncpdp/index.d.ts +4 -1
- package/dist/ncpdp/index.mjs +142 -14
- package/dist/ncpdp/index.mjs.map +1 -1
- package/dist/{quirk-BfObsiWV.d.ts → quirk-CzknuYUo.d.ts} +16 -5
- package/dist/{quirk-BCSs1jPt.d.cts → quirk-Q5AxzHSC.d.cts} +16 -5
- package/dist/x12/index.cjs +142 -14
- package/dist/x12/index.cjs.map +1 -1
- package/dist/x12/index.d.cts +4 -1
- package/dist/x12/index.d.ts +4 -1
- package/dist/x12/index.mjs +142 -14
- package/dist/x12/index.mjs.map +1 -1
- package/package.json +9 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,44 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.11
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- c913601: Docs: no example prints a whole message or a value that identifies a person any more, and a new section, What is safe to log, lists the result fields never to log. No code changes.
|
|
8
|
+
|
|
9
|
+
The HL7 v2 generators' TSDoc examples, the README, the quickstart and the HL7 example program now print the message type, codes and booleans, and show an identifying value only in a comment marked synthetic. The README and the installation page say to pin the exact version, since below 1.0 a breaking change can ship in a patch. What is safe to log, on the fatal codes page, names `content`, `original`, `deidentified` and a sentinel's `token` as the fields that hold a whole artifact or a planted value. Nothing a caller does changes.
|
|
10
|
+
|
|
11
|
+
- 70c322c: Docs: the golden fixture page is now `pin-a-golden-fixture` (it was `guides-overview`), the limitations page moves to Reference, and the reason the deid pairing loop reads only the patient fields has its own explanation page. The README links follow. No code changes.
|
|
12
|
+
|
|
13
|
+
## 0.1.10
|
|
14
|
+
|
|
15
|
+
### Patch Changes
|
|
16
|
+
|
|
17
|
+
- 3156ab8: The README's Status section now points at the page that lists what is not generated yet, instead of repeating that list. Its Documentation section links the new pages: a tutorial for each of FHIR, C-CDA, X12, NCPDP and ASTM beside the HL7 v2 one, a reference of every generator option, corpus kind and profile, a page on how `@cosyte/synth` differs from Synthea, faker-style libraries and other healthcare generators, and a glossary. No code changed, and generated output is unchanged.
|
|
18
|
+
- 3156ab8: An empty selection list is now refused with `SYNTH_EMPTY_POOL`, in every format, before anything is generated and whatever the `count`. That covers an empty `mix` in `hl7Corpus`, `fhirCorpus`, `ccdaCorpus`, `x12Corpus`, `ncpdpCorpus` and `astmCorpus`, an empty `triggers` list in `hl7Corpus`, and an empty `quirks` list, or a `profile` whose `quirks` is an empty list, in `hl7QuirkCorpus`, `ccdaQuirkCorpus` and `astmQuirkCorpus`. Before, an empty `mix` with a count above zero quietly filled the corpus with one hard-coded kind (`hl7Corpus({ seed, mix: [] })` returned one `ADT^A01`), an empty `mix` with no count in the X12, NCPDP and ASTM corpora returned an empty corpus, and an empty `quirks` list was read as every quirk. What a caller changes: leave `mix`, `triggers`, `quirks` and `profile` out to get the default, instead of passing an empty list. Output for every non-empty selection is unchanged, byte for byte.
|
|
19
|
+
- 3156ab8: HL7 v2 now generates ten more kinds, each built through `@cosyte/hl7`, read back with zero warnings, and fixed by its seed: `ACK`, `ADT^A02` (transfer), `ADT^A03` (discharge), `ADT^A11` (cancel admit), `ADT^A13` (cancel discharge), `ADT^A28` and `ADT^A31` (add and update person information), `MDM^T02` (a document and its text), `DFT^P03` (a posted charge) and `OML^O21` (a laboratory order). New exports from `@cosyte/synth/hl7`: `generateAck`, which answers any message (generated or your own) with an `AA`, `AE` or `AR` acknowledgment whose MSA-2 echoes its control id; `generateMdm`, `generateDft` and `generateOml`; and `HL7_MESSAGE_KINDS`, every kind `generateHl7` and `hl7Corpus` accept. `generateAdt` and the `triggers` option take the six new ADT triggers. The new kinds name their message structure in MSH-9.3. `generateAck` refuses an inbound message with no MSH-10 with the new fatal code `SYNTH_ACK_UNCORRELATED`.
|
|
20
|
+
|
|
21
|
+
Every message is still stamped HL7 v2.5: there is no option to stamp another version, because `@cosyte/hl7` has no per-version structure check to prove such a message valid. `RDE^O11` is not generated yet, because `@cosyte/hl7` publishes no structure for it.
|
|
22
|
+
|
|
23
|
+
Output for every existing kind is unchanged, byte for byte, and the default `hl7Corpus` mix is still the first seven kinds, so a seed's default corpus is unchanged: name the new kinds in `mix`, or pass `HL7_MESSAGE_KINDS`, to include them. What a caller changes: nothing, unless your code switches exhaustively over `Hl7MessageKind`, `AdtTrigger` or `SynthFatalCode`, each of which now has more members; add cases for them, or a default branch.
|
|
24
|
+
|
|
25
|
+
- 3156ab8: HL7 v2 `generateAdt` and `generateOru` take a new option, `subject`: `"person"` (the default) or `"animal"`. An animal message carries the pet's name in PID-5 (the owner's family name, then the pet's), the record number and a 15-digit microchip with identifier type `MCN` in PID-3, a birth date from 0 to 20 years before the message date in PID-7, the species in PID-35 and the breed in PID-36 as `code^text^99COSYTE`, and the owner in an `NK1` after the `PID` with relationship `OWN^Owner^HL70063`. PID-19 stays empty. Every trigger and `ORU^R01` read back through `@cosyte/hl7` with zero warnings.
|
|
26
|
+
|
|
27
|
+
Species and breed come from our own short list (dog, cat, horse and cattle, two or three breeds each) under the local code system `99COSYTE`: no SNOMED CT, VetSCT or VeNom content. Microchips begin `999`. We have not verified three things against the standards: that `999` is outside every country and manufacturer code ISO 11784 gives a real chip, that `MCN` is the HL7 table 0203 identifier type for a microchip, and that `OWN` is a value of HL7 table 0063. We emit them as written; the how-to marks each as not verified.
|
|
28
|
+
|
|
29
|
+
New exports from `@cosyte/synth`: `petName`, `animalBreed`, `microchip`, `animalBirthDate` (also on `safe`), `isSyntheticMicrochip`, `SYNTHETIC_PET_NAMES`, `SYNTHETIC_ANIMAL_SPECIES`, `ANIMAL_CODE_SYSTEM`, `MICROCHIP_TEST_PREFIX`, and the types `AnimalCode`, `AnimalSpecies` and `AnimalBreedDraw`. New exports from `@cosyte/synth/hl7`: `animalIdentity`, `animalPidSegment`, `ownerNk1Segment`, `MICROCHIP_IDENTIFIER_TYPE`, `OWNER_RELATIONSHIP`, and the types `AnimalIdentity`, `AnimalOwner` and `Hl7Subject`. The animal identity is exported from the package root and `@cosyte/synth/hl7` because synth has no `./arbitraries` subpath yet; adding one, with a fast-check arbitrary for the animal identity, is left for a later change.
|
|
30
|
+
|
|
31
|
+
Output with `subject` left out is unchanged, byte for byte. What a caller changes: nothing, unless your code enumerates the keys of `safe`, which now has four more.
|
|
32
|
+
|
|
33
|
+
- 3156ab8: A profile that leaves `quirks` out is no longer read as an empty quirk list. `defineSynthProfile({ name, givenNames })` now returns a profile with no `quirks` field, and `hl7QuirkCorpus`, `ccdaQuirkCorpus` and `astmQuirkCorpus` given such a profile use their `quirks` option, or every quirk when that is left out too, the same as with no profile. A profile whose `quirks` is an empty list still selects nothing and throws `SYNTH_EMPTY_POOL`. Before, `defineSynthProfile` filled a missing `quirks` with an empty list, so a profile made only for its name pools threw `SYNTH_EMPTY_POOL` in a quirk corpus. The `defineSynthProfile` example passed `quirks: []`, which a quirk corpus refuses; it now builds a profile of name pools that leaves `quirks` out. `validateProfileQuirks` now returns every quirk in the registry for a profile that leaves `quirks` out. What a caller changes: `SynthProfile.quirks` is now optional, so code that reads it directly handles `undefined` (`profile.quirks ?? []` keeps the old reading). Output for every profile that sets `quirks` is unchanged, byte for byte.
|
|
34
|
+
- 3156ab8: A generator whose parser is not installed now throws a `SynthError` with the new code `SYNTH_PEER_NOT_INSTALLED`, the same from the ES module build and the CommonJS build. Its message names the package and the command that installs it ("@cosyte/hl7 is not installed, and this generator builds through it. Install it beside @cosyte/synth: npm install @cosyte/hl7"), `err.peer` is the package, and `err.cause` is the error Node.js raised. Before, the CommonJS build threw `require`'s "Cannot find module '@cosyte/hl7'" (`MODULE_NOT_FOUND`), and the ES module build threw the same until the change below, which would have made it throw the resolver's "Cannot find package '@cosyte/hl7' imported from ..." (`ERR_MODULE_NOT_FOUND`). A caller that matched either message, or either Node.js code, checks `err.code === "SYNTH_PEER_NOT_INSTALLED"` instead, or reads `err.cause.code`. A missing dependency of an installed parser is not reported as the parser missing: that error is thrown as Node.js raised it. `SynthError` takes an optional second argument, `{ cause, peer }`, and the package root exports `SYNTH_PEER_MESSAGES` (one fixed message for each parser package) and the types `SynthPeer` and `SynthErrorOptions`.
|
|
35
|
+
|
|
36
|
+
A generator falls back to loading a parser by name only where `import.meta.resolve` throws the error Deno's test runner throws (`TypeError: Invalid arguments`), or answers with a URL that is not a `file:` URL. Before, any error from resolving fell back, so on Node.js a resolve that failed for another reason could load the CommonJS copy of a parser, whose classes `instanceof` does not recognize. Now such an error is thrown, and so is a `file:` URL that names no file path, which also used to fall back. The Deno case matches on that error's message, and no CI job runs under Deno yet, so it is tested only against a stand-in that throws the same error.
|
|
37
|
+
|
|
38
|
+
The synthetic-safety providers are now tested against the published ranges they cite, and three functions in that module read more simply with the same results. `isSyntheticPhone` checks that the number's digits end in `55501NN` with one pattern where it used two, and gives the same answer for every input. `luhnMod10` tests a doubled digit for 10 or more before subtracting 9, where it tested for more than 9. `ssn(rng, block?)` declares its second parameter optional instead of giving it a default; leaving it out still draws the never-issued space, and its type is unchanged. Generated output is unchanged.
|
|
39
|
+
|
|
40
|
+
- 3156ab8: The release version script now regenerates the generated API reference, so the version shown on the API page matches the `VERSION` constant after a version bump. No change to the package contents or its API.
|
|
41
|
+
|
|
3
42
|
## 0.1.9
|
|
4
43
|
|
|
5
44
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -34,10 +34,10 @@ date, phone, and address from a **guaranteed-non-colliding synthetic source**. I
|
|
|
34
34
|
## Status
|
|
35
35
|
|
|
36
36
|
The 0.1 line, published to npm. The version shown on the npm package page is the one
|
|
37
|
-
that is live; this page never repeats it.
|
|
38
|
-
changelog says what broke. The generator is **feature-complete**: the
|
|
37
|
+
that is live; this page never repeats it. Every release is a patch, a breaking change included, and
|
|
38
|
+
the changelog says what broke and what a caller must change. The generator is **feature-complete**: the
|
|
39
39
|
seeded-PRNG core, the synthetic-safety providers, and the round-trip harness; **generation across all
|
|
40
|
-
six formats**, HL7 v2 (`ADT`/`ORU`/`ORM`/`SIU`/`VXU`), FHIR R4 / US Core (the full clinical
|
|
40
|
+
six formats**, HL7 v2 (`ADT`/`ORU`/`ORM`/`SIU`/`VXU`/`MDM`/`DFT`/`OML` and `ACK`), FHIR R4 / US Core (the full clinical
|
|
41
41
|
set + `collection`/`transaction`/`document` Bundles), C-CDA R2.1 (CCD + Referral Note), X12 005010
|
|
42
42
|
(837P/I/D, 835, 271), NCPDP (SCRIPT NewRx / RxRenewal / RxChange + Telecom B1/B2/B3), and ASTM (E1394
|
|
43
43
|
record reports + E1381 framing), each built **through its parser's own builder/serializer**; **vendor-quirk
|
|
@@ -46,15 +46,22 @@ each round-tripping to exactly the intended parser warning (the intended-warning
|
|
|
46
46
|
**`@cosyte/deid` pairing loop**, a closed-loop co-validation harness that de-identifies generated output
|
|
47
47
|
and proves every planted synthetic PHI sentinel is removed.
|
|
48
48
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
pairing (no `@cosyte/deid` adapter, or not generated); and optional **Synthea** clinical-content ingestion.
|
|
49
|
+
What is not generated yet (quirks for some formats, a few transactions, some pairing loops) is
|
|
50
|
+
listed in one place, [what it does and does not do](https://docs.cosyte.com/synth/limitations),
|
|
51
|
+
so this page does not repeat it.
|
|
53
52
|
|
|
54
53
|
## Install
|
|
55
54
|
|
|
56
55
|
```bash
|
|
57
|
-
npm install @cosyte/synth @cosyte/hl7 @cosyte/fhir @cosyte/ccda @cosyte/x12 @cosyte/ncpdp @cosyte/astm
|
|
56
|
+
npm install @cosyte/synth @cosyte/hl7 @cosyte/fhir @cosyte/ccda @cosyte/x12 @cosyte/ncpdp @cosyte/astm @cosyte/deid
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
**Pin the exact version.** Below 1.0 a breaking change can ship in a patch release, so a caret
|
|
60
|
+
range (`^0.1.x`) does not protect you from one. Install with `--save-exact` (pnpm takes the same flag;
|
|
61
|
+
Yarn's is `--exact`) and read the changelog before you move the pin:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
npm install --save-exact @cosyte/synth
|
|
58
65
|
```
|
|
59
66
|
|
|
60
67
|
Node.js 22.12 or later, tested on 22.12, 22, 24, and 26. A generator loads its parser on first use
|
|
@@ -68,10 +75,14 @@ Rollup and Vite `external`, webpack `externals`) and install them beside the bun
|
|
|
68
75
|
`@cosyte/hl7`, `@cosyte/fhir`, `@cosyte/ccda`, `@cosyte/x12`, `@cosyte/ncpdp`, `@cosyte/astm`, and
|
|
69
76
|
`@cosyte/deid` are **optional peer dependencies**, each needed only for its subpath (`@cosyte/synth/hl7`,
|
|
70
77
|
`@cosyte/synth/fhir`, `@cosyte/synth/ccda`, `@cosyte/synth/x12`, `@cosyte/synth/ncpdp`,
|
|
71
|
-
`@cosyte/synth/astm`, and `@cosyte/synth/deid
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
78
|
+
`@cosyte/synth/astm`, and `@cosyte/synth/deid`, whose peer is `@cosyte/deid`). Importing a subpath
|
|
79
|
+
loads no parser: each loads the first time a generator needs it, so install only the packages whose
|
|
80
|
+
fixtures you generate. A `@cosyte/synth/deid` loop also loads the parser of its own format, so
|
|
81
|
+
`hl7DeidLoop` needs `@cosyte/deid` and `@cosyte/hl7`. A parser that is missing throws
|
|
82
|
+
`SYNTH_PEER_NOT_INSTALLED` on first use, except under Bun in a project with no `node_modules` folder,
|
|
83
|
+
where Bun downloads and loads it instead
|
|
84
|
+
([Troubleshooting](https://docs.cosyte.com/synth/troubleshooting#a-parser-is-not-installed)). The
|
|
85
|
+
package core has **zero third-party runtime dependencies**.
|
|
75
86
|
|
|
76
87
|
## Usage
|
|
77
88
|
|
|
@@ -80,8 +91,10 @@ on [docs.cosyte.com/synth](https://docs.cosyte.com/synth).
|
|
|
80
91
|
|
|
81
92
|
### Generate a spec-clean HL7 v2 message
|
|
82
93
|
|
|
83
|
-
The HL7 v2 set covers `ADT` (`A01`/`A04`/`A08`), `ORU^R01`,
|
|
84
|
-
|
|
94
|
+
The HL7 v2 set covers `ADT` (`A01`/`A02`/`A03`/`A04`/`A08`/`A11`/`A13`/`A28`/`A31`), `ORU^R01`,
|
|
95
|
+
`ORM^O01`, `SIU^S12`, `VXU^V04`, `MDM^T02`, `DFT^P03`, `OML^O21`, and an `ACK` for any of them:
|
|
96
|
+
each built through `@cosyte/hl7`'s `buildMessage`, so it is spec-clean by construction. Every
|
|
97
|
+
message is stamped HL7 v2.5.
|
|
85
98
|
|
|
86
99
|
```ts
|
|
87
100
|
import { generateAdt, generateOru, generateHl7, hl7Corpus, roundTrip } from "@cosyte/synth/hl7";
|
|
@@ -99,7 +112,8 @@ const corpus = hl7Corpus({ seed: 42, count: 7 }); // one of each family, cycled
|
|
|
99
112
|
console.log(corpus.artifacts.every((a) => a.warnings.length === 0)); // true, all spec-clean
|
|
100
113
|
|
|
101
114
|
// Dispatch by kind when the message type is data:
|
|
102
|
-
|
|
115
|
+
const vxu = roundTrip(generateHl7("VXU^V04", 12345)).content;
|
|
116
|
+
console.log(vxu.split("\r")[0]?.split("|")[8]); // VXU^V04, the type from MSH-9, never the whole segment
|
|
103
117
|
```
|
|
104
118
|
|
|
105
119
|
### Generate a spec-clean FHIR R4 / US Core resource
|
|
@@ -375,14 +389,20 @@ isSyntheticNpi(safe.npi(rng)); // true, always a deliberately-invalid-Luhn NPI (
|
|
|
375
389
|
The full reference is on [docs.cosyte.com/synth](https://docs.cosyte.com/synth):
|
|
376
390
|
|
|
377
391
|
- [Installation](https://docs.cosyte.com/synth/installation), the [quickstart](https://docs.cosyte.com/synth/quickstart), and
|
|
378
|
-
a tutorial:
|
|
392
|
+
a tutorial per format: test your [HL7 v2](https://docs.cosyte.com/synth/test-hl7-handling-with-fixtures),
|
|
393
|
+
[FHIR](https://docs.cosyte.com/synth/test-fhir-handling-with-fixtures), [C-CDA](https://docs.cosyte.com/synth/test-ccda-handling-with-fixtures),
|
|
394
|
+
[X12](https://docs.cosyte.com/synth/test-x12-handling-with-fixtures), [NCPDP](https://docs.cosyte.com/synth/test-ncpdp-handling-with-fixtures),
|
|
395
|
+
and [ASTM](https://docs.cosyte.com/synth/test-astm-handling-with-fixtures) handling with generated fixtures.
|
|
379
396
|
- How-to guides: [HL7 v2](https://docs.cosyte.com/synth/guides-hl7), [FHIR R4 and US Core](https://docs.cosyte.com/synth/guides-fhir),
|
|
380
397
|
[C-CDA](https://docs.cosyte.com/synth/guides-ccda), [X12](https://docs.cosyte.com/synth/guides-x12), [NCPDP](https://docs.cosyte.com/synth/guides-ncpdp),
|
|
381
398
|
[ASTM](https://docs.cosyte.com/synth/guides-astm), [vendor quirks](https://docs.cosyte.com/synth/guides-quirks),
|
|
382
399
|
[the `@cosyte/deid` pairing loop](https://docs.cosyte.com/synth/guides-deid), [synthetic values](https://docs.cosyte.com/synth/draw-synthetic-values),
|
|
383
|
-
and [pinning a golden fixture](https://docs.cosyte.com/synth/
|
|
384
|
-
- Explanation: [how it works](https://docs.cosyte.com/synth/concepts-archetype)
|
|
385
|
-
|
|
400
|
+
and [pinning a golden fixture](https://docs.cosyte.com/synth/pin-a-golden-fixture).
|
|
401
|
+
- Explanation: [how it works](https://docs.cosyte.com/synth/concepts-archetype), [why the removal check reads only the patient fields](https://docs.cosyte.com/synth/why-deid-checks-patient-fields),
|
|
402
|
+
and [Synthea, faker and other generators](https://docs.cosyte.com/synth/synthea-faker-and-other-generators).
|
|
403
|
+
- Reference: [generator options, kinds and profiles](https://docs.cosyte.com/synth/generator-options), [fatal codes](https://docs.cosyte.com/synth/fatal-codes),
|
|
404
|
+
the [quirk catalog](https://docs.cosyte.com/synth/quirk-catalog), the [glossary](https://docs.cosyte.com/synth/glossary), [what it does and does not do](https://docs.cosyte.com/synth/limitations), and the
|
|
405
|
+
[API reference](https://github.com/cosyte/synth/blob/main/docs-content/api/README.md), generated from the source.
|
|
386
406
|
- [Troubleshooting](https://docs.cosyte.com/synth/troubleshooting), [testing and quality](https://docs.cosyte.com/synth/testing-and-quality), and [performance](https://docs.cosyte.com/synth/performance).
|
|
387
407
|
- [`CHANGELOG.md`](https://github.com/cosyte/synth/blob/main/CHANGELOG.md): every release.
|
|
388
408
|
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The two helpers that build the animal species and breed list in `./animal.ts`. They live in a
|
|
3
|
+
* module of their own, which the package does not export, so a test can call them directly and
|
|
4
|
+
* every mutant in them meets a test of its own.
|
|
5
|
+
*
|
|
6
|
+
* @module
|
|
7
|
+
*/
|
|
8
|
+
/** A species or breed: our own code, its plain text, and the local code system. */
|
|
9
|
+
interface AnimalCode {
|
|
10
|
+
/** Our own short code, for example `DOG` or `DOG-BGL`. */
|
|
11
|
+
readonly code: string;
|
|
12
|
+
/** The plain text, for example `Dog` or `Beagle`. */
|
|
13
|
+
readonly text: string;
|
|
14
|
+
/** Always {@link ANIMAL_CODE_SYSTEM}. */
|
|
15
|
+
readonly system: string;
|
|
16
|
+
}
|
|
17
|
+
/** A species and the breeds this package draws for it. */
|
|
18
|
+
interface AnimalSpecies extends AnimalCode {
|
|
19
|
+
/** The breeds of this species, at least one. */
|
|
20
|
+
readonly breeds: readonly AnimalCode[];
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* The local code system for species and breed codes, in the HL7 table 0396 form for a local
|
|
24
|
+
* system (`99` followed by letters). The codes under it are ours and name nothing outside this
|
|
25
|
+
* package.
|
|
26
|
+
*/
|
|
27
|
+
declare const ANIMAL_CODE_SYSTEM = "99COSYTE";
|
|
28
|
+
|
|
29
|
+
export { ANIMAL_CODE_SYSTEM as b };
|
|
30
|
+
export type { AnimalCode as A, AnimalSpecies as a };
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The two helpers that build the animal species and breed list in `./animal.ts`. They live in a
|
|
3
|
+
* module of their own, which the package does not export, so a test can call them directly and
|
|
4
|
+
* every mutant in them meets a test of its own.
|
|
5
|
+
*
|
|
6
|
+
* @module
|
|
7
|
+
*/
|
|
8
|
+
/** A species or breed: our own code, its plain text, and the local code system. */
|
|
9
|
+
interface AnimalCode {
|
|
10
|
+
/** Our own short code, for example `DOG` or `DOG-BGL`. */
|
|
11
|
+
readonly code: string;
|
|
12
|
+
/** The plain text, for example `Dog` or `Beagle`. */
|
|
13
|
+
readonly text: string;
|
|
14
|
+
/** Always {@link ANIMAL_CODE_SYSTEM}. */
|
|
15
|
+
readonly system: string;
|
|
16
|
+
}
|
|
17
|
+
/** A species and the breeds this package draws for it. */
|
|
18
|
+
interface AnimalSpecies extends AnimalCode {
|
|
19
|
+
/** The breeds of this species, at least one. */
|
|
20
|
+
readonly breeds: readonly AnimalCode[];
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* The local code system for species and breed codes, in the HL7 table 0396 form for a local
|
|
24
|
+
* system (`99` followed by letters). The codes under it are ours and name nothing outside this
|
|
25
|
+
* package.
|
|
26
|
+
*/
|
|
27
|
+
declare const ANIMAL_CODE_SYSTEM = "99COSYTE";
|
|
28
|
+
|
|
29
|
+
export { ANIMAL_CODE_SYSTEM as b };
|
|
30
|
+
export type { AnimalCode as A, AnimalSpecies as a };
|
package/dist/astm/index.cjs
CHANGED
|
@@ -73,7 +73,11 @@ var SYNTH_FATAL_CODES = Object.freeze({
|
|
|
73
73
|
SYNTH_INVALID_DECIMAL: "SYNTH_INVALID_DECIMAL",
|
|
74
74
|
/** An integer range was requested with its maximum below its minimum. Fatal. */
|
|
75
75
|
SYNTH_INVALID_RANGE: "SYNTH_INVALID_RANGE",
|
|
76
|
-
/**
|
|
76
|
+
/**
|
|
77
|
+
* A value was drawn from an empty pool. Fatal, never a fabricated substitute. A corpus given an
|
|
78
|
+
* empty `mix`, `triggers` or `quirks` list (or a profile whose `quirks` is an empty list) raises it
|
|
79
|
+
* too: there is no kind to cycle through, and an empty list is never read as the default.
|
|
80
|
+
*/
|
|
77
81
|
SYNTH_EMPTY_POOL: "SYNTH_EMPTY_POOL",
|
|
78
82
|
/** A `defineSynthProfile` spec was not usable (a missing or blank `name`). Fatal. */
|
|
79
83
|
SYNTH_INVALID_PROFILE: "SYNTH_INVALID_PROFILE",
|
|
@@ -92,7 +96,21 @@ var SYNTH_FATAL_CODES = Object.freeze({
|
|
|
92
96
|
* apart without matching message text. Fatal, and raised **before** anything is generated: a
|
|
93
97
|
* mislabelled artifact is worse than no artifact.
|
|
94
98
|
*/
|
|
95
|
-
SYNTH_PROFILE_NOT_GENERATED: "SYNTH_PROFILE_NOT_GENERATED"
|
|
99
|
+
SYNTH_PROFILE_NOT_GENERATED: "SYNTH_PROFILE_NOT_GENERATED",
|
|
100
|
+
/**
|
|
101
|
+
* The sibling parser a generator builds through is not installed. Raised on the first call that
|
|
102
|
+
* needs it, by both builds, only when the missing package is the parser itself: a missing
|
|
103
|
+
* dependency of an installed parser is thrown as Node.js raised it. The error carries the parser's
|
|
104
|
+
* package name on `peer`, a message from {@link SYNTH_PEER_MESSAGES} that names its install
|
|
105
|
+
* command, and the error Node.js raised on `cause`.
|
|
106
|
+
*/
|
|
107
|
+
SYNTH_PEER_NOT_INSTALLED: "SYNTH_PEER_NOT_INSTALLED",
|
|
108
|
+
/**
|
|
109
|
+
* An HL7 v2 `ACK` was asked for an inbound message that carries no MSH-10 message control id.
|
|
110
|
+
* MSA-2 must echo that id, and an acknowledgment that cannot be matched to the message it answers
|
|
111
|
+
* is not a fixture worth having, so nothing is generated.
|
|
112
|
+
*/
|
|
113
|
+
SYNTH_ACK_UNCORRELATED: "SYNTH_ACK_UNCORRELATED"
|
|
96
114
|
});
|
|
97
115
|
var SYNTH_FATAL_MESSAGES = Object.freeze({
|
|
98
116
|
SYNTH_UNSUPPORTED_FORMAT: "The requested format is not generable by this build. A generator has no byte fallback: it builds through a parser's own serializer or it refuses.",
|
|
@@ -105,18 +123,38 @@ var SYNTH_FATAL_MESSAGES = Object.freeze({
|
|
|
105
123
|
SYNTH_EMPTY_POOL: "A value was drawn from an empty pool.",
|
|
106
124
|
SYNTH_INVALID_PROFILE: "defineSynthProfile requires a non-empty string name.",
|
|
107
125
|
SYNTH_UNSUPPORTED_KIND: "The requested kind, document type, corpus mix entry, variant or profile is not one this generator supports. The supported set is the exported union for that option.",
|
|
108
|
-
SYNTH_PROFILE_NOT_GENERATED: "The requested profile is published by the adopted implementation guide, and this build does not generate it. The coverage surface reports, per adopted profile, whether it is generated."
|
|
126
|
+
SYNTH_PROFILE_NOT_GENERATED: "The requested profile is published by the adopted implementation guide, and this build does not generate it. The coverage surface reports, per adopted profile, whether it is generated.",
|
|
127
|
+
SYNTH_PEER_NOT_INSTALLED: "A sibling @cosyte parser this generator builds through is not installed. Install it beside @cosyte/synth.",
|
|
128
|
+
SYNTH_ACK_UNCORRELATED: (
|
|
129
|
+
// Stryker disable next-line all: a module-level table runs once, at import; the docs test holds it.
|
|
130
|
+
"The inbound message has no MSH-10 control id for an ACK to echo in MSA-2."
|
|
131
|
+
)
|
|
132
|
+
});
|
|
133
|
+
var SYNTH_PEER_MESSAGES = Object.freeze({
|
|
134
|
+
"@cosyte/hl7": "@cosyte/hl7 is not installed, and this generator builds through it. Install it beside @cosyte/synth: npm install @cosyte/hl7",
|
|
135
|
+
"@cosyte/fhir": "@cosyte/fhir is not installed, and this generator builds through it. Install it beside @cosyte/synth: npm install @cosyte/fhir",
|
|
136
|
+
"@cosyte/ccda": "@cosyte/ccda is not installed, and this generator builds through it. Install it beside @cosyte/synth: npm install @cosyte/ccda",
|
|
137
|
+
"@cosyte/x12": "@cosyte/x12 is not installed, and this generator builds through it. Install it beside @cosyte/synth: npm install @cosyte/x12",
|
|
138
|
+
"@cosyte/ncpdp": "@cosyte/ncpdp is not installed, and this generator builds through it. Install it beside @cosyte/synth: npm install @cosyte/ncpdp",
|
|
139
|
+
"@cosyte/astm": "@cosyte/astm is not installed, and this generator builds through it. Install it beside @cosyte/synth: npm install @cosyte/astm",
|
|
140
|
+
"@cosyte/deid": "@cosyte/deid is not installed, and this generator builds through it. Install it beside @cosyte/synth: npm install @cosyte/deid"
|
|
109
141
|
});
|
|
110
142
|
var SynthError = class extends Error {
|
|
111
143
|
/** The stable fatal code. */
|
|
112
144
|
code;
|
|
113
145
|
/**
|
|
114
146
|
* @param code - The stable {@link SynthFatalCode}. The message comes from the frozen registry.
|
|
147
|
+
* @param options - The error that caused this one, and for a missing parser its package.
|
|
115
148
|
*/
|
|
116
|
-
constructor(code) {
|
|
117
|
-
|
|
149
|
+
constructor(code, options = {}) {
|
|
150
|
+
const peer = code === SYNTH_FATAL_CODES.SYNTH_PEER_NOT_INSTALLED ? options.peer : void 0;
|
|
151
|
+
super(
|
|
152
|
+
peer === void 0 ? SYNTH_FATAL_MESSAGES[code] : SYNTH_PEER_MESSAGES[peer],
|
|
153
|
+
"cause" in options ? { cause: options.cause } : void 0
|
|
154
|
+
);
|
|
118
155
|
this.name = "SynthError";
|
|
119
156
|
this.code = code;
|
|
157
|
+
if (peer !== void 0) Object.defineProperty(this, "peer", { value: peer, enumerable: true });
|
|
120
158
|
}
|
|
121
159
|
};
|
|
122
160
|
|
|
@@ -224,7 +262,7 @@ function luhnMod10(digits) {
|
|
|
224
262
|
if (d < 0 || d > 9) continue;
|
|
225
263
|
if (double) {
|
|
226
264
|
d *= 2;
|
|
227
|
-
if (d
|
|
265
|
+
if (d >= 10) d -= 9;
|
|
228
266
|
}
|
|
229
267
|
sum += d;
|
|
230
268
|
double = !double;
|
|
@@ -321,7 +359,7 @@ var SYNTHETIC_CITY_NAMES = Object.freeze([
|
|
|
321
359
|
]);
|
|
322
360
|
|
|
323
361
|
// src/safe/providers.ts
|
|
324
|
-
function ssn(rng, block
|
|
362
|
+
function ssn(rng, block) {
|
|
325
363
|
if (block === "advertising") {
|
|
326
364
|
return `987-00-432${String(rng.int(0, 9))}`;
|
|
327
365
|
}
|
|
@@ -448,6 +486,63 @@ var US_STATES = Object.freeze([
|
|
|
448
486
|
"WY"
|
|
449
487
|
]);
|
|
450
488
|
|
|
489
|
+
// src/safe/animal-code.ts
|
|
490
|
+
var ANIMAL_CODE_SYSTEM = "99COSYTE";
|
|
491
|
+
function animalCode(value, text) {
|
|
492
|
+
return Object.freeze({ code: value, text, system: ANIMAL_CODE_SYSTEM });
|
|
493
|
+
}
|
|
494
|
+
function animalSpecies(value, text, breeds) {
|
|
495
|
+
return Object.freeze({ ...animalCode(value, text), breeds: Object.freeze([...breeds]) });
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
// src/safe/animal.ts
|
|
499
|
+
var SYNTHETIC_ANIMAL_SPECIES = Object.freeze([
|
|
500
|
+
animalSpecies("DOG", "Dog", [
|
|
501
|
+
animalCode("DOG-LAB", "Labrador Retriever"),
|
|
502
|
+
animalCode("DOG-BGL", "Beagle"),
|
|
503
|
+
animalCode("DOG-MIX", "Mixed breed dog")
|
|
504
|
+
]),
|
|
505
|
+
animalSpecies("CAT", "Cat", [animalCode("CAT-DSH", "Domestic Shorthair"), animalCode("CAT-SIA", "Siamese")]),
|
|
506
|
+
animalSpecies("HORSE", "Horse", [animalCode("HRS-QTR", "Quarter Horse"), animalCode("HRS-TB", "Thoroughbred")]),
|
|
507
|
+
animalSpecies("CATTLE", "Cattle", [animalCode("CTL-HOL", "Holstein"), animalCode("CTL-ANG", "Angus")])
|
|
508
|
+
]);
|
|
509
|
+
var SYNTHETIC_PET_NAMES = Object.freeze([
|
|
510
|
+
"Fixturepaw",
|
|
511
|
+
"Mockwhisker",
|
|
512
|
+
"Stubtail",
|
|
513
|
+
"Seedpup",
|
|
514
|
+
"Testbiscuit",
|
|
515
|
+
"Placepurr",
|
|
516
|
+
"Synthfur",
|
|
517
|
+
"Dummymane",
|
|
518
|
+
"Examplehoof",
|
|
519
|
+
"Fauxfluff",
|
|
520
|
+
"Samplesnout",
|
|
521
|
+
"Voidwag"
|
|
522
|
+
]);
|
|
523
|
+
var MICROCHIP_TEST_PREFIX = "999";
|
|
524
|
+
function petName(rng) {
|
|
525
|
+
return rng.pick(SYNTHETIC_PET_NAMES);
|
|
526
|
+
}
|
|
527
|
+
function animalBreed(rng) {
|
|
528
|
+
const picked = rng.pick(SYNTHETIC_ANIMAL_SPECIES);
|
|
529
|
+
const { breeds, ...bare } = picked;
|
|
530
|
+
return { species: Object.freeze(bare), breed: rng.pick(breeds) };
|
|
531
|
+
}
|
|
532
|
+
function microchip(rng) {
|
|
533
|
+
return `${MICROCHIP_TEST_PREFIX}${rng.digits(12)}`;
|
|
534
|
+
}
|
|
535
|
+
function animalBirthDate(rng, onDate) {
|
|
536
|
+
const year = Number(onDate.slice(0, 4));
|
|
537
|
+
const month = Number(onDate.slice(4, 6)) - 1;
|
|
538
|
+
const day = Number(onDate.slice(6, 8));
|
|
539
|
+
const latest = Date.UTC(year, month, day);
|
|
540
|
+
const earliest = Date.UTC(year - 20, month, day);
|
|
541
|
+
const span = Math.round((latest - earliest) / 864e5);
|
|
542
|
+
const born = new Date(latest - rng.int(0, span) * 864e5);
|
|
543
|
+
return born.toISOString().slice(0, 10).replace(/-/g, "");
|
|
544
|
+
}
|
|
545
|
+
|
|
451
546
|
// src/safe/index.ts
|
|
452
547
|
var safe = Object.freeze({
|
|
453
548
|
ssn,
|
|
@@ -461,7 +556,11 @@ var safe = Object.freeze({
|
|
|
461
556
|
address,
|
|
462
557
|
dateYmd,
|
|
463
558
|
npi,
|
|
464
|
-
dea
|
|
559
|
+
dea,
|
|
560
|
+
petName,
|
|
561
|
+
animalBreed,
|
|
562
|
+
microchip,
|
|
563
|
+
animalBirthDate
|
|
465
564
|
});
|
|
466
565
|
|
|
467
566
|
// src/astm/identity.ts
|
|
@@ -600,19 +699,44 @@ function defaultHost() {
|
|
|
600
699
|
if (typeof undefined !== "function") return { require: __require, resolve: void 0 };
|
|
601
700
|
return { require: module$1.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href))), resolve: (s) => undefined(s) };
|
|
602
701
|
}
|
|
702
|
+
function isDenoResolveRefusal(error) {
|
|
703
|
+
return error instanceof TypeError && !("code" in error) && error.message === "Invalid arguments";
|
|
704
|
+
}
|
|
603
705
|
function isRequireEsmRefusal(error) {
|
|
604
706
|
return error?.code === "ERR_REQUIRE_ESM";
|
|
605
707
|
}
|
|
708
|
+
function peerPackage(specifier) {
|
|
709
|
+
const name2 = specifier.split("/").slice(0, 2).join("/");
|
|
710
|
+
return Object.hasOwn(SYNTH_PEER_MESSAGES, name2) ? name2 : void 0;
|
|
711
|
+
}
|
|
712
|
+
function isPeerNotFound(error, specifier, peer) {
|
|
713
|
+
const { code, message } = error ?? {};
|
|
714
|
+
if (typeof message !== "string") return false;
|
|
715
|
+
if (code === "ERR_MODULE_NOT_FOUND") return message.startsWith(`Cannot find package '${peer}'`);
|
|
716
|
+
if (code === "MODULE_NOT_FOUND") return message.startsWith(`Cannot find module '${specifier}'`);
|
|
717
|
+
return false;
|
|
718
|
+
}
|
|
606
719
|
function loadPeer(specifier, host) {
|
|
720
|
+
try {
|
|
721
|
+
return loadFrom(specifier, host);
|
|
722
|
+
} catch (error) {
|
|
723
|
+
const peer = peerPackage(specifier);
|
|
724
|
+
if (peer === void 0 || !isPeerNotFound(error, specifier, peer)) throw error;
|
|
725
|
+
throw new SynthError(SYNTH_FATAL_CODES.SYNTH_PEER_NOT_INSTALLED, { peer, cause: error });
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
function loadFrom(specifier, host) {
|
|
607
729
|
if (host.resolve === void 0) return host.require(specifier);
|
|
608
|
-
let
|
|
730
|
+
let url$1;
|
|
609
731
|
try {
|
|
610
|
-
|
|
611
|
-
} catch {
|
|
732
|
+
url$1 = host.resolve(specifier);
|
|
733
|
+
} catch (error) {
|
|
734
|
+
if (!isDenoResolveRefusal(error)) throw error;
|
|
612
735
|
return host.require(specifier);
|
|
613
736
|
}
|
|
737
|
+
if (!url$1.startsWith("file:")) return host.require(specifier);
|
|
614
738
|
try {
|
|
615
|
-
return host.require(
|
|
739
|
+
return host.require(url.fileURLToPath(url$1));
|
|
616
740
|
} catch (error) {
|
|
617
741
|
if (!isRequireEsmRefusal(error)) throw error;
|
|
618
742
|
return host.require(specifier);
|
|
@@ -735,6 +859,10 @@ function resolveMix(allowed, requested, fallback) {
|
|
|
735
859
|
if (requested === void 0) return fallback;
|
|
736
860
|
return requested.map((entry) => resolveKind(allowed, entry));
|
|
737
861
|
}
|
|
862
|
+
function requireSelection(selection) {
|
|
863
|
+
if (selection.length === 0) throw new SynthError(SYNTH_FATAL_CODES.SYNTH_EMPTY_POOL);
|
|
864
|
+
return selection;
|
|
865
|
+
}
|
|
738
866
|
|
|
739
867
|
// src/profile.ts
|
|
740
868
|
function defineSynthProfile(spec) {
|
|
@@ -745,7 +873,7 @@ function defineSynthProfile(spec) {
|
|
|
745
873
|
name: spec.name,
|
|
746
874
|
...spec.givenNames ? { givenNames: Object.freeze([...spec.givenNames]) } : {},
|
|
747
875
|
...spec.familyNames ? { familyNames: Object.freeze([...spec.familyNames]) } : {},
|
|
748
|
-
quirks: Object.freeze([...spec.quirks
|
|
876
|
+
...spec.quirks !== void 0 ? { quirks: Object.freeze([...spec.quirks]) } : {}
|
|
749
877
|
});
|
|
750
878
|
}
|
|
751
879
|
|
|
@@ -787,6 +915,7 @@ function assertIntendedWarnings(intendedWarnings, bareWarnings) {
|
|
|
787
915
|
}
|
|
788
916
|
}
|
|
789
917
|
function validateProfileQuirks(profile, registry, format) {
|
|
918
|
+
if (profile.quirks === void 0) return Object.keys(registry);
|
|
790
919
|
for (const name2 of profile.quirks) resolveQuirk(registry, format, name2);
|
|
791
920
|
return profile.quirks;
|
|
792
921
|
}
|
|
@@ -895,8 +1024,9 @@ var ALL_ASTM_QUIRKS = Object.freeze(
|
|
|
895
1024
|
Object.keys(ASTM_QUIRKS)
|
|
896
1025
|
);
|
|
897
1026
|
function astmQuirkCorpus(options) {
|
|
898
|
-
const
|
|
899
|
-
|
|
1027
|
+
const names = requireSelection(
|
|
1028
|
+
options.profile?.quirks !== void 0 ? validateProfileQuirks(options.profile, ASTM_QUIRKS, "astm") : options.quirks ?? ALL_ASTM_QUIRKS
|
|
1029
|
+
);
|
|
900
1030
|
for (const name2 of names) resolveQuirk(ASTM_QUIRKS, "astm", name2);
|
|
901
1031
|
const count = options.count ?? names.length;
|
|
902
1032
|
const seedStream = createRng(options.seed);
|
|
@@ -931,11 +1061,11 @@ function generateKind(kind, seed, verify) {
|
|
|
931
1061
|
}
|
|
932
1062
|
function astmCorpus(options) {
|
|
933
1063
|
const { seed, verify = true } = options;
|
|
934
|
-
const mix = resolveMix(ALL_KINDS, options.mix, DEFAULT_MIX);
|
|
1064
|
+
const mix = requireSelection(resolveMix(ALL_KINDS, options.mix, DEFAULT_MIX));
|
|
935
1065
|
const count = options.count ?? mix.length;
|
|
936
1066
|
const seedStream = createRng(seed);
|
|
937
1067
|
const artifacts = Array.from({ length: count }, (_unused, i) => {
|
|
938
|
-
const kind = mix[i % mix.length]
|
|
1068
|
+
const kind = mix[i % mix.length];
|
|
939
1069
|
const msgSeed = seedStream.nextUint32();
|
|
940
1070
|
const rt = generateKind(kind, msgSeed, verify);
|
|
941
1071
|
return {
|