@cosyte/synth 0.1.4 → 0.1.6

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.
Files changed (49) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/README.md +14 -10
  3. package/dist/astm/index.cjs +164 -102
  4. package/dist/astm/index.cjs.map +1 -1
  5. package/dist/astm/index.d.cts +7 -0
  6. package/dist/astm/index.d.ts +7 -0
  7. package/dist/astm/index.mjs +163 -102
  8. package/dist/astm/index.mjs.map +1 -1
  9. package/dist/ccda/index.cjs +100 -28
  10. package/dist/ccda/index.cjs.map +1 -1
  11. package/dist/ccda/index.d.cts +6 -0
  12. package/dist/ccda/index.d.ts +6 -0
  13. package/dist/ccda/index.mjs +87 -16
  14. package/dist/ccda/index.mjs.map +1 -1
  15. package/dist/deid/index.cjs +305 -165
  16. package/dist/deid/index.cjs.map +1 -1
  17. package/dist/deid/index.mjs +292 -153
  18. package/dist/deid/index.mjs.map +1 -1
  19. package/dist/fhir/index.cjs +179 -76
  20. package/dist/fhir/index.cjs.map +1 -1
  21. package/dist/fhir/index.d.cts +6 -0
  22. package/dist/fhir/index.d.ts +6 -0
  23. package/dist/fhir/index.mjs +178 -76
  24. package/dist/fhir/index.mjs.map +1 -1
  25. package/dist/hl7/index.cjs +86 -12
  26. package/dist/hl7/index.cjs.map +1 -1
  27. package/dist/hl7/index.d.cts +6 -0
  28. package/dist/hl7/index.d.ts +6 -0
  29. package/dist/hl7/index.mjs +85 -12
  30. package/dist/hl7/index.mjs.map +1 -1
  31. package/dist/index.cjs +3 -5
  32. package/dist/index.cjs.map +1 -1
  33. package/dist/index.d.cts +8 -4
  34. package/dist/index.d.ts +8 -4
  35. package/dist/index.mjs +3 -5
  36. package/dist/index.mjs.map +1 -1
  37. package/dist/ncpdp/index.cjs +131 -36
  38. package/dist/ncpdp/index.cjs.map +1 -1
  39. package/dist/ncpdp/index.d.cts +7 -0
  40. package/dist/ncpdp/index.d.ts +7 -0
  41. package/dist/ncpdp/index.mjs +130 -36
  42. package/dist/ncpdp/index.mjs.map +1 -1
  43. package/dist/x12/index.cjs +147 -50
  44. package/dist/x12/index.cjs.map +1 -1
  45. package/dist/x12/index.d.cts +6 -0
  46. package/dist/x12/index.d.ts +6 -0
  47. package/dist/x12/index.mjs +146 -50
  48. package/dist/x12/index.mjs.map +1 -1
  49. package/package.json +20 -15
@@ -465,6 +465,12 @@ interface X12CorpusOptions {
465
465
  readonly count?: number;
466
466
  /** The transaction kinds to cycle through. Defaults to one of each. */
467
467
  readonly mix?: readonly X12CorpusKind[];
468
+ /**
469
+ * Whether each artifact is read back through its parser and the parser's findings recorded as its
470
+ * `warnings`. Defaults to `true`. `false` skips the read, a large share of a corpus's time; every
471
+ * artifact's `warnings` is then empty without having been checked.
472
+ */
473
+ readonly verify?: boolean;
468
474
  }
469
475
  /**
470
476
  * Build a reproducible {@link Corpus} of spec-clean X12 transactions. Each transaction is generated from
@@ -465,6 +465,12 @@ interface X12CorpusOptions {
465
465
  readonly count?: number;
466
466
  /** The transaction kinds to cycle through. Defaults to one of each. */
467
467
  readonly mix?: readonly X12CorpusKind[];
468
+ /**
469
+ * Whether each artifact is read back through its parser and the parser's findings recorded as its
470
+ * `warnings`. Defaults to `true`. `false` skips the read, a large share of a corpus's time; every
471
+ * artifact's `warnings` is then empty without having been checked.
472
+ */
473
+ readonly verify?: boolean;
468
474
  }
469
475
  /**
470
476
  * Build a reproducible {@link Corpus} of spec-clean X12 transactions. Each transaction is generated from
@@ -1,4 +1,12 @@
1
- import { X12Decimal, build837D, build837I, build837P, build835, build271, serializeX12, parseX12 } from '@cosyte/x12';
1
+ import { createRequire } from 'module';
2
+ import { fileURLToPath } from 'url';
3
+
4
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
5
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
6
+ }) : x)(function(x) {
7
+ if (typeof require !== "undefined") return require.apply(this, arguments);
8
+ throw Error('Dynamic require of "' + x + '" is not supported');
9
+ });
2
10
 
3
11
  // src/rng/splitmix32.ts
4
12
  function splitmix32(seed) {
@@ -30,20 +38,59 @@ function sfc32Next(s) {
30
38
  }
31
39
 
32
40
  // src/codes.ts
33
- var SYNTH_FATAL_CODES = {
41
+ var SYNTH_FATAL_CODES = Object.freeze({
42
+ /**
43
+ * A format was requested that this build cannot generate through a real parser builder/serializer.
44
+ * Fatal, never a hand-written byte fallback.
45
+ *
46
+ * **No code path in this build raises it.** All six formats generate, so it is reserved for a
47
+ * future format that does not, and is kept because removing a published code is a breaking change.
48
+ * An unsupported *kind* within a format that does generate is `SYNTH_UNSUPPORTED_KIND`.
49
+ */
50
+ SYNTH_UNSUPPORTED_FORMAT: "SYNTH_UNSUPPORTED_FORMAT",
51
+ /**
52
+ * A vendor quirk was requested that the target format's profile system does not support. Fatal,
53
+ * never a silent no-op and never a fabricated quirk.
54
+ */
55
+ SYNTH_UNSUPPORTED_QUIRK: "SYNTH_UNSUPPORTED_QUIRK",
56
+ /**
57
+ * A quirk transform found no structural anchor to mutate, so the fixture would not carry the
58
+ * deviation it is labelled with. Fatal: a golden file that lies about its parser verdict is worse
59
+ * than no golden file.
60
+ */
61
+ SYNTH_QUIRK_ANCHOR_ABSENT: "SYNTH_QUIRK_ANCHOR_ABSENT",
62
+ /**
63
+ * A bare parse of a freshly-generated quirk artifact did not produce exactly the declared intended
64
+ * warning code(s). Fatal, never emit a mislabeled fixture.
65
+ */
66
+ SYNTH_INTENDED_WARNING_MISMATCH: "SYNTH_INTENDED_WARNING_MISMATCH",
67
+ /** A concept's code-system URI has no OID mapping in the C-CDA example-code table. Fatal. */
68
+ SYNTH_UNMAPPED_CODE_SYSTEM: "SYNTH_UNMAPPED_CODE_SYSTEM",
34
69
  /** A money value could not be read as an X12 decimal. Fatal: a generator never rounds to a float. */
35
70
  SYNTH_INVALID_DECIMAL: "SYNTH_INVALID_DECIMAL",
36
71
  /** An integer range was requested with its maximum below its minimum. Fatal. */
37
72
  SYNTH_INVALID_RANGE: "SYNTH_INVALID_RANGE",
38
73
  /** A value was drawn from an empty pool. Fatal, never a fabricated substitute. */
39
74
  SYNTH_EMPTY_POOL: "SYNTH_EMPTY_POOL",
75
+ /** A `defineSynthProfile` spec was not usable (a missing or blank `name`). Fatal. */
76
+ SYNTH_INVALID_PROFILE: "SYNTH_INVALID_PROFILE",
40
77
  /**
41
78
  * A caller-supplied selector (a message kind, a document type, a corpus mix entry, a claim
42
79
  * variant, a Bundle type, a resource profile) is not in the closed set that governs it. Fatal:
43
80
  * see `resolveKind`: a selector union is erased at run time, and a selector that falls through
44
81
  * either mislabels the fixture or hands the value to a peer builder that quotes it back.
45
82
  */
46
- SYNTH_UNSUPPORTED_KIND: "SYNTH_UNSUPPORTED_KIND"};
83
+ SYNTH_UNSUPPORTED_KIND: "SYNTH_UNSUPPORTED_KIND",
84
+ /**
85
+ * A requested profile IS published by the adopted implementation guide, and this build does not
86
+ * generate it. Deliberately distinct from `SYNTH_UNSUPPORTED_KIND`, which says the name is not in
87
+ * the adopted set at all: "the guide does not publish this" and "we do not generate this yet" are
88
+ * different answers, and a caller building to a regulatory profile set has to be able to tell them
89
+ * apart without matching message text. Fatal, and raised **before** anything is generated: a
90
+ * mislabelled artifact is worse than no artifact.
91
+ */
92
+ SYNTH_PROFILE_NOT_GENERATED: "SYNTH_PROFILE_NOT_GENERATED"
93
+ });
47
94
  var SYNTH_FATAL_MESSAGES = Object.freeze({
48
95
  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.",
49
96
  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).",
@@ -109,6 +156,7 @@ function createRng(seed) {
109
156
  }
110
157
 
111
158
  // src/corpus.ts
159
+ var UNCHECKED = Object.freeze([]);
112
160
  function makeCorpus(seed, artifacts, quirks = []) {
113
161
  const counts = {};
114
162
  const formats = /* @__PURE__ */ new Set();
@@ -128,8 +176,32 @@ function makeCorpus(seed, artifacts, quirks = []) {
128
176
  artifacts: Object.freeze(frozenArtifacts)
129
177
  });
130
178
  }
179
+ function defaultHost() {
180
+ if (typeof import.meta.resolve !== "function") return { require: __require, resolve: void 0 };
181
+ return { require: createRequire(import.meta.url), resolve: (s) => import.meta.resolve(s) };
182
+ }
183
+ function isRequireEsmRefusal(error) {
184
+ return error?.code === "ERR_REQUIRE_ESM";
185
+ }
186
+ function loadPeer(specifier, host) {
187
+ if (host.resolve === void 0) return host.require(specifier);
188
+ const entry = fileURLToPath(host.resolve(specifier));
189
+ try {
190
+ return host.require(entry);
191
+ } catch (error) {
192
+ if (!isRequireEsmRefusal(error)) throw error;
193
+ return host.require(specifier);
194
+ }
195
+ }
196
+ function lazyPeer(specifier, host) {
197
+ let loaded;
198
+ return () => loaded ??= loadPeer(specifier, defaultHost());
199
+ }
200
+ var x12Peer = lazyPeer("@cosyte/x12");
201
+
202
+ // src/x12/money.ts
131
203
  function dec(value) {
132
- const d = X12Decimal.fromString(value);
204
+ const d = x12Peer().X12Decimal.fromString(value);
133
205
  if (d === void 0) throw new SynthError(SYNTH_FATAL_CODES.SYNTH_INVALID_DECIMAL);
134
206
  return d;
135
207
  }
@@ -482,39 +554,49 @@ function x12EnvelopeTiming(rng) {
482
554
  }
483
555
 
484
556
  // src/x12/example-codes.ts
485
- var PROFESSIONAL_PROCEDURES = Object.freeze([
486
- { code: "99213", label: "office/outpatient visit, established patient" },
487
- { code: "99214", label: "office/outpatient visit, established patient, moderate" },
488
- { code: "99203", label: "office/outpatient visit, new patient" },
489
- { code: "85025", label: "complete blood count with differential" },
490
- { code: "80053", label: "comprehensive metabolic panel" },
491
- { code: "93000", label: "electrocardiogram, routine" }
492
- ]);
557
+ var PROFESSIONAL_PROCEDURES = Object.freeze(
558
+ [
559
+ { code: "99213", label: "office/outpatient visit, established patient" },
560
+ { code: "99214", label: "office/outpatient visit, established patient, moderate" },
561
+ { code: "99203", label: "office/outpatient visit, new patient" },
562
+ { code: "85025", label: "complete blood count with differential" },
563
+ { code: "80053", label: "comprehensive metabolic panel" },
564
+ { code: "93000", label: "electrocardiogram, routine" }
565
+ ].map((entry) => Object.freeze(entry))
566
+ );
493
567
  var PROCEDURE_MODIFIERS = Object.freeze(["25", "59", "76", "GT"]);
494
568
  var PLACES_OF_SERVICE = Object.freeze(["11", "22", "21", "12", "49"]);
495
- var DIAGNOSES = Object.freeze([
496
- { code: "J20.9", label: "acute bronchitis, unspecified" },
497
- { code: "E11.9", label: "type 2 diabetes mellitus without complications" },
498
- { code: "I10", label: "essential (primary) hypertension" },
499
- { code: "M54.5", label: "low back pain" },
500
- { code: "R07.9", label: "chest pain, unspecified" },
501
- { code: "Z00.00", label: "general adult medical exam without abnormal findings" }
502
- ]);
503
- var REVENUE_CODES = Object.freeze([
504
- { code: "0120", label: "room & board, semi-private" },
505
- { code: "0300", label: "laboratory, general" },
506
- { code: "0450", label: "emergency room, general" },
507
- { code: "0636", label: "drugs requiring detailed coding" }
508
- ]);
509
- var INSTITUTIONAL_PROCEDURES = Object.freeze([
510
- { code: "99221", label: "initial hospital care, per day" },
511
- { code: "99231", label: "subsequent hospital care, per day" }
512
- ]);
513
- var DENTAL_PROCEDURES = Object.freeze([
514
- { code: "D2391", label: "resin-based composite, one surface, posterior" },
515
- { code: "D1110", label: "prophylaxis, adult" },
516
- { code: "D0120", label: "periodic oral evaluation" }
517
- ]);
569
+ var DIAGNOSES = Object.freeze(
570
+ [
571
+ { code: "J20.9", label: "acute bronchitis, unspecified" },
572
+ { code: "E11.9", label: "type 2 diabetes mellitus without complications" },
573
+ { code: "I10", label: "essential (primary) hypertension" },
574
+ { code: "M54.5", label: "low back pain" },
575
+ { code: "R07.9", label: "chest pain, unspecified" },
576
+ { code: "Z00.00", label: "general adult medical exam without abnormal findings" }
577
+ ].map((entry) => Object.freeze(entry))
578
+ );
579
+ var REVENUE_CODES = Object.freeze(
580
+ [
581
+ { code: "0120", label: "room & board, semi-private" },
582
+ { code: "0300", label: "laboratory, general" },
583
+ { code: "0450", label: "emergency room, general" },
584
+ { code: "0636", label: "drugs requiring detailed coding" }
585
+ ].map((entry) => Object.freeze(entry))
586
+ );
587
+ var INSTITUTIONAL_PROCEDURES = Object.freeze(
588
+ [
589
+ { code: "99221", label: "initial hospital care, per day" },
590
+ { code: "99231", label: "subsequent hospital care, per day" }
591
+ ].map((entry) => Object.freeze(entry))
592
+ );
593
+ var DENTAL_PROCEDURES = Object.freeze(
594
+ [
595
+ { code: "D2391", label: "resin-based composite, one surface, posterior" },
596
+ { code: "D1110", label: "prophylaxis, adult" },
597
+ { code: "D0120", label: "periodic oral evaluation" }
598
+ ].map((entry) => Object.freeze(entry))
599
+ );
518
600
  var TOOTH_CODES = Object.freeze(["14", "3", "19", "30"]);
519
601
  var TOOTH_SURFACES = Object.freeze(["M", "O", "D", "B", "L"]);
520
602
  var CARC_CODES = Object.freeze(["1", "2", "3", "45", "96", "197"]);
@@ -720,11 +802,11 @@ function generate837(variant, options) {
720
802
  const spec = specOf(rng, resolved);
721
803
  switch (resolved) {
722
804
  case "P":
723
- return build837P(spec);
805
+ return x12Peer().build837P(spec);
724
806
  case "I":
725
- return build837I(spec);
807
+ return x12Peer().build837I(spec);
726
808
  case "D":
727
- return build837D(spec);
809
+ return x12Peer().build837D(spec);
728
810
  }
729
811
  }
730
812
  function generate837P(options) {
@@ -736,6 +818,8 @@ function generate837I(options) {
736
818
  function generate837D(options) {
737
819
  return generate837("D", options);
738
820
  }
821
+
822
+ // src/x12/remit-835.ts
739
823
  function generate835(options) {
740
824
  const rng = createRng(options.seed);
741
825
  const partners = x12TradingPartners(rng);
@@ -836,8 +920,10 @@ function generate835(options) {
836
920
  },
837
921
  claims: [claim]
838
922
  };
839
- return build835(spec);
923
+ return x12Peer().build835(spec);
840
924
  }
925
+
926
+ // src/x12/eligibility-271.ts
841
927
  function generate271(options) {
842
928
  const rng = createRng(options.seed);
843
929
  const partners = x12TradingPartners(rng);
@@ -917,13 +1003,15 @@ function generate271(options) {
917
1003
  }
918
1004
  ]
919
1005
  };
920
- return build271(spec);
1006
+ return x12Peer().build271(spec);
921
1007
  }
1008
+
1009
+ // src/x12/round-trip.ts
922
1010
  function roundTrip(interchange) {
923
- const content = serializeX12(interchange);
924
- const reparsed = parseX12(content);
1011
+ const content = x12Peer().serializeX12(interchange);
1012
+ const reparsed = x12Peer().parseX12(content);
925
1013
  const warnings = reparsed.warnings.map((w) => String(w.code));
926
- const byteStable = serializeX12(reparsed) === content;
1014
+ const byteStable = x12Peer().serializeX12(reparsed) === content;
927
1015
  return {
928
1016
  content,
929
1017
  warnings,
@@ -931,6 +1019,14 @@ function roundTrip(interchange) {
931
1019
  specClean: warnings.length === 0 && byteStable
932
1020
  };
933
1021
  }
1022
+ function x12CorpusArtifact(interchange, verify) {
1023
+ const content = x12Peer().serializeX12(interchange);
1024
+ if (!verify) return { content, warnings: UNCHECKED };
1025
+ return {
1026
+ content,
1027
+ warnings: x12Peer().parseX12(content).warnings.map((w) => String(w.code))
1028
+ };
1029
+ }
934
1030
 
935
1031
  // src/x12/index.ts
936
1032
  var ALL_KINDS = Object.freeze(["837P", "837I", "837D", "835", "271"]);
@@ -938,26 +1034,26 @@ var DEFAULT_MIX = ALL_KINDS;
938
1034
  function generateKind(kind, seed) {
939
1035
  switch (kind) {
940
1036
  case "835":
941
- return roundTrip(generate835({ seed }));
1037
+ return generate835({ seed });
942
1038
  case "271":
943
- return roundTrip(generate271({ seed }));
1039
+ return generate271({ seed });
944
1040
  case "837P":
945
- return roundTrip(generate837("P", { seed }));
1041
+ return generate837("P", { seed });
946
1042
  case "837I":
947
- return roundTrip(generate837("I", { seed }));
1043
+ return generate837("I", { seed });
948
1044
  case "837D":
949
- return roundTrip(generate837("D", { seed }));
1045
+ return generate837("D", { seed });
950
1046
  }
951
1047
  }
952
1048
  function x12Corpus(options) {
953
- const { seed } = options;
1049
+ const { seed, verify = true } = options;
954
1050
  const mix = resolveMix(ALL_KINDS, options.mix, DEFAULT_MIX);
955
1051
  const count = options.count ?? mix.length;
956
1052
  const seedStream = createRng(seed);
957
1053
  const artifacts = Array.from({ length: count }, (_unused, i) => {
958
1054
  const kind = mix[i % mix.length] ?? "837P";
959
1055
  const txSeed = seedStream.nextUint32();
960
- const rt = generateKind(kind, txSeed);
1056
+ const rt = x12CorpusArtifact(generateKind(kind, txSeed), verify);
961
1057
  return {
962
1058
  format: "x12",
963
1059
  kind,