@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
 
@@ -229,7 +267,7 @@ function luhnMod10(digits) {
229
267
  if (d < 0 || d > 9) continue;
230
268
  if (double) {
231
269
  d *= 2;
232
- if (d > 9) d -= 9;
270
+ if (d >= 10) d -= 9;
233
271
  }
234
272
  sum += d;
235
273
  double = !double;
@@ -326,7 +364,7 @@ var SYNTHETIC_CITY_NAMES = Object.freeze([
326
364
  ]);
327
365
 
328
366
  // src/safe/providers.ts
329
- function ssn(rng, block = "never-issued") {
367
+ function ssn(rng, block) {
330
368
  if (block === "advertising") {
331
369
  return `987-00-432${String(rng.int(0, 9))}`;
332
370
  }
@@ -453,6 +491,63 @@ var US_STATES = Object.freeze([
453
491
  "WY"
454
492
  ]);
455
493
 
494
+ // src/safe/animal-code.ts
495
+ var ANIMAL_CODE_SYSTEM = "99COSYTE";
496
+ function animalCode(value, text) {
497
+ return Object.freeze({ code: value, text, system: ANIMAL_CODE_SYSTEM });
498
+ }
499
+ function animalSpecies(value, text, breeds) {
500
+ return Object.freeze({ ...animalCode(value, text), breeds: Object.freeze([...breeds]) });
501
+ }
502
+
503
+ // src/safe/animal.ts
504
+ var SYNTHETIC_ANIMAL_SPECIES = Object.freeze([
505
+ animalSpecies("DOG", "Dog", [
506
+ animalCode("DOG-LAB", "Labrador Retriever"),
507
+ animalCode("DOG-BGL", "Beagle"),
508
+ animalCode("DOG-MIX", "Mixed breed dog")
509
+ ]),
510
+ animalSpecies("CAT", "Cat", [animalCode("CAT-DSH", "Domestic Shorthair"), animalCode("CAT-SIA", "Siamese")]),
511
+ animalSpecies("HORSE", "Horse", [animalCode("HRS-QTR", "Quarter Horse"), animalCode("HRS-TB", "Thoroughbred")]),
512
+ animalSpecies("CATTLE", "Cattle", [animalCode("CTL-HOL", "Holstein"), animalCode("CTL-ANG", "Angus")])
513
+ ]);
514
+ var SYNTHETIC_PET_NAMES = Object.freeze([
515
+ "Fixturepaw",
516
+ "Mockwhisker",
517
+ "Stubtail",
518
+ "Seedpup",
519
+ "Testbiscuit",
520
+ "Placepurr",
521
+ "Synthfur",
522
+ "Dummymane",
523
+ "Examplehoof",
524
+ "Fauxfluff",
525
+ "Samplesnout",
526
+ "Voidwag"
527
+ ]);
528
+ var MICROCHIP_TEST_PREFIX = "999";
529
+ function petName(rng) {
530
+ return rng.pick(SYNTHETIC_PET_NAMES);
531
+ }
532
+ function animalBreed(rng) {
533
+ const picked = rng.pick(SYNTHETIC_ANIMAL_SPECIES);
534
+ const { breeds, ...bare } = picked;
535
+ return { species: Object.freeze(bare), breed: rng.pick(breeds) };
536
+ }
537
+ function microchip(rng) {
538
+ return `${MICROCHIP_TEST_PREFIX}${rng.digits(12)}`;
539
+ }
540
+ function animalBirthDate(rng, onDate) {
541
+ const year = Number(onDate.slice(0, 4));
542
+ const month = Number(onDate.slice(4, 6)) - 1;
543
+ const day = Number(onDate.slice(6, 8));
544
+ const latest = Date.UTC(year, month, day);
545
+ const earliest = Date.UTC(year - 20, month, day);
546
+ const span = Math.round((latest - earliest) / 864e5);
547
+ const born = new Date(latest - rng.int(0, span) * 864e5);
548
+ return born.toISOString().slice(0, 10).replace(/-/g, "");
549
+ }
550
+
456
551
  // src/safe/index.ts
457
552
  var safe = Object.freeze({
458
553
  ssn,
@@ -466,25 +561,54 @@ var safe = Object.freeze({
466
561
  address,
467
562
  dateYmd,
468
563
  npi,
469
- dea
564
+ dea,
565
+ petName,
566
+ animalBreed,
567
+ microchip,
568
+ animalBirthDate
470
569
  });
471
570
  function defaultHost() {
472
571
  if (typeof import.meta.resolve !== "function") return { require: __require, resolve: void 0 };
473
572
  return { require: createRequire(import.meta.url), resolve: (s) => import.meta.resolve(s) };
474
573
  }
574
+ function isDenoResolveRefusal(error) {
575
+ return error instanceof TypeError && !("code" in error) && error.message === "Invalid arguments";
576
+ }
475
577
  function isRequireEsmRefusal(error) {
476
578
  return error?.code === "ERR_REQUIRE_ESM";
477
579
  }
580
+ function peerPackage(specifier) {
581
+ const name2 = specifier.split("/").slice(0, 2).join("/");
582
+ return Object.hasOwn(SYNTH_PEER_MESSAGES, name2) ? name2 : void 0;
583
+ }
584
+ function isPeerNotFound(error, specifier, peer) {
585
+ const { code, message } = error ?? {};
586
+ if (typeof message !== "string") return false;
587
+ if (code === "ERR_MODULE_NOT_FOUND") return message.startsWith(`Cannot find package '${peer}'`);
588
+ if (code === "MODULE_NOT_FOUND") return message.startsWith(`Cannot find module '${specifier}'`);
589
+ return false;
590
+ }
478
591
  function loadPeer(specifier, host) {
592
+ try {
593
+ return loadFrom(specifier, host);
594
+ } catch (error) {
595
+ const peer = peerPackage(specifier);
596
+ if (peer === void 0 || !isPeerNotFound(error, specifier, peer)) throw error;
597
+ throw new SynthError(SYNTH_FATAL_CODES.SYNTH_PEER_NOT_INSTALLED, { peer, cause: error });
598
+ }
599
+ }
600
+ function loadFrom(specifier, host) {
479
601
  if (host.resolve === void 0) return host.require(specifier);
480
- let entry;
602
+ let url;
481
603
  try {
482
- entry = fileURLToPath(host.resolve(specifier));
483
- } catch {
604
+ url = host.resolve(specifier);
605
+ } catch (error) {
606
+ if (!isDenoResolveRefusal(error)) throw error;
484
607
  return host.require(specifier);
485
608
  }
609
+ if (!url.startsWith("file:")) return host.require(specifier);
486
610
  try {
487
- return host.require(entry);
611
+ return host.require(fileURLToPath(url));
488
612
  } catch (error) {
489
613
  if (!isRequireEsmRefusal(error)) throw error;
490
614
  return host.require(specifier);
@@ -506,6 +630,20 @@ function seededTimestamp(rng) {
506
630
  const ss = String(rng.int(0, 59)).padStart(2, "0");
507
631
  return `${date}${hh}${mm}${ss}`;
508
632
  }
633
+ function shiftTimestamp(timestamp, seconds) {
634
+ const at = new Date(
635
+ Date.UTC(
636
+ Number(timestamp.slice(0, 4)),
637
+ Number(timestamp.slice(4, 6)) - 1,
638
+ Number(timestamp.slice(6, 8)),
639
+ Number(timestamp.slice(8, 10)),
640
+ Number(timestamp.slice(10, 12)),
641
+ Number(timestamp.slice(12, 14)) + seconds
642
+ )
643
+ );
644
+ const pad = (n, width = 2) => String(n).padStart(width, "0");
645
+ return pad(at.getUTCFullYear(), 4) + pad(at.getUTCMonth() + 1) + pad(at.getUTCDate()) + pad(at.getUTCHours()) + pad(at.getUTCMinutes()) + pad(at.getUTCSeconds());
646
+ }
509
647
  function mshScaffold(rng, type) {
510
648
  const timestamp = seededTimestamp(rng);
511
649
  const controlId = `SYNTH${rng.digits(10)}`;
@@ -576,6 +714,107 @@ function pidSegment(id) {
576
714
  // PID-19 SSN (digits)
577
715
  ];
578
716
  }
717
+ function hl7Subjects() {
718
+ return ["person", "animal"];
719
+ }
720
+ var MICROCHIP_IDENTIFIER_TYPE = "MCN";
721
+ var OWNER_RELATIONSHIP = Object.freeze({ code: "OWN", text: "Owner", system: "HL70063" });
722
+ function animalIdentity(rng, onDate) {
723
+ const person = safe.name(rng);
724
+ const petName2 = safe.petName(rng);
725
+ const record = safe.identifier(rng, "MR");
726
+ const microchip2 = safe.microchip(rng);
727
+ const { species, breed } = safe.animalBreed(rng);
728
+ const dob = safe.animalBirthDate(rng, onDate);
729
+ const sex = rng.pick(["M", "F"]);
730
+ const address2 = safe.address(rng);
731
+ const phone2 = safe.phone(rng);
732
+ return {
733
+ petName: petName2,
734
+ record,
735
+ microchip: microchip2,
736
+ species,
737
+ breed,
738
+ dob,
739
+ sex,
740
+ owner: { person, address: address2, phone: phone2 }
741
+ };
742
+ }
743
+ function codedField(value) {
744
+ return componentsField([value.code, value.text, value.system]);
745
+ }
746
+ function animalPidSegment(id) {
747
+ const authority = id.record.assigningAuthority;
748
+ const cx = (value, type) => ({
749
+ components: [value, "", "", authority, type].map((c) => ({ subcomponents: [c] }))
750
+ });
751
+ const identifiers = {
752
+ repetitions: [
753
+ cx(id.record.value, id.record.typeCode),
754
+ cx(id.microchip, MICROCHIP_IDENTIFIER_TYPE)
755
+ ],
756
+ isNull: false
757
+ };
758
+ const { person, address: address2, phone: phone2 } = id.owner;
759
+ return [
760
+ "1",
761
+ // PID-1 set id
762
+ "",
763
+ // PID-2
764
+ identifiers,
765
+ // PID-3 CX: record number, then microchip
766
+ "",
767
+ // PID-4
768
+ componentsField([person.family, id.petName]),
769
+ // PID-5 XPN: owner family, pet name
770
+ "",
771
+ // PID-6
772
+ id.dob,
773
+ // PID-7 DOB
774
+ id.sex,
775
+ // PID-8 sex
776
+ "",
777
+ // PID-9
778
+ "",
779
+ // PID-10
780
+ componentsField([address2.street, "", address2.city, address2.state, address2.zip]),
781
+ // PID-11 XAD
782
+ "",
783
+ // PID-12
784
+ phone2,
785
+ // PID-13 phone
786
+ ...Array(21).fill(""),
787
+ // PID-14 to PID-34
788
+ codedField(id.species),
789
+ // PID-35 species code (CE)
790
+ codedField(id.breed)
791
+ // PID-36 breed code (CE)
792
+ ];
793
+ }
794
+ function ownerNk1Segment(id) {
795
+ const { person, address: address2, phone: phone2 } = id.owner;
796
+ return [
797
+ "1",
798
+ // NK1-1 set id
799
+ componentsField([person.family, person.given]),
800
+ // NK1-2 name
801
+ codedField(OWNER_RELATIONSHIP),
802
+ // NK1-3 relationship (CE)
803
+ componentsField([address2.street, "", address2.city, address2.state, address2.zip]),
804
+ // NK1-4
805
+ phone2
806
+ // NK1-5 phone
807
+ ];
808
+ }
809
+ function addSubject(message, rng, subject, timestamp) {
810
+ if (subject === "person") {
811
+ message.addSegment("PID", pidSegment(patientIdentity(rng)));
812
+ return;
813
+ }
814
+ const animal = animalIdentity(rng, timestamp);
815
+ message.addSegment("PID", animalPidSegment(animal));
816
+ message.addSegment("NK1", ownerNk1Segment(animal));
817
+ }
579
818
 
580
819
  // src/select.ts
581
820
  function resolveKind(allowed, requested) {
@@ -587,27 +826,90 @@ function resolveMix(allowed, requested, fallback) {
587
826
  if (requested === void 0) return fallback;
588
827
  return requested.map((entry) => resolveKind(allowed, entry));
589
828
  }
829
+ function requireSelection(selection) {
830
+ if (selection.length === 0) throw new SynthError(SYNTH_FATAL_CODES.SYNTH_EMPTY_POOL);
831
+ return selection;
832
+ }
590
833
 
591
834
  // src/hl7/adt.ts
592
- var ADT_TRIGGERS = Object.freeze(["A01", "A04", "A08"]);
835
+ function adtTriggers() {
836
+ return ["A01", "A04", "A08", "A02", "A03", "A11", "A13", "A28", "A31"];
837
+ }
838
+ function structureOf(trigger) {
839
+ switch (trigger) {
840
+ case "A02":
841
+ return "ADT_A02";
842
+ case "A03":
843
+ return "ADT_A03";
844
+ case "A11":
845
+ return "ADT_A09";
846
+ case "A13":
847
+ return "ADT_A01";
848
+ case "A28":
849
+ case "A31":
850
+ return "ADT_A05";
851
+ }
852
+ }
853
+ function ward(room) {
854
+ return componentsField(["SYNTHWARD", String(room).padStart(3, "0"), "01"]);
855
+ }
856
+ function location(rng) {
857
+ return ward(rng.int(1, 999));
858
+ }
859
+ function admittedBefore(rng, timestamp) {
860
+ return shiftTimestamp(timestamp, -rng.int(3600, 14 * 86400));
861
+ }
593
862
  function generateAdt(options = {}) {
594
863
  const seed = options.seed ?? 0;
595
- const trigger = resolveKind(ADT_TRIGGERS, options.trigger ?? "A01");
864
+ const trigger = resolveKind(adtTriggers(), options.trigger ?? "A01");
865
+ const subject = resolveKind(hl7Subjects(), options.subject ?? "person");
596
866
  const rng = createRng(seed);
597
- const { message, timestamp } = mshScaffold(rng, `ADT^${trigger}`);
867
+ if (trigger === "A01" || trigger === "A04" || trigger === "A08") {
868
+ const { message: message2, timestamp: timestamp2 } = mshScaffold(rng, `ADT^${trigger}`);
869
+ message2.addSegment("EVN", [trigger, timestamp2]);
870
+ addSubject(message2, rng, subject, timestamp2);
871
+ const patientClass = rng.pick(["I", "O", "E"]);
872
+ message2.addSegment("PV1", [
873
+ "1",
874
+ // PV1-1 set id
875
+ patientClass,
876
+ // PV1-2 patient class
877
+ location(rng)
878
+ // PV1-3 PL
879
+ ]);
880
+ return message2;
881
+ }
882
+ const { message, timestamp } = mshScaffold(rng, `ADT^${trigger}^${structureOf(trigger)}`);
598
883
  message.addSegment("EVN", [trigger, timestamp]);
599
- message.addSegment("PID", pidSegment(patientIdentity(rng)));
600
- const patientClass = rng.pick(["I", "O", "E"]);
601
- message.addSegment("PV1", [
602
- "1",
603
- // PV1-1 set id
604
- patientClass,
605
- // PV1-2 patient class
606
- componentsField(["SYNTHWARD", String(rng.int(1, 999)).padStart(3, "0"), "01"])
607
- // PV1-3 PL
608
- ]);
884
+ addSubject(message, rng, subject, timestamp);
885
+ message.addSegment("PV1", visit(rng, trigger, timestamp));
609
886
  return message;
610
887
  }
888
+ function visit(rng, trigger, timestamp) {
889
+ switch (trigger) {
890
+ case "A02": {
891
+ const room = rng.int(1, 998);
892
+ const priorRoom = room + rng.int(1, 999 - room);
893
+ return ["1", "I", ward(room), "", "", ward(priorRoom)];
894
+ }
895
+ case "A03": {
896
+ const patientClass = rng.pick(["I", "O", "E"]);
897
+ const where = location(rng);
898
+ const admitted = admittedBefore(rng, timestamp);
899
+ return ["1", patientClass, where, ...Array(40).fill(""), admitted, timestamp];
900
+ }
901
+ case "A11":
902
+ return ["1", rng.pick(["I", "O", "E"]), location(rng)];
903
+ case "A13": {
904
+ const where = location(rng);
905
+ const admitted = admittedBefore(rng, timestamp);
906
+ return ["1", "I", where, ...Array(40).fill(""), admitted];
907
+ }
908
+ case "A28":
909
+ case "A31":
910
+ return ["1", "N"];
911
+ }
912
+ }
611
913
 
612
914
  // src/hl7/example-codes.ts
613
915
  var EXAMPLE_LAB_OBSERVATIONS = Object.freeze([
@@ -672,9 +974,10 @@ function obxSegment(rng, setId) {
672
974
  }
673
975
  function generateOru(options = {}) {
674
976
  const seed = options.seed ?? 0;
977
+ const subject = resolveKind(hl7Subjects(), options.subject ?? "person");
675
978
  const rng = createRng(seed);
676
- const { message } = mshScaffold(rng, "ORU^R01");
677
- message.addSegment("PID", pidSegment(patientIdentity(rng)));
979
+ const { message, timestamp } = mshScaffold(rng, "ORU^R01");
980
+ addSubject(message, rng, subject, timestamp);
678
981
  const service = rng.pick(EXAMPLE_ORDER_SERVICES);
679
982
  const placer = rng.digits(8);
680
983
  const filler = rng.digits(8);
@@ -865,6 +1168,184 @@ function generateVxu(options = {}) {
865
1168
  return message;
866
1169
  }
867
1170
 
1171
+ // src/hl7/mdm.ts
1172
+ var DOCUMENT_TYPES = Object.freeze(["CN", "DS", "HP", "PN"]);
1173
+ var NOTE_LINES = Object.freeze([
1174
+ "Synthetic test document for interface testing.",
1175
+ "Synthetic note text: no real patient is described here.",
1176
+ "Synthetic content generated from a seed."
1177
+ ]);
1178
+ function generateMdm(options = {}) {
1179
+ const seed = options.seed ?? 0;
1180
+ const rng = createRng(seed);
1181
+ const { message, timestamp } = mshScaffold(rng, "MDM^T02^MDM_T02");
1182
+ message.addSegment("EVN", ["T02", timestamp]);
1183
+ message.addSegment("PID", pidSegment(patientIdentity(rng)));
1184
+ message.addSegment("PV1", ["1", rng.pick(["I", "O", "E"])]);
1185
+ const documentType = rng.pick(DOCUMENT_TYPES);
1186
+ const documentNumber = `SYNTHDOC${rng.digits(10)}`;
1187
+ message.addSegment("TXA", [
1188
+ "1",
1189
+ // TXA-1 set id
1190
+ documentType,
1191
+ // TXA-2 document type
1192
+ "TX",
1193
+ // TXA-3 document content presentation (text)
1194
+ timestamp,
1195
+ // TXA-4 activity date/time
1196
+ ...Array(7).fill(""),
1197
+ // TXA-5 to TXA-11
1198
+ componentsField([documentNumber, "COSYTE-SYNTH"]),
1199
+ // TXA-12 unique document number (EI)
1200
+ ...Array(4).fill(""),
1201
+ // TXA-13 to TXA-16
1202
+ "DO"
1203
+ // TXA-17 document completion status (documented)
1204
+ ]);
1205
+ const lines = rng.int(1, 3);
1206
+ for (let i = 1; i <= lines; i += 1) {
1207
+ message.addSegment("OBX", [
1208
+ String(i),
1209
+ // OBX-1 set id
1210
+ "TX",
1211
+ // OBX-2 value type
1212
+ componentsField(["SYNTHNOTE", "Synthetic note", "99SYN"]),
1213
+ // OBX-3 observation id (local)
1214
+ "",
1215
+ // OBX-4 sub-id
1216
+ rng.pick(NOTE_LINES),
1217
+ // OBX-5 the document text
1218
+ ...Array(5).fill(""),
1219
+ // OBX-6 to OBX-10
1220
+ "F"
1221
+ // OBX-11 result status (final)
1222
+ ]);
1223
+ }
1224
+ return message;
1225
+ }
1226
+
1227
+ // src/hl7/dft.ts
1228
+ function charges() {
1229
+ return [
1230
+ { code: "SYNTHCHG01", text: "Synthetic office visit charge" },
1231
+ { code: "SYNTHCHG02", text: "Synthetic laboratory charge" },
1232
+ { code: "SYNTHCHG03", text: "Synthetic imaging charge" },
1233
+ { code: "SYNTHCHG04", text: "Synthetic supply charge" }
1234
+ ];
1235
+ }
1236
+ function generateDft(options = {}) {
1237
+ const seed = options.seed ?? 0;
1238
+ const rng = createRng(seed);
1239
+ const { message, timestamp } = mshScaffold(rng, "DFT^P03^DFT_P03");
1240
+ message.addSegment("EVN", ["P03", timestamp]);
1241
+ message.addSegment("PID", pidSegment(patientIdentity(rng)));
1242
+ message.addSegment("PV1", ["1", rng.pick(["I", "O", "E"])]);
1243
+ const transactionId = `SYNTHTXN${rng.digits(8)}`;
1244
+ const charge = rng.pick(charges());
1245
+ const quantity = String(rng.int(1, 3));
1246
+ message.addSegment("FT1", [
1247
+ "1",
1248
+ // FT1-1 set id
1249
+ transactionId,
1250
+ // FT1-2 transaction id
1251
+ "",
1252
+ // FT1-3 transaction batch id
1253
+ timestamp,
1254
+ // FT1-4 transaction date
1255
+ "",
1256
+ // FT1-5 transaction posting date
1257
+ "CG",
1258
+ // FT1-6 transaction type (charge)
1259
+ componentsField([charge.code, charge.text, "99SYN"]),
1260
+ // FT1-7 transaction code (local)
1261
+ "",
1262
+ // FT1-8 transaction description
1263
+ "",
1264
+ // FT1-9 transaction description, alternate
1265
+ quantity
1266
+ // FT1-10 transaction quantity
1267
+ ]);
1268
+ return message;
1269
+ }
1270
+
1271
+ // src/hl7/oml.ts
1272
+ function generateOml(options = {}) {
1273
+ const seed = options.seed ?? 0;
1274
+ const rng = createRng(seed);
1275
+ const { message } = mshScaffold(rng, "OML^O21^OML_O21");
1276
+ message.addSegment("PID", pidSegment(patientIdentity(rng)));
1277
+ const service = rng.pick(EXAMPLE_ORDER_SERVICES);
1278
+ const placer = rng.digits(8);
1279
+ const filler = rng.digits(8);
1280
+ const orderedAt = seededTimestamp(rng);
1281
+ message.addSegment("ORC", [
1282
+ "NW",
1283
+ // ORC-1 order control (new order)
1284
+ placer,
1285
+ // ORC-2 placer order number
1286
+ filler,
1287
+ // ORC-3 filler order number
1288
+ ...Array(5).fill(""),
1289
+ // ORC-4 to ORC-8
1290
+ orderedAt
1291
+ // ORC-9 date/time of transaction
1292
+ ]);
1293
+ message.addSegment("OBR", [
1294
+ "1",
1295
+ // OBR-1 set id
1296
+ placer,
1297
+ // OBR-2 placer order number (matches ORC)
1298
+ filler,
1299
+ // OBR-3 filler order number (matches ORC)
1300
+ componentsField([service.code, service.text, service.system])
1301
+ // OBR-4 universal service id
1302
+ ]);
1303
+ return message;
1304
+ }
1305
+
1306
+ // src/hl7/ack.ts
1307
+ function generateAck(options = {}) {
1308
+ const seed = options.seed ?? 0;
1309
+ const allowed = ["AA", "AE", "AR"];
1310
+ const code = resolveKind(allowed, options.code ?? "AA");
1311
+ const inbound = options.inbound ?? generateAdt({ seed });
1312
+ const inboundControlId = inbound.get("MSH.10") ?? "";
1313
+ if (inboundControlId === "") throw new SynthError(SYNTH_FATAL_CODES.SYNTH_ACK_UNCORRELATED);
1314
+ const rng = createRng(seed);
1315
+ const delaySeconds = rng.int(1, 59);
1316
+ const controlId = `SYNTHACK${rng.digits(10)}`;
1317
+ const inboundTime = String(inbound.get("MSH.7"));
1318
+ const timestamp = /^\d{14}$/.test(inboundTime) ? shiftTimestamp(inboundTime, delaySeconds) : seededTimestamp(rng);
1319
+ const trigger = inbound.get("MSH.9.2") ?? "";
1320
+ const message = hl7Peer().buildMessage({
1321
+ type: trigger === "" ? "ACK" : `ACK^${trigger}^ACK`,
1322
+ sendingApp: inbound.get("MSH.5") ?? "",
1323
+ sendingFacility: inbound.get("MSH.6") ?? "",
1324
+ receivingApp: inbound.get("MSH.3") ?? "",
1325
+ receivingFacility: inbound.get("MSH.4") ?? "",
1326
+ controlId,
1327
+ timestamp,
1328
+ version: inbound.get("MSH.12") ?? "2.5",
1329
+ // MSH-11 processing ID `T` (HL7 table 0103: training): an acknowledgment of test data is test
1330
+ // data, so it never echoes a `P` from the inbound message.
1331
+ processingId: "T"
1332
+ });
1333
+ message.addSegment("MSA", [code, inboundControlId]);
1334
+ if (code !== "AA") {
1335
+ message.addSegment("ERR", [
1336
+ "",
1337
+ // ERR-1 error code and location (backward compatible only)
1338
+ "",
1339
+ // ERR-2 error location
1340
+ componentsField(["207", "Application internal error", "HL70357"]),
1341
+ // ERR-3 HL7 error code
1342
+ "E"
1343
+ // ERR-4 severity (error)
1344
+ ]);
1345
+ }
1346
+ return message;
1347
+ }
1348
+
868
1349
  // src/hl7/round-trip.ts
869
1350
  function roundTrip(message) {
870
1351
  const content = message.toString();
@@ -896,7 +1377,7 @@ function defineSynthProfile(spec) {
896
1377
  name: spec.name,
897
1378
  ...spec.givenNames ? { givenNames: Object.freeze([...spec.givenNames]) } : {},
898
1379
  ...spec.familyNames ? { familyNames: Object.freeze([...spec.familyNames]) } : {},
899
- quirks: Object.freeze([...spec.quirks ?? []])
1380
+ ...spec.quirks !== void 0 ? { quirks: Object.freeze([...spec.quirks]) } : {}
900
1381
  });
901
1382
  }
902
1383
 
@@ -938,6 +1419,7 @@ function assertIntendedWarnings(intendedWarnings, bareWarnings) {
938
1419
  }
939
1420
  }
940
1421
  function validateProfileQuirks(profile, registry, format) {
1422
+ if (profile.quirks === void 0) return Object.keys(registry);
941
1423
  for (const name2 of profile.quirks) resolveQuirk(registry, format, name2);
942
1424
  return profile.quirks;
943
1425
  }
@@ -1051,8 +1533,9 @@ var ALL_HL7_QUIRKS = Object.freeze(
1051
1533
  Object.keys(HL7_QUIRKS)
1052
1534
  );
1053
1535
  function hl7QuirkCorpus(options) {
1054
- const quirks = options.profile ? validateProfileQuirks(options.profile, HL7_QUIRKS, "hl7v2") : options.quirks ?? ALL_HL7_QUIRKS;
1055
- const names = quirks.length > 0 ? quirks : ALL_HL7_QUIRKS;
1536
+ const names = requireSelection(
1537
+ options.profile?.quirks !== void 0 ? validateProfileQuirks(options.profile, HL7_QUIRKS, "hl7v2") : options.quirks ?? ALL_HL7_QUIRKS
1538
+ );
1056
1539
  for (const name2 of names) resolveQuirk(HL7_QUIRKS, "hl7v2", name2);
1057
1540
  const kind = options.kind ?? "ORU^R01";
1058
1541
  const count = options.count ?? names.length;
@@ -1076,19 +1559,27 @@ var hl7QuirkProfile = defineSynthProfile({
1076
1559
  });
1077
1560
 
1078
1561
  // src/hl7/index.ts
1079
- var ALL_KINDS = Object.freeze([
1562
+ var HL7_MESSAGE_KINDS = Object.freeze([
1080
1563
  "ADT^A01",
1081
1564
  "ADT^A04",
1082
1565
  "ADT^A08",
1083
1566
  "ORU^R01",
1084
1567
  "ORM^O01",
1085
1568
  "SIU^S12",
1086
- "VXU^V04"
1569
+ "VXU^V04",
1570
+ "ACK",
1571
+ "ADT^A02",
1572
+ "ADT^A03",
1573
+ "ADT^A11",
1574
+ "ADT^A13",
1575
+ "ADT^A28",
1576
+ "ADT^A31",
1577
+ "MDM^T02",
1578
+ "DFT^P03",
1579
+ "OML^O21"
1087
1580
  ]);
1088
- var DEFAULT_MIX = ALL_KINDS;
1089
- var ADT_TRIGGERS2 = Object.freeze(["A01", "A04", "A08"]);
1090
1581
  function generateHl7(kind, seed) {
1091
- switch (resolveKind(ALL_KINDS, kind)) {
1582
+ switch (resolveKind(HL7_MESSAGE_KINDS, kind)) {
1092
1583
  case "ADT^A01":
1093
1584
  return generateAdt({ seed, trigger: "A01" });
1094
1585
  case "ADT^A04":
@@ -1103,16 +1594,42 @@ function generateHl7(kind, seed) {
1103
1594
  return generateSiu({ seed });
1104
1595
  case "VXU^V04":
1105
1596
  return generateVxu({ seed });
1597
+ case "ACK":
1598
+ return generateAck({ seed });
1599
+ case "ADT^A02":
1600
+ return generateAdt({ seed, trigger: "A02" });
1601
+ case "ADT^A03":
1602
+ return generateAdt({ seed, trigger: "A03" });
1603
+ case "ADT^A11":
1604
+ return generateAdt({ seed, trigger: "A11" });
1605
+ case "ADT^A13":
1606
+ return generateAdt({ seed, trigger: "A13" });
1607
+ case "ADT^A28":
1608
+ return generateAdt({ seed, trigger: "A28" });
1609
+ case "ADT^A31":
1610
+ return generateAdt({ seed, trigger: "A31" });
1611
+ case "MDM^T02":
1612
+ return generateMdm({ seed });
1613
+ case "DFT^P03":
1614
+ return generateDft({ seed });
1615
+ case "OML^O21":
1616
+ return generateOml({ seed });
1106
1617
  }
1107
1618
  }
1108
1619
  function hl7Corpus(options) {
1109
1620
  const { seed, count = 1, verify = true } = options;
1110
- const kinds = options.triggers !== void 0 ? options.triggers.map(
1111
- (t) => `ADT^${resolveKind(ADT_TRIGGERS2, t)}`
1112
- ) : resolveMix(ALL_KINDS, options.mix, DEFAULT_MIX);
1621
+ const kinds = requireSelection(
1622
+ options.triggers !== void 0 ? options.triggers.map(
1623
+ (t) => `ADT^${resolveKind(adtTriggers(), t)}`
1624
+ ) : (
1625
+ // The default mix is the first seven kinds: those added since are not in it, so a seed's
1626
+ // default corpus keeps the bytes it had.
1627
+ resolveMix(HL7_MESSAGE_KINDS, options.mix, HL7_MESSAGE_KINDS.slice(0, 7))
1628
+ )
1629
+ );
1113
1630
  const seedStream = createRng(seed);
1114
1631
  const artifacts = Array.from({ length: count }, (_unused, i) => {
1115
- const kind = kinds[i % kinds.length] ?? "ADT^A01";
1632
+ const kind = kinds[i % kinds.length];
1116
1633
  const messageSeed = seedStream.nextUint32();
1117
1634
  const rt = hl7CorpusArtifact(generateHl7(kind, messageSeed), verify);
1118
1635
  return {
@@ -1125,6 +1642,6 @@ function hl7Corpus(options) {
1125
1642
  return makeCorpus(seed, artifacts);
1126
1643
  }
1127
1644
 
1128
- export { EXAMPLE_LAB_OBSERVATIONS, EXAMPLE_ORDER_SERVICES, EXAMPLE_VACCINES, HL7_QUIRKS, componentsField, generateAdt, generateHl7, generateHl7Quirk, generateOrm, generateOru, generateSiu, generateVxu, hl7Corpus, hl7QuirkCorpus, hl7QuirkProfile, hl7QuirkRoundTrip, mshScaffold, patientIdentity, pidSegment, roundTrip, seededTimestamp };
1645
+ export { EXAMPLE_LAB_OBSERVATIONS, EXAMPLE_ORDER_SERVICES, EXAMPLE_VACCINES, HL7_MESSAGE_KINDS, HL7_QUIRKS, MICROCHIP_IDENTIFIER_TYPE, OWNER_RELATIONSHIP, animalIdentity, animalPidSegment, componentsField, generateAck, generateAdt, generateDft, generateHl7, generateHl7Quirk, generateMdm, generateOml, generateOrm, generateOru, generateSiu, generateVxu, hl7Corpus, hl7QuirkCorpus, hl7QuirkProfile, hl7QuirkRoundTrip, mshScaffold, ownerNk1Segment, patientIdentity, pidSegment, roundTrip, seededTimestamp };
1129
1646
  //# sourceMappingURL=index.mjs.map
1130
1647
  //# sourceMappingURL=index.mjs.map