@cosyte/synth 0.1.9 → 0.1.11

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 +39 -0
  2. package/README.md +39 -19
  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 +416 -116
  32. package/dist/hl7/index.d.ts +416 -116
  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 +9 -5
@@ -70,7 +70,11 @@ var SYNTH_FATAL_CODES = Object.freeze({
70
70
  SYNTH_INVALID_DECIMAL: "SYNTH_INVALID_DECIMAL",
71
71
  /** An integer range was requested with its maximum below its minimum. Fatal. */
72
72
  SYNTH_INVALID_RANGE: "SYNTH_INVALID_RANGE",
73
- /** A value was drawn from an empty pool. Fatal, never a fabricated substitute. */
73
+ /**
74
+ * A value was drawn from an empty pool. Fatal, never a fabricated substitute. A corpus given an
75
+ * empty `mix`, `triggers` or `quirks` list (or a profile whose `quirks` is an empty list) raises it
76
+ * too: there is no kind to cycle through, and an empty list is never read as the default.
77
+ */
74
78
  SYNTH_EMPTY_POOL: "SYNTH_EMPTY_POOL",
75
79
  /** A `defineSynthProfile` spec was not usable (a missing or blank `name`). Fatal. */
76
80
  SYNTH_INVALID_PROFILE: "SYNTH_INVALID_PROFILE",
@@ -89,7 +93,21 @@ var SYNTH_FATAL_CODES = Object.freeze({
89
93
  * apart without matching message text. Fatal, and raised **before** anything is generated: a
90
94
  * mislabelled artifact is worse than no artifact.
91
95
  */
92
- SYNTH_PROFILE_NOT_GENERATED: "SYNTH_PROFILE_NOT_GENERATED"
96
+ SYNTH_PROFILE_NOT_GENERATED: "SYNTH_PROFILE_NOT_GENERATED",
97
+ /**
98
+ * The sibling parser a generator builds through is not installed. Raised on the first call that
99
+ * needs it, by both builds, only when the missing package is the parser itself: a missing
100
+ * dependency of an installed parser is thrown as Node.js raised it. The error carries the parser's
101
+ * package name on `peer`, a message from {@link SYNTH_PEER_MESSAGES} that names its install
102
+ * command, and the error Node.js raised on `cause`.
103
+ */
104
+ SYNTH_PEER_NOT_INSTALLED: "SYNTH_PEER_NOT_INSTALLED",
105
+ /**
106
+ * An HL7 v2 `ACK` was asked for an inbound message that carries no MSH-10 message control id.
107
+ * MSA-2 must echo that id, and an acknowledgment that cannot be matched to the message it answers
108
+ * is not a fixture worth having, so nothing is generated.
109
+ */
110
+ SYNTH_ACK_UNCORRELATED: "SYNTH_ACK_UNCORRELATED"
93
111
  });
94
112
  var SYNTH_FATAL_MESSAGES = Object.freeze({
95
113
  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.",
@@ -102,18 +120,38 @@ var SYNTH_FATAL_MESSAGES = Object.freeze({
102
120
  SYNTH_EMPTY_POOL: "A value was drawn from an empty pool.",
103
121
  SYNTH_INVALID_PROFILE: "defineSynthProfile requires a non-empty string name.",
104
122
  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.",
105
- 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."
123
+ 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.",
124
+ SYNTH_PEER_NOT_INSTALLED: "A sibling @cosyte parser this generator builds through is not installed. Install it beside @cosyte/synth.",
125
+ SYNTH_ACK_UNCORRELATED: (
126
+ // Stryker disable next-line all: a module-level table runs once, at import; the docs test holds it.
127
+ "The inbound message has no MSH-10 control id for an ACK to echo in MSA-2."
128
+ )
129
+ });
130
+ var SYNTH_PEER_MESSAGES = Object.freeze({
131
+ "@cosyte/hl7": "@cosyte/hl7 is not installed, and this generator builds through it. Install it beside @cosyte/synth: npm install @cosyte/hl7",
132
+ "@cosyte/fhir": "@cosyte/fhir is not installed, and this generator builds through it. Install it beside @cosyte/synth: npm install @cosyte/fhir",
133
+ "@cosyte/ccda": "@cosyte/ccda is not installed, and this generator builds through it. Install it beside @cosyte/synth: npm install @cosyte/ccda",
134
+ "@cosyte/x12": "@cosyte/x12 is not installed, and this generator builds through it. Install it beside @cosyte/synth: npm install @cosyte/x12",
135
+ "@cosyte/ncpdp": "@cosyte/ncpdp is not installed, and this generator builds through it. Install it beside @cosyte/synth: npm install @cosyte/ncpdp",
136
+ "@cosyte/astm": "@cosyte/astm is not installed, and this generator builds through it. Install it beside @cosyte/synth: npm install @cosyte/astm",
137
+ "@cosyte/deid": "@cosyte/deid is not installed, and this generator builds through it. Install it beside @cosyte/synth: npm install @cosyte/deid"
106
138
  });
107
139
  var SynthError = class extends Error {
108
140
  /** The stable fatal code. */
109
141
  code;
110
142
  /**
111
143
  * @param code - The stable {@link SynthFatalCode}. The message comes from the frozen registry.
144
+ * @param options - The error that caused this one, and for a missing parser its package.
112
145
  */
113
- constructor(code) {
114
- super(SYNTH_FATAL_MESSAGES[code]);
146
+ constructor(code, options = {}) {
147
+ const peer = code === SYNTH_FATAL_CODES.SYNTH_PEER_NOT_INSTALLED ? options.peer : void 0;
148
+ super(
149
+ peer === void 0 ? SYNTH_FATAL_MESSAGES[code] : SYNTH_PEER_MESSAGES[peer],
150
+ "cause" in options ? { cause: options.cause } : void 0
151
+ );
115
152
  this.name = "SynthError";
116
153
  this.code = code;
154
+ if (peer !== void 0) Object.defineProperty(this, "peer", { value: peer, enumerable: true });
117
155
  }
118
156
  };
119
157
 
@@ -207,7 +245,7 @@ function luhnMod10(digits) {
207
245
  if (d < 0 || d > 9) continue;
208
246
  if (double) {
209
247
  d *= 2;
210
- if (d > 9) d -= 9;
248
+ if (d >= 10) d -= 9;
211
249
  }
212
250
  sum += d;
213
251
  double = !double;
@@ -304,7 +342,7 @@ var SYNTHETIC_CITY_NAMES = Object.freeze([
304
342
  ]);
305
343
 
306
344
  // src/safe/providers.ts
307
- function ssn(rng, block = "never-issued") {
345
+ function ssn(rng, block) {
308
346
  if (block === "advertising") {
309
347
  return `987-00-432${String(rng.int(0, 9))}`;
310
348
  }
@@ -431,6 +469,63 @@ var US_STATES = Object.freeze([
431
469
  "WY"
432
470
  ]);
433
471
 
472
+ // src/safe/animal-code.ts
473
+ var ANIMAL_CODE_SYSTEM = "99COSYTE";
474
+ function animalCode(value, text) {
475
+ return Object.freeze({ code: value, text, system: ANIMAL_CODE_SYSTEM });
476
+ }
477
+ function animalSpecies(value, text, breeds) {
478
+ return Object.freeze({ ...animalCode(value, text), breeds: Object.freeze([...breeds]) });
479
+ }
480
+
481
+ // src/safe/animal.ts
482
+ var SYNTHETIC_ANIMAL_SPECIES = Object.freeze([
483
+ animalSpecies("DOG", "Dog", [
484
+ animalCode("DOG-LAB", "Labrador Retriever"),
485
+ animalCode("DOG-BGL", "Beagle"),
486
+ animalCode("DOG-MIX", "Mixed breed dog")
487
+ ]),
488
+ animalSpecies("CAT", "Cat", [animalCode("CAT-DSH", "Domestic Shorthair"), animalCode("CAT-SIA", "Siamese")]),
489
+ animalSpecies("HORSE", "Horse", [animalCode("HRS-QTR", "Quarter Horse"), animalCode("HRS-TB", "Thoroughbred")]),
490
+ animalSpecies("CATTLE", "Cattle", [animalCode("CTL-HOL", "Holstein"), animalCode("CTL-ANG", "Angus")])
491
+ ]);
492
+ var SYNTHETIC_PET_NAMES = Object.freeze([
493
+ "Fixturepaw",
494
+ "Mockwhisker",
495
+ "Stubtail",
496
+ "Seedpup",
497
+ "Testbiscuit",
498
+ "Placepurr",
499
+ "Synthfur",
500
+ "Dummymane",
501
+ "Examplehoof",
502
+ "Fauxfluff",
503
+ "Samplesnout",
504
+ "Voidwag"
505
+ ]);
506
+ var MICROCHIP_TEST_PREFIX = "999";
507
+ function petName(rng) {
508
+ return rng.pick(SYNTHETIC_PET_NAMES);
509
+ }
510
+ function animalBreed(rng) {
511
+ const picked = rng.pick(SYNTHETIC_ANIMAL_SPECIES);
512
+ const { breeds, ...bare } = picked;
513
+ return { species: Object.freeze(bare), breed: rng.pick(breeds) };
514
+ }
515
+ function microchip(rng) {
516
+ return `${MICROCHIP_TEST_PREFIX}${rng.digits(12)}`;
517
+ }
518
+ function animalBirthDate(rng, onDate) {
519
+ const year = Number(onDate.slice(0, 4));
520
+ const month = Number(onDate.slice(4, 6)) - 1;
521
+ const day = Number(onDate.slice(6, 8));
522
+ const latest = Date.UTC(year, month, day);
523
+ const earliest = Date.UTC(year - 20, month, day);
524
+ const span = Math.round((latest - earliest) / 864e5);
525
+ const born = new Date(latest - rng.int(0, span) * 864e5);
526
+ return born.toISOString().slice(0, 10).replace(/-/g, "");
527
+ }
528
+
434
529
  // src/safe/index.ts
435
530
  var safe = Object.freeze({
436
531
  ssn,
@@ -444,25 +539,54 @@ var safe = Object.freeze({
444
539
  address,
445
540
  dateYmd,
446
541
  npi,
447
- dea
542
+ dea,
543
+ petName,
544
+ animalBreed,
545
+ microchip,
546
+ animalBirthDate
448
547
  });
449
548
  function defaultHost() {
450
549
  if (typeof import.meta.resolve !== "function") return { require: __require, resolve: void 0 };
451
550
  return { require: createRequire(import.meta.url), resolve: (s) => import.meta.resolve(s) };
452
551
  }
552
+ function isDenoResolveRefusal(error) {
553
+ return error instanceof TypeError && !("code" in error) && error.message === "Invalid arguments";
554
+ }
453
555
  function isRequireEsmRefusal(error) {
454
556
  return error?.code === "ERR_REQUIRE_ESM";
455
557
  }
558
+ function peerPackage(specifier) {
559
+ const name2 = specifier.split("/").slice(0, 2).join("/");
560
+ return Object.hasOwn(SYNTH_PEER_MESSAGES, name2) ? name2 : void 0;
561
+ }
562
+ function isPeerNotFound(error, specifier, peer) {
563
+ const { code, message } = error ?? {};
564
+ if (typeof message !== "string") return false;
565
+ if (code === "ERR_MODULE_NOT_FOUND") return message.startsWith(`Cannot find package '${peer}'`);
566
+ if (code === "MODULE_NOT_FOUND") return message.startsWith(`Cannot find module '${specifier}'`);
567
+ return false;
568
+ }
456
569
  function loadPeer(specifier, host) {
570
+ try {
571
+ return loadFrom(specifier, host);
572
+ } catch (error) {
573
+ const peer = peerPackage(specifier);
574
+ if (peer === void 0 || !isPeerNotFound(error, specifier, peer)) throw error;
575
+ throw new SynthError(SYNTH_FATAL_CODES.SYNTH_PEER_NOT_INSTALLED, { peer, cause: error });
576
+ }
577
+ }
578
+ function loadFrom(specifier, host) {
457
579
  if (host.resolve === void 0) return host.require(specifier);
458
- let entry;
580
+ let url;
459
581
  try {
460
- entry = fileURLToPath(host.resolve(specifier));
461
- } catch {
582
+ url = host.resolve(specifier);
583
+ } catch (error) {
584
+ if (!isDenoResolveRefusal(error)) throw error;
462
585
  return host.require(specifier);
463
586
  }
587
+ if (!url.startsWith("file:")) return host.require(specifier);
464
588
  try {
465
- return host.require(entry);
589
+ return host.require(fileURLToPath(url));
466
590
  } catch (error) {
467
591
  if (!isRequireEsmRefusal(error)) throw error;
468
592
  return host.require(specifier);
@@ -494,6 +618,20 @@ function seededTimestamp(rng) {
494
618
  const ss = String(rng.int(0, 59)).padStart(2, "0");
495
619
  return `${date}${hh}${mm}${ss}`;
496
620
  }
621
+ function shiftTimestamp(timestamp, seconds) {
622
+ const at = new Date(
623
+ Date.UTC(
624
+ Number(timestamp.slice(0, 4)),
625
+ Number(timestamp.slice(4, 6)) - 1,
626
+ Number(timestamp.slice(6, 8)),
627
+ Number(timestamp.slice(8, 10)),
628
+ Number(timestamp.slice(10, 12)),
629
+ Number(timestamp.slice(12, 14)) + seconds
630
+ )
631
+ );
632
+ const pad = (n, width = 2) => String(n).padStart(width, "0");
633
+ return pad(at.getUTCFullYear(), 4) + pad(at.getUTCMonth() + 1) + pad(at.getUTCDate()) + pad(at.getUTCHours()) + pad(at.getUTCMinutes()) + pad(at.getUTCSeconds());
634
+ }
497
635
  function mshScaffold(rng, type) {
498
636
  const timestamp = seededTimestamp(rng);
499
637
  const controlId = `SYNTH${rng.digits(10)}`;
@@ -564,6 +702,107 @@ function pidSegment(id) {
564
702
  // PID-19 SSN (digits)
565
703
  ];
566
704
  }
705
+ function hl7Subjects() {
706
+ return ["person", "animal"];
707
+ }
708
+ var MICROCHIP_IDENTIFIER_TYPE = "MCN";
709
+ var OWNER_RELATIONSHIP = Object.freeze({ code: "OWN", text: "Owner", system: "HL70063" });
710
+ function animalIdentity(rng, onDate) {
711
+ const person = safe.name(rng);
712
+ const petName2 = safe.petName(rng);
713
+ const record = safe.identifier(rng, "MR");
714
+ const microchip2 = safe.microchip(rng);
715
+ const { species, breed } = safe.animalBreed(rng);
716
+ const dob = safe.animalBirthDate(rng, onDate);
717
+ const sex = rng.pick(["M", "F"]);
718
+ const address2 = safe.address(rng);
719
+ const phone2 = safe.phone(rng);
720
+ return {
721
+ petName: petName2,
722
+ record,
723
+ microchip: microchip2,
724
+ species,
725
+ breed,
726
+ dob,
727
+ sex,
728
+ owner: { person, address: address2, phone: phone2 }
729
+ };
730
+ }
731
+ function codedField(value) {
732
+ return componentsField([value.code, value.text, value.system]);
733
+ }
734
+ function animalPidSegment(id) {
735
+ const authority = id.record.assigningAuthority;
736
+ const cx = (value, type) => ({
737
+ components: [value, "", "", authority, type].map((c) => ({ subcomponents: [c] }))
738
+ });
739
+ const identifiers = {
740
+ repetitions: [
741
+ cx(id.record.value, id.record.typeCode),
742
+ cx(id.microchip, MICROCHIP_IDENTIFIER_TYPE)
743
+ ],
744
+ isNull: false
745
+ };
746
+ const { person, address: address2, phone: phone2 } = id.owner;
747
+ return [
748
+ "1",
749
+ // PID-1 set id
750
+ "",
751
+ // PID-2
752
+ identifiers,
753
+ // PID-3 CX: record number, then microchip
754
+ "",
755
+ // PID-4
756
+ componentsField([person.family, id.petName]),
757
+ // PID-5 XPN: owner family, pet name
758
+ "",
759
+ // PID-6
760
+ id.dob,
761
+ // PID-7 DOB
762
+ id.sex,
763
+ // PID-8 sex
764
+ "",
765
+ // PID-9
766
+ "",
767
+ // PID-10
768
+ componentsField([address2.street, "", address2.city, address2.state, address2.zip]),
769
+ // PID-11 XAD
770
+ "",
771
+ // PID-12
772
+ phone2,
773
+ // PID-13 phone
774
+ ...Array(21).fill(""),
775
+ // PID-14 to PID-34
776
+ codedField(id.species),
777
+ // PID-35 species code (CE)
778
+ codedField(id.breed)
779
+ // PID-36 breed code (CE)
780
+ ];
781
+ }
782
+ function ownerNk1Segment(id) {
783
+ const { person, address: address2, phone: phone2 } = id.owner;
784
+ return [
785
+ "1",
786
+ // NK1-1 set id
787
+ componentsField([person.family, person.given]),
788
+ // NK1-2 name
789
+ codedField(OWNER_RELATIONSHIP),
790
+ // NK1-3 relationship (CE)
791
+ componentsField([address2.street, "", address2.city, address2.state, address2.zip]),
792
+ // NK1-4
793
+ phone2
794
+ // NK1-5 phone
795
+ ];
796
+ }
797
+ function addSubject(message, rng, subject, timestamp) {
798
+ if (subject === "person") {
799
+ message.addSegment("PID", pidSegment(patientIdentity(rng)));
800
+ return;
801
+ }
802
+ const animal = animalIdentity(rng, timestamp);
803
+ message.addSegment("PID", animalPidSegment(animal));
804
+ message.addSegment("NK1", ownerNk1Segment(animal));
805
+ }
567
806
 
568
807
  // src/select.ts
569
808
  function resolveKind(allowed, requested) {
@@ -573,25 +812,84 @@ function resolveKind(allowed, requested) {
573
812
  }
574
813
 
575
814
  // src/hl7/adt.ts
576
- var ADT_TRIGGERS = Object.freeze(["A01", "A04", "A08"]);
815
+ function adtTriggers() {
816
+ return ["A01", "A04", "A08", "A02", "A03", "A11", "A13", "A28", "A31"];
817
+ }
818
+ function structureOf(trigger) {
819
+ switch (trigger) {
820
+ case "A02":
821
+ return "ADT_A02";
822
+ case "A03":
823
+ return "ADT_A03";
824
+ case "A11":
825
+ return "ADT_A09";
826
+ case "A13":
827
+ return "ADT_A01";
828
+ case "A28":
829
+ case "A31":
830
+ return "ADT_A05";
831
+ }
832
+ }
833
+ function ward(room) {
834
+ return componentsField(["SYNTHWARD", String(room).padStart(3, "0"), "01"]);
835
+ }
836
+ function location(rng) {
837
+ return ward(rng.int(1, 999));
838
+ }
839
+ function admittedBefore(rng, timestamp) {
840
+ return shiftTimestamp(timestamp, -rng.int(3600, 14 * 86400));
841
+ }
577
842
  function generateAdt(options = {}) {
578
843
  const seed = options.seed ?? 0;
579
- const trigger = resolveKind(ADT_TRIGGERS, options.trigger ?? "A01");
844
+ const trigger = resolveKind(adtTriggers(), options.trigger ?? "A01");
845
+ const subject = resolveKind(hl7Subjects(), options.subject ?? "person");
580
846
  const rng = createRng(seed);
581
- const { message, timestamp } = mshScaffold(rng, `ADT^${trigger}`);
847
+ if (trigger === "A01" || trigger === "A04" || trigger === "A08") {
848
+ const { message: message2, timestamp: timestamp2 } = mshScaffold(rng, `ADT^${trigger}`);
849
+ message2.addSegment("EVN", [trigger, timestamp2]);
850
+ addSubject(message2, rng, subject, timestamp2);
851
+ const patientClass = rng.pick(["I", "O", "E"]);
852
+ message2.addSegment("PV1", [
853
+ "1",
854
+ // PV1-1 set id
855
+ patientClass,
856
+ // PV1-2 patient class
857
+ location(rng)
858
+ // PV1-3 PL
859
+ ]);
860
+ return message2;
861
+ }
862
+ const { message, timestamp } = mshScaffold(rng, `ADT^${trigger}^${structureOf(trigger)}`);
582
863
  message.addSegment("EVN", [trigger, timestamp]);
583
- message.addSegment("PID", pidSegment(patientIdentity(rng)));
584
- const patientClass = rng.pick(["I", "O", "E"]);
585
- message.addSegment("PV1", [
586
- "1",
587
- // PV1-1 set id
588
- patientClass,
589
- // PV1-2 patient class
590
- componentsField(["SYNTHWARD", String(rng.int(1, 999)).padStart(3, "0"), "01"])
591
- // PV1-3 PL
592
- ]);
864
+ addSubject(message, rng, subject, timestamp);
865
+ message.addSegment("PV1", visit(rng, trigger, timestamp));
593
866
  return message;
594
867
  }
868
+ function visit(rng, trigger, timestamp) {
869
+ switch (trigger) {
870
+ case "A02": {
871
+ const room = rng.int(1, 998);
872
+ const priorRoom = room + rng.int(1, 999 - room);
873
+ return ["1", "I", ward(room), "", "", ward(priorRoom)];
874
+ }
875
+ case "A03": {
876
+ const patientClass = rng.pick(["I", "O", "E"]);
877
+ const where = location(rng);
878
+ const admitted = admittedBefore(rng, timestamp);
879
+ return ["1", patientClass, where, ...Array(40).fill(""), admitted, timestamp];
880
+ }
881
+ case "A11":
882
+ return ["1", rng.pick(["I", "O", "E"]), location(rng)];
883
+ case "A13": {
884
+ const where = location(rng);
885
+ const admitted = admittedBefore(rng, timestamp);
886
+ return ["1", "I", where, ...Array(40).fill(""), admitted];
887
+ }
888
+ case "A28":
889
+ case "A31":
890
+ return ["1", "N"];
891
+ }
892
+ }
595
893
 
596
894
  // src/hl7/example-codes.ts
597
895
  var EXAMPLE_LAB_OBSERVATIONS = Object.freeze([
@@ -656,9 +954,10 @@ function obxSegment(rng, setId) {
656
954
  }
657
955
  function generateOru(options = {}) {
658
956
  const seed = options.seed ?? 0;
957
+ const subject = resolveKind(hl7Subjects(), options.subject ?? "person");
659
958
  const rng = createRng(seed);
660
- const { message } = mshScaffold(rng, "ORU^R01");
661
- message.addSegment("PID", pidSegment(patientIdentity(rng)));
959
+ const { message, timestamp } = mshScaffold(rng, "ORU^R01");
960
+ addSubject(message, rng, subject, timestamp);
662
961
  const service = rng.pick(EXAMPLE_ORDER_SERVICES);
663
962
  const placer = rng.digits(8);
664
963
  const filler = rng.digits(8);
@@ -849,18 +1148,206 @@ function generateVxu(options = {}) {
849
1148
  return message;
850
1149
  }
851
1150
 
1151
+ // src/hl7/mdm.ts
1152
+ var DOCUMENT_TYPES = Object.freeze(["CN", "DS", "HP", "PN"]);
1153
+ var NOTE_LINES = Object.freeze([
1154
+ "Synthetic test document for interface testing.",
1155
+ "Synthetic note text: no real patient is described here.",
1156
+ "Synthetic content generated from a seed."
1157
+ ]);
1158
+ function generateMdm(options = {}) {
1159
+ const seed = options.seed ?? 0;
1160
+ const rng = createRng(seed);
1161
+ const { message, timestamp } = mshScaffold(rng, "MDM^T02^MDM_T02");
1162
+ message.addSegment("EVN", ["T02", timestamp]);
1163
+ message.addSegment("PID", pidSegment(patientIdentity(rng)));
1164
+ message.addSegment("PV1", ["1", rng.pick(["I", "O", "E"])]);
1165
+ const documentType = rng.pick(DOCUMENT_TYPES);
1166
+ const documentNumber = `SYNTHDOC${rng.digits(10)}`;
1167
+ message.addSegment("TXA", [
1168
+ "1",
1169
+ // TXA-1 set id
1170
+ documentType,
1171
+ // TXA-2 document type
1172
+ "TX",
1173
+ // TXA-3 document content presentation (text)
1174
+ timestamp,
1175
+ // TXA-4 activity date/time
1176
+ ...Array(7).fill(""),
1177
+ // TXA-5 to TXA-11
1178
+ componentsField([documentNumber, "COSYTE-SYNTH"]),
1179
+ // TXA-12 unique document number (EI)
1180
+ ...Array(4).fill(""),
1181
+ // TXA-13 to TXA-16
1182
+ "DO"
1183
+ // TXA-17 document completion status (documented)
1184
+ ]);
1185
+ const lines = rng.int(1, 3);
1186
+ for (let i = 1; i <= lines; i += 1) {
1187
+ message.addSegment("OBX", [
1188
+ String(i),
1189
+ // OBX-1 set id
1190
+ "TX",
1191
+ // OBX-2 value type
1192
+ componentsField(["SYNTHNOTE", "Synthetic note", "99SYN"]),
1193
+ // OBX-3 observation id (local)
1194
+ "",
1195
+ // OBX-4 sub-id
1196
+ rng.pick(NOTE_LINES),
1197
+ // OBX-5 the document text
1198
+ ...Array(5).fill(""),
1199
+ // OBX-6 to OBX-10
1200
+ "F"
1201
+ // OBX-11 result status (final)
1202
+ ]);
1203
+ }
1204
+ return message;
1205
+ }
1206
+
1207
+ // src/hl7/dft.ts
1208
+ function charges() {
1209
+ return [
1210
+ { code: "SYNTHCHG01", text: "Synthetic office visit charge" },
1211
+ { code: "SYNTHCHG02", text: "Synthetic laboratory charge" },
1212
+ { code: "SYNTHCHG03", text: "Synthetic imaging charge" },
1213
+ { code: "SYNTHCHG04", text: "Synthetic supply charge" }
1214
+ ];
1215
+ }
1216
+ function generateDft(options = {}) {
1217
+ const seed = options.seed ?? 0;
1218
+ const rng = createRng(seed);
1219
+ const { message, timestamp } = mshScaffold(rng, "DFT^P03^DFT_P03");
1220
+ message.addSegment("EVN", ["P03", timestamp]);
1221
+ message.addSegment("PID", pidSegment(patientIdentity(rng)));
1222
+ message.addSegment("PV1", ["1", rng.pick(["I", "O", "E"])]);
1223
+ const transactionId = `SYNTHTXN${rng.digits(8)}`;
1224
+ const charge = rng.pick(charges());
1225
+ const quantity = String(rng.int(1, 3));
1226
+ message.addSegment("FT1", [
1227
+ "1",
1228
+ // FT1-1 set id
1229
+ transactionId,
1230
+ // FT1-2 transaction id
1231
+ "",
1232
+ // FT1-3 transaction batch id
1233
+ timestamp,
1234
+ // FT1-4 transaction date
1235
+ "",
1236
+ // FT1-5 transaction posting date
1237
+ "CG",
1238
+ // FT1-6 transaction type (charge)
1239
+ componentsField([charge.code, charge.text, "99SYN"]),
1240
+ // FT1-7 transaction code (local)
1241
+ "",
1242
+ // FT1-8 transaction description
1243
+ "",
1244
+ // FT1-9 transaction description, alternate
1245
+ quantity
1246
+ // FT1-10 transaction quantity
1247
+ ]);
1248
+ return message;
1249
+ }
1250
+
1251
+ // src/hl7/oml.ts
1252
+ function generateOml(options = {}) {
1253
+ const seed = options.seed ?? 0;
1254
+ const rng = createRng(seed);
1255
+ const { message } = mshScaffold(rng, "OML^O21^OML_O21");
1256
+ message.addSegment("PID", pidSegment(patientIdentity(rng)));
1257
+ const service = rng.pick(EXAMPLE_ORDER_SERVICES);
1258
+ const placer = rng.digits(8);
1259
+ const filler = rng.digits(8);
1260
+ const orderedAt = seededTimestamp(rng);
1261
+ message.addSegment("ORC", [
1262
+ "NW",
1263
+ // ORC-1 order control (new order)
1264
+ placer,
1265
+ // ORC-2 placer order number
1266
+ filler,
1267
+ // ORC-3 filler order number
1268
+ ...Array(5).fill(""),
1269
+ // ORC-4 to ORC-8
1270
+ orderedAt
1271
+ // ORC-9 date/time of transaction
1272
+ ]);
1273
+ message.addSegment("OBR", [
1274
+ "1",
1275
+ // OBR-1 set id
1276
+ placer,
1277
+ // OBR-2 placer order number (matches ORC)
1278
+ filler,
1279
+ // OBR-3 filler order number (matches ORC)
1280
+ componentsField([service.code, service.text, service.system])
1281
+ // OBR-4 universal service id
1282
+ ]);
1283
+ return message;
1284
+ }
1285
+
1286
+ // src/hl7/ack.ts
1287
+ function generateAck(options = {}) {
1288
+ const seed = options.seed ?? 0;
1289
+ const allowed = ["AA", "AE", "AR"];
1290
+ const code = resolveKind(allowed, options.code ?? "AA");
1291
+ const inbound = options.inbound ?? generateAdt({ seed });
1292
+ const inboundControlId = inbound.get("MSH.10") ?? "";
1293
+ if (inboundControlId === "") throw new SynthError(SYNTH_FATAL_CODES.SYNTH_ACK_UNCORRELATED);
1294
+ const rng = createRng(seed);
1295
+ const delaySeconds = rng.int(1, 59);
1296
+ const controlId = `SYNTHACK${rng.digits(10)}`;
1297
+ const inboundTime = String(inbound.get("MSH.7"));
1298
+ const timestamp = /^\d{14}$/.test(inboundTime) ? shiftTimestamp(inboundTime, delaySeconds) : seededTimestamp(rng);
1299
+ const trigger = inbound.get("MSH.9.2") ?? "";
1300
+ const message = hl7Peer().buildMessage({
1301
+ type: trigger === "" ? "ACK" : `ACK^${trigger}^ACK`,
1302
+ sendingApp: inbound.get("MSH.5") ?? "",
1303
+ sendingFacility: inbound.get("MSH.6") ?? "",
1304
+ receivingApp: inbound.get("MSH.3") ?? "",
1305
+ receivingFacility: inbound.get("MSH.4") ?? "",
1306
+ controlId,
1307
+ timestamp,
1308
+ version: inbound.get("MSH.12") ?? "2.5",
1309
+ // MSH-11 processing ID `T` (HL7 table 0103: training): an acknowledgment of test data is test
1310
+ // data, so it never echoes a `P` from the inbound message.
1311
+ processingId: "T"
1312
+ });
1313
+ message.addSegment("MSA", [code, inboundControlId]);
1314
+ if (code !== "AA") {
1315
+ message.addSegment("ERR", [
1316
+ "",
1317
+ // ERR-1 error code and location (backward compatible only)
1318
+ "",
1319
+ // ERR-2 error location
1320
+ componentsField(["207", "Application internal error", "HL70357"]),
1321
+ // ERR-3 HL7 error code
1322
+ "E"
1323
+ // ERR-4 severity (error)
1324
+ ]);
1325
+ }
1326
+ return message;
1327
+ }
1328
+
852
1329
  // src/hl7/index.ts
853
- var ALL_KINDS = Object.freeze([
1330
+ var HL7_MESSAGE_KINDS = Object.freeze([
854
1331
  "ADT^A01",
855
1332
  "ADT^A04",
856
1333
  "ADT^A08",
857
1334
  "ORU^R01",
858
1335
  "ORM^O01",
859
1336
  "SIU^S12",
860
- "VXU^V04"
1337
+ "VXU^V04",
1338
+ "ACK",
1339
+ "ADT^A02",
1340
+ "ADT^A03",
1341
+ "ADT^A11",
1342
+ "ADT^A13",
1343
+ "ADT^A28",
1344
+ "ADT^A31",
1345
+ "MDM^T02",
1346
+ "DFT^P03",
1347
+ "OML^O21"
861
1348
  ]);
862
1349
  function generateHl7(kind, seed) {
863
- switch (resolveKind(ALL_KINDS, kind)) {
1350
+ switch (resolveKind(HL7_MESSAGE_KINDS, kind)) {
864
1351
  case "ADT^A01":
865
1352
  return generateAdt({ seed, trigger: "A01" });
866
1353
  case "ADT^A04":
@@ -875,6 +1362,26 @@ function generateHl7(kind, seed) {
875
1362
  return generateSiu({ seed });
876
1363
  case "VXU^V04":
877
1364
  return generateVxu({ seed });
1365
+ case "ACK":
1366
+ return generateAck({ seed });
1367
+ case "ADT^A02":
1368
+ return generateAdt({ seed, trigger: "A02" });
1369
+ case "ADT^A03":
1370
+ return generateAdt({ seed, trigger: "A03" });
1371
+ case "ADT^A11":
1372
+ return generateAdt({ seed, trigger: "A11" });
1373
+ case "ADT^A13":
1374
+ return generateAdt({ seed, trigger: "A13" });
1375
+ case "ADT^A28":
1376
+ return generateAdt({ seed, trigger: "A28" });
1377
+ case "ADT^A31":
1378
+ return generateAdt({ seed, trigger: "A31" });
1379
+ case "MDM^T02":
1380
+ return generateMdm({ seed });
1381
+ case "DFT^P03":
1382
+ return generateDft({ seed });
1383
+ case "OML^O21":
1384
+ return generateOml({ seed });
878
1385
  }
879
1386
  }
880
1387