@cosyte/synth 0.0.2 → 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 +321 -0
- package/README.md +13 -4
- 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
package/dist/ccda/index.cjs
CHANGED
|
@@ -31,6 +31,65 @@ function sfc32Next(s) {
|
|
|
31
31
|
return t >>> 0;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
// src/codes.ts
|
|
35
|
+
var SYNTH_FATAL_CODES = {
|
|
36
|
+
/**
|
|
37
|
+
* A vendor quirk was requested that the target format's profile system does not support. Fatal —
|
|
38
|
+
* never a silent no-op and never a fabricated quirk.
|
|
39
|
+
*/
|
|
40
|
+
SYNTH_UNSUPPORTED_QUIRK: "SYNTH_UNSUPPORTED_QUIRK",
|
|
41
|
+
/**
|
|
42
|
+
* A quirk transform found no structural anchor to mutate, so the fixture would not carry the
|
|
43
|
+
* deviation it is labelled with. Fatal — a golden file that lies about its parser verdict is worse
|
|
44
|
+
* than no golden file.
|
|
45
|
+
*/
|
|
46
|
+
SYNTH_QUIRK_ANCHOR_ABSENT: "SYNTH_QUIRK_ANCHOR_ABSENT",
|
|
47
|
+
/**
|
|
48
|
+
* A bare parse of a freshly-generated quirk artifact did not produce exactly the declared intended
|
|
49
|
+
* warning code(s). Fatal — never emit a mislabeled fixture.
|
|
50
|
+
*/
|
|
51
|
+
SYNTH_INTENDED_WARNING_MISMATCH: "SYNTH_INTENDED_WARNING_MISMATCH",
|
|
52
|
+
/** A concept's code-system URI has no OID mapping in the C-CDA example-code table. Fatal. */
|
|
53
|
+
SYNTH_UNMAPPED_CODE_SYSTEM: "SYNTH_UNMAPPED_CODE_SYSTEM",
|
|
54
|
+
/** An integer range was requested with its maximum below its minimum. Fatal. */
|
|
55
|
+
SYNTH_INVALID_RANGE: "SYNTH_INVALID_RANGE",
|
|
56
|
+
/** A value was drawn from an empty pool. Fatal — never a fabricated substitute. */
|
|
57
|
+
SYNTH_EMPTY_POOL: "SYNTH_EMPTY_POOL",
|
|
58
|
+
/** A `defineSynthProfile` spec was not usable (a missing or blank `name`). Fatal. */
|
|
59
|
+
SYNTH_INVALID_PROFILE: "SYNTH_INVALID_PROFILE",
|
|
60
|
+
/**
|
|
61
|
+
* A caller-supplied selector (a message kind, a document type, a corpus mix entry, a claim
|
|
62
|
+
* variant, a Bundle type, a resource profile) is not in the closed set that governs it. Fatal —
|
|
63
|
+
* see `resolveKind`: a selector union is erased at run time, and a selector that falls through
|
|
64
|
+
* either mislabels the fixture or hands the value to a peer builder that quotes it back.
|
|
65
|
+
*/
|
|
66
|
+
SYNTH_UNSUPPORTED_KIND: "SYNTH_UNSUPPORTED_KIND"
|
|
67
|
+
};
|
|
68
|
+
var SYNTH_FATAL_MESSAGES = Object.freeze({
|
|
69
|
+
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.",
|
|
70
|
+
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).",
|
|
71
|
+
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.",
|
|
72
|
+
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.",
|
|
73
|
+
SYNTH_UNMAPPED_CODE_SYSTEM: "The concept's code-system URI has no OID mapping in the C-CDA example-code table.",
|
|
74
|
+
SYNTH_INVALID_DECIMAL: "The value could not be read as an X12 decimal.",
|
|
75
|
+
SYNTH_INVALID_RANGE: "An integer range was requested with its maximum below its minimum.",
|
|
76
|
+
SYNTH_EMPTY_POOL: "A value was drawn from an empty pool.",
|
|
77
|
+
SYNTH_INVALID_PROFILE: "defineSynthProfile requires a non-empty string name.",
|
|
78
|
+
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."
|
|
79
|
+
});
|
|
80
|
+
var SynthError = class extends Error {
|
|
81
|
+
/** The stable fatal code. */
|
|
82
|
+
code;
|
|
83
|
+
/**
|
|
84
|
+
* @param code - The stable {@link SynthFatalCode}. The message comes from the frozen registry.
|
|
85
|
+
*/
|
|
86
|
+
constructor(code) {
|
|
87
|
+
super(SYNTH_FATAL_MESSAGES[code]);
|
|
88
|
+
this.name = "SynthError";
|
|
89
|
+
this.code = code;
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
|
|
34
93
|
// src/rng/rng.ts
|
|
35
94
|
var Sfc32Rng = class {
|
|
36
95
|
seed;
|
|
@@ -48,7 +107,7 @@ var Sfc32Rng = class {
|
|
|
48
107
|
return this.nextUint32() / 4294967296;
|
|
49
108
|
}
|
|
50
109
|
int(min, max) {
|
|
51
|
-
if (max < min) throw new
|
|
110
|
+
if (max < min) throw new SynthError(SYNTH_FATAL_CODES.SYNTH_INVALID_RANGE);
|
|
52
111
|
const span = max - min + 1;
|
|
53
112
|
return min + Math.floor(this.float() * span);
|
|
54
113
|
}
|
|
@@ -56,7 +115,7 @@ var Sfc32Rng = class {
|
|
|
56
115
|
return this.float() < p;
|
|
57
116
|
}
|
|
58
117
|
pick(items) {
|
|
59
|
-
if (items.length === 0) throw new
|
|
118
|
+
if (items.length === 0) throw new SynthError(SYNTH_FATAL_CODES.SYNTH_EMPTY_POOL);
|
|
60
119
|
return items[this.int(0, items.length - 1)];
|
|
61
120
|
}
|
|
62
121
|
digits(n) {
|
|
@@ -673,7 +732,7 @@ var URI_TO_OID = Object.freeze({
|
|
|
673
732
|
function toBuildCode(concept) {
|
|
674
733
|
const codeSystem = URI_TO_OID[concept.system];
|
|
675
734
|
if (codeSystem === void 0) {
|
|
676
|
-
throw new
|
|
735
|
+
throw new SynthError(SYNTH_FATAL_CODES.SYNTH_UNMAPPED_CODE_SYSTEM);
|
|
677
736
|
}
|
|
678
737
|
return { code: concept.code, codeSystem, displayName: concept.display };
|
|
679
738
|
}
|
|
@@ -730,7 +789,19 @@ function ccdaPatientIdentity(rng) {
|
|
|
730
789
|
return { patient, person, mrn };
|
|
731
790
|
}
|
|
732
791
|
|
|
792
|
+
// src/select.ts
|
|
793
|
+
function resolveKind(allowed, requested) {
|
|
794
|
+
const match = allowed.find((value) => value === requested);
|
|
795
|
+
if (match === void 0) throw new SynthError(SYNTH_FATAL_CODES.SYNTH_UNSUPPORTED_KIND);
|
|
796
|
+
return match;
|
|
797
|
+
}
|
|
798
|
+
function resolveMix(allowed, requested, fallback) {
|
|
799
|
+
if (requested === void 0) return fallback;
|
|
800
|
+
return requested.map((entry) => resolveKind(allowed, entry));
|
|
801
|
+
}
|
|
802
|
+
|
|
733
803
|
// src/ccda/ccd.ts
|
|
804
|
+
var CCDA_DOCUMENT_TYPES = Object.freeze(["ccd", "referralNote"]);
|
|
734
805
|
function pickN(rng, pool, n) {
|
|
735
806
|
const take = Math.min(n, pool.length);
|
|
736
807
|
const indices = pool.map((_v, i) => i);
|
|
@@ -813,7 +884,8 @@ function buildInit(rng, documentType) {
|
|
|
813
884
|
return base;
|
|
814
885
|
}
|
|
815
886
|
function generateCcda(options = {}) {
|
|
816
|
-
const { seed = 0
|
|
887
|
+
const { seed = 0 } = options;
|
|
888
|
+
const documentType = resolveKind(CCDA_DOCUMENT_TYPES, options.documentType ?? "ccd");
|
|
817
889
|
const rng = createRng(seed);
|
|
818
890
|
return ccda.buildCcda(buildInit(rng, documentType));
|
|
819
891
|
}
|
|
@@ -839,7 +911,7 @@ function roundTrip(doc) {
|
|
|
839
911
|
// src/profile.ts
|
|
840
912
|
function defineSynthProfile(spec) {
|
|
841
913
|
if (typeof spec.name !== "string" || spec.name.trim().length === 0) {
|
|
842
|
-
throw new
|
|
914
|
+
throw new SynthError(SYNTH_FATAL_CODES.SYNTH_INVALID_PROFILE);
|
|
843
915
|
}
|
|
844
916
|
return Object.freeze({
|
|
845
917
|
name: spec.name,
|
|
@@ -849,28 +921,6 @@ function defineSynthProfile(spec) {
|
|
|
849
921
|
});
|
|
850
922
|
}
|
|
851
923
|
|
|
852
|
-
// src/codes.ts
|
|
853
|
-
var SYNTH_FATAL_CODES = {
|
|
854
|
-
/**
|
|
855
|
-
* A vendor quirk was requested that the target format's profile system does not support. Fatal —
|
|
856
|
-
* never a silent no-op and never a fabricated quirk.
|
|
857
|
-
*/
|
|
858
|
-
SYNTH_UNSUPPORTED_QUIRK: "SYNTH_UNSUPPORTED_QUIRK"
|
|
859
|
-
};
|
|
860
|
-
var SynthError = class extends Error {
|
|
861
|
-
/** The stable fatal code. */
|
|
862
|
-
code;
|
|
863
|
-
/**
|
|
864
|
-
* @param code - The stable {@link SynthFatalCode}.
|
|
865
|
-
* @param message - A human-readable detail (never contains PHI — there is none).
|
|
866
|
-
*/
|
|
867
|
-
constructor(code, message) {
|
|
868
|
-
super(message);
|
|
869
|
-
this.name = "SynthError";
|
|
870
|
-
this.code = code;
|
|
871
|
-
}
|
|
872
|
-
};
|
|
873
|
-
|
|
874
924
|
// src/quirk.ts
|
|
875
925
|
var PROFILE_QUIRK_APPLIED = "PROFILE_QUIRK_APPLIED";
|
|
876
926
|
function sameCodeSet(a, b) {
|
|
@@ -887,12 +937,8 @@ function sameCodeSet(a, b) {
|
|
|
887
937
|
}
|
|
888
938
|
function resolveQuirk(registry, format, name2) {
|
|
889
939
|
const descriptor = registry[name2];
|
|
890
|
-
if (descriptor === void 0) {
|
|
891
|
-
|
|
892
|
-
throw new SynthError(
|
|
893
|
-
SYNTH_FATAL_CODES.SYNTH_UNSUPPORTED_QUIRK,
|
|
894
|
-
`${format}: unsupported quirk "${name2}". The ${format} profile system supports: ${supported}.`
|
|
895
|
-
);
|
|
940
|
+
if (descriptor === void 0 || descriptor.format !== format) {
|
|
941
|
+
throw new SynthError(SYNTH_FATAL_CODES.SYNTH_UNSUPPORTED_QUIRK);
|
|
896
942
|
}
|
|
897
943
|
return descriptor;
|
|
898
944
|
}
|
|
@@ -907,11 +953,9 @@ function profileTolerated(disposition, intendedWarnings, warningsUnderProfile) {
|
|
|
907
953
|
return false;
|
|
908
954
|
}
|
|
909
955
|
}
|
|
910
|
-
function assertIntendedWarnings(
|
|
956
|
+
function assertIntendedWarnings(intendedWarnings, bareWarnings) {
|
|
911
957
|
if (!sameCodeSet(bareWarnings, intendedWarnings)) {
|
|
912
|
-
throw new
|
|
913
|
-
`quirk "${quirk}": the intended-warning contract does not hold \u2014 expected exactly [${intendedWarnings.join(", ")}] but a bare parse produced [${bareWarnings.join(", ")}]. Refusing to emit a mislabeled fixture.`
|
|
914
|
-
);
|
|
958
|
+
throw new SynthError(SYNTH_FATAL_CODES.SYNTH_INTENDED_WARNING_MISMATCH);
|
|
915
959
|
}
|
|
916
960
|
}
|
|
917
961
|
function validateProfileQuirks(profile, registry, format) {
|
|
@@ -976,11 +1020,10 @@ function applyQuirk(quirk, xml) {
|
|
|
976
1020
|
}
|
|
977
1021
|
}
|
|
978
1022
|
function injectCcdaQuirk(quirk, cleanXml) {
|
|
979
|
-
const
|
|
1023
|
+
const descriptor = resolveQuirk(CCDA_QUIRKS, "ccda", quirk);
|
|
1024
|
+
const content = applyQuirk(descriptor.name, cleanXml);
|
|
980
1025
|
if (content === cleanXml) {
|
|
981
|
-
throw new
|
|
982
|
-
`injectCcdaQuirk: quirk "${quirk}" found no structural anchor to mutate \u2014 refusing to emit a fixture that does not carry the intended deviation.`
|
|
983
|
-
);
|
|
1026
|
+
throw new SynthError(SYNTH_FATAL_CODES.SYNTH_QUIRK_ANCHOR_ABSENT);
|
|
984
1027
|
}
|
|
985
1028
|
return content;
|
|
986
1029
|
}
|
|
@@ -991,7 +1034,6 @@ function generateCcdaQuirk(options) {
|
|
|
991
1034
|
const clean = ccda.serializeCcda(generateCcda({ seed, documentType }));
|
|
992
1035
|
const content = injectCcdaQuirk(options.quirk, clean);
|
|
993
1036
|
assertIntendedWarnings(
|
|
994
|
-
descriptor.name,
|
|
995
1037
|
descriptor.intendedWarnings,
|
|
996
1038
|
ccda.parseCcda(content).warnings.map((w) => String(w.code))
|
|
997
1039
|
);
|
|
@@ -1034,6 +1076,7 @@ var ALL_CCDA_QUIRKS = Object.freeze(
|
|
|
1034
1076
|
function ccdaQuirkCorpus(options) {
|
|
1035
1077
|
const quirks = options.profile ? validateProfileQuirks(options.profile, CCDA_QUIRKS, "ccda") : options.quirks ?? ALL_CCDA_QUIRKS;
|
|
1036
1078
|
const names = quirks.length > 0 ? quirks : ALL_CCDA_QUIRKS;
|
|
1079
|
+
for (const name2 of names) resolveQuirk(CCDA_QUIRKS, "ccda", name2);
|
|
1037
1080
|
const documentType = options.documentType ?? "ccd";
|
|
1038
1081
|
const count = options.count ?? names.length;
|
|
1039
1082
|
const seedStream = createRng(options.seed);
|
|
@@ -1056,9 +1099,11 @@ var ccdaQuirkProfile = defineSynthProfile({
|
|
|
1056
1099
|
});
|
|
1057
1100
|
|
|
1058
1101
|
// src/ccda/index.ts
|
|
1059
|
-
var
|
|
1102
|
+
var ALL_KINDS = Object.freeze(["ccd", "referralNote"]);
|
|
1103
|
+
var DEFAULT_MIX = ALL_KINDS;
|
|
1060
1104
|
function ccdaCorpus(options) {
|
|
1061
|
-
const { seed, count = 1
|
|
1105
|
+
const { seed, count = 1 } = options;
|
|
1106
|
+
const mix = resolveMix(ALL_KINDS, options.mix, DEFAULT_MIX);
|
|
1062
1107
|
const seedStream = createRng(seed);
|
|
1063
1108
|
const artifacts = Array.from({ length: count }, (_unused, i) => {
|
|
1064
1109
|
const documentType = mix[i % mix.length] ?? "ccd";
|