@cosyte/synth 0.0.3 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/CHANGELOG.md +297 -0
  2. package/README.md +6 -2
  3. package/dist/astm/index.cjs +83 -42
  4. package/dist/astm/index.cjs.map +1 -1
  5. package/dist/astm/index.d.cts +1 -1
  6. package/dist/astm/index.d.ts +1 -1
  7. package/dist/astm/index.mjs +83 -42
  8. package/dist/astm/index.mjs.map +1 -1
  9. package/dist/ccda/index.cjs +89 -44
  10. package/dist/ccda/index.cjs.map +1 -1
  11. package/dist/ccda/index.d.cts +5 -3
  12. package/dist/ccda/index.d.ts +5 -3
  13. package/dist/ccda/index.mjs +89 -44
  14. package/dist/ccda/index.mjs.map +1 -1
  15. package/dist/deid/index.cjs +92 -13
  16. package/dist/deid/index.cjs.map +1 -1
  17. package/dist/deid/index.d.cts +1 -1
  18. package/dist/deid/index.d.ts +1 -1
  19. package/dist/deid/index.mjs +93 -14
  20. package/dist/deid/index.mjs.map +1 -1
  21. package/dist/fhir/index.cjs +78 -5
  22. package/dist/fhir/index.cjs.map +1 -1
  23. package/dist/fhir/index.mjs +78 -5
  24. package/dist/fhir/index.mjs.map +1 -1
  25. package/dist/hl7/index.cjs +89 -41
  26. package/dist/hl7/index.cjs.map +1 -1
  27. package/dist/hl7/index.d.cts +1 -1
  28. package/dist/hl7/index.d.ts +1 -1
  29. package/dist/hl7/index.mjs +89 -41
  30. package/dist/hl7/index.mjs.map +1 -1
  31. package/dist/index.cjs +91 -40
  32. package/dist/index.cjs.map +1 -1
  33. package/dist/index.d.cts +126 -8
  34. package/dist/index.d.ts +126 -8
  35. package/dist/index.mjs +89 -41
  36. package/dist/index.mjs.map +1 -1
  37. package/dist/ncpdp/index.cjs +56 -4
  38. package/dist/ncpdp/index.cjs.map +1 -1
  39. package/dist/ncpdp/index.mjs +56 -4
  40. package/dist/ncpdp/index.mjs.map +1 -1
  41. package/dist/{quirk-JLyO1Ncj.d.ts → quirk-C9t9CkPS.d.ts} +17 -6
  42. package/dist/{quirk-DmkgoZdh.d.cts → quirk-DYMDojVw.d.cts} +17 -6
  43. package/dist/x12/index.cjs +82 -13
  44. package/dist/x12/index.cjs.map +1 -1
  45. package/dist/x12/index.d.cts +2 -1
  46. package/dist/x12/index.d.ts +2 -1
  47. package/dist/x12/index.mjs +83 -14
  48. package/dist/x12/index.mjs.map +1 -1
  49. package/package.json +2 -2
@@ -2,7 +2,7 @@ import { Hl7MessageKind } from '../hl7/index.cjs';
2
2
  import { DeidPolicy, GenericLocus } from '@cosyte/deid';
3
3
  import '@cosyte/hl7';
4
4
  import '../providers-OLz3zAc-.cjs';
5
- import '../quirk-DmkgoZdh.cjs';
5
+ import '../quirk-DYMDojVw.cjs';
6
6
 
7
7
  /**
8
8
  * The format-agnostic core of the **`@cosyte/deid` pairing loop** — a **closed-loop
@@ -2,7 +2,7 @@ import { Hl7MessageKind } from '../hl7/index.js';
2
2
  import { DeidPolicy, GenericLocus } from '@cosyte/deid';
3
3
  import '@cosyte/hl7';
4
4
  import '../providers-OLz3zAc-.js';
5
- import '../quirk-JLyO1Ncj.js';
5
+ import '../quirk-C9t9CkPS.js';
6
6
 
7
7
  /**
8
8
  * The format-agnostic core of the **`@cosyte/deid` pairing loop** — a **closed-loop
@@ -4,7 +4,7 @@ import { extractX12Loci, deidentifyX12 } from '@cosyte/deid/x12';
4
4
  import { extractTelecomLoci, deidentifyTelecom } from '@cosyte/deid/ncpdp';
5
5
  import { deidentifyCcda } from '@cosyte/deid/ccda';
6
6
  import { serializeResource, complex, list, primitive, decimal } from '@cosyte/fhir';
7
- import { serializeX12, parseX12, build835, build271, X12Decimal, build837P, build837I, build837D } from '@cosyte/x12';
7
+ import { serializeX12, parseX12, build835, build271, X12Decimal, build837D, build837I, build837P } from '@cosyte/x12';
8
8
  import { serializeCcda, buildCcda, NCI_ROUTE, SNOMED_CT, CVX, RXNORM, LOINC } from '@cosyte/ccda';
9
9
  import { parseTelecom, serializeTelecom, buildTelecomRequest } from '@cosyte/ncpdp/telecom';
10
10
  import { buildMessage } from '@cosyte/hl7';
@@ -41,6 +41,49 @@ function sfc32Next(s) {
41
41
  return t >>> 0;
42
42
  }
43
43
 
44
+ // src/codes.ts
45
+ var SYNTH_FATAL_CODES = {
46
+ /** A concept's code-system URI has no OID mapping in the C-CDA example-code table. Fatal. */
47
+ SYNTH_UNMAPPED_CODE_SYSTEM: "SYNTH_UNMAPPED_CODE_SYSTEM",
48
+ /** A money value could not be read as an X12 decimal. Fatal — a generator never rounds to a float. */
49
+ SYNTH_INVALID_DECIMAL: "SYNTH_INVALID_DECIMAL",
50
+ /** An integer range was requested with its maximum below its minimum. Fatal. */
51
+ SYNTH_INVALID_RANGE: "SYNTH_INVALID_RANGE",
52
+ /** A value was drawn from an empty pool. Fatal — never a fabricated substitute. */
53
+ SYNTH_EMPTY_POOL: "SYNTH_EMPTY_POOL",
54
+ /**
55
+ * A caller-supplied selector (a message kind, a document type, a corpus mix entry, a claim
56
+ * variant, a Bundle type, a resource profile) is not in the closed set that governs it. Fatal —
57
+ * see `resolveKind`: a selector union is erased at run time, and a selector that falls through
58
+ * either mislabels the fixture or hands the value to a peer builder that quotes it back.
59
+ */
60
+ SYNTH_UNSUPPORTED_KIND: "SYNTH_UNSUPPORTED_KIND"
61
+ };
62
+ var SYNTH_FATAL_MESSAGES = Object.freeze({
63
+ 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.",
64
+ SYNTH_UNSUPPORTED_QUIRK: "The requested vendor quirk is not in the target format's quirk registry. Compare the request against that format's exported registry (HL7_QUIRKS, CCDA_QUIRKS, ASTM_QUIRKS).",
65
+ SYNTH_QUIRK_ANCHOR_ABSENT: "The quirk transform found no structural anchor to mutate, so the fixture would not carry the deviation it is labelled with. Refusing to emit a mislabeled fixture.",
66
+ SYNTH_INTENDED_WARNING_MISMATCH: "A bare parse of the generated quirk artifact did not produce exactly the declared intended warning code(s). Refusing to emit a mislabeled fixture.",
67
+ SYNTH_UNMAPPED_CODE_SYSTEM: "The concept's code-system URI has no OID mapping in the C-CDA example-code table.",
68
+ SYNTH_INVALID_DECIMAL: "The value could not be read as an X12 decimal.",
69
+ SYNTH_INVALID_RANGE: "An integer range was requested with its maximum below its minimum.",
70
+ SYNTH_EMPTY_POOL: "A value was drawn from an empty pool.",
71
+ SYNTH_INVALID_PROFILE: "defineSynthProfile requires a non-empty string name.",
72
+ 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."
73
+ });
74
+ var SynthError = class extends Error {
75
+ /** The stable fatal code. */
76
+ code;
77
+ /**
78
+ * @param code - The stable {@link SynthFatalCode}. The message comes from the frozen registry.
79
+ */
80
+ constructor(code) {
81
+ super(SYNTH_FATAL_MESSAGES[code]);
82
+ this.name = "SynthError";
83
+ this.code = code;
84
+ }
85
+ };
86
+
44
87
  // src/rng/rng.ts
45
88
  var Sfc32Rng = class {
46
89
  seed;
@@ -58,7 +101,7 @@ var Sfc32Rng = class {
58
101
  return this.nextUint32() / 4294967296;
59
102
  }
60
103
  int(min, max) {
61
- if (max < min) throw new RangeError(`Rng.int: max (${String(max)}) < min (${String(min)})`);
104
+ if (max < min) throw new SynthError(SYNTH_FATAL_CODES.SYNTH_INVALID_RANGE);
62
105
  const span = max - min + 1;
63
106
  return min + Math.floor(this.float() * span);
64
107
  }
@@ -66,7 +109,7 @@ var Sfc32Rng = class {
66
109
  return this.float() < p;
67
110
  }
68
111
  pick(items) {
69
- if (items.length === 0) throw new RangeError("Rng.pick: empty array");
112
+ if (items.length === 0) throw new SynthError(SYNTH_FATAL_CODES.SYNTH_EMPTY_POOL);
70
113
  return items[this.int(0, items.length - 1)];
71
114
  }
72
115
  digits(n) {
@@ -436,10 +479,18 @@ function pidSegment(id) {
436
479
  ];
437
480
  }
438
481
 
482
+ // src/select.ts
483
+ function resolveKind(allowed, requested) {
484
+ const match = allowed.find((value) => value === requested);
485
+ if (match === void 0) throw new SynthError(SYNTH_FATAL_CODES.SYNTH_UNSUPPORTED_KIND);
486
+ return match;
487
+ }
488
+
439
489
  // src/hl7/adt.ts
490
+ var ADT_TRIGGERS = Object.freeze(["A01", "A04", "A08"]);
440
491
  function generateAdt(options = {}) {
441
492
  const seed = options.seed ?? 0;
442
- const trigger = options.trigger ?? "A01";
493
+ const trigger = resolveKind(ADT_TRIGGERS, options.trigger ?? "A01");
443
494
  const rng = createRng(seed);
444
495
  const { message, timestamp } = mshScaffold(rng, `ADT^${trigger}`);
445
496
  message.addSegment("EVN", [trigger, timestamp]);
@@ -710,8 +761,19 @@ function generateVxu(options = {}) {
710
761
  ]);
711
762
  return message;
712
763
  }
764
+
765
+ // src/hl7/index.ts
766
+ var ALL_KINDS = Object.freeze([
767
+ "ADT^A01",
768
+ "ADT^A04",
769
+ "ADT^A08",
770
+ "ORU^R01",
771
+ "ORM^O01",
772
+ "SIU^S12",
773
+ "VXU^V04"
774
+ ]);
713
775
  function generateHl7(kind, seed) {
714
- switch (kind) {
776
+ switch (resolveKind(ALL_KINDS, kind)) {
715
777
  case "ADT^A01":
716
778
  return generateAdt({ seed, trigger: "A01" });
717
779
  case "ADT^A04":
@@ -1130,6 +1192,7 @@ var EXAMPLE_ENCOUNTER_CLASSES = Object.freeze([
1130
1192
  ]);
1131
1193
 
1132
1194
  // src/fhir/patient.ts
1195
+ var PATIENT_PROFILES = Object.freeze(["base", "us-core"]);
1133
1196
  function ombExtension(url, category) {
1134
1197
  return complex([
1135
1198
  prop("url", str(url)),
@@ -1143,7 +1206,8 @@ function ombExtension(url, category) {
1143
1206
  ]);
1144
1207
  }
1145
1208
  function generatePatient(options = {}) {
1146
- const { seed = 0, profile = "base" } = options;
1209
+ const { seed = 0 } = options;
1210
+ const profile = resolveKind(PATIENT_PROFILES, options.profile ?? "base");
1147
1211
  const rng = createRng(seed);
1148
1212
  const id = fhirPatientIdentity(rng);
1149
1213
  const usCore = profile === "us-core";
@@ -1526,6 +1590,11 @@ function buildComposition(rng, input) {
1526
1590
  }
1527
1591
 
1528
1592
  // src/fhir/bundle.ts
1593
+ var BUNDLE_TYPES = Object.freeze([
1594
+ "collection",
1595
+ "transaction",
1596
+ "document"
1597
+ ]);
1529
1598
  function resourceTypeOf(resource) {
1530
1599
  const rt = resource.properties.find((p) => p.name === "resourceType");
1531
1600
  return rt !== void 0 && rt.value.kind === "primitive" ? String(rt.value.value) : "Resource";
@@ -1627,7 +1696,8 @@ function documentSections(urls) {
1627
1696
  ];
1628
1697
  }
1629
1698
  function generateBundle(options = {}) {
1630
- const { seed = 0, type = "collection" } = options;
1699
+ const { seed = 0 } = options;
1700
+ const type = resolveKind(BUNDLE_TYPES, options.type ?? "collection");
1631
1701
  const rng = createRng(seed);
1632
1702
  const spine = buildSpine(rng);
1633
1703
  const members = type === "document" ? [
@@ -1680,7 +1750,7 @@ function generateBundle(options = {}) {
1680
1750
  }
1681
1751
  function dec2(value) {
1682
1752
  const d = X12Decimal.fromString(value);
1683
- if (d === void 0) throw new Error(`invalid synthetic decimal: ${value}`);
1753
+ if (d === void 0) throw new SynthError(SYNTH_FATAL_CODES.SYNTH_INVALID_DECIMAL);
1684
1754
  return d;
1685
1755
  }
1686
1756
  function money(n) {
@@ -1786,6 +1856,7 @@ var TOOTH_SURFACES = Object.freeze(["M", "O", "D", "B", "L"]);
1786
1856
  var SERVICE_TYPE_CODES = Object.freeze(["30", "1", "35", "88", "98"]);
1787
1857
 
1788
1858
  // src/x12/claim-837.ts
1859
+ var CLAIM_837_VARIANTS = Object.freeze(["P", "I", "D"]);
1789
1860
  function serviceDtp(serviceDate2) {
1790
1861
  return { qualifier: "472", formatQualifier: "D8", value: serviceDate2 };
1791
1862
  }
@@ -1968,11 +2039,17 @@ function specOf(rng, variant) {
1968
2039
  };
1969
2040
  }
1970
2041
  function generate837(variant, options) {
2042
+ const resolved = resolveKind(CLAIM_837_VARIANTS, variant);
1971
2043
  const rng = createRng(options.seed);
1972
- const spec = specOf(rng, variant);
1973
- if (variant === "P") return build837P(spec);
1974
- if (variant === "I") return build837I(spec);
1975
- return build837D(spec);
2044
+ const spec = specOf(rng, resolved);
2045
+ switch (resolved) {
2046
+ case "P":
2047
+ return build837P(spec);
2048
+ case "I":
2049
+ return build837I(spec);
2050
+ case "D":
2051
+ return build837D(spec);
2052
+ }
1976
2053
  }
1977
2054
  function generate837P(options) {
1978
2055
  return generate837("P", options);
@@ -2337,7 +2414,7 @@ var URI_TO_OID = Object.freeze({
2337
2414
  function toBuildCode(concept) {
2338
2415
  const codeSystem = URI_TO_OID[concept.system];
2339
2416
  if (codeSystem === void 0) {
2340
- throw new Error(`ccda example-codes: no OID mapping for code system "${concept.system}"`);
2417
+ throw new SynthError(SYNTH_FATAL_CODES.SYNTH_UNMAPPED_CODE_SYSTEM);
2341
2418
  }
2342
2419
  return { code: concept.code, codeSystem, displayName: concept.display };
2343
2420
  }
@@ -2395,6 +2472,7 @@ function ccdaPatientIdentity(rng) {
2395
2472
  }
2396
2473
 
2397
2474
  // src/ccda/ccd.ts
2475
+ var CCDA_DOCUMENT_TYPES = Object.freeze(["ccd", "referralNote"]);
2398
2476
  function pickN(rng, pool, n) {
2399
2477
  const take = Math.min(n, pool.length);
2400
2478
  const indices = pool.map((_v, i) => i);
@@ -2477,7 +2555,8 @@ function buildInit(rng, documentType) {
2477
2555
  return base;
2478
2556
  }
2479
2557
  function generateCcda(options = {}) {
2480
- const { seed = 0, documentType = "ccd" } = options;
2558
+ const { seed = 0 } = options;
2559
+ const documentType = resolveKind(CCDA_DOCUMENT_TYPES, options.documentType ?? "ccd");
2481
2560
  const rng = createRng(seed);
2482
2561
  return buildCcda(buildInit(rng, documentType));
2483
2562
  }