@cosyte/synth 0.0.9 → 0.1.0

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 +73 -0
  2. package/README.md +21 -12
  3. package/dist/astm/index.cjs +18 -6
  4. package/dist/astm/index.cjs.map +1 -1
  5. package/dist/astm/index.d.cts +2 -2
  6. package/dist/astm/index.d.ts +2 -2
  7. package/dist/astm/index.mjs +18 -6
  8. package/dist/astm/index.mjs.map +1 -1
  9. package/dist/ccda/index.cjs +23 -7
  10. package/dist/ccda/index.cjs.map +1 -1
  11. package/dist/ccda/index.d.cts +2 -2
  12. package/dist/ccda/index.d.ts +2 -2
  13. package/dist/ccda/index.mjs +23 -7
  14. package/dist/ccda/index.mjs.map +1 -1
  15. package/dist/deid/index.cjs +26 -8
  16. package/dist/deid/index.cjs.map +1 -1
  17. package/dist/deid/index.d.cts +2 -2
  18. package/dist/deid/index.d.ts +2 -2
  19. package/dist/deid/index.mjs +26 -8
  20. package/dist/deid/index.mjs.map +1 -1
  21. package/dist/fhir/index.cjs +174 -7
  22. package/dist/fhir/index.cjs.map +1 -1
  23. package/dist/fhir/index.d.cts +163 -2
  24. package/dist/fhir/index.d.ts +163 -2
  25. package/dist/fhir/index.mjs +171 -9
  26. package/dist/fhir/index.mjs.map +1 -1
  27. package/dist/hl7/index.cjs +18 -6
  28. package/dist/hl7/index.cjs.map +1 -1
  29. package/dist/hl7/index.d.cts +2 -2
  30. package/dist/hl7/index.d.ts +2 -2
  31. package/dist/hl7/index.mjs +18 -6
  32. package/dist/hl7/index.mjs.map +1 -1
  33. package/dist/index.cjs +37 -6
  34. package/dist/index.cjs.map +1 -1
  35. package/dist/index.d.cts +172 -38
  36. package/dist/index.d.ts +172 -38
  37. package/dist/index.mjs +36 -7
  38. package/dist/index.mjs.map +1 -1
  39. package/dist/ncpdp/index.cjs +18 -6
  40. package/dist/ncpdp/index.cjs.map +1 -1
  41. package/dist/ncpdp/index.d.cts +1 -1
  42. package/dist/ncpdp/index.d.ts +1 -1
  43. package/dist/ncpdp/index.mjs +18 -6
  44. package/dist/ncpdp/index.mjs.map +1 -1
  45. package/dist/{providers-B9uVinAe.d.cts → providers-BQtPk3PN.d.cts} +18 -4
  46. package/dist/{providers-B9uVinAe.d.ts → providers-BQtPk3PN.d.ts} +18 -4
  47. package/dist/{quirk-HZdznAkM.d.ts → quirk-Bzx9g8KG.d.ts} +1 -1
  48. package/dist/{quirk-IaHp4z7N.d.cts → quirk-C_lZrspq.d.cts} +1 -1
  49. package/dist/x12/index.cjs +18 -6
  50. package/dist/x12/index.cjs.map +1 -1
  51. package/dist/x12/index.d.cts +1 -1
  52. package/dist/x12/index.d.ts +1 -1
  53. package/dist/x12/index.mjs +18 -6
  54. package/dist/x12/index.mjs.map +1 -1
  55. package/package.json +54 -30
package/CHANGELOG.md CHANGED
@@ -1,5 +1,78 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - b2155cb: `0.1.0` is the first release of `@cosyte/synth` whose public API we ask you to build on.
8
+
9
+ **What is covered, and what you can depend on.** Seeded generation of spec-clean test fixtures in six formats, each built through that format's own `@cosyte/*` builder or serializer and read back by its parser with zero warnings: HL7 v2 (ADT, ORU, ORM, SIU and VXU), FHIR R4 and US Core (the clinical resources, fixtures requested by US Core 6.1.0 profile name, and `collection`, `transaction` and `document` Bundles), C-CDA R2.1 (CCD and Referral Note), X12 005010 (837P, 837I, 837D, 835 and 271), NCPDP SCRIPT (NewRx, RxRenewalRequest and RxChangeRequest) and Telecom (B1, B2 and B3), and ASTM E1394 records with E1381 framing. Every identifier, name, date, phone and address comes from a reserved range or the shipped fake-name pool, never from real data. Also covered: vendor-quirk fixtures for HL7 v2, C-CDA and ASTM, each round-tripping to exactly one intended warning; the pairing loop with `@cosyte/deid`; the `safe` value providers and their validators; and the stable `SYNTH_*` fatal codes. Node.js 22 and 24, ESM and CommonJS, with type declarations for both, and no third-party runtime dependency.
10
+
11
+ **What the version promises.** The same seed yields byte-identical output, verified across Node 22 and Node 24 on every change. Each format's bytes come from that format's own `@cosyte/*` builder, so the mapping holds for a given set of installed parsers: for a long-lived golden fixture, pin `@cosyte/synth` and the parsers you generate with alongside the seed. With the parsers unchanged, a change to what a seed maps to ships only in a release that declares a major version. For the rest of the API: until 1.0, a breaking change raises the minor version (0.1 to 0.2), and the changelog entry says what broke and what to change. A patch release (0.1.x) does not break you, so a `^0.1.0` range takes the patches and stops before 0.2.0.
12
+
13
+ **What is not covered yet.** Quirk recipes for FHIR, X12 and NCPDP; the X12 270 eligibility request and the NCPDP SCRIPT lifecycle responses, which wait on builders in the parsers; `@cosyte/deid` pairing for NCPDP SCRIPT, ASTM and DICOM; DICOM generation; and clinical realism, since this is a format and conformance generator rather than a clinical simulator. The pairing loop co-validates the two packages on this package's own output and is not an independent audit of `@cosyte/deid`. The documentation at https://docs.cosyte.com/synth lists the known limitations in full.
14
+
15
+ The repository now carries runnable examples under `examples/`, run against the built package on every change.
16
+
17
+ ### Patch Changes
18
+
19
+ - 32fd419: The repository's PHI commit-gate (`pnpm phi-scan`) no longer honours a whole-file `--allow-fixture` bypass. A path the run enumerated and then did not open is now refused (exit 2) and named on stderr, because a scan that did not read a file has no clean verdict to give about it. The flag and its `phi-scan-overrides.md` log are both kept, so an attempt is still recorded and reviewable; what it can no longer do is produce a clean report.
20
+
21
+ This changes an observable exit code for an existing invocation shape: an argv carrying `--allow-fixture` that used to exit 0, or to exit with the hits code, now exits 2 in every mode, `--staged` included. Declaring a genuinely synthetic value in `scripts/phi-allow-list.txt` is the way to clear it, and unlike a whole-file bypass it leaves the file in the sweep. Nothing a consumer installs is affected: no generator, export, warning code or published artifact changes.
22
+
23
+ - 9b69baf: The synthetic-safety floor table now cites, for every locus, the authority that reserves the range or defines the check digit, by that authority's own published identifier and with the sentence the row rests on, so you can open the text and check the claim instead of taking the page's word for it. Where a row was previously grounded on a bare hostname (`ssa.gov`, `nanpa.com`) or on nothing at all, it now names a document: SSA POMS RM 10201.035, IRS Internal Revenue Manual 3.21.263, 69 FR 3434 (FR Doc 04-1149), the NANPA 555 Line Numbers page, RFC 2606, RFC 6761, RFC 5737, RFC 3849, and USPS Postal Facts.
24
+
25
+ Two justifications were wrong and are corrected rather than restated. The NPI check-digit rule was attributed, with no citation, to a "CMS NPI check-digit rule, ISO 7812". The rule that requires the `80840`-prefixed Luhn check is the final rule at 69 FR 3434 (FR Doc 04-1149, docket CMS-0045-F), and the doc comments on `NPI_LUHN_PREFIX`, `npiCheckDigit` and `isSyntheticNpi` now cite it. Only the ISO half of the old attribution is retracted: the rule names no ISO document number, and what replaces `ISO 7812` is the rule's own name for the algorithm, "the ISO standard Luhn check digit algorithm", a modulus 10 double-add-double algorithm. The CMS half stands and is now cited instead of asserted, that document recording its issuing agency as "Centers for Medicare & Medicaid Services, HHS" and stating that "The specification for calculation of the NPI check digit will be made available on the CMS Web site". The SSN row said SSA "never issues" the areas it draws from; what SSA's manual states is that those areas identify an **invalid** SSN, defined there as one SSA never assigned, so that is what the row and `isSyntheticSsn` now say.
26
+
27
+ Three limits that were implicit are now stated on the surface a consumer reads.
28
+ - **The DEA check digit is non-normatively sourced.** The formula `deaCheckDigit` implements is quoted from a pharmacy journal article (PMC3847977), not from the DEA, and no DEA-published statement of the algorithm is cited anywhere in this package. `isSyntheticDea` returning `true` means "fails that formula", not "the DEA could not have issued this", and both doc comments now say so, together with the consequence: if the formula is wrong, a value built to fail it could pass the real check.
29
+ - **Two loci have no reserving authority at all**, and their rows now say so and name what the floor rests on instead: the MRN / member / account namespace (the assigning authority, not the digits) and the name / street / city pool (the shipped clearly-fake pool itself).
30
+ - **The ZIP row draws an inference**, and now presents it as one. USPS publishes that the lowest ZIP Code is `00501`; that `00000` is therefore unassigned is this package's inference, not a Postal Service statement.
31
+
32
+ The claim that `2.16.840.1.113883.19` is a designated example root has been dropped from `SYNTHETIC_ASSIGNING_AUTHORITY`: no published designation could be shown, so none is claimed. The OID is unchanged and the guarantee never rested on it.
33
+
34
+ No generated value changes. Every reserved range, check-digit computation and predicate result is byte-for-byte what it was, so a pinned golden corpus is unaffected: this release changes what the package says about its floors, not where it draws from. A new suite fails the build if a floor-table locus is ever added without an authority identifier and the supporting text to go with it.
35
+
36
+ - 7daedf4: Seed determinism is now verified ACROSS Node majors, not just twice inside one process. Until now every determinism assertion in this repository generated the same seed twice in a single Node process and compared the two, and a single process cannot disagree with itself about the engine it is running on. A change in the JavaScript engine that shifted number formatting, sort stability or key order between Node majors would have landed with every check green, while a consumer's committed golden file quietly stopped matching.
37
+
38
+ On every change, a declared seed corpus covering all six formats is now generated in its own job on **Node 22 and on Node 24**, each job carries out one digest per `(format, seed)` pair and nothing else, and a third job fails the build if the two engines disagree. A mismatch is reported by seed, format and engine identity; the differing bytes are never printed, because the corpus is reproducible from the seed. There is no tolerance on a digest, no warning-only mode and no suppression list. Taking a pair out of the compared set means naming it and its reason in a committed declaration, which every report republishes.
39
+
40
+ Each per-engine run is also measured against a **committed baseline** for the current compatibility window, so a toolchain or dependency change that moves every engine together, the one thing a cross-engine comparison is blind to, is caught as well. That baseline cannot change without a release declaring a breaking change, so a golden fixture pinned to a version inside the window keeps matching.
41
+
42
+ `docs-content/limitations.md` now states which Node majors the seed-to-bytes mapping is verified byte-identical across, that the verification is a digest comparison across separate runs, and that a change to that mapping is released as a breaking change.
43
+
44
+ Nothing about what the generator emits changed. Same seeds, same bytes.
45
+
46
+ - 5e46b8d: The Quickstart page now opens with the same program as the README: an HL7 v2 message generated from a seed and round-tripped through `@cosyte/hl7`, plus a mixed corpus.
47
+
48
+ Before this, the page opened with the seeded value providers while the README opened with the HL7 v2 generators, so a reader arriving from npm and one arriving from the documentation site met two different first programs. The value-provider, round-trip, corpus and determinism examples follow it. The test suite now fails if the page's first program differs from the README's quickstart, and runs it against the built package. Documentation and test change only.
49
+
50
+ - 8389346: You can now ask for a FHIR fixture by **US Core 6.1.0 profile name** instead of by generator name, and the answer is one of exactly two things: an artifact claiming that profile, or a refusal raised before any artifact exists.
51
+
52
+ New exports on `@cosyte/synth/fhir`: `usCoreCoverage()` reports one entry for every one of the **49 resource profiles** the adopted implementation guide publishes, each carrying its canonical URL and whether this build generates it; `generateUsCoreProfile({ profile, seed })` resolves the requested name against that closed set before generating anything. `US_CORE_ADOPTED_PROFILES`, `US_CORE_PROFILE_BASE` and the `UsCoreProfileId` / `UsCoreProfileCoverage` types are exported alongside them. Identifiers only, as ever: no implementation-guide content is bundled.
53
+
54
+ The two refusals are deliberately different codes, because they are different facts about your request. `SYNTH_PROFILE_NOT_GENERATED` (new) says the guide publishes the profile and this build does not generate it yet. `SYNTH_UNSUPPORTED_KIND` says the name is not in the adopted set at all: a typo, a blank, a value that is not a string, or one of the guide's extension definitions, which are not standalone artifacts. Neither quotes your value back, and neither returns a mislabelled artifact.
55
+
56
+ Coverage moves from 10 profiles to 11: **`generateProvenance()`** generates a US Core `Provenance`, built through `@cosyte/fhir`'s model constructors like every other resource here, and validated against the published `us-core-provenance` StructureDefinition with zero errors over arbitrary seeds. The other 38 adopted profiles are reported as uncovered and refuse: this release closes the contract, not the breadth.
57
+
58
+ The conformance suite is now driven by the coverage surface rather than by a list kept beside it, so a profile reported as generated with no committed StructureDefinition behind it fails the suite instead of being skipped.
59
+
60
+ - fd8c32d: Generated FHIR R4 output is now graded by a validator that is not ours. Until now, every "spec-clean" claim this package made was the sibling parser reading back what the sibling parser wrote: a strong structural property, and one that cannot see a misreading of a standard that the builder and the reader share. On every change, the HL7-maintained FHIR validator, pinned by version and by content digest, now validates a seeded corpus of generated FHIR R4 resources against US Core 6.1.0, and any issue it reports at severity `error` or `fatal` fails the build. There is no severity downgrade, no warning-only mode and no suppression list.
61
+
62
+ The run publishes a coverage declaration alongside its verdict. It names every format the library generates and marks each one either independently graded, with the grader named, or ungraded, with a reason, and it is built from the set of formats the generators actually emit rather than from a hand-kept list, so a format cannot ship without that judgement being made. Today FHIR R4 is the graded one; HL7 v2, C-CDA, X12, NCPDP and ASTM carry only the sibling parser's round-trip, and the declaration says so in those words. The verdict also records the validator version, the package version, and every external endpoint the run was configured to contact.
63
+
64
+ `docs-content/limitations.md` gains the distinction a consumer needs to read the promise correctly: which formats carry an independent external verdict, which carry only a round-trip, and what the external verdict deliberately leaves out (it runs with no terminology server, so code membership in a value set is not part of it).
65
+
66
+ Nothing about what the generator emits changed. Same seeds, same bytes.
67
+
68
+ - f6d18d7: Synthetic SSNs now clear both federal authorities that share the SSN number space, not just SSA's. An IRS ITIN is itself an SSN-format number beginning with `9`, so the never-issued `900-999` area alone never ruled one out: 44 of the 100 group values placed a generated value inside a published ITIN group range, and the fixed advertising block sat inside one for every seed. `safe.ssn()` now draws its group only from values outside every published ITIN group range (and outside the two the IRS reserves for other programs), and its fixed block is `987-00-4320` through `987-00-4329`. No draw can produce an ITIN-shaped candidate, so generation still returns a value for every seed.
69
+
70
+ New exports: `isItinFormatted(value)` and `ITIN_GROUP_RANGES`, so a consumer can assert the second half of the guarantee directly (`isSyntheticSsn(v) && !isItinFormatted(v)`). The synthetic-safety sweeps for HL7 v2, FHIR, C-CDA, X12, the cross-format suite and the de-identification pairing loop now fail on an ITIN-formatted value at an SSN-bearing locus, over arbitrary seeds.
71
+
72
+ `ssn(rng, "advertising")` keeps its name and its shape (a fixed ten-value display block) but loses one property: its old value was the block the Social Security Administration itself prints in advertising, and that block is ITIN-formatted, so it could not stay. If you chose that option because the number is the one SSA publishes, it no longer is. The option name is unchanged so no call site breaks.
73
+
74
+ If you pin a golden corpus: this changes more than the SSN field. The default block now takes one random draw fewer than before (a single pick from the safe group pool replaced two independent group digits), so **every value drawn after an SSN in the same seeded stream moves too**: identifiers, codes, amounts and timestamps in an affected artifact all regenerate with different bytes, not only its SSN.
75
+
3
76
  ## 0.0.9
4
77
 
5
78
  ### Patch Changes
package/README.md CHANGED
@@ -18,8 +18,9 @@ date, phone, and address from a **guaranteed-non-colliding synthetic source**. I
18
18
  **format/conformance generator, not a clinical simulator**: it does not model disease progression
19
19
  (that is Synthea).
20
20
 
21
- > **Status:** pre-alpha (`0.0.x`), published to npm. The version shown on the npm package page is the
22
- > one that is live; this page never repeats it. The generator is **feature-complete**: the
21
+ > **Status:** `0.1`, published to npm. The version shown on the npm package page is the one that is
22
+ > live; this page never repeats it. Below 1.0, a breaking change raises the minor version and the
23
+ > changelog says what broke. The generator is **feature-complete**: the
23
24
  > seeded-PRNG core, the synthetic-safety providers, and the round-trip harness; **spec-clean generation
24
25
  > across all six formats**, HL7 v2 (`ADT`/`ORU`/`ORM`/`SIU`/`VXU`), FHIR R4 / US Core (the full clinical
25
26
  > set + `collection`/`transaction`/`document` Bundles), C-CDA R2.1 (CCD + Referral Note), X12 005010
@@ -61,15 +62,15 @@ const adt = generateAdt({ seed: 12345, trigger: "A01" });
61
62
  const oru = generateOru({ seed: 12345 });
62
63
 
63
64
  // Spec-clean by construction: it round-trips through @cosyte/hl7 with zero warnings.
64
- roundTrip(adt).specClean; // true
65
- roundTrip(oru).specClean; // true
65
+ console.log(roundTrip(adt).specClean); // true
66
+ console.log(roundTrip(oru).specClean); // true
66
67
 
67
68
  // Or generate a reproducible mixed corpus across every family:
68
69
  const corpus = hl7Corpus({ seed: 42, count: 7 }); // one of each family, cycled
69
- corpus.artifacts.every((a) => a.warnings.length === 0); // true, all spec-clean
70
+ console.log(corpus.artifacts.every((a) => a.warnings.length === 0)); // true, all spec-clean
70
71
 
71
72
  // Dispatch by kind when the message type is data:
72
- generateHl7("VXU^V04", 12345);
73
+ console.log(roundTrip(generateHl7("VXU^V04", 12345)).content.split("\r")[0]); // its MSH segment
73
74
  ```
74
75
 
75
76
  ## Generate a spec-clean FHIR R4 / US Core resource
@@ -79,8 +80,13 @@ they are spec-clean by construction: validating under `validateResource` and, ag
79
80
  Core 6.1.0 profiles** (bring your own `StructureDefinition`s, none is bundled), conformant to US Core.
80
81
  The clinical set covers `Patient` (base + US Core), `Condition`, `Observation` (US Core Laboratory
81
82
  Result + Vital Signs), `MedicationRequest`, `Encounter`, `DiagnosticReport`, `Immunization`,
82
- `AllergyIntolerance`, and `Procedure`, assembled into a `collection`, `transaction`, or `document`
83
- `Bundle`.
83
+ `AllergyIntolerance`, `Procedure`, and `Provenance`, assembled into a `collection`, `transaction`, or
84
+ `document` `Bundle`.
85
+
86
+ A fixture can also be requested **by US Core 6.1.0 profile name**. `usCoreCoverage()` reports one
87
+ entry for every profile the guide publishes, with its canonical URL and whether this build generates
88
+ it; `generateUsCoreProfile()` returns an artifact for a covered profile and otherwise refuses before
89
+ generating anything. See [what it does and does not do](./docs-content/limitations.md).
84
90
 
85
91
  ```ts
86
92
  import { generatePatient, generateBundle, fhirCorpus, roundTrip } from "@cosyte/synth/fhir";
@@ -310,18 +316,21 @@ NCPDP **SCRIPT** and **ASTM** (no `@cosyte/deid` adapter) and **DICOM** (not gen
310
316
  ## Draw a synthetic value
311
317
 
312
318
  ```ts
313
- import { createRng, safe, isSyntheticSsn, isSyntheticNpi } from "@cosyte/synth";
319
+ import { createRng, safe, isSyntheticSsn, isItinFormatted, isSyntheticNpi } from "@cosyte/synth";
314
320
 
315
321
  const rng = createRng(42);
316
- isSyntheticSsn(safe.ssn(rng)); // true, always an SSA never-issued SSN
322
+ const nationalId = safe.ssn(rng);
323
+ isSyntheticSsn(nationalId); // true, always an SSA never-issued SSN
324
+ isItinFormatted(nationalId); // false, never a validly formatted IRS ITIN either
317
325
  isSyntheticNpi(safe.npi(rng)); // true, always a deliberately-invalid-Luhn NPI (never a real NPI)
318
326
  ```
319
327
 
320
328
  ## What makes it trustworthy
321
329
 
322
330
  - **Synthetic-by-construction**, no code path emits a value not drawn from a reserved range or the
323
- shipped fake-name pool (SSA never-issued SSNs, NANP `555-01xx` phones, RFC 2606/6761 `example.*`
324
- domains, RFC 5737/3849 TEST-NET IPs, a synthetic assigning authority for MRNs). A CI gate proves it.
331
+ shipped fake-name pool (SSA never-issued SSNs that are not IRS ITINs either, NANP `555-01xx`
332
+ phones, RFC 2606/6761 `example.*` domains, RFC 5737/3849 TEST-NET IPs, a synthetic assigning
333
+ authority for MRNs). A CI gate proves it.
325
334
  **No generated value can be real or plausibly-real PHI.**
326
335
  - **Spec-clean by the parser's own judgment**, built through the parser's conservative serializer, and
327
336
  checked by feeding the artifact straight back in: a spec-clean artifact re-parses with zero warnings.
@@ -61,8 +61,7 @@ var SYNTH_FATAL_CODES = {
61
61
  * see `resolveKind`: a selector union is erased at run time, and a selector that falls through
62
62
  * either mislabels the fixture or hands the value to a peer builder that quotes it back.
63
63
  */
64
- SYNTH_UNSUPPORTED_KIND: "SYNTH_UNSUPPORTED_KIND"
65
- };
64
+ SYNTH_UNSUPPORTED_KIND: "SYNTH_UNSUPPORTED_KIND"};
66
65
  var SYNTH_FATAL_MESSAGES = Object.freeze({
67
66
  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.",
68
67
  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).",
@@ -73,7 +72,8 @@ var SYNTH_FATAL_MESSAGES = Object.freeze({
73
72
  SYNTH_INVALID_RANGE: "An integer range was requested with its maximum below its minimum.",
74
73
  SYNTH_EMPTY_POOL: "A value was drawn from an empty pool.",
75
74
  SYNTH_INVALID_PROFILE: "defineSynthProfile requires a non-empty string name.",
76
- 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."
75
+ 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.",
76
+ 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."
77
77
  });
78
78
  var SynthError = class extends Error {
79
79
  /** The stable fatal code. */
@@ -151,7 +151,7 @@ function makeCorpus(seed, artifacts, quirks = []) {
151
151
  var SYNTHETIC_ASSIGNING_AUTHORITY = Object.freeze({
152
152
  /** The human-readable assigning-authority namespace id (HL7 HD.1). */
153
153
  namespaceId: "COSYTE-SYNTH",
154
- /** The universal id, an OID under HL7's example arc `2.16.840.1.113883.19` (HD.2). */
154
+ /** The universal id, an OID this package chose under the root `2.16.840.1.113883.19` (HD.2). */
155
155
  universalId: "2.16.840.1.113883.19.999",
156
156
  /** The universal id type (HD.3). */
157
157
  universalIdType: "ISO"
@@ -170,6 +170,18 @@ var TEST_NET_V4_PREFIXES = Object.freeze([
170
170
  // TEST-NET-3
171
171
  ]);
172
172
  var DOC_V6_PREFIX = "2001:db8";
173
+ var ITIN_GROUP_RANGES = Object.freeze([
174
+ Object.freeze({ min: 50, max: 65 }),
175
+ Object.freeze({ min: 70, max: 88 }),
176
+ Object.freeze({ min: 90, max: 92 }),
177
+ Object.freeze({ min: 94, max: 99 })
178
+ ]);
179
+ var ITIN_EXCLUDED_GROUPS = Object.freeze([89, 93]);
180
+ var SSN_SYNTHETIC_GROUPS = Object.freeze(
181
+ Array.from({ length: 100 }, (_unused, group) => group).filter(
182
+ (group) => !ITIN_GROUP_RANGES.some((range) => group >= range.min && group <= range.max) && !ITIN_EXCLUDED_GROUPS.includes(group)
183
+ ).map((group) => String(group).padStart(2, "0"))
184
+ );
173
185
  var NPI_LUHN_PREFIX = "80840";
174
186
  function luhnMod10(digits) {
175
187
  let sum = 0;
@@ -278,10 +290,10 @@ var SYNTHETIC_CITY_NAMES = Object.freeze([
278
290
  // src/safe/providers.ts
279
291
  function ssn(rng, block = "never-issued") {
280
292
  if (block === "advertising") {
281
- return `987-65-432${String(rng.int(0, 9))}`;
293
+ return `987-00-432${String(rng.int(0, 9))}`;
282
294
  }
283
295
  const area = rng.int(900, 999);
284
- const group = rng.digits(2);
296
+ const group = rng.pick(SSN_SYNTHETIC_GROUPS);
285
297
  const serial = rng.digits(4);
286
298
  return `${String(area)}-${group}-${serial}`;
287
299
  }