@cosyte/synth 0.1.9 → 0.1.10

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 (55) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/README.md +28 -17
  3. package/dist/animal-code-Bdl_9Jma.d.cts +30 -0
  4. package/dist/animal-code-Bdl_9Jma.d.ts +30 -0
  5. package/dist/astm/index.cjs +147 -17
  6. package/dist/astm/index.cjs.map +1 -1
  7. package/dist/astm/index.d.cts +22 -9
  8. package/dist/astm/index.d.ts +22 -9
  9. package/dist/astm/index.mjs +147 -17
  10. package/dist/astm/index.mjs.map +1 -1
  11. package/dist/ccda/index.cjs +147 -17
  12. package/dist/ccda/index.cjs.map +1 -1
  13. package/dist/ccda/index.d.cts +12 -3
  14. package/dist/ccda/index.d.ts +12 -3
  15. package/dist/ccda/index.mjs +147 -17
  16. package/dist/ccda/index.mjs.map +1 -1
  17. package/dist/deid/index.cjs +537 -30
  18. package/dist/deid/index.cjs.map +1 -1
  19. package/dist/deid/index.d.cts +2 -1
  20. package/dist/deid/index.d.ts +2 -1
  21. package/dist/deid/index.mjs +537 -30
  22. package/dist/deid/index.mjs.map +1 -1
  23. package/dist/fhir/index.cjs +142 -14
  24. package/dist/fhir/index.cjs.map +1 -1
  25. package/dist/fhir/index.d.cts +4 -1
  26. package/dist/fhir/index.d.ts +4 -1
  27. package/dist/fhir/index.mjs +142 -14
  28. package/dist/fhir/index.mjs.map +1 -1
  29. package/dist/hl7/index.cjs +566 -39
  30. package/dist/hl7/index.cjs.map +1 -1
  31. package/dist/hl7/index.d.cts +402 -111
  32. package/dist/hl7/index.d.ts +402 -111
  33. package/dist/hl7/index.mjs +557 -40
  34. package/dist/hl7/index.mjs.map +1 -1
  35. package/dist/index.cjs +124 -13
  36. package/dist/index.cjs.map +1 -1
  37. package/dist/index.d.cts +183 -10
  38. package/dist/index.d.ts +183 -10
  39. package/dist/index.mjs +115 -14
  40. package/dist/index.mjs.map +1 -1
  41. package/dist/ncpdp/index.cjs +142 -14
  42. package/dist/ncpdp/index.cjs.map +1 -1
  43. package/dist/ncpdp/index.d.cts +4 -1
  44. package/dist/ncpdp/index.d.ts +4 -1
  45. package/dist/ncpdp/index.mjs +142 -14
  46. package/dist/ncpdp/index.mjs.map +1 -1
  47. package/dist/{quirk-BfObsiWV.d.ts → quirk-CzknuYUo.d.ts} +16 -5
  48. package/dist/{quirk-BCSs1jPt.d.cts → quirk-Q5AxzHSC.d.cts} +16 -5
  49. package/dist/x12/index.cjs +142 -14
  50. package/dist/x12/index.cjs.map +1 -1
  51. package/dist/x12/index.d.cts +4 -1
  52. package/dist/x12/index.d.ts +4 -1
  53. package/dist/x12/index.mjs +142 -14
  54. package/dist/x12/index.mjs.map +1 -1
  55. package/package.json +7 -4
@@ -73,7 +73,11 @@ var SYNTH_FATAL_CODES = Object.freeze({
73
73
  SYNTH_INVALID_DECIMAL: "SYNTH_INVALID_DECIMAL",
74
74
  /** An integer range was requested with its maximum below its minimum. Fatal. */
75
75
  SYNTH_INVALID_RANGE: "SYNTH_INVALID_RANGE",
76
- /** A value was drawn from an empty pool. Fatal, never a fabricated substitute. */
76
+ /**
77
+ * A value was drawn from an empty pool. Fatal, never a fabricated substitute. A corpus given an
78
+ * empty `mix`, `triggers` or `quirks` list (or a profile whose `quirks` is an empty list) raises it
79
+ * too: there is no kind to cycle through, and an empty list is never read as the default.
80
+ */
77
81
  SYNTH_EMPTY_POOL: "SYNTH_EMPTY_POOL",
78
82
  /** A `defineSynthProfile` spec was not usable (a missing or blank `name`). Fatal. */
79
83
  SYNTH_INVALID_PROFILE: "SYNTH_INVALID_PROFILE",
@@ -92,7 +96,21 @@ var SYNTH_FATAL_CODES = Object.freeze({
92
96
  * apart without matching message text. Fatal, and raised **before** anything is generated: a
93
97
  * mislabelled artifact is worse than no artifact.
94
98
  */
95
- SYNTH_PROFILE_NOT_GENERATED: "SYNTH_PROFILE_NOT_GENERATED"
99
+ SYNTH_PROFILE_NOT_GENERATED: "SYNTH_PROFILE_NOT_GENERATED",
100
+ /**
101
+ * The sibling parser a generator builds through is not installed. Raised on the first call that
102
+ * needs it, by both builds, only when the missing package is the parser itself: a missing
103
+ * dependency of an installed parser is thrown as Node.js raised it. The error carries the parser's
104
+ * package name on `peer`, a message from {@link SYNTH_PEER_MESSAGES} that names its install
105
+ * command, and the error Node.js raised on `cause`.
106
+ */
107
+ SYNTH_PEER_NOT_INSTALLED: "SYNTH_PEER_NOT_INSTALLED",
108
+ /**
109
+ * An HL7 v2 `ACK` was asked for an inbound message that carries no MSH-10 message control id.
110
+ * MSA-2 must echo that id, and an acknowledgment that cannot be matched to the message it answers
111
+ * is not a fixture worth having, so nothing is generated.
112
+ */
113
+ SYNTH_ACK_UNCORRELATED: "SYNTH_ACK_UNCORRELATED"
96
114
  });
97
115
  var SYNTH_FATAL_MESSAGES = Object.freeze({
98
116
  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.",
@@ -105,18 +123,38 @@ var SYNTH_FATAL_MESSAGES = Object.freeze({
105
123
  SYNTH_EMPTY_POOL: "A value was drawn from an empty pool.",
106
124
  SYNTH_INVALID_PROFILE: "defineSynthProfile requires a non-empty string name.",
107
125
  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.",
108
- SYNTH_PROFILE_NOT_GENERATED: "The requested profile is published by the adopted implementation guide, and this build does not generate it. The coverage surface reports, per adopted profile, whether it is generated."
126
+ SYNTH_PROFILE_NOT_GENERATED: "The requested profile is published by the adopted implementation guide, and this build does not generate it. The coverage surface reports, per adopted profile, whether it is generated.",
127
+ SYNTH_PEER_NOT_INSTALLED: "A sibling @cosyte parser this generator builds through is not installed. Install it beside @cosyte/synth.",
128
+ SYNTH_ACK_UNCORRELATED: (
129
+ // Stryker disable next-line all: a module-level table runs once, at import; the docs test holds it.
130
+ "The inbound message has no MSH-10 control id for an ACK to echo in MSA-2."
131
+ )
132
+ });
133
+ var SYNTH_PEER_MESSAGES = Object.freeze({
134
+ "@cosyte/hl7": "@cosyte/hl7 is not installed, and this generator builds through it. Install it beside @cosyte/synth: npm install @cosyte/hl7",
135
+ "@cosyte/fhir": "@cosyte/fhir is not installed, and this generator builds through it. Install it beside @cosyte/synth: npm install @cosyte/fhir",
136
+ "@cosyte/ccda": "@cosyte/ccda is not installed, and this generator builds through it. Install it beside @cosyte/synth: npm install @cosyte/ccda",
137
+ "@cosyte/x12": "@cosyte/x12 is not installed, and this generator builds through it. Install it beside @cosyte/synth: npm install @cosyte/x12",
138
+ "@cosyte/ncpdp": "@cosyte/ncpdp is not installed, and this generator builds through it. Install it beside @cosyte/synth: npm install @cosyte/ncpdp",
139
+ "@cosyte/astm": "@cosyte/astm is not installed, and this generator builds through it. Install it beside @cosyte/synth: npm install @cosyte/astm",
140
+ "@cosyte/deid": "@cosyte/deid is not installed, and this generator builds through it. Install it beside @cosyte/synth: npm install @cosyte/deid"
109
141
  });
110
142
  var SynthError = class extends Error {
111
143
  /** The stable fatal code. */
112
144
  code;
113
145
  /**
114
146
  * @param code - The stable {@link SynthFatalCode}. The message comes from the frozen registry.
147
+ * @param options - The error that caused this one, and for a missing parser its package.
115
148
  */
116
- constructor(code) {
117
- super(SYNTH_FATAL_MESSAGES[code]);
149
+ constructor(code, options = {}) {
150
+ const peer = code === SYNTH_FATAL_CODES.SYNTH_PEER_NOT_INSTALLED ? options.peer : void 0;
151
+ super(
152
+ peer === void 0 ? SYNTH_FATAL_MESSAGES[code] : SYNTH_PEER_MESSAGES[peer],
153
+ "cause" in options ? { cause: options.cause } : void 0
154
+ );
118
155
  this.name = "SynthError";
119
156
  this.code = code;
157
+ if (peer !== void 0) Object.defineProperty(this, "peer", { value: peer, enumerable: true });
120
158
  }
121
159
  };
122
160
 
@@ -232,7 +270,7 @@ function luhnMod10(digits) {
232
270
  if (d < 0 || d > 9) continue;
233
271
  if (double) {
234
272
  d *= 2;
235
- if (d > 9) d -= 9;
273
+ if (d >= 10) d -= 9;
236
274
  }
237
275
  sum += d;
238
276
  double = !double;
@@ -329,7 +367,7 @@ var SYNTHETIC_CITY_NAMES = Object.freeze([
329
367
  ]);
330
368
 
331
369
  // src/safe/providers.ts
332
- function ssn(rng, block = "never-issued") {
370
+ function ssn(rng, block) {
333
371
  if (block === "advertising") {
334
372
  return `987-00-432${String(rng.int(0, 9))}`;
335
373
  }
@@ -456,6 +494,63 @@ var US_STATES = Object.freeze([
456
494
  "WY"
457
495
  ]);
458
496
 
497
+ // src/safe/animal-code.ts
498
+ var ANIMAL_CODE_SYSTEM = "99COSYTE";
499
+ function animalCode(value, text) {
500
+ return Object.freeze({ code: value, text, system: ANIMAL_CODE_SYSTEM });
501
+ }
502
+ function animalSpecies(value, text, breeds) {
503
+ return Object.freeze({ ...animalCode(value, text), breeds: Object.freeze([...breeds]) });
504
+ }
505
+
506
+ // src/safe/animal.ts
507
+ var SYNTHETIC_ANIMAL_SPECIES = Object.freeze([
508
+ animalSpecies("DOG", "Dog", [
509
+ animalCode("DOG-LAB", "Labrador Retriever"),
510
+ animalCode("DOG-BGL", "Beagle"),
511
+ animalCode("DOG-MIX", "Mixed breed dog")
512
+ ]),
513
+ animalSpecies("CAT", "Cat", [animalCode("CAT-DSH", "Domestic Shorthair"), animalCode("CAT-SIA", "Siamese")]),
514
+ animalSpecies("HORSE", "Horse", [animalCode("HRS-QTR", "Quarter Horse"), animalCode("HRS-TB", "Thoroughbred")]),
515
+ animalSpecies("CATTLE", "Cattle", [animalCode("CTL-HOL", "Holstein"), animalCode("CTL-ANG", "Angus")])
516
+ ]);
517
+ var SYNTHETIC_PET_NAMES = Object.freeze([
518
+ "Fixturepaw",
519
+ "Mockwhisker",
520
+ "Stubtail",
521
+ "Seedpup",
522
+ "Testbiscuit",
523
+ "Placepurr",
524
+ "Synthfur",
525
+ "Dummymane",
526
+ "Examplehoof",
527
+ "Fauxfluff",
528
+ "Samplesnout",
529
+ "Voidwag"
530
+ ]);
531
+ var MICROCHIP_TEST_PREFIX = "999";
532
+ function petName(rng) {
533
+ return rng.pick(SYNTHETIC_PET_NAMES);
534
+ }
535
+ function animalBreed(rng) {
536
+ const picked = rng.pick(SYNTHETIC_ANIMAL_SPECIES);
537
+ const { breeds, ...bare } = picked;
538
+ return { species: Object.freeze(bare), breed: rng.pick(breeds) };
539
+ }
540
+ function microchip(rng) {
541
+ return `${MICROCHIP_TEST_PREFIX}${rng.digits(12)}`;
542
+ }
543
+ function animalBirthDate(rng, onDate) {
544
+ const year = Number(onDate.slice(0, 4));
545
+ const month = Number(onDate.slice(4, 6)) - 1;
546
+ const day = Number(onDate.slice(6, 8));
547
+ const latest = Date.UTC(year, month, day);
548
+ const earliest = Date.UTC(year - 20, month, day);
549
+ const span = Math.round((latest - earliest) / 864e5);
550
+ const born = new Date(latest - rng.int(0, span) * 864e5);
551
+ return born.toISOString().slice(0, 10).replace(/-/g, "");
552
+ }
553
+
459
554
  // src/safe/index.ts
460
555
  var safe = Object.freeze({
461
556
  ssn,
@@ -469,25 +564,54 @@ var safe = Object.freeze({
469
564
  address,
470
565
  dateYmd,
471
566
  npi,
472
- dea
567
+ dea,
568
+ petName,
569
+ animalBreed,
570
+ microchip,
571
+ animalBirthDate
473
572
  });
474
573
  function defaultHost() {
475
574
  if (typeof undefined !== "function") return { require: __require, resolve: void 0 };
476
575
  return { require: module$1.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href))), resolve: (s) => undefined(s) };
477
576
  }
577
+ function isDenoResolveRefusal(error) {
578
+ return error instanceof TypeError && !("code" in error) && error.message === "Invalid arguments";
579
+ }
478
580
  function isRequireEsmRefusal(error) {
479
581
  return error?.code === "ERR_REQUIRE_ESM";
480
582
  }
583
+ function peerPackage(specifier) {
584
+ const name2 = specifier.split("/").slice(0, 2).join("/");
585
+ return Object.hasOwn(SYNTH_PEER_MESSAGES, name2) ? name2 : void 0;
586
+ }
587
+ function isPeerNotFound(error, specifier, peer) {
588
+ const { code, message } = error ?? {};
589
+ if (typeof message !== "string") return false;
590
+ if (code === "ERR_MODULE_NOT_FOUND") return message.startsWith(`Cannot find package '${peer}'`);
591
+ if (code === "MODULE_NOT_FOUND") return message.startsWith(`Cannot find module '${specifier}'`);
592
+ return false;
593
+ }
481
594
  function loadPeer(specifier, host) {
595
+ try {
596
+ return loadFrom(specifier, host);
597
+ } catch (error) {
598
+ const peer = peerPackage(specifier);
599
+ if (peer === void 0 || !isPeerNotFound(error, specifier, peer)) throw error;
600
+ throw new SynthError(SYNTH_FATAL_CODES.SYNTH_PEER_NOT_INSTALLED, { peer, cause: error });
601
+ }
602
+ }
603
+ function loadFrom(specifier, host) {
482
604
  if (host.resolve === void 0) return host.require(specifier);
483
- let entry;
605
+ let url$1;
484
606
  try {
485
- entry = url.fileURLToPath(host.resolve(specifier));
486
- } catch {
607
+ url$1 = host.resolve(specifier);
608
+ } catch (error) {
609
+ if (!isDenoResolveRefusal(error)) throw error;
487
610
  return host.require(specifier);
488
611
  }
612
+ if (!url$1.startsWith("file:")) return host.require(specifier);
489
613
  try {
490
- return host.require(entry);
614
+ return host.require(url.fileURLToPath(url$1));
491
615
  } catch (error) {
492
616
  if (!isRequireEsmRefusal(error)) throw error;
493
617
  return host.require(specifier);
@@ -509,6 +633,20 @@ function seededTimestamp(rng) {
509
633
  const ss = String(rng.int(0, 59)).padStart(2, "0");
510
634
  return `${date}${hh}${mm}${ss}`;
511
635
  }
636
+ function shiftTimestamp(timestamp, seconds) {
637
+ const at = new Date(
638
+ Date.UTC(
639
+ Number(timestamp.slice(0, 4)),
640
+ Number(timestamp.slice(4, 6)) - 1,
641
+ Number(timestamp.slice(6, 8)),
642
+ Number(timestamp.slice(8, 10)),
643
+ Number(timestamp.slice(10, 12)),
644
+ Number(timestamp.slice(12, 14)) + seconds
645
+ )
646
+ );
647
+ const pad = (n, width = 2) => String(n).padStart(width, "0");
648
+ return pad(at.getUTCFullYear(), 4) + pad(at.getUTCMonth() + 1) + pad(at.getUTCDate()) + pad(at.getUTCHours()) + pad(at.getUTCMinutes()) + pad(at.getUTCSeconds());
649
+ }
512
650
  function mshScaffold(rng, type) {
513
651
  const timestamp = seededTimestamp(rng);
514
652
  const controlId = `SYNTH${rng.digits(10)}`;
@@ -579,6 +717,107 @@ function pidSegment(id) {
579
717
  // PID-19 SSN (digits)
580
718
  ];
581
719
  }
720
+ function hl7Subjects() {
721
+ return ["person", "animal"];
722
+ }
723
+ var MICROCHIP_IDENTIFIER_TYPE = "MCN";
724
+ var OWNER_RELATIONSHIP = Object.freeze({ code: "OWN", text: "Owner", system: "HL70063" });
725
+ function animalIdentity(rng, onDate) {
726
+ const person = safe.name(rng);
727
+ const petName2 = safe.petName(rng);
728
+ const record = safe.identifier(rng, "MR");
729
+ const microchip2 = safe.microchip(rng);
730
+ const { species, breed } = safe.animalBreed(rng);
731
+ const dob = safe.animalBirthDate(rng, onDate);
732
+ const sex = rng.pick(["M", "F"]);
733
+ const address2 = safe.address(rng);
734
+ const phone2 = safe.phone(rng);
735
+ return {
736
+ petName: petName2,
737
+ record,
738
+ microchip: microchip2,
739
+ species,
740
+ breed,
741
+ dob,
742
+ sex,
743
+ owner: { person, address: address2, phone: phone2 }
744
+ };
745
+ }
746
+ function codedField(value) {
747
+ return componentsField([value.code, value.text, value.system]);
748
+ }
749
+ function animalPidSegment(id) {
750
+ const authority = id.record.assigningAuthority;
751
+ const cx = (value, type) => ({
752
+ components: [value, "", "", authority, type].map((c) => ({ subcomponents: [c] }))
753
+ });
754
+ const identifiers = {
755
+ repetitions: [
756
+ cx(id.record.value, id.record.typeCode),
757
+ cx(id.microchip, MICROCHIP_IDENTIFIER_TYPE)
758
+ ],
759
+ isNull: false
760
+ };
761
+ const { person, address: address2, phone: phone2 } = id.owner;
762
+ return [
763
+ "1",
764
+ // PID-1 set id
765
+ "",
766
+ // PID-2
767
+ identifiers,
768
+ // PID-3 CX: record number, then microchip
769
+ "",
770
+ // PID-4
771
+ componentsField([person.family, id.petName]),
772
+ // PID-5 XPN: owner family, pet name
773
+ "",
774
+ // PID-6
775
+ id.dob,
776
+ // PID-7 DOB
777
+ id.sex,
778
+ // PID-8 sex
779
+ "",
780
+ // PID-9
781
+ "",
782
+ // PID-10
783
+ componentsField([address2.street, "", address2.city, address2.state, address2.zip]),
784
+ // PID-11 XAD
785
+ "",
786
+ // PID-12
787
+ phone2,
788
+ // PID-13 phone
789
+ ...Array(21).fill(""),
790
+ // PID-14 to PID-34
791
+ codedField(id.species),
792
+ // PID-35 species code (CE)
793
+ codedField(id.breed)
794
+ // PID-36 breed code (CE)
795
+ ];
796
+ }
797
+ function ownerNk1Segment(id) {
798
+ const { person, address: address2, phone: phone2 } = id.owner;
799
+ return [
800
+ "1",
801
+ // NK1-1 set id
802
+ componentsField([person.family, person.given]),
803
+ // NK1-2 name
804
+ codedField(OWNER_RELATIONSHIP),
805
+ // NK1-3 relationship (CE)
806
+ componentsField([address2.street, "", address2.city, address2.state, address2.zip]),
807
+ // NK1-4
808
+ phone2
809
+ // NK1-5 phone
810
+ ];
811
+ }
812
+ function addSubject(message, rng, subject, timestamp) {
813
+ if (subject === "person") {
814
+ message.addSegment("PID", pidSegment(patientIdentity(rng)));
815
+ return;
816
+ }
817
+ const animal = animalIdentity(rng, timestamp);
818
+ message.addSegment("PID", animalPidSegment(animal));
819
+ message.addSegment("NK1", ownerNk1Segment(animal));
820
+ }
582
821
 
583
822
  // src/select.ts
584
823
  function resolveKind(allowed, requested) {
@@ -590,27 +829,90 @@ function resolveMix(allowed, requested, fallback) {
590
829
  if (requested === void 0) return fallback;
591
830
  return requested.map((entry) => resolveKind(allowed, entry));
592
831
  }
832
+ function requireSelection(selection) {
833
+ if (selection.length === 0) throw new SynthError(SYNTH_FATAL_CODES.SYNTH_EMPTY_POOL);
834
+ return selection;
835
+ }
593
836
 
594
837
  // src/hl7/adt.ts
595
- var ADT_TRIGGERS = Object.freeze(["A01", "A04", "A08"]);
838
+ function adtTriggers() {
839
+ return ["A01", "A04", "A08", "A02", "A03", "A11", "A13", "A28", "A31"];
840
+ }
841
+ function structureOf(trigger) {
842
+ switch (trigger) {
843
+ case "A02":
844
+ return "ADT_A02";
845
+ case "A03":
846
+ return "ADT_A03";
847
+ case "A11":
848
+ return "ADT_A09";
849
+ case "A13":
850
+ return "ADT_A01";
851
+ case "A28":
852
+ case "A31":
853
+ return "ADT_A05";
854
+ }
855
+ }
856
+ function ward(room) {
857
+ return componentsField(["SYNTHWARD", String(room).padStart(3, "0"), "01"]);
858
+ }
859
+ function location(rng) {
860
+ return ward(rng.int(1, 999));
861
+ }
862
+ function admittedBefore(rng, timestamp) {
863
+ return shiftTimestamp(timestamp, -rng.int(3600, 14 * 86400));
864
+ }
596
865
  function generateAdt(options = {}) {
597
866
  const seed = options.seed ?? 0;
598
- const trigger = resolveKind(ADT_TRIGGERS, options.trigger ?? "A01");
867
+ const trigger = resolveKind(adtTriggers(), options.trigger ?? "A01");
868
+ const subject = resolveKind(hl7Subjects(), options.subject ?? "person");
599
869
  const rng = createRng(seed);
600
- const { message, timestamp } = mshScaffold(rng, `ADT^${trigger}`);
870
+ if (trigger === "A01" || trigger === "A04" || trigger === "A08") {
871
+ const { message: message2, timestamp: timestamp2 } = mshScaffold(rng, `ADT^${trigger}`);
872
+ message2.addSegment("EVN", [trigger, timestamp2]);
873
+ addSubject(message2, rng, subject, timestamp2);
874
+ const patientClass = rng.pick(["I", "O", "E"]);
875
+ message2.addSegment("PV1", [
876
+ "1",
877
+ // PV1-1 set id
878
+ patientClass,
879
+ // PV1-2 patient class
880
+ location(rng)
881
+ // PV1-3 PL
882
+ ]);
883
+ return message2;
884
+ }
885
+ const { message, timestamp } = mshScaffold(rng, `ADT^${trigger}^${structureOf(trigger)}`);
601
886
  message.addSegment("EVN", [trigger, timestamp]);
602
- message.addSegment("PID", pidSegment(patientIdentity(rng)));
603
- const patientClass = rng.pick(["I", "O", "E"]);
604
- message.addSegment("PV1", [
605
- "1",
606
- // PV1-1 set id
607
- patientClass,
608
- // PV1-2 patient class
609
- componentsField(["SYNTHWARD", String(rng.int(1, 999)).padStart(3, "0"), "01"])
610
- // PV1-3 PL
611
- ]);
887
+ addSubject(message, rng, subject, timestamp);
888
+ message.addSegment("PV1", visit(rng, trigger, timestamp));
612
889
  return message;
613
890
  }
891
+ function visit(rng, trigger, timestamp) {
892
+ switch (trigger) {
893
+ case "A02": {
894
+ const room = rng.int(1, 998);
895
+ const priorRoom = room + rng.int(1, 999 - room);
896
+ return ["1", "I", ward(room), "", "", ward(priorRoom)];
897
+ }
898
+ case "A03": {
899
+ const patientClass = rng.pick(["I", "O", "E"]);
900
+ const where = location(rng);
901
+ const admitted = admittedBefore(rng, timestamp);
902
+ return ["1", patientClass, where, ...Array(40).fill(""), admitted, timestamp];
903
+ }
904
+ case "A11":
905
+ return ["1", rng.pick(["I", "O", "E"]), location(rng)];
906
+ case "A13": {
907
+ const where = location(rng);
908
+ const admitted = admittedBefore(rng, timestamp);
909
+ return ["1", "I", where, ...Array(40).fill(""), admitted];
910
+ }
911
+ case "A28":
912
+ case "A31":
913
+ return ["1", "N"];
914
+ }
915
+ }
614
916
 
615
917
  // src/hl7/example-codes.ts
616
918
  var EXAMPLE_LAB_OBSERVATIONS = Object.freeze([
@@ -675,9 +977,10 @@ function obxSegment(rng, setId) {
675
977
  }
676
978
  function generateOru(options = {}) {
677
979
  const seed = options.seed ?? 0;
980
+ const subject = resolveKind(hl7Subjects(), options.subject ?? "person");
678
981
  const rng = createRng(seed);
679
- const { message } = mshScaffold(rng, "ORU^R01");
680
- message.addSegment("PID", pidSegment(patientIdentity(rng)));
982
+ const { message, timestamp } = mshScaffold(rng, "ORU^R01");
983
+ addSubject(message, rng, subject, timestamp);
681
984
  const service = rng.pick(EXAMPLE_ORDER_SERVICES);
682
985
  const placer = rng.digits(8);
683
986
  const filler = rng.digits(8);
@@ -868,6 +1171,184 @@ function generateVxu(options = {}) {
868
1171
  return message;
869
1172
  }
870
1173
 
1174
+ // src/hl7/mdm.ts
1175
+ var DOCUMENT_TYPES = Object.freeze(["CN", "DS", "HP", "PN"]);
1176
+ var NOTE_LINES = Object.freeze([
1177
+ "Synthetic test document for interface testing.",
1178
+ "Synthetic note text: no real patient is described here.",
1179
+ "Synthetic content generated from a seed."
1180
+ ]);
1181
+ function generateMdm(options = {}) {
1182
+ const seed = options.seed ?? 0;
1183
+ const rng = createRng(seed);
1184
+ const { message, timestamp } = mshScaffold(rng, "MDM^T02^MDM_T02");
1185
+ message.addSegment("EVN", ["T02", timestamp]);
1186
+ message.addSegment("PID", pidSegment(patientIdentity(rng)));
1187
+ message.addSegment("PV1", ["1", rng.pick(["I", "O", "E"])]);
1188
+ const documentType = rng.pick(DOCUMENT_TYPES);
1189
+ const documentNumber = `SYNTHDOC${rng.digits(10)}`;
1190
+ message.addSegment("TXA", [
1191
+ "1",
1192
+ // TXA-1 set id
1193
+ documentType,
1194
+ // TXA-2 document type
1195
+ "TX",
1196
+ // TXA-3 document content presentation (text)
1197
+ timestamp,
1198
+ // TXA-4 activity date/time
1199
+ ...Array(7).fill(""),
1200
+ // TXA-5 to TXA-11
1201
+ componentsField([documentNumber, "COSYTE-SYNTH"]),
1202
+ // TXA-12 unique document number (EI)
1203
+ ...Array(4).fill(""),
1204
+ // TXA-13 to TXA-16
1205
+ "DO"
1206
+ // TXA-17 document completion status (documented)
1207
+ ]);
1208
+ const lines = rng.int(1, 3);
1209
+ for (let i = 1; i <= lines; i += 1) {
1210
+ message.addSegment("OBX", [
1211
+ String(i),
1212
+ // OBX-1 set id
1213
+ "TX",
1214
+ // OBX-2 value type
1215
+ componentsField(["SYNTHNOTE", "Synthetic note", "99SYN"]),
1216
+ // OBX-3 observation id (local)
1217
+ "",
1218
+ // OBX-4 sub-id
1219
+ rng.pick(NOTE_LINES),
1220
+ // OBX-5 the document text
1221
+ ...Array(5).fill(""),
1222
+ // OBX-6 to OBX-10
1223
+ "F"
1224
+ // OBX-11 result status (final)
1225
+ ]);
1226
+ }
1227
+ return message;
1228
+ }
1229
+
1230
+ // src/hl7/dft.ts
1231
+ function charges() {
1232
+ return [
1233
+ { code: "SYNTHCHG01", text: "Synthetic office visit charge" },
1234
+ { code: "SYNTHCHG02", text: "Synthetic laboratory charge" },
1235
+ { code: "SYNTHCHG03", text: "Synthetic imaging charge" },
1236
+ { code: "SYNTHCHG04", text: "Synthetic supply charge" }
1237
+ ];
1238
+ }
1239
+ function generateDft(options = {}) {
1240
+ const seed = options.seed ?? 0;
1241
+ const rng = createRng(seed);
1242
+ const { message, timestamp } = mshScaffold(rng, "DFT^P03^DFT_P03");
1243
+ message.addSegment("EVN", ["P03", timestamp]);
1244
+ message.addSegment("PID", pidSegment(patientIdentity(rng)));
1245
+ message.addSegment("PV1", ["1", rng.pick(["I", "O", "E"])]);
1246
+ const transactionId = `SYNTHTXN${rng.digits(8)}`;
1247
+ const charge = rng.pick(charges());
1248
+ const quantity = String(rng.int(1, 3));
1249
+ message.addSegment("FT1", [
1250
+ "1",
1251
+ // FT1-1 set id
1252
+ transactionId,
1253
+ // FT1-2 transaction id
1254
+ "",
1255
+ // FT1-3 transaction batch id
1256
+ timestamp,
1257
+ // FT1-4 transaction date
1258
+ "",
1259
+ // FT1-5 transaction posting date
1260
+ "CG",
1261
+ // FT1-6 transaction type (charge)
1262
+ componentsField([charge.code, charge.text, "99SYN"]),
1263
+ // FT1-7 transaction code (local)
1264
+ "",
1265
+ // FT1-8 transaction description
1266
+ "",
1267
+ // FT1-9 transaction description, alternate
1268
+ quantity
1269
+ // FT1-10 transaction quantity
1270
+ ]);
1271
+ return message;
1272
+ }
1273
+
1274
+ // src/hl7/oml.ts
1275
+ function generateOml(options = {}) {
1276
+ const seed = options.seed ?? 0;
1277
+ const rng = createRng(seed);
1278
+ const { message } = mshScaffold(rng, "OML^O21^OML_O21");
1279
+ message.addSegment("PID", pidSegment(patientIdentity(rng)));
1280
+ const service = rng.pick(EXAMPLE_ORDER_SERVICES);
1281
+ const placer = rng.digits(8);
1282
+ const filler = rng.digits(8);
1283
+ const orderedAt = seededTimestamp(rng);
1284
+ message.addSegment("ORC", [
1285
+ "NW",
1286
+ // ORC-1 order control (new order)
1287
+ placer,
1288
+ // ORC-2 placer order number
1289
+ filler,
1290
+ // ORC-3 filler order number
1291
+ ...Array(5).fill(""),
1292
+ // ORC-4 to ORC-8
1293
+ orderedAt
1294
+ // ORC-9 date/time of transaction
1295
+ ]);
1296
+ message.addSegment("OBR", [
1297
+ "1",
1298
+ // OBR-1 set id
1299
+ placer,
1300
+ // OBR-2 placer order number (matches ORC)
1301
+ filler,
1302
+ // OBR-3 filler order number (matches ORC)
1303
+ componentsField([service.code, service.text, service.system])
1304
+ // OBR-4 universal service id
1305
+ ]);
1306
+ return message;
1307
+ }
1308
+
1309
+ // src/hl7/ack.ts
1310
+ function generateAck(options = {}) {
1311
+ const seed = options.seed ?? 0;
1312
+ const allowed = ["AA", "AE", "AR"];
1313
+ const code = resolveKind(allowed, options.code ?? "AA");
1314
+ const inbound = options.inbound ?? generateAdt({ seed });
1315
+ const inboundControlId = inbound.get("MSH.10") ?? "";
1316
+ if (inboundControlId === "") throw new SynthError(SYNTH_FATAL_CODES.SYNTH_ACK_UNCORRELATED);
1317
+ const rng = createRng(seed);
1318
+ const delaySeconds = rng.int(1, 59);
1319
+ const controlId = `SYNTHACK${rng.digits(10)}`;
1320
+ const inboundTime = String(inbound.get("MSH.7"));
1321
+ const timestamp = /^\d{14}$/.test(inboundTime) ? shiftTimestamp(inboundTime, delaySeconds) : seededTimestamp(rng);
1322
+ const trigger = inbound.get("MSH.9.2") ?? "";
1323
+ const message = hl7Peer().buildMessage({
1324
+ type: trigger === "" ? "ACK" : `ACK^${trigger}^ACK`,
1325
+ sendingApp: inbound.get("MSH.5") ?? "",
1326
+ sendingFacility: inbound.get("MSH.6") ?? "",
1327
+ receivingApp: inbound.get("MSH.3") ?? "",
1328
+ receivingFacility: inbound.get("MSH.4") ?? "",
1329
+ controlId,
1330
+ timestamp,
1331
+ version: inbound.get("MSH.12") ?? "2.5",
1332
+ // MSH-11 processing ID `T` (HL7 table 0103: training): an acknowledgment of test data is test
1333
+ // data, so it never echoes a `P` from the inbound message.
1334
+ processingId: "T"
1335
+ });
1336
+ message.addSegment("MSA", [code, inboundControlId]);
1337
+ if (code !== "AA") {
1338
+ message.addSegment("ERR", [
1339
+ "",
1340
+ // ERR-1 error code and location (backward compatible only)
1341
+ "",
1342
+ // ERR-2 error location
1343
+ componentsField(["207", "Application internal error", "HL70357"]),
1344
+ // ERR-3 HL7 error code
1345
+ "E"
1346
+ // ERR-4 severity (error)
1347
+ ]);
1348
+ }
1349
+ return message;
1350
+ }
1351
+
871
1352
  // src/hl7/round-trip.ts
872
1353
  function roundTrip(message) {
873
1354
  const content = message.toString();
@@ -899,7 +1380,7 @@ function defineSynthProfile(spec) {
899
1380
  name: spec.name,
900
1381
  ...spec.givenNames ? { givenNames: Object.freeze([...spec.givenNames]) } : {},
901
1382
  ...spec.familyNames ? { familyNames: Object.freeze([...spec.familyNames]) } : {},
902
- quirks: Object.freeze([...spec.quirks ?? []])
1383
+ ...spec.quirks !== void 0 ? { quirks: Object.freeze([...spec.quirks]) } : {}
903
1384
  });
904
1385
  }
905
1386
 
@@ -941,6 +1422,7 @@ function assertIntendedWarnings(intendedWarnings, bareWarnings) {
941
1422
  }
942
1423
  }
943
1424
  function validateProfileQuirks(profile, registry, format) {
1425
+ if (profile.quirks === void 0) return Object.keys(registry);
944
1426
  for (const name2 of profile.quirks) resolveQuirk(registry, format, name2);
945
1427
  return profile.quirks;
946
1428
  }
@@ -1054,8 +1536,9 @@ var ALL_HL7_QUIRKS = Object.freeze(
1054
1536
  Object.keys(HL7_QUIRKS)
1055
1537
  );
1056
1538
  function hl7QuirkCorpus(options) {
1057
- const quirks = options.profile ? validateProfileQuirks(options.profile, HL7_QUIRKS, "hl7v2") : options.quirks ?? ALL_HL7_QUIRKS;
1058
- const names = quirks.length > 0 ? quirks : ALL_HL7_QUIRKS;
1539
+ const names = requireSelection(
1540
+ options.profile?.quirks !== void 0 ? validateProfileQuirks(options.profile, HL7_QUIRKS, "hl7v2") : options.quirks ?? ALL_HL7_QUIRKS
1541
+ );
1059
1542
  for (const name2 of names) resolveQuirk(HL7_QUIRKS, "hl7v2", name2);
1060
1543
  const kind = options.kind ?? "ORU^R01";
1061
1544
  const count = options.count ?? names.length;
@@ -1079,19 +1562,27 @@ var hl7QuirkProfile = defineSynthProfile({
1079
1562
  });
1080
1563
 
1081
1564
  // src/hl7/index.ts
1082
- var ALL_KINDS = Object.freeze([
1565
+ var HL7_MESSAGE_KINDS = Object.freeze([
1083
1566
  "ADT^A01",
1084
1567
  "ADT^A04",
1085
1568
  "ADT^A08",
1086
1569
  "ORU^R01",
1087
1570
  "ORM^O01",
1088
1571
  "SIU^S12",
1089
- "VXU^V04"
1572
+ "VXU^V04",
1573
+ "ACK",
1574
+ "ADT^A02",
1575
+ "ADT^A03",
1576
+ "ADT^A11",
1577
+ "ADT^A13",
1578
+ "ADT^A28",
1579
+ "ADT^A31",
1580
+ "MDM^T02",
1581
+ "DFT^P03",
1582
+ "OML^O21"
1090
1583
  ]);
1091
- var DEFAULT_MIX = ALL_KINDS;
1092
- var ADT_TRIGGERS2 = Object.freeze(["A01", "A04", "A08"]);
1093
1584
  function generateHl7(kind, seed) {
1094
- switch (resolveKind(ALL_KINDS, kind)) {
1585
+ switch (resolveKind(HL7_MESSAGE_KINDS, kind)) {
1095
1586
  case "ADT^A01":
1096
1587
  return generateAdt({ seed, trigger: "A01" });
1097
1588
  case "ADT^A04":
@@ -1106,16 +1597,42 @@ function generateHl7(kind, seed) {
1106
1597
  return generateSiu({ seed });
1107
1598
  case "VXU^V04":
1108
1599
  return generateVxu({ seed });
1600
+ case "ACK":
1601
+ return generateAck({ seed });
1602
+ case "ADT^A02":
1603
+ return generateAdt({ seed, trigger: "A02" });
1604
+ case "ADT^A03":
1605
+ return generateAdt({ seed, trigger: "A03" });
1606
+ case "ADT^A11":
1607
+ return generateAdt({ seed, trigger: "A11" });
1608
+ case "ADT^A13":
1609
+ return generateAdt({ seed, trigger: "A13" });
1610
+ case "ADT^A28":
1611
+ return generateAdt({ seed, trigger: "A28" });
1612
+ case "ADT^A31":
1613
+ return generateAdt({ seed, trigger: "A31" });
1614
+ case "MDM^T02":
1615
+ return generateMdm({ seed });
1616
+ case "DFT^P03":
1617
+ return generateDft({ seed });
1618
+ case "OML^O21":
1619
+ return generateOml({ seed });
1109
1620
  }
1110
1621
  }
1111
1622
  function hl7Corpus(options) {
1112
1623
  const { seed, count = 1, verify = true } = options;
1113
- const kinds = options.triggers !== void 0 ? options.triggers.map(
1114
- (t) => `ADT^${resolveKind(ADT_TRIGGERS2, t)}`
1115
- ) : resolveMix(ALL_KINDS, options.mix, DEFAULT_MIX);
1624
+ const kinds = requireSelection(
1625
+ options.triggers !== void 0 ? options.triggers.map(
1626
+ (t) => `ADT^${resolveKind(adtTriggers(), t)}`
1627
+ ) : (
1628
+ // The default mix is the first seven kinds: those added since are not in it, so a seed's
1629
+ // default corpus keeps the bytes it had.
1630
+ resolveMix(HL7_MESSAGE_KINDS, options.mix, HL7_MESSAGE_KINDS.slice(0, 7))
1631
+ )
1632
+ );
1116
1633
  const seedStream = createRng(seed);
1117
1634
  const artifacts = Array.from({ length: count }, (_unused, i) => {
1118
- const kind = kinds[i % kinds.length] ?? "ADT^A01";
1635
+ const kind = kinds[i % kinds.length];
1119
1636
  const messageSeed = seedStream.nextUint32();
1120
1637
  const rt = hl7CorpusArtifact(generateHl7(kind, messageSeed), verify);
1121
1638
  return {
@@ -1131,11 +1648,20 @@ function hl7Corpus(options) {
1131
1648
  exports.EXAMPLE_LAB_OBSERVATIONS = EXAMPLE_LAB_OBSERVATIONS;
1132
1649
  exports.EXAMPLE_ORDER_SERVICES = EXAMPLE_ORDER_SERVICES;
1133
1650
  exports.EXAMPLE_VACCINES = EXAMPLE_VACCINES;
1651
+ exports.HL7_MESSAGE_KINDS = HL7_MESSAGE_KINDS;
1134
1652
  exports.HL7_QUIRKS = HL7_QUIRKS;
1653
+ exports.MICROCHIP_IDENTIFIER_TYPE = MICROCHIP_IDENTIFIER_TYPE;
1654
+ exports.OWNER_RELATIONSHIP = OWNER_RELATIONSHIP;
1655
+ exports.animalIdentity = animalIdentity;
1656
+ exports.animalPidSegment = animalPidSegment;
1135
1657
  exports.componentsField = componentsField;
1658
+ exports.generateAck = generateAck;
1136
1659
  exports.generateAdt = generateAdt;
1660
+ exports.generateDft = generateDft;
1137
1661
  exports.generateHl7 = generateHl7;
1138
1662
  exports.generateHl7Quirk = generateHl7Quirk;
1663
+ exports.generateMdm = generateMdm;
1664
+ exports.generateOml = generateOml;
1139
1665
  exports.generateOrm = generateOrm;
1140
1666
  exports.generateOru = generateOru;
1141
1667
  exports.generateSiu = generateSiu;
@@ -1145,6 +1671,7 @@ exports.hl7QuirkCorpus = hl7QuirkCorpus;
1145
1671
  exports.hl7QuirkProfile = hl7QuirkProfile;
1146
1672
  exports.hl7QuirkRoundTrip = hl7QuirkRoundTrip;
1147
1673
  exports.mshScaffold = mshScaffold;
1674
+ exports.ownerNk1Segment = ownerNk1Segment;
1148
1675
  exports.patientIdentity = patientIdentity;
1149
1676
  exports.pidSegment = pidSegment;
1150
1677
  exports.roundTrip = roundTrip;