@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
@@ -91,6 +91,7 @@ declare function generateAstmResultFramed(options: GenerateAstmOptions): Uint8Ar
91
91
  *
92
92
  * @module
93
93
  */
94
+
94
95
  /** The verdict of one round-trip through `@cosyte/astm`. */
95
96
  interface AstmRoundTripResult {
96
97
  /** The serialized ASTM wire text (records: the `CR`-terminated stream; framed: the raw bytes as latin1). */
@@ -398,6 +399,12 @@ interface AstmCorpusOptions {
398
399
  readonly count?: number;
399
400
  /** The message kinds to cycle through. Defaults to one of each. */
400
401
  readonly mix?: readonly AstmCorpusKind[];
402
+ /**
403
+ * Whether each artifact is read back through its parser and the parser's findings recorded as its
404
+ * `warnings`. Defaults to `true`. `false` skips the read, a large share of a corpus's time; every
405
+ * artifact's `warnings` is then empty without having been checked.
406
+ */
407
+ readonly verify?: boolean;
401
408
  }
402
409
  /**
403
410
  * Build a reproducible {@link Corpus} of spec-clean ASTM messages. Each message is generated from a
@@ -91,6 +91,7 @@ declare function generateAstmResultFramed(options: GenerateAstmOptions): Uint8Ar
91
91
  *
92
92
  * @module
93
93
  */
94
+
94
95
  /** The verdict of one round-trip through `@cosyte/astm`. */
95
96
  interface AstmRoundTripResult {
96
97
  /** The serialized ASTM wire text (records: the `CR`-terminated stream; framed: the raw bytes as latin1). */
@@ -398,6 +399,12 @@ interface AstmCorpusOptions {
398
399
  readonly count?: number;
399
400
  /** The message kinds to cycle through. Defaults to one of each. */
400
401
  readonly mix?: readonly AstmCorpusKind[];
402
+ /**
403
+ * Whether each artifact is read back through its parser and the parser's findings recorded as its
404
+ * `warnings`. Defaults to `true`. `false` skips the read, a large share of a corpus's time; every
405
+ * artifact's `warnings` is then empty without having been checked.
406
+ */
407
+ readonly verify?: boolean;
401
408
  }
402
409
  /**
403
410
  * Build a reproducible {@link Corpus} of spec-clean ASTM messages. Each message is generated from a
@@ -1,4 +1,12 @@
1
- import { buildAstmMessage, composeAstmFrames, parseAstmRecords, serializeAstmRecords, parseFramedAstm, serializeFramedAstm, astmProfiles } from '@cosyte/astm';
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,7 +38,16 @@ 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.
@@ -47,6 +64,10 @@ var SYNTH_FATAL_CODES = {
47
64
  * warning code(s). Fatal, never emit a mislabeled fixture.
48
65
  */
49
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",
50
71
  /** An integer range was requested with its maximum below its minimum. Fatal. */
51
72
  SYNTH_INVALID_RANGE: "SYNTH_INVALID_RANGE",
52
73
  /** A value was drawn from an empty pool. Fatal, never a fabricated substitute. */
@@ -59,7 +80,17 @@ var SYNTH_FATAL_CODES = {
59
80
  * see `resolveKind`: a selector union is erased at run time, and a selector that falls through
60
81
  * either mislabels the fixture or hands the value to a peer builder that quotes it back.
61
82
  */
62
- 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
+ });
63
94
  var SYNTH_FATAL_MESSAGES = Object.freeze({
64
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.",
65
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).",
@@ -125,6 +156,7 @@ function createRng(seed) {
125
156
  }
126
157
 
127
158
  // src/corpus.ts
159
+ var UNCHECKED = Object.freeze([]);
128
160
  function makeCorpus(seed, artifacts, quirks = []) {
129
161
  const counts = {};
130
162
  const formats = /* @__PURE__ */ new Set();
@@ -470,88 +502,90 @@ function astmHeaderIdentity(rng) {
470
502
  }
471
503
 
472
504
  // src/astm/example-codes.ts
473
- var EXAMPLE_ASTM_TESTS = Object.freeze([
474
- {
475
- localCode: "GLU",
476
- loinc: "2345-7",
477
- name: "Glucose",
478
- units: "mg/dL",
479
- referenceRange: "70-110",
480
- valueLow: 55,
481
- valueHigh: 260,
482
- decimals: 0
483
- },
484
- {
485
- localCode: "K",
486
- loinc: "2823-3",
487
- name: "Potassium",
488
- units: "mmol/L",
489
- referenceRange: "3.5-5.1",
490
- valueLow: 28,
491
- valueHigh: 62,
492
- decimals: 1
493
- },
494
- {
495
- localCode: "NA",
496
- loinc: "2951-2",
497
- name: "Sodium",
498
- units: "mmol/L",
499
- referenceRange: "136-145",
500
- valueLow: 125,
501
- valueHigh: 155,
502
- decimals: 0
503
- },
504
- {
505
- localCode: "CREA",
506
- loinc: "2160-0",
507
- name: "Creatinine",
508
- units: "mg/dL",
509
- referenceRange: "0.6-1.3",
510
- valueLow: 4,
511
- valueHigh: 32,
512
- decimals: 1
513
- },
514
- {
515
- localCode: "HGB",
516
- loinc: "718-7",
517
- name: "Hemoglobin",
518
- units: "g/dL",
519
- referenceRange: "12.0-17.5",
520
- valueLow: 80,
521
- valueHigh: 190,
522
- decimals: 1
523
- },
524
- {
525
- localCode: "WBC",
526
- loinc: "6690-2",
527
- name: "Leukocytes",
528
- units: "10*3/uL",
529
- referenceRange: "4.5-11.0",
530
- valueLow: 30,
531
- valueHigh: 150,
532
- decimals: 1
533
- },
534
- {
535
- localCode: "TSH",
536
- loinc: "3016-3",
537
- name: "Thyrotropin",
538
- units: "mIU/L",
539
- referenceRange: "0.40-4.50",
540
- valueLow: 2,
541
- valueHigh: 90,
542
- decimals: 2
543
- },
544
- {
545
- localCode: "ALT",
546
- loinc: "1742-6",
547
- name: "Alanine aminotransferase",
548
- units: "U/L",
549
- referenceRange: "7-56",
550
- valueLow: 5,
551
- valueHigh: 120,
552
- decimals: 0
553
- }
554
- ]);
505
+ var EXAMPLE_ASTM_TESTS = Object.freeze(
506
+ [
507
+ {
508
+ localCode: "GLU",
509
+ loinc: "2345-7",
510
+ name: "Glucose",
511
+ units: "mg/dL",
512
+ referenceRange: "70-110",
513
+ valueLow: 55,
514
+ valueHigh: 260,
515
+ decimals: 0
516
+ },
517
+ {
518
+ localCode: "K",
519
+ loinc: "2823-3",
520
+ name: "Potassium",
521
+ units: "mmol/L",
522
+ referenceRange: "3.5-5.1",
523
+ valueLow: 28,
524
+ valueHigh: 62,
525
+ decimals: 1
526
+ },
527
+ {
528
+ localCode: "NA",
529
+ loinc: "2951-2",
530
+ name: "Sodium",
531
+ units: "mmol/L",
532
+ referenceRange: "136-145",
533
+ valueLow: 125,
534
+ valueHigh: 155,
535
+ decimals: 0
536
+ },
537
+ {
538
+ localCode: "CREA",
539
+ loinc: "2160-0",
540
+ name: "Creatinine",
541
+ units: "mg/dL",
542
+ referenceRange: "0.6-1.3",
543
+ valueLow: 4,
544
+ valueHigh: 32,
545
+ decimals: 1
546
+ },
547
+ {
548
+ localCode: "HGB",
549
+ loinc: "718-7",
550
+ name: "Hemoglobin",
551
+ units: "g/dL",
552
+ referenceRange: "12.0-17.5",
553
+ valueLow: 80,
554
+ valueHigh: 190,
555
+ decimals: 1
556
+ },
557
+ {
558
+ localCode: "WBC",
559
+ loinc: "6690-2",
560
+ name: "Leukocytes",
561
+ units: "10*3/uL",
562
+ referenceRange: "4.5-11.0",
563
+ valueLow: 30,
564
+ valueHigh: 150,
565
+ decimals: 1
566
+ },
567
+ {
568
+ localCode: "TSH",
569
+ loinc: "3016-3",
570
+ name: "Thyrotropin",
571
+ units: "mIU/L",
572
+ referenceRange: "0.40-4.50",
573
+ valueLow: 2,
574
+ valueHigh: 90,
575
+ decimals: 2
576
+ },
577
+ {
578
+ localCode: "ALT",
579
+ loinc: "1742-6",
580
+ name: "Alanine aminotransferase",
581
+ units: "U/L",
582
+ referenceRange: "7-56",
583
+ valueLow: 5,
584
+ valueHigh: 120,
585
+ decimals: 0
586
+ }
587
+ ].map((entry) => Object.freeze(entry))
588
+ );
555
589
  var ASTM_ABNORMAL_FLAGS = Object.freeze(["N", "L", "H", "A"]);
556
590
  var ASTM_RESULT_STATUSES = Object.freeze(["F", "P", "C"]);
557
591
  var ASTM_COMMENT_TEXT = Object.freeze([
@@ -559,6 +593,28 @@ var ASTM_COMMENT_TEXT = Object.freeze([
559
593
  "Sample generated by @cosyte/synth.",
560
594
  "Placeholder result comment for conformance testing."
561
595
  ]);
596
+ function defaultHost() {
597
+ if (typeof import.meta.resolve !== "function") return { require: __require, resolve: void 0 };
598
+ return { require: createRequire(import.meta.url), resolve: (s) => import.meta.resolve(s) };
599
+ }
600
+ function isRequireEsmRefusal(error) {
601
+ return error?.code === "ERR_REQUIRE_ESM";
602
+ }
603
+ function loadPeer(specifier, host) {
604
+ if (host.resolve === void 0) return host.require(specifier);
605
+ const entry = fileURLToPath(host.resolve(specifier));
606
+ try {
607
+ return host.require(entry);
608
+ } catch (error) {
609
+ if (!isRequireEsmRefusal(error)) throw error;
610
+ return host.require(specifier);
611
+ }
612
+ }
613
+ function lazyPeer(specifier, host) {
614
+ let loaded;
615
+ return () => loaded ??= loadPeer(specifier, defaultHost());
616
+ }
617
+ var astmPeer = lazyPeer("@cosyte/astm");
562
618
 
563
619
  // src/astm/message.ts
564
620
  function resultValue(rng, low, high, decimals) {
@@ -613,7 +669,7 @@ function buildResultInput(options) {
613
669
  };
614
670
  }
615
671
  function generateAstmResult(options) {
616
- return buildAstmMessage(buildResultInput(options));
672
+ return astmPeer().buildAstmMessage(buildResultInput(options));
617
673
  }
618
674
  function generateAstmOrder(options) {
619
675
  return generateAstmResult({ ...options, resultCount: 0, comment: false });
@@ -621,21 +677,23 @@ function generateAstmOrder(options) {
621
677
  function generateAstmResultFramed(options) {
622
678
  const raw = generateAstmResult(options);
623
679
  const recordLines = raw.split("\r").filter((line) => line.length > 0).map((line) => `${line}\r`);
624
- return composeAstmFrames(recordLines);
680
+ return astmPeer().composeAstmFrames(recordLines);
625
681
  }
682
+
683
+ // src/astm/round-trip.ts
626
684
  function astmRoundTrip(raw) {
627
- const message = parseAstmRecords(raw);
685
+ const message = astmPeer().parseAstmRecords(raw);
628
686
  const warnings = message.warnings.map((w) => String(w.code));
629
- const byteStable = serializeAstmRecords(message) === raw;
687
+ const byteStable = astmPeer().serializeAstmRecords(message) === raw;
630
688
  return { content: raw, warnings, byteStable, specClean: warnings.length === 0 && byteStable };
631
689
  }
632
690
  function astmFramedRoundTrip(bytes) {
633
- const { message, frameWarnings } = parseFramedAstm(bytes);
691
+ const { message, frameWarnings } = astmPeer().parseFramedAstm(bytes);
634
692
  const warnings = [
635
693
  ...frameWarnings.map((w) => String(w.code)),
636
694
  ...message.warnings.map((w) => String(w.code))
637
695
  ];
638
- const reframed = serializeFramedAstm(message);
696
+ const reframed = astmPeer().serializeFramedAstm(message);
639
697
  const byteStable = bytesEqual(reframed, bytes);
640
698
  const content = latin1(bytes);
641
699
  return { content, warnings, byteStable, specClean: warnings.length === 0 && byteStable };
@@ -650,6 +708,11 @@ function latin1(bytes) {
650
708
  for (const b of bytes) out += String.fromCharCode(b);
651
709
  return out;
652
710
  }
711
+ function astmCorpusArtifact(raw, verify) {
712
+ if (!verify) return { content: raw, warnings: UNCHECKED };
713
+ const warnings = astmPeer().parseAstmRecords(raw).warnings.map((w) => String(w.code));
714
+ return { content: raw, warnings };
715
+ }
653
716
 
654
717
  // src/select.ts
655
718
  function resolveKind(allowed, requested) {
@@ -737,7 +800,7 @@ var ASTM_QUIRKS = Object.freeze({
737
800
  })
738
801
  });
739
802
  function toleratingProfile(quirk) {
740
- return quirk === "unknown-escape" ? astmProfiles.referenceCorpus : void 0;
803
+ return quirk === "unknown-escape" ? astmPeer().astmProfiles.referenceCorpus : void 0;
741
804
  }
742
805
  var CR = "\r";
743
806
  function applyQuirk(quirk, records) {
@@ -781,7 +844,7 @@ function generateAstmQuirk(options) {
781
844
  }
782
845
  assertIntendedWarnings(
783
846
  descriptor.intendedWarnings,
784
- parseAstmRecords(content).warnings.map((w) => String(w.code))
847
+ astmPeer().parseAstmRecords(content).warnings.map((w) => String(w.code))
785
848
  );
786
849
  return Object.freeze({
787
850
  format: "astm",
@@ -794,12 +857,10 @@ function generateAstmQuirk(options) {
794
857
  function astmQuirkRoundTrip(artifact) {
795
858
  const quirk = artifact.quirk;
796
859
  const descriptor = resolveQuirk(ASTM_QUIRKS, "astm", quirk);
797
- const bare = parseAstmRecords(artifact.content).warnings.map((w) => String(w.code));
860
+ const bare = astmPeer().parseAstmRecords(artifact.content).warnings.map((w) => String(w.code));
798
861
  const profile = toleratingProfile(quirk);
799
862
  const withProfile = profile !== void 0 && descriptor.toleratingProfile !== void 0 ? (() => {
800
- const warnings = parseAstmRecords(artifact.content, { profile }).warnings.map(
801
- (w) => String(w.code)
802
- );
863
+ const warnings = astmPeer().parseAstmRecords(artifact.content, { profile }).warnings.map((w) => String(w.code));
803
864
  return {
804
865
  profileName: descriptor.toleratingProfile,
805
866
  disposition: descriptor.disposition,
@@ -849,23 +910,23 @@ var astmQuirkProfile = defineSynthProfile({
849
910
  // src/astm/index.ts
850
911
  var ALL_KINDS = Object.freeze(["Result", "Order"]);
851
912
  var DEFAULT_MIX = ALL_KINDS;
852
- function generateKind(kind, seed) {
913
+ function generateKind(kind, seed, verify) {
853
914
  switch (kind) {
854
915
  case "Result":
855
- return astmRoundTrip(generateAstmResult({ seed }));
916
+ return astmCorpusArtifact(generateAstmResult({ seed }), verify);
856
917
  case "Order":
857
- return astmRoundTrip(generateAstmOrder({ seed }));
918
+ return astmCorpusArtifact(generateAstmOrder({ seed }), verify);
858
919
  }
859
920
  }
860
921
  function astmCorpus(options) {
861
- const { seed } = options;
922
+ const { seed, verify = true } = options;
862
923
  const mix = resolveMix(ALL_KINDS, options.mix, DEFAULT_MIX);
863
924
  const count = options.count ?? mix.length;
864
925
  const seedStream = createRng(seed);
865
926
  const artifacts = Array.from({ length: count }, (_unused, i) => {
866
927
  const kind = mix[i % mix.length] ?? "Result";
867
928
  const msgSeed = seedStream.nextUint32();
868
- const rt = generateKind(kind, msgSeed);
929
+ const rt = generateKind(kind, msgSeed, verify);
869
930
  return {
870
931
  format: "astm",
871
932
  kind,