@cosyte/synth 0.0.3 → 0.0.4
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 +297 -0
- package/README.md +6 -2
- package/dist/astm/index.cjs +83 -42
- package/dist/astm/index.cjs.map +1 -1
- package/dist/astm/index.d.cts +1 -1
- package/dist/astm/index.d.ts +1 -1
- package/dist/astm/index.mjs +83 -42
- package/dist/astm/index.mjs.map +1 -1
- package/dist/ccda/index.cjs +89 -44
- package/dist/ccda/index.cjs.map +1 -1
- package/dist/ccda/index.d.cts +5 -3
- package/dist/ccda/index.d.ts +5 -3
- package/dist/ccda/index.mjs +89 -44
- package/dist/ccda/index.mjs.map +1 -1
- package/dist/deid/index.cjs +92 -13
- package/dist/deid/index.cjs.map +1 -1
- package/dist/deid/index.d.cts +1 -1
- package/dist/deid/index.d.ts +1 -1
- package/dist/deid/index.mjs +93 -14
- package/dist/deid/index.mjs.map +1 -1
- package/dist/fhir/index.cjs +78 -5
- package/dist/fhir/index.cjs.map +1 -1
- package/dist/fhir/index.mjs +78 -5
- package/dist/fhir/index.mjs.map +1 -1
- package/dist/hl7/index.cjs +89 -41
- package/dist/hl7/index.cjs.map +1 -1
- package/dist/hl7/index.d.cts +1 -1
- package/dist/hl7/index.d.ts +1 -1
- package/dist/hl7/index.mjs +89 -41
- package/dist/hl7/index.mjs.map +1 -1
- package/dist/index.cjs +91 -40
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +126 -8
- package/dist/index.d.ts +126 -8
- package/dist/index.mjs +89 -41
- package/dist/index.mjs.map +1 -1
- package/dist/ncpdp/index.cjs +56 -4
- package/dist/ncpdp/index.cjs.map +1 -1
- package/dist/ncpdp/index.mjs +56 -4
- package/dist/ncpdp/index.mjs.map +1 -1
- package/dist/{quirk-JLyO1Ncj.d.ts → quirk-C9t9CkPS.d.ts} +17 -6
- package/dist/{quirk-DmkgoZdh.d.cts → quirk-DYMDojVw.d.cts} +17 -6
- package/dist/x12/index.cjs +82 -13
- package/dist/x12/index.cjs.map +1 -1
- package/dist/x12/index.d.cts +2 -1
- package/dist/x12/index.d.ts +2 -1
- package/dist/x12/index.mjs +83 -14
- package/dist/x12/index.mjs.map +1 -1
- package/package.json +2 -2
|
@@ -39,7 +39,7 @@ interface SynthProfile {
|
|
|
39
39
|
*
|
|
40
40
|
* @param spec - The profile spec; `name` is required and non-empty.
|
|
41
41
|
* @returns A deep-frozen {@link SynthProfile}.
|
|
42
|
-
* @throws
|
|
42
|
+
* @throws SynthError `SYNTH_INVALID_PROFILE` when `name` is missing or blank.
|
|
43
43
|
* @example
|
|
44
44
|
* ```ts
|
|
45
45
|
* import { defineSynthProfile } from "@cosyte/synth";
|
|
@@ -167,8 +167,14 @@ declare function sameCodeSet(a: readonly string[], b: readonly string[]): boolea
|
|
|
167
167
|
* profile system does not support is a fatal `SYNTH_UNSUPPORTED_QUIRK` — never a silent no-op and never
|
|
168
168
|
* a fabricated quirk with a made-up warning.
|
|
169
169
|
*
|
|
170
|
+
* The refusal names neither the request nor the registry. `registry`, `format` and `name` are all
|
|
171
|
+
* caller-supplied, and a diagnostic that quotes its input is a diagnostic that can be made to carry
|
|
172
|
+
* anything the caller was holding — which for a fixture generator wired into someone else's pipeline
|
|
173
|
+
* is not a hypothetical. Branch on `err.code`; the supported set is the registry you passed
|
|
174
|
+
* (`HL7_QUIRKS`, `CCDA_QUIRKS`, `ASTM_QUIRKS`), which you can enumerate directly.
|
|
175
|
+
*
|
|
170
176
|
* @param registry - The format's quirk descriptors, keyed by name.
|
|
171
|
-
* @param format - The format being generated
|
|
177
|
+
* @param format - The format being generated.
|
|
172
178
|
* @param name - The requested quirk name.
|
|
173
179
|
* @returns The matching {@link QuirkDescriptor}.
|
|
174
180
|
* @throws SynthError with code `SYNTH_UNSUPPORTED_QUIRK` when `name` is not a supported quirk.
|
|
@@ -205,17 +211,22 @@ declare function profileTolerated(disposition: QuirkProfileDisposition, intended
|
|
|
205
211
|
* warning. Every format's `generate*Quirk` calls this after transforming, so the contract is enforced at
|
|
206
212
|
* generation time, not merely at round-trip time.
|
|
207
213
|
*
|
|
208
|
-
*
|
|
214
|
+
* It no longer takes the quirk name. That parameter existed for one reason — to be interpolated into
|
|
215
|
+
* the refusal — and a parameter whose only job is to reach a message is the exact shape this package
|
|
216
|
+
* is removing, so it is gone rather than merely unused. The refusal names neither code list either;
|
|
217
|
+
* both are caller-supplied, and the caller reads the comparison back off the arguments it holds.
|
|
218
|
+
*
|
|
209
219
|
* @param intendedWarnings - The declared intended code(s).
|
|
210
220
|
* @param bareWarnings - The code(s) a bare parse of the generated artifact actually produced.
|
|
211
|
-
* @throws
|
|
221
|
+
* @throws SynthError `SYNTH_INTENDED_WARNING_MISMATCH` when the bare parse did not produce exactly
|
|
222
|
+
* the intended code(s).
|
|
212
223
|
* @example
|
|
213
224
|
* ```ts
|
|
214
225
|
* import { assertIntendedWarnings } from "@cosyte/synth";
|
|
215
|
-
* assertIntendedWarnings(
|
|
226
|
+
* assertIntendedWarnings(["UNKNOWN_SEGMENT"], ["UNKNOWN_SEGMENT"]); // ok
|
|
216
227
|
* ```
|
|
217
228
|
*/
|
|
218
|
-
declare function assertIntendedWarnings(
|
|
229
|
+
declare function assertIntendedWarnings(intendedWarnings: readonly string[], bareWarnings: readonly string[]): void;
|
|
219
230
|
/**
|
|
220
231
|
* Validate the quirk names carried by a {@link SynthProfile} against a format's registry, failing closed
|
|
221
232
|
* on the first unsupported one. Lets a consumer author a fixture recipe with `defineSynthProfile` and
|
package/dist/x12/index.cjs
CHANGED
|
@@ -31,6 +31,47 @@ function sfc32Next(s) {
|
|
|
31
31
|
return t >>> 0;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
// src/codes.ts
|
|
35
|
+
var SYNTH_FATAL_CODES = {
|
|
36
|
+
/** A money value could not be read as an X12 decimal. Fatal — a generator never rounds to a float. */
|
|
37
|
+
SYNTH_INVALID_DECIMAL: "SYNTH_INVALID_DECIMAL",
|
|
38
|
+
/** An integer range was requested with its maximum below its minimum. Fatal. */
|
|
39
|
+
SYNTH_INVALID_RANGE: "SYNTH_INVALID_RANGE",
|
|
40
|
+
/** A value was drawn from an empty pool. Fatal — never a fabricated substitute. */
|
|
41
|
+
SYNTH_EMPTY_POOL: "SYNTH_EMPTY_POOL",
|
|
42
|
+
/**
|
|
43
|
+
* A caller-supplied selector (a message kind, a document type, a corpus mix entry, a claim
|
|
44
|
+
* variant, a Bundle type, a resource profile) is not in the closed set that governs it. Fatal —
|
|
45
|
+
* see `resolveKind`: a selector union is erased at run time, and a selector that falls through
|
|
46
|
+
* either mislabels the fixture or hands the value to a peer builder that quotes it back.
|
|
47
|
+
*/
|
|
48
|
+
SYNTH_UNSUPPORTED_KIND: "SYNTH_UNSUPPORTED_KIND"
|
|
49
|
+
};
|
|
50
|
+
var SYNTH_FATAL_MESSAGES = Object.freeze({
|
|
51
|
+
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.",
|
|
52
|
+
SYNTH_UNSUPPORTED_QUIRK: "The requested vendor quirk is not in the target format's quirk registry. Compare the request against that format's exported registry (HL7_QUIRKS, CCDA_QUIRKS, ASTM_QUIRKS).",
|
|
53
|
+
SYNTH_QUIRK_ANCHOR_ABSENT: "The quirk transform found no structural anchor to mutate, so the fixture would not carry the deviation it is labelled with. Refusing to emit a mislabeled fixture.",
|
|
54
|
+
SYNTH_INTENDED_WARNING_MISMATCH: "A bare parse of the generated quirk artifact did not produce exactly the declared intended warning code(s). Refusing to emit a mislabeled fixture.",
|
|
55
|
+
SYNTH_UNMAPPED_CODE_SYSTEM: "The concept's code-system URI has no OID mapping in the C-CDA example-code table.",
|
|
56
|
+
SYNTH_INVALID_DECIMAL: "The value could not be read as an X12 decimal.",
|
|
57
|
+
SYNTH_INVALID_RANGE: "An integer range was requested with its maximum below its minimum.",
|
|
58
|
+
SYNTH_EMPTY_POOL: "A value was drawn from an empty pool.",
|
|
59
|
+
SYNTH_INVALID_PROFILE: "defineSynthProfile requires a non-empty string name.",
|
|
60
|
+
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."
|
|
61
|
+
});
|
|
62
|
+
var SynthError = class extends Error {
|
|
63
|
+
/** The stable fatal code. */
|
|
64
|
+
code;
|
|
65
|
+
/**
|
|
66
|
+
* @param code - The stable {@link SynthFatalCode}. The message comes from the frozen registry.
|
|
67
|
+
*/
|
|
68
|
+
constructor(code) {
|
|
69
|
+
super(SYNTH_FATAL_MESSAGES[code]);
|
|
70
|
+
this.name = "SynthError";
|
|
71
|
+
this.code = code;
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
|
|
34
75
|
// src/rng/rng.ts
|
|
35
76
|
var Sfc32Rng = class {
|
|
36
77
|
seed;
|
|
@@ -48,7 +89,7 @@ var Sfc32Rng = class {
|
|
|
48
89
|
return this.nextUint32() / 4294967296;
|
|
49
90
|
}
|
|
50
91
|
int(min, max) {
|
|
51
|
-
if (max < min) throw new
|
|
92
|
+
if (max < min) throw new SynthError(SYNTH_FATAL_CODES.SYNTH_INVALID_RANGE);
|
|
52
93
|
const span = max - min + 1;
|
|
53
94
|
return min + Math.floor(this.float() * span);
|
|
54
95
|
}
|
|
@@ -56,7 +97,7 @@ var Sfc32Rng = class {
|
|
|
56
97
|
return this.float() < p;
|
|
57
98
|
}
|
|
58
99
|
pick(items) {
|
|
59
|
-
if (items.length === 0) throw new
|
|
100
|
+
if (items.length === 0) throw new SynthError(SYNTH_FATAL_CODES.SYNTH_EMPTY_POOL);
|
|
60
101
|
return items[this.int(0, items.length - 1)];
|
|
61
102
|
}
|
|
62
103
|
digits(n) {
|
|
@@ -91,7 +132,7 @@ function makeCorpus(seed, artifacts, quirks = []) {
|
|
|
91
132
|
}
|
|
92
133
|
function dec(value) {
|
|
93
134
|
const d = x12.X12Decimal.fromString(value);
|
|
94
|
-
if (d === void 0) throw new
|
|
135
|
+
if (d === void 0) throw new SynthError(SYNTH_FATAL_CODES.SYNTH_INVALID_DECIMAL);
|
|
95
136
|
return d;
|
|
96
137
|
}
|
|
97
138
|
function money(n) {
|
|
@@ -469,7 +510,19 @@ var TOOTH_SURFACES = Object.freeze(["M", "O", "D", "B", "L"]);
|
|
|
469
510
|
var CARC_CODES = Object.freeze(["1", "2", "3", "45", "96", "197"]);
|
|
470
511
|
var SERVICE_TYPE_CODES = Object.freeze(["30", "1", "35", "88", "98"]);
|
|
471
512
|
|
|
513
|
+
// src/select.ts
|
|
514
|
+
function resolveKind(allowed, requested) {
|
|
515
|
+
const match = allowed.find((value) => value === requested);
|
|
516
|
+
if (match === void 0) throw new SynthError(SYNTH_FATAL_CODES.SYNTH_UNSUPPORTED_KIND);
|
|
517
|
+
return match;
|
|
518
|
+
}
|
|
519
|
+
function resolveMix(allowed, requested, fallback) {
|
|
520
|
+
if (requested === void 0) return fallback;
|
|
521
|
+
return requested.map((entry) => resolveKind(allowed, entry));
|
|
522
|
+
}
|
|
523
|
+
|
|
472
524
|
// src/x12/claim-837.ts
|
|
525
|
+
var CLAIM_837_VARIANTS = Object.freeze(["P", "I", "D"]);
|
|
473
526
|
function serviceDtp(serviceDate) {
|
|
474
527
|
return { qualifier: "472", formatQualifier: "D8", value: serviceDate };
|
|
475
528
|
}
|
|
@@ -652,11 +705,17 @@ function specOf(rng, variant) {
|
|
|
652
705
|
};
|
|
653
706
|
}
|
|
654
707
|
function generate837(variant, options) {
|
|
708
|
+
const resolved = resolveKind(CLAIM_837_VARIANTS, variant);
|
|
655
709
|
const rng = createRng(options.seed);
|
|
656
|
-
const spec = specOf(rng,
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
710
|
+
const spec = specOf(rng, resolved);
|
|
711
|
+
switch (resolved) {
|
|
712
|
+
case "P":
|
|
713
|
+
return x12.build837P(spec);
|
|
714
|
+
case "I":
|
|
715
|
+
return x12.build837I(spec);
|
|
716
|
+
case "D":
|
|
717
|
+
return x12.build837D(spec);
|
|
718
|
+
}
|
|
660
719
|
}
|
|
661
720
|
function generate837P(options) {
|
|
662
721
|
return generate837("P", options);
|
|
@@ -864,15 +923,25 @@ function roundTrip(interchange) {
|
|
|
864
923
|
}
|
|
865
924
|
|
|
866
925
|
// src/x12/index.ts
|
|
867
|
-
var
|
|
926
|
+
var ALL_KINDS = Object.freeze(["837P", "837I", "837D", "835", "271"]);
|
|
927
|
+
var DEFAULT_MIX = ALL_KINDS;
|
|
868
928
|
function generateKind(kind, seed) {
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
929
|
+
switch (kind) {
|
|
930
|
+
case "835":
|
|
931
|
+
return roundTrip(generate835({ seed }));
|
|
932
|
+
case "271":
|
|
933
|
+
return roundTrip(generate271({ seed }));
|
|
934
|
+
case "837P":
|
|
935
|
+
return roundTrip(generate837("P", { seed }));
|
|
936
|
+
case "837I":
|
|
937
|
+
return roundTrip(generate837("I", { seed }));
|
|
938
|
+
case "837D":
|
|
939
|
+
return roundTrip(generate837("D", { seed }));
|
|
940
|
+
}
|
|
873
941
|
}
|
|
874
942
|
function x12Corpus(options) {
|
|
875
|
-
const { seed
|
|
943
|
+
const { seed } = options;
|
|
944
|
+
const mix = resolveMix(ALL_KINDS, options.mix, DEFAULT_MIX);
|
|
876
945
|
const count = options.count ?? mix.length;
|
|
877
946
|
const seedStream = createRng(seed);
|
|
878
947
|
const artifacts = Array.from({ length: count }, (_unused, i) => {
|