@cosyte/synth 0.1.8 → 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 +37 -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 +150 -15
  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 +150 -15
  10. package/dist/astm/index.mjs.map +1 -1
  11. package/dist/ccda/index.cjs +150 -15
  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 +150 -15
  16. package/dist/ccda/index.mjs.map +1 -1
  17. package/dist/deid/index.cjs +540 -28
  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 +540 -28
  22. package/dist/deid/index.mjs.map +1 -1
  23. package/dist/fhir/index.cjs +145 -12
  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 +145 -12
  28. package/dist/fhir/index.mjs.map +1 -1
  29. package/dist/hl7/index.cjs +569 -37
  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 +560 -38
  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 +145 -12
  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 +145 -12
  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 +145 -12
  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 +145 -12
  54. package/dist/x12/index.mjs.map +1 -1
  55. package/package.json +17 -6
@@ -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
 
@@ -210,7 +248,7 @@ function luhnMod10(digits) {
210
248
  if (d < 0 || d > 9) continue;
211
249
  if (double) {
212
250
  d *= 2;
213
- if (d > 9) d -= 9;
251
+ if (d >= 10) d -= 9;
214
252
  }
215
253
  sum += d;
216
254
  double = !double;
@@ -307,7 +345,7 @@ var SYNTHETIC_CITY_NAMES = Object.freeze([
307
345
  ]);
308
346
 
309
347
  // src/safe/providers.ts
310
- function ssn(rng, block = "never-issued") {
348
+ function ssn(rng, block) {
311
349
  if (block === "advertising") {
312
350
  return `987-00-432${String(rng.int(0, 9))}`;
313
351
  }
@@ -434,6 +472,63 @@ var US_STATES = Object.freeze([
434
472
  "WY"
435
473
  ]);
436
474
 
475
+ // src/safe/animal-code.ts
476
+ var ANIMAL_CODE_SYSTEM = "99COSYTE";
477
+ function animalCode(value, text) {
478
+ return Object.freeze({ code: value, text, system: ANIMAL_CODE_SYSTEM });
479
+ }
480
+ function animalSpecies(value, text, breeds) {
481
+ return Object.freeze({ ...animalCode(value, text), breeds: Object.freeze([...breeds]) });
482
+ }
483
+
484
+ // src/safe/animal.ts
485
+ var SYNTHETIC_ANIMAL_SPECIES = Object.freeze([
486
+ animalSpecies("DOG", "Dog", [
487
+ animalCode("DOG-LAB", "Labrador Retriever"),
488
+ animalCode("DOG-BGL", "Beagle"),
489
+ animalCode("DOG-MIX", "Mixed breed dog")
490
+ ]),
491
+ animalSpecies("CAT", "Cat", [animalCode("CAT-DSH", "Domestic Shorthair"), animalCode("CAT-SIA", "Siamese")]),
492
+ animalSpecies("HORSE", "Horse", [animalCode("HRS-QTR", "Quarter Horse"), animalCode("HRS-TB", "Thoroughbred")]),
493
+ animalSpecies("CATTLE", "Cattle", [animalCode("CTL-HOL", "Holstein"), animalCode("CTL-ANG", "Angus")])
494
+ ]);
495
+ var SYNTHETIC_PET_NAMES = Object.freeze([
496
+ "Fixturepaw",
497
+ "Mockwhisker",
498
+ "Stubtail",
499
+ "Seedpup",
500
+ "Testbiscuit",
501
+ "Placepurr",
502
+ "Synthfur",
503
+ "Dummymane",
504
+ "Examplehoof",
505
+ "Fauxfluff",
506
+ "Samplesnout",
507
+ "Voidwag"
508
+ ]);
509
+ var MICROCHIP_TEST_PREFIX = "999";
510
+ function petName(rng) {
511
+ return rng.pick(SYNTHETIC_PET_NAMES);
512
+ }
513
+ function animalBreed(rng) {
514
+ const picked = rng.pick(SYNTHETIC_ANIMAL_SPECIES);
515
+ const { breeds, ...bare } = picked;
516
+ return { species: Object.freeze(bare), breed: rng.pick(breeds) };
517
+ }
518
+ function microchip(rng) {
519
+ return `${MICROCHIP_TEST_PREFIX}${rng.digits(12)}`;
520
+ }
521
+ function animalBirthDate(rng, onDate) {
522
+ const year = Number(onDate.slice(0, 4));
523
+ const month = Number(onDate.slice(4, 6)) - 1;
524
+ const day = Number(onDate.slice(6, 8));
525
+ const latest = Date.UTC(year, month, day);
526
+ const earliest = Date.UTC(year - 20, month, day);
527
+ const span = Math.round((latest - earliest) / 864e5);
528
+ const born = new Date(latest - rng.int(0, span) * 864e5);
529
+ return born.toISOString().slice(0, 10).replace(/-/g, "");
530
+ }
531
+
437
532
  // src/safe/index.ts
438
533
  var safe = Object.freeze({
439
534
  ssn,
@@ -447,20 +542,54 @@ var safe = Object.freeze({
447
542
  address,
448
543
  dateYmd,
449
544
  npi,
450
- dea
545
+ dea,
546
+ petName,
547
+ animalBreed,
548
+ microchip,
549
+ animalBirthDate
451
550
  });
452
551
  function defaultHost() {
453
552
  if (typeof undefined !== "function") return { require: __require, resolve: void 0 };
454
553
  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) };
455
554
  }
555
+ function isDenoResolveRefusal(error) {
556
+ return error instanceof TypeError && !("code" in error) && error.message === "Invalid arguments";
557
+ }
456
558
  function isRequireEsmRefusal(error) {
457
559
  return error?.code === "ERR_REQUIRE_ESM";
458
560
  }
561
+ function peerPackage(specifier) {
562
+ const name2 = specifier.split("/").slice(0, 2).join("/");
563
+ return Object.hasOwn(SYNTH_PEER_MESSAGES, name2) ? name2 : void 0;
564
+ }
565
+ function isPeerNotFound(error, specifier, peer) {
566
+ const { code, message } = error ?? {};
567
+ if (typeof message !== "string") return false;
568
+ if (code === "ERR_MODULE_NOT_FOUND") return message.startsWith(`Cannot find package '${peer}'`);
569
+ if (code === "MODULE_NOT_FOUND") return message.startsWith(`Cannot find module '${specifier}'`);
570
+ return false;
571
+ }
459
572
  function loadPeer(specifier, host) {
573
+ try {
574
+ return loadFrom(specifier, host);
575
+ } catch (error) {
576
+ const peer = peerPackage(specifier);
577
+ if (peer === void 0 || !isPeerNotFound(error, specifier, peer)) throw error;
578
+ throw new SynthError(SYNTH_FATAL_CODES.SYNTH_PEER_NOT_INSTALLED, { peer, cause: error });
579
+ }
580
+ }
581
+ function loadFrom(specifier, host) {
460
582
  if (host.resolve === void 0) return host.require(specifier);
461
- const entry = url.fileURLToPath(host.resolve(specifier));
583
+ let url$1;
584
+ try {
585
+ url$1 = host.resolve(specifier);
586
+ } catch (error) {
587
+ if (!isDenoResolveRefusal(error)) throw error;
588
+ return host.require(specifier);
589
+ }
590
+ if (!url$1.startsWith("file:")) return host.require(specifier);
462
591
  try {
463
- return host.require(entry);
592
+ return host.require(url.fileURLToPath(url$1));
464
593
  } catch (error) {
465
594
  if (!isRequireEsmRefusal(error)) throw error;
466
595
  return host.require(specifier);
@@ -492,6 +621,20 @@ function seededTimestamp(rng) {
492
621
  const ss = String(rng.int(0, 59)).padStart(2, "0");
493
622
  return `${date}${hh}${mm}${ss}`;
494
623
  }
624
+ function shiftTimestamp(timestamp, seconds) {
625
+ const at = new Date(
626
+ Date.UTC(
627
+ Number(timestamp.slice(0, 4)),
628
+ Number(timestamp.slice(4, 6)) - 1,
629
+ Number(timestamp.slice(6, 8)),
630
+ Number(timestamp.slice(8, 10)),
631
+ Number(timestamp.slice(10, 12)),
632
+ Number(timestamp.slice(12, 14)) + seconds
633
+ )
634
+ );
635
+ const pad = (n, width = 2) => String(n).padStart(width, "0");
636
+ return pad(at.getUTCFullYear(), 4) + pad(at.getUTCMonth() + 1) + pad(at.getUTCDate()) + pad(at.getUTCHours()) + pad(at.getUTCMinutes()) + pad(at.getUTCSeconds());
637
+ }
495
638
  function mshScaffold(rng, type) {
496
639
  const timestamp = seededTimestamp(rng);
497
640
  const controlId = `SYNTH${rng.digits(10)}`;
@@ -562,6 +705,107 @@ function pidSegment(id) {
562
705
  // PID-19 SSN (digits)
563
706
  ];
564
707
  }
708
+ function hl7Subjects() {
709
+ return ["person", "animal"];
710
+ }
711
+ var MICROCHIP_IDENTIFIER_TYPE = "MCN";
712
+ var OWNER_RELATIONSHIP = Object.freeze({ code: "OWN", text: "Owner", system: "HL70063" });
713
+ function animalIdentity(rng, onDate) {
714
+ const person = safe.name(rng);
715
+ const petName2 = safe.petName(rng);
716
+ const record = safe.identifier(rng, "MR");
717
+ const microchip2 = safe.microchip(rng);
718
+ const { species, breed } = safe.animalBreed(rng);
719
+ const dob = safe.animalBirthDate(rng, onDate);
720
+ const sex = rng.pick(["M", "F"]);
721
+ const address2 = safe.address(rng);
722
+ const phone2 = safe.phone(rng);
723
+ return {
724
+ petName: petName2,
725
+ record,
726
+ microchip: microchip2,
727
+ species,
728
+ breed,
729
+ dob,
730
+ sex,
731
+ owner: { person, address: address2, phone: phone2 }
732
+ };
733
+ }
734
+ function codedField(value) {
735
+ return componentsField([value.code, value.text, value.system]);
736
+ }
737
+ function animalPidSegment(id) {
738
+ const authority = id.record.assigningAuthority;
739
+ const cx = (value, type) => ({
740
+ components: [value, "", "", authority, type].map((c) => ({ subcomponents: [c] }))
741
+ });
742
+ const identifiers = {
743
+ repetitions: [
744
+ cx(id.record.value, id.record.typeCode),
745
+ cx(id.microchip, MICROCHIP_IDENTIFIER_TYPE)
746
+ ],
747
+ isNull: false
748
+ };
749
+ const { person, address: address2, phone: phone2 } = id.owner;
750
+ return [
751
+ "1",
752
+ // PID-1 set id
753
+ "",
754
+ // PID-2
755
+ identifiers,
756
+ // PID-3 CX: record number, then microchip
757
+ "",
758
+ // PID-4
759
+ componentsField([person.family, id.petName]),
760
+ // PID-5 XPN: owner family, pet name
761
+ "",
762
+ // PID-6
763
+ id.dob,
764
+ // PID-7 DOB
765
+ id.sex,
766
+ // PID-8 sex
767
+ "",
768
+ // PID-9
769
+ "",
770
+ // PID-10
771
+ componentsField([address2.street, "", address2.city, address2.state, address2.zip]),
772
+ // PID-11 XAD
773
+ "",
774
+ // PID-12
775
+ phone2,
776
+ // PID-13 phone
777
+ ...Array(21).fill(""),
778
+ // PID-14 to PID-34
779
+ codedField(id.species),
780
+ // PID-35 species code (CE)
781
+ codedField(id.breed)
782
+ // PID-36 breed code (CE)
783
+ ];
784
+ }
785
+ function ownerNk1Segment(id) {
786
+ const { person, address: address2, phone: phone2 } = id.owner;
787
+ return [
788
+ "1",
789
+ // NK1-1 set id
790
+ componentsField([person.family, person.given]),
791
+ // NK1-2 name
792
+ codedField(OWNER_RELATIONSHIP),
793
+ // NK1-3 relationship (CE)
794
+ componentsField([address2.street, "", address2.city, address2.state, address2.zip]),
795
+ // NK1-4
796
+ phone2
797
+ // NK1-5 phone
798
+ ];
799
+ }
800
+ function addSubject(message, rng, subject, timestamp) {
801
+ if (subject === "person") {
802
+ message.addSegment("PID", pidSegment(patientIdentity(rng)));
803
+ return;
804
+ }
805
+ const animal = animalIdentity(rng, timestamp);
806
+ message.addSegment("PID", animalPidSegment(animal));
807
+ message.addSegment("NK1", ownerNk1Segment(animal));
808
+ }
565
809
 
566
810
  // src/select.ts
567
811
  function resolveKind(allowed, requested) {
@@ -571,25 +815,84 @@ function resolveKind(allowed, requested) {
571
815
  }
572
816
 
573
817
  // src/hl7/adt.ts
574
- var ADT_TRIGGERS = Object.freeze(["A01", "A04", "A08"]);
818
+ function adtTriggers() {
819
+ return ["A01", "A04", "A08", "A02", "A03", "A11", "A13", "A28", "A31"];
820
+ }
821
+ function structureOf(trigger) {
822
+ switch (trigger) {
823
+ case "A02":
824
+ return "ADT_A02";
825
+ case "A03":
826
+ return "ADT_A03";
827
+ case "A11":
828
+ return "ADT_A09";
829
+ case "A13":
830
+ return "ADT_A01";
831
+ case "A28":
832
+ case "A31":
833
+ return "ADT_A05";
834
+ }
835
+ }
836
+ function ward(room) {
837
+ return componentsField(["SYNTHWARD", String(room).padStart(3, "0"), "01"]);
838
+ }
839
+ function location(rng) {
840
+ return ward(rng.int(1, 999));
841
+ }
842
+ function admittedBefore(rng, timestamp) {
843
+ return shiftTimestamp(timestamp, -rng.int(3600, 14 * 86400));
844
+ }
575
845
  function generateAdt(options = {}) {
576
846
  const seed = options.seed ?? 0;
577
- const trigger = resolveKind(ADT_TRIGGERS, options.trigger ?? "A01");
847
+ const trigger = resolveKind(adtTriggers(), options.trigger ?? "A01");
848
+ const subject = resolveKind(hl7Subjects(), options.subject ?? "person");
578
849
  const rng = createRng(seed);
579
- const { message, timestamp } = mshScaffold(rng, `ADT^${trigger}`);
850
+ if (trigger === "A01" || trigger === "A04" || trigger === "A08") {
851
+ const { message: message2, timestamp: timestamp2 } = mshScaffold(rng, `ADT^${trigger}`);
852
+ message2.addSegment("EVN", [trigger, timestamp2]);
853
+ addSubject(message2, rng, subject, timestamp2);
854
+ const patientClass = rng.pick(["I", "O", "E"]);
855
+ message2.addSegment("PV1", [
856
+ "1",
857
+ // PV1-1 set id
858
+ patientClass,
859
+ // PV1-2 patient class
860
+ location(rng)
861
+ // PV1-3 PL
862
+ ]);
863
+ return message2;
864
+ }
865
+ const { message, timestamp } = mshScaffold(rng, `ADT^${trigger}^${structureOf(trigger)}`);
580
866
  message.addSegment("EVN", [trigger, timestamp]);
581
- message.addSegment("PID", pidSegment(patientIdentity(rng)));
582
- const patientClass = rng.pick(["I", "O", "E"]);
583
- message.addSegment("PV1", [
584
- "1",
585
- // PV1-1 set id
586
- patientClass,
587
- // PV1-2 patient class
588
- componentsField(["SYNTHWARD", String(rng.int(1, 999)).padStart(3, "0"), "01"])
589
- // PV1-3 PL
590
- ]);
867
+ addSubject(message, rng, subject, timestamp);
868
+ message.addSegment("PV1", visit(rng, trigger, timestamp));
591
869
  return message;
592
870
  }
871
+ function visit(rng, trigger, timestamp) {
872
+ switch (trigger) {
873
+ case "A02": {
874
+ const room = rng.int(1, 998);
875
+ const priorRoom = room + rng.int(1, 999 - room);
876
+ return ["1", "I", ward(room), "", "", ward(priorRoom)];
877
+ }
878
+ case "A03": {
879
+ const patientClass = rng.pick(["I", "O", "E"]);
880
+ const where = location(rng);
881
+ const admitted = admittedBefore(rng, timestamp);
882
+ return ["1", patientClass, where, ...Array(40).fill(""), admitted, timestamp];
883
+ }
884
+ case "A11":
885
+ return ["1", rng.pick(["I", "O", "E"]), location(rng)];
886
+ case "A13": {
887
+ const where = location(rng);
888
+ const admitted = admittedBefore(rng, timestamp);
889
+ return ["1", "I", where, ...Array(40).fill(""), admitted];
890
+ }
891
+ case "A28":
892
+ case "A31":
893
+ return ["1", "N"];
894
+ }
895
+ }
593
896
 
594
897
  // src/hl7/example-codes.ts
595
898
  var EXAMPLE_LAB_OBSERVATIONS = Object.freeze([
@@ -654,9 +957,10 @@ function obxSegment(rng, setId) {
654
957
  }
655
958
  function generateOru(options = {}) {
656
959
  const seed = options.seed ?? 0;
960
+ const subject = resolveKind(hl7Subjects(), options.subject ?? "person");
657
961
  const rng = createRng(seed);
658
- const { message } = mshScaffold(rng, "ORU^R01");
659
- message.addSegment("PID", pidSegment(patientIdentity(rng)));
962
+ const { message, timestamp } = mshScaffold(rng, "ORU^R01");
963
+ addSubject(message, rng, subject, timestamp);
660
964
  const service = rng.pick(EXAMPLE_ORDER_SERVICES);
661
965
  const placer = rng.digits(8);
662
966
  const filler = rng.digits(8);
@@ -847,18 +1151,206 @@ function generateVxu(options = {}) {
847
1151
  return message;
848
1152
  }
849
1153
 
1154
+ // src/hl7/mdm.ts
1155
+ var DOCUMENT_TYPES = Object.freeze(["CN", "DS", "HP", "PN"]);
1156
+ var NOTE_LINES = Object.freeze([
1157
+ "Synthetic test document for interface testing.",
1158
+ "Synthetic note text: no real patient is described here.",
1159
+ "Synthetic content generated from a seed."
1160
+ ]);
1161
+ function generateMdm(options = {}) {
1162
+ const seed = options.seed ?? 0;
1163
+ const rng = createRng(seed);
1164
+ const { message, timestamp } = mshScaffold(rng, "MDM^T02^MDM_T02");
1165
+ message.addSegment("EVN", ["T02", timestamp]);
1166
+ message.addSegment("PID", pidSegment(patientIdentity(rng)));
1167
+ message.addSegment("PV1", ["1", rng.pick(["I", "O", "E"])]);
1168
+ const documentType = rng.pick(DOCUMENT_TYPES);
1169
+ const documentNumber = `SYNTHDOC${rng.digits(10)}`;
1170
+ message.addSegment("TXA", [
1171
+ "1",
1172
+ // TXA-1 set id
1173
+ documentType,
1174
+ // TXA-2 document type
1175
+ "TX",
1176
+ // TXA-3 document content presentation (text)
1177
+ timestamp,
1178
+ // TXA-4 activity date/time
1179
+ ...Array(7).fill(""),
1180
+ // TXA-5 to TXA-11
1181
+ componentsField([documentNumber, "COSYTE-SYNTH"]),
1182
+ // TXA-12 unique document number (EI)
1183
+ ...Array(4).fill(""),
1184
+ // TXA-13 to TXA-16
1185
+ "DO"
1186
+ // TXA-17 document completion status (documented)
1187
+ ]);
1188
+ const lines = rng.int(1, 3);
1189
+ for (let i = 1; i <= lines; i += 1) {
1190
+ message.addSegment("OBX", [
1191
+ String(i),
1192
+ // OBX-1 set id
1193
+ "TX",
1194
+ // OBX-2 value type
1195
+ componentsField(["SYNTHNOTE", "Synthetic note", "99SYN"]),
1196
+ // OBX-3 observation id (local)
1197
+ "",
1198
+ // OBX-4 sub-id
1199
+ rng.pick(NOTE_LINES),
1200
+ // OBX-5 the document text
1201
+ ...Array(5).fill(""),
1202
+ // OBX-6 to OBX-10
1203
+ "F"
1204
+ // OBX-11 result status (final)
1205
+ ]);
1206
+ }
1207
+ return message;
1208
+ }
1209
+
1210
+ // src/hl7/dft.ts
1211
+ function charges() {
1212
+ return [
1213
+ { code: "SYNTHCHG01", text: "Synthetic office visit charge" },
1214
+ { code: "SYNTHCHG02", text: "Synthetic laboratory charge" },
1215
+ { code: "SYNTHCHG03", text: "Synthetic imaging charge" },
1216
+ { code: "SYNTHCHG04", text: "Synthetic supply charge" }
1217
+ ];
1218
+ }
1219
+ function generateDft(options = {}) {
1220
+ const seed = options.seed ?? 0;
1221
+ const rng = createRng(seed);
1222
+ const { message, timestamp } = mshScaffold(rng, "DFT^P03^DFT_P03");
1223
+ message.addSegment("EVN", ["P03", timestamp]);
1224
+ message.addSegment("PID", pidSegment(patientIdentity(rng)));
1225
+ message.addSegment("PV1", ["1", rng.pick(["I", "O", "E"])]);
1226
+ const transactionId = `SYNTHTXN${rng.digits(8)}`;
1227
+ const charge = rng.pick(charges());
1228
+ const quantity = String(rng.int(1, 3));
1229
+ message.addSegment("FT1", [
1230
+ "1",
1231
+ // FT1-1 set id
1232
+ transactionId,
1233
+ // FT1-2 transaction id
1234
+ "",
1235
+ // FT1-3 transaction batch id
1236
+ timestamp,
1237
+ // FT1-4 transaction date
1238
+ "",
1239
+ // FT1-5 transaction posting date
1240
+ "CG",
1241
+ // FT1-6 transaction type (charge)
1242
+ componentsField([charge.code, charge.text, "99SYN"]),
1243
+ // FT1-7 transaction code (local)
1244
+ "",
1245
+ // FT1-8 transaction description
1246
+ "",
1247
+ // FT1-9 transaction description, alternate
1248
+ quantity
1249
+ // FT1-10 transaction quantity
1250
+ ]);
1251
+ return message;
1252
+ }
1253
+
1254
+ // src/hl7/oml.ts
1255
+ function generateOml(options = {}) {
1256
+ const seed = options.seed ?? 0;
1257
+ const rng = createRng(seed);
1258
+ const { message } = mshScaffold(rng, "OML^O21^OML_O21");
1259
+ message.addSegment("PID", pidSegment(patientIdentity(rng)));
1260
+ const service = rng.pick(EXAMPLE_ORDER_SERVICES);
1261
+ const placer = rng.digits(8);
1262
+ const filler = rng.digits(8);
1263
+ const orderedAt = seededTimestamp(rng);
1264
+ message.addSegment("ORC", [
1265
+ "NW",
1266
+ // ORC-1 order control (new order)
1267
+ placer,
1268
+ // ORC-2 placer order number
1269
+ filler,
1270
+ // ORC-3 filler order number
1271
+ ...Array(5).fill(""),
1272
+ // ORC-4 to ORC-8
1273
+ orderedAt
1274
+ // ORC-9 date/time of transaction
1275
+ ]);
1276
+ message.addSegment("OBR", [
1277
+ "1",
1278
+ // OBR-1 set id
1279
+ placer,
1280
+ // OBR-2 placer order number (matches ORC)
1281
+ filler,
1282
+ // OBR-3 filler order number (matches ORC)
1283
+ componentsField([service.code, service.text, service.system])
1284
+ // OBR-4 universal service id
1285
+ ]);
1286
+ return message;
1287
+ }
1288
+
1289
+ // src/hl7/ack.ts
1290
+ function generateAck(options = {}) {
1291
+ const seed = options.seed ?? 0;
1292
+ const allowed = ["AA", "AE", "AR"];
1293
+ const code = resolveKind(allowed, options.code ?? "AA");
1294
+ const inbound = options.inbound ?? generateAdt({ seed });
1295
+ const inboundControlId = inbound.get("MSH.10") ?? "";
1296
+ if (inboundControlId === "") throw new SynthError(SYNTH_FATAL_CODES.SYNTH_ACK_UNCORRELATED);
1297
+ const rng = createRng(seed);
1298
+ const delaySeconds = rng.int(1, 59);
1299
+ const controlId = `SYNTHACK${rng.digits(10)}`;
1300
+ const inboundTime = String(inbound.get("MSH.7"));
1301
+ const timestamp = /^\d{14}$/.test(inboundTime) ? shiftTimestamp(inboundTime, delaySeconds) : seededTimestamp(rng);
1302
+ const trigger = inbound.get("MSH.9.2") ?? "";
1303
+ const message = hl7Peer().buildMessage({
1304
+ type: trigger === "" ? "ACK" : `ACK^${trigger}^ACK`,
1305
+ sendingApp: inbound.get("MSH.5") ?? "",
1306
+ sendingFacility: inbound.get("MSH.6") ?? "",
1307
+ receivingApp: inbound.get("MSH.3") ?? "",
1308
+ receivingFacility: inbound.get("MSH.4") ?? "",
1309
+ controlId,
1310
+ timestamp,
1311
+ version: inbound.get("MSH.12") ?? "2.5",
1312
+ // MSH-11 processing ID `T` (HL7 table 0103: training): an acknowledgment of test data is test
1313
+ // data, so it never echoes a `P` from the inbound message.
1314
+ processingId: "T"
1315
+ });
1316
+ message.addSegment("MSA", [code, inboundControlId]);
1317
+ if (code !== "AA") {
1318
+ message.addSegment("ERR", [
1319
+ "",
1320
+ // ERR-1 error code and location (backward compatible only)
1321
+ "",
1322
+ // ERR-2 error location
1323
+ componentsField(["207", "Application internal error", "HL70357"]),
1324
+ // ERR-3 HL7 error code
1325
+ "E"
1326
+ // ERR-4 severity (error)
1327
+ ]);
1328
+ }
1329
+ return message;
1330
+ }
1331
+
850
1332
  // src/hl7/index.ts
851
- var ALL_KINDS = Object.freeze([
1333
+ var HL7_MESSAGE_KINDS = Object.freeze([
852
1334
  "ADT^A01",
853
1335
  "ADT^A04",
854
1336
  "ADT^A08",
855
1337
  "ORU^R01",
856
1338
  "ORM^O01",
857
1339
  "SIU^S12",
858
- "VXU^V04"
1340
+ "VXU^V04",
1341
+ "ACK",
1342
+ "ADT^A02",
1343
+ "ADT^A03",
1344
+ "ADT^A11",
1345
+ "ADT^A13",
1346
+ "ADT^A28",
1347
+ "ADT^A31",
1348
+ "MDM^T02",
1349
+ "DFT^P03",
1350
+ "OML^O21"
859
1351
  ]);
860
1352
  function generateHl7(kind, seed) {
861
- switch (resolveKind(ALL_KINDS, kind)) {
1353
+ switch (resolveKind(HL7_MESSAGE_KINDS, kind)) {
862
1354
  case "ADT^A01":
863
1355
  return generateAdt({ seed, trigger: "A01" });
864
1356
  case "ADT^A04":
@@ -873,6 +1365,26 @@ function generateHl7(kind, seed) {
873
1365
  return generateSiu({ seed });
874
1366
  case "VXU^V04":
875
1367
  return generateVxu({ seed });
1368
+ case "ACK":
1369
+ return generateAck({ seed });
1370
+ case "ADT^A02":
1371
+ return generateAdt({ seed, trigger: "A02" });
1372
+ case "ADT^A03":
1373
+ return generateAdt({ seed, trigger: "A03" });
1374
+ case "ADT^A11":
1375
+ return generateAdt({ seed, trigger: "A11" });
1376
+ case "ADT^A13":
1377
+ return generateAdt({ seed, trigger: "A13" });
1378
+ case "ADT^A28":
1379
+ return generateAdt({ seed, trigger: "A28" });
1380
+ case "ADT^A31":
1381
+ return generateAdt({ seed, trigger: "A31" });
1382
+ case "MDM^T02":
1383
+ return generateMdm({ seed });
1384
+ case "DFT^P03":
1385
+ return generateDft({ seed });
1386
+ case "OML^O21":
1387
+ return generateOml({ seed });
876
1388
  }
877
1389
  }
878
1390