@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
@@ -527,6 +527,12 @@ interface Hl7CorpusOptions {
527
527
  * supplied it takes precedence over `mix` and restricts the corpus to `ADT` messages.
528
528
  */
529
529
  readonly triggers?: readonly AdtTrigger[];
530
+ /**
531
+ * Whether each artifact is read back through its parser and the parser's findings recorded as its
532
+ * `warnings`. Defaults to `true`. `false` skips the read, a large share of a corpus's time; every
533
+ * artifact's `warnings` is then empty without having been checked.
534
+ */
535
+ readonly verify?: boolean;
530
536
  }
531
537
  /**
532
538
  * Build a reproducible {@link Corpus} of spec-clean HL7 messages across the families. Each
@@ -527,6 +527,12 @@ interface Hl7CorpusOptions {
527
527
  * supplied it takes precedence over `mix` and restricts the corpus to `ADT` messages.
528
528
  */
529
529
  readonly triggers?: readonly AdtTrigger[];
530
+ /**
531
+ * Whether each artifact is read back through its parser and the parser's findings recorded as its
532
+ * `warnings`. Defaults to `true`. `false` skips the read, a large share of a corpus's time; every
533
+ * artifact's `warnings` is then empty without having been checked.
534
+ */
535
+ readonly verify?: boolean;
530
536
  }
531
537
  /**
532
538
  * Build a reproducible {@link Corpus} of spec-clean HL7 messages across the families. Each
@@ -1,4 +1,12 @@
1
- import { buildMessage, parseHL7, profiles } from '@cosyte/hl7';
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,17 +38,36 @@ 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",
34
51
  /**
35
52
  * A vendor quirk was requested that the target format's profile system does not support. Fatal,
36
53
  * never a silent no-op and never a fabricated quirk.
37
54
  */
38
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",
39
62
  /**
40
63
  * A bare parse of a freshly-generated quirk artifact did not produce exactly the declared intended
41
64
  * warning code(s). Fatal, never emit a mislabeled fixture.
42
65
  */
43
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",
69
+ /** A money value could not be read as an X12 decimal. Fatal: a generator never rounds to a float. */
70
+ SYNTH_INVALID_DECIMAL: "SYNTH_INVALID_DECIMAL",
44
71
  /** An integer range was requested with its maximum below its minimum. Fatal. */
45
72
  SYNTH_INVALID_RANGE: "SYNTH_INVALID_RANGE",
46
73
  /** A value was drawn from an empty pool. Fatal, never a fabricated substitute. */
@@ -53,7 +80,17 @@ var SYNTH_FATAL_CODES = {
53
80
  * see `resolveKind`: a selector union is erased at run time, and a selector that falls through
54
81
  * either mislabels the fixture or hands the value to a peer builder that quotes it back.
55
82
  */
56
- 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
+ });
57
94
  var SYNTH_FATAL_MESSAGES = Object.freeze({
58
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.",
59
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).",
@@ -119,6 +156,7 @@ function createRng(seed) {
119
156
  }
120
157
 
121
158
  // src/corpus.ts
159
+ var UNCHECKED = Object.freeze([]);
122
160
  function makeCorpus(seed, artifacts, quirks = []) {
123
161
  const counts = {};
124
162
  const formats = /* @__PURE__ */ new Set();
@@ -430,6 +468,30 @@ var safe = Object.freeze({
430
468
  npi,
431
469
  dea
432
470
  });
471
+ function defaultHost() {
472
+ if (typeof import.meta.resolve !== "function") return { require: __require, resolve: void 0 };
473
+ return { require: createRequire(import.meta.url), resolve: (s) => import.meta.resolve(s) };
474
+ }
475
+ function isRequireEsmRefusal(error) {
476
+ return error?.code === "ERR_REQUIRE_ESM";
477
+ }
478
+ function loadPeer(specifier, host) {
479
+ if (host.resolve === void 0) return host.require(specifier);
480
+ const entry = fileURLToPath(host.resolve(specifier));
481
+ try {
482
+ return host.require(entry);
483
+ } catch (error) {
484
+ if (!isRequireEsmRefusal(error)) throw error;
485
+ return host.require(specifier);
486
+ }
487
+ }
488
+ function lazyPeer(specifier, host) {
489
+ let loaded;
490
+ return () => loaded ??= loadPeer(specifier, defaultHost());
491
+ }
492
+
493
+ // src/peers.ts
494
+ var hl7Peer = lazyPeer("@cosyte/hl7");
433
495
 
434
496
  // src/hl7/common.ts
435
497
  function seededTimestamp(rng) {
@@ -442,7 +504,7 @@ function seededTimestamp(rng) {
442
504
  function mshScaffold(rng, type) {
443
505
  const timestamp = seededTimestamp(rng);
444
506
  const controlId = `SYNTH${rng.digits(10)}`;
445
- const message = buildMessage({
507
+ const message = hl7Peer().buildMessage({
446
508
  type,
447
509
  sendingApp: "COSYTE-SYNTH",
448
510
  sendingFacility: "SYNTH-FAC",
@@ -569,6 +631,7 @@ function obxSegment(rng, setId) {
569
631
  const obs = rng.pick(EXAMPLE_LAB_OBSERVATIONS);
570
632
  const value = `${String(rng.int(1, 300))}.${String(rng.int(0, 9))}`;
571
633
  const observedAt = seededTimestamp(rng);
634
+ const units = obs.units ?? "";
572
635
  return [
573
636
  String(setId),
574
637
  // OBX-1 set id
@@ -580,7 +643,7 @@ function obxSegment(rng, setId) {
580
643
  // OBX-4 observation sub-id
581
644
  value,
582
645
  // OBX-5 observation value
583
- obs.units ?? "",
646
+ units,
584
647
  // OBX-6 units
585
648
  "",
586
649
  // OBX-7 reference range
@@ -794,9 +857,11 @@ function generateVxu(options = {}) {
794
857
  ]);
795
858
  return message;
796
859
  }
860
+
861
+ // src/hl7/round-trip.ts
797
862
  function roundTrip(message) {
798
863
  const content = message.toString();
799
- const reparsed = parseHL7(content);
864
+ const reparsed = hl7Peer().parseHL7(content);
800
865
  const warnings = reparsed.warnings.map((w) => String(w.code));
801
866
  const byteStable = reparsed.toString() === content;
802
867
  return {
@@ -806,6 +871,14 @@ function roundTrip(message) {
806
871
  specClean: warnings.length === 0 && byteStable
807
872
  };
808
873
  }
874
+ function hl7CorpusArtifact(message, verify) {
875
+ const content = message.toString();
876
+ if (!verify) return { content, warnings: UNCHECKED };
877
+ return {
878
+ content,
879
+ warnings: hl7Peer().parseHL7(content).warnings.map((w) => String(w.code))
880
+ };
881
+ }
809
882
 
810
883
  // src/profile.ts
811
884
  function defineSynthProfile(spec) {
@@ -881,7 +954,7 @@ var HL7_QUIRKS = Object.freeze({
881
954
  })
882
955
  });
883
956
  function toleratingProfile(quirk) {
884
- return quirk === "unknown-zsegment" ? profiles.visage : void 0;
957
+ return quirk === "unknown-zsegment" ? hl7Peer().profiles.visage : void 0;
885
958
  }
886
959
  function segmentsOf(wire) {
887
960
  return wire.split(/\r\n|\r|\n/).filter((s) => s.length > 0);
@@ -929,7 +1002,7 @@ function generateHl7Quirk(options) {
929
1002
  const content = applyQuirk(options.quirk, baseMessage(kind, seed).toString());
930
1003
  assertIntendedWarnings(
931
1004
  descriptor.intendedWarnings,
932
- parseHL7(content).warnings.map((w) => String(w.code))
1005
+ hl7Peer().parseHL7(content).warnings.map((w) => String(w.code))
933
1006
  );
934
1007
  return Object.freeze({
935
1008
  format: "hl7v2",
@@ -942,12 +1015,12 @@ function generateHl7Quirk(options) {
942
1015
  function hl7QuirkRoundTrip(artifact) {
943
1016
  const quirk = artifact.quirk;
944
1017
  const descriptor = resolveQuirk(HL7_QUIRKS, "hl7v2", quirk);
945
- const bare = parseHL7(artifact.content).warnings.map((w) => String(w.code));
1018
+ const bare = hl7Peer().parseHL7(artifact.content).warnings.map((w) => String(w.code));
946
1019
  const profile = toleratingProfile(quirk);
947
1020
  const withProfile = profile !== void 0 && descriptor.toleratingProfile !== void 0 ? {
948
1021
  profileName: descriptor.toleratingProfile,
949
1022
  disposition: descriptor.disposition,
950
- warnings: parseHL7(artifact.content, profile).warnings.map((w) => String(w.code)),
1023
+ warnings: hl7Peer().parseHL7(artifact.content, profile).warnings.map((w) => String(w.code)),
951
1024
  tolerated: false
952
1025
  } : void 0;
953
1026
  return {
@@ -1026,7 +1099,7 @@ function generateHl7(kind, seed) {
1026
1099
  }
1027
1100
  }
1028
1101
  function hl7Corpus(options) {
1029
- const { seed, count = 1 } = options;
1102
+ const { seed, count = 1, verify = true } = options;
1030
1103
  const kinds = options.triggers !== void 0 ? options.triggers.map(
1031
1104
  (t) => `ADT^${resolveKind(ADT_TRIGGERS2, t)}`
1032
1105
  ) : resolveMix(ALL_KINDS, options.mix, DEFAULT_MIX);
@@ -1034,7 +1107,7 @@ function hl7Corpus(options) {
1034
1107
  const artifacts = Array.from({ length: count }, (_unused, i) => {
1035
1108
  const kind = kinds[i % kinds.length] ?? "ADT^A01";
1036
1109
  const messageSeed = seedStream.nextUint32();
1037
- const rt = roundTrip(generateHl7(kind, messageSeed));
1110
+ const rt = hl7CorpusArtifact(generateHl7(kind, messageSeed), verify);
1038
1111
  return {
1039
1112
  format: "hl7v2",
1040
1113
  kind,