@cosyte/terminology 0.0.1 → 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.
package/dist/index.mjs CHANGED
@@ -73,13 +73,13 @@ var DIAGNOSTIC_CODES = {
73
73
  * code system was not supplied, an unresolvable referenced value set, or a `filter` operator the
74
74
  * engine does not implement. Surfaced as a typed outcome that marks the expansion **incomplete**;
75
75
  * the engine **never** fabricates a member and **never** returns a silently-empty "no members"
76
- * answer for a part it could not compute (roadmap §4.4 — a false "not a member" is a clinical error).
76
+ * answer for a part it could not compute (a false "not a member" is a clinical error).
77
77
  */
78
78
  TERM_VALUESET_CANNOT_EXPAND: "TERM_VALUESET_CANNOT_EXPAND",
79
79
  /**
80
80
  * A **pre-computed** `ValueSet.expansion` is **incomplete** — its `total` exceeds the number of
81
- * `contains` entries, or it is flagged too-costly (the VSAC `$expand` >1200-code truncation hazard,
82
- * roadmap §4.4). The engine **never treats a truncated expansion as complete membership**: a code
81
+ * `contains` entries, or it is flagged too-costly (the VSAC `$expand` >1200-code truncation
82
+ * hazard). The engine **never treats a truncated expansion as complete membership**: a code
83
83
  * absent from a truncated expansion is `undetermined`, never a confident "not a member".
84
84
  */
85
85
  TERM_VALUESET_EXPANSION_TRUNCATED: "TERM_VALUESET_EXPANSION_TRUNCATED",
@@ -87,7 +87,7 @@ var DIAGNOSTIC_CODES = {
87
87
  * A UCUM unit expression is **not valid** — it does not parse against the UCUM grammar, or it
88
88
  * names an atom absent from the vendored UCUM table. A **first-class typed outcome, never an
89
89
  * error and never a guess**: {@link validateUcum} returns `{ valid: false, reason }`, never a
90
- * coerced or "nearest" unit (the never-fabricate invariant, applied to units — roadmap §4.3). The
90
+ * coerced or "nearest" unit (the never-fabricate invariant, applied to units). The
91
91
  * `reason` is value-free (a grammar fault or an unknown-atom shape), never PHI.
92
92
  */
93
93
  TERM_UCUM_INVALID: "TERM_UCUM_INVALID",
@@ -95,7 +95,7 @@ var DIAGNOSTIC_CODES = {
95
95
  * A crosswalk source code has an **authored No-Map** — the steward declares it has no valid target
96
96
  * in the other classification (a GEM `NoDx`/`NoPCS` sentinel, or a SNOMED complex-map `447638001`
97
97
  * "cannot be classified" category). A **first-class typed outcome, never an error and never a
98
- * guess** (the never-fabricate invariant, applied to crosswalks — roadmap §4.1). Distinct from
98
+ * guess** (the never-fabricate invariant, applied to crosswalks). Distinct from
99
99
  * {@link TERM_CROSSWALK_UNMAPPED}: the source *is* in the map, and the map says "no target".
100
100
  */
101
101
  TERM_CROSSWALK_NO_MAP: "TERM_CROSSWALK_NO_MAP",
@@ -109,7 +109,7 @@ var DIAGNOSTIC_CODES = {
109
109
  /**
110
110
  * A SNOMED→ICD-10-CM complex-map group's decision needs **patient context** (an `IFA` age band or
111
111
  * gender) the caller did not supply. The engine surfaces the candidate rules + advice and **refuses
112
- * to pick a branch** it lacks the data for (roadmap §4.1) — never a silently-chosen target.
112
+ * to pick a branch** it lacks the data for — never a silently-chosen target.
113
113
  */
114
114
  TERM_CROSSWALK_CONTEXT_REQUIRED: "TERM_CROSSWALK_CONTEXT_REQUIRED",
115
115
  /**
@@ -131,17 +131,27 @@ var DIAGNOSTIC_CODES = {
131
131
  * **silently** — they are expected, not faults.
132
132
  */
133
133
  TERM_RXNORM_MALFORMED_ROW: "TERM_RXNORM_MALFORMED_ROW",
134
+ /**
135
+ * An `RXCUI` appeared in the release's `SAB=RXNORM` atoms but **none of them could establish its
136
+ * term type**, so it is **not** in the graph. Either every atom carried a synonym-class `TTY`
137
+ * (`PSN`/`SY`/`TMSY`, which Appendix 5 defines as a "synonym of another TTY" and which therefore
138
+ * types a *name* rather than a concept) or every atom carried a `TTY` this engine does not model.
139
+ * The concept is skipped rather than typed from a synonym atom: an absent concept is already a
140
+ * first-class typed answer (`TERM_RXNORM_UNKNOWN_RXCUI`), while a concept whose term type reads
141
+ * `TMSY` is a fabricated claim about what a drug *is* that a caller cannot detect.
142
+ */
143
+ TERM_RXNORM_UNTYPED_CONCEPT: "TERM_RXNORM_UNTYPED_CONCEPT",
134
144
  /**
135
145
  * A RxNorm graph navigation was asked about an `RXCUI` **absent from the loaded release** — a
136
146
  * first-class typed outcome, never a fabricated concept and never an empty "success" (the
137
- * never-fabricate invariant, applied to the drug graph — roadmap §4.2). Distinct from a *present*
147
+ * never-fabricate invariant, applied to the drug graph). Distinct from a *present*
138
148
  * concept that simply has no edge of the requested relationship (that is a found result with empty
139
149
  * targets).
140
150
  */
141
151
  TERM_RXNORM_UNKNOWN_RXCUI: "TERM_RXNORM_UNKNOWN_RXCUI",
142
152
  /**
143
153
  * An NDC could **not be resolved** to any `RXCUI` in the loaded RxNorm release — a typed, surfaced
144
- * absence, never a guessed `RXCUI`. NDC↔RXCUI is many:1 and temporal (roadmap §4.2), so a resolution
154
+ * absence, never a guessed `RXCUI`. NDC↔RXCUI is many:1 and temporal, so a resolution
145
155
  * is always release-scoped; an NDC not present in the loaded release is this diagnostic.
146
156
  */
147
157
  TERM_RXNORM_NDC_UNMAPPED: "TERM_RXNORM_NDC_UNMAPPED"
@@ -176,7 +186,7 @@ var FATAL_CODES = {
176
186
  /**
177
187
  * An **inversion** of a directional crosswalk was requested — a forbidden operation. The GEMs and
178
188
  * the SNOMED→ICD-10-CM map are authoritative in **one direction only**; a forward map is not the
179
- * inverse of the backward map (roadmap §4.1). Thrown by {@link ../crosswalk/gems.invertGem} so the
189
+ * inverse of the backward map. Thrown by {@link ../crosswalk/gems.invertGem} so the
180
190
  * never-invert refusal is a first-class, discoverable contract rather than a silent absence.
181
191
  */
182
192
  TERM_MAP_NOT_INVERTIBLE: "TERM_MAP_NOT_INVERTIBLE"
@@ -4358,12 +4368,17 @@ var TERM_TYPES = Object.freeze({
4358
4368
  BN: "Brand Name",
4359
4369
  SCDC: "Semantic Clinical Drug Component",
4360
4370
  SBDC: "Semantic Branded Drug Component",
4371
+ SCDF: "Semantic Clinical Drug Form",
4372
+ SBDF: "Semantic Branded Drug Form",
4373
+ SCDFP: "Semantic Clinical Drug Form Precise",
4374
+ SBDFP: "Semantic Branded Drug Form Precise",
4361
4375
  SCD: "Semantic Clinical Drug",
4362
4376
  SBD: "Semantic Branded Drug",
4363
4377
  SCDG: "Semantic Clinical Drug Group",
4364
4378
  SBDG: "Semantic Branded Drug Group",
4379
+ SCDGP: "Semantic Clinical Drug Form Group Precise",
4365
4380
  GPCK: "Generic Pack",
4366
- BPCK: "Branded Pack",
4381
+ BPCK: "Brand Name Pack",
4367
4382
  DF: "Dose Form",
4368
4383
  DFG: "Dose Form Group",
4369
4384
  PSN: "Prescribable Name",
@@ -4372,9 +4387,13 @@ var TERM_TYPES = Object.freeze({
4372
4387
  ET: "Entry Term"
4373
4388
  });
4374
4389
  var KNOWN = new Set(Object.keys(TERM_TYPES));
4390
+ var SYNONYM = /* @__PURE__ */ new Set(["PSN", "SY", "TMSY"]);
4375
4391
  function asTermType(tty) {
4376
4392
  return KNOWN.has(tty) ? tty : void 0;
4377
4393
  }
4394
+ function isSynonymTermType(tty) {
4395
+ return SYNONYM.has(tty);
4396
+ }
4378
4397
 
4379
4398
  // src/rxnorm/load.ts
4380
4399
  var SAB_RXNORM = "RXNORM";
@@ -4388,13 +4407,13 @@ function cell2(cells, index) {
4388
4407
  }
4389
4408
  function parseConso(content, warnings) {
4390
4409
  const concepts = /* @__PURE__ */ new Map();
4391
- const lines = content.split(/\r?\n/);
4392
- for (let i = 0; i < lines.length; i++) {
4393
- const raw = lines[i] ?? "";
4410
+ const conso = [];
4411
+ const untyped = /* @__PURE__ */ new Map();
4412
+ for (const [i, raw] of content.split(/\r?\n/).entries()) {
4394
4413
  if (raw === "") continue;
4395
4414
  const cells = parseRrfLine(raw);
4396
4415
  if (cells.length <= CONSO.STR) {
4397
- warnings.push(
4416
+ conso.push(
4398
4417
  Object.freeze({
4399
4418
  code: "TERM_RXNORM_MALFORMED_ROW",
4400
4419
  file: "RXNCONSO",
@@ -4405,11 +4424,9 @@ function parseConso(content, warnings) {
4405
4424
  continue;
4406
4425
  }
4407
4426
  if (cell2(cells, CONSO.SAB) !== SAB_RXNORM) continue;
4408
- const tty = asTermType(cell2(cells, CONSO.TTY) ?? "");
4409
- if (tty === void 0) continue;
4410
4427
  const rxcui = cell2(cells, CONSO.RXCUI);
4411
4428
  if (rxcui === void 0 || rxcui === "") {
4412
- warnings.push(
4429
+ conso.push(
4413
4430
  Object.freeze({
4414
4431
  code: "TERM_RXNORM_MALFORMED_ROW",
4415
4432
  file: "RXNCONSO",
@@ -4419,7 +4436,13 @@ function parseConso(content, warnings) {
4419
4436
  );
4420
4437
  continue;
4421
4438
  }
4439
+ const tty = asTermType(cell2(cells, CONSO.TTY) ?? "");
4440
+ if (tty === void 0 || isSynonymTermType(tty)) {
4441
+ if (!concepts.has(rxcui) && !untyped.has(rxcui)) untyped.set(rxcui, i + 1);
4442
+ continue;
4443
+ }
4422
4444
  if (concepts.has(rxcui)) continue;
4445
+ untyped.delete(rxcui);
4423
4446
  const name = cell2(cells, CONSO.STR) ?? "";
4424
4447
  const suppress = cell2(cells, CONSO.SUPPRESS) ?? "N";
4425
4448
  concepts.set(
@@ -4427,14 +4450,24 @@ function parseConso(content, warnings) {
4427
4450
  Object.freeze({ rxcui, tty, name, suppressed: suppress !== "" && suppress !== "N" })
4428
4451
  );
4429
4452
  }
4453
+ for (const line of untyped.values()) {
4454
+ conso.push(
4455
+ Object.freeze({
4456
+ code: "TERM_RXNORM_UNTYPED_CONCEPT",
4457
+ file: "RXNCONSO",
4458
+ line,
4459
+ detail: "no RXNORM atom established this RXCUI's term type (every atom was synonym-class or an unmodelled TTY); skipped rather than typed from a synonym"
4460
+ })
4461
+ );
4462
+ }
4463
+ conso.sort((a, b) => a.line - b.line);
4464
+ for (const w of conso) warnings.push(w);
4430
4465
  return concepts;
4431
4466
  }
4432
4467
  function parseRel(content, warnings) {
4433
4468
  const edges = /* @__PURE__ */ new Map();
4434
4469
  let edgeCount = 0;
4435
- const lines = content.split(/\r?\n/);
4436
- for (let i = 0; i < lines.length; i++) {
4437
- const raw = lines[i] ?? "";
4470
+ for (const [i, raw] of content.split(/\r?\n/).entries()) {
4438
4471
  if (raw === "") continue;
4439
4472
  const cells = parseRrfLine(raw);
4440
4473
  if (cells.length <= REL.RELA) {
@@ -4463,9 +4496,7 @@ function parseRel(content, warnings) {
4463
4496
  }
4464
4497
  function parseNdcs(content, warnings) {
4465
4498
  const ndcs = /* @__PURE__ */ new Map();
4466
- const lines = content.split(/\r?\n/);
4467
- for (let i = 0; i < lines.length; i++) {
4468
- const raw = lines[i] ?? "";
4499
+ for (const [i, raw] of content.split(/\r?\n/).entries()) {
4469
4500
  if (raw === "") continue;
4470
4501
  const cells = parseRrfLine(raw);
4471
4502
  if (cells.length <= SAT.ATV) {
@@ -4517,9 +4548,17 @@ function loadRxNormGraph(source) {
4517
4548
 
4518
4549
  // src/rxnorm/rela.ts
4519
4550
  var RELA = {
4520
- /** `has_ingredient` — subject (a drug/component) *has ingredient* object (an `IN`/`PIN`). */
4551
+ /**
4552
+ * `has_ingredient`: subject *has ingredient* object. Authored from the clinical side
4553
+ * (`SCDC`/`SCDF`/`SCDG`) to an `IN` and from the branded side (`SBD`/`SBDC`/`SBDF`/`SBDG`) to a
4554
+ * `BN`; RxNorm authors no `SCD ⟶ IN` row.
4555
+ */
4521
4556
  HAS_INGREDIENT: "has_ingredient",
4522
- /** `ingredient_of` — subject (an `IN`/`PIN`) *is an ingredient of* object (a drug/component). */
4557
+ /**
4558
+ * `ingredient_of`: subject *is an ingredient of* object. The authored inverse, so an `IN` reaches
4559
+ * clinical components and dose forms and a `BN` reaches branded concepts (the `SBD` included).
4560
+ * Neither reaches an `SCD`.
4561
+ */
4523
4562
  INGREDIENT_OF: "ingredient_of",
4524
4563
  /** `has_precise_ingredient` — subject *has precise ingredient* object (a `PIN`). */
4525
4564
  HAS_PRECISE_INGREDIENT: "has_precise_ingredient",
@@ -4662,8 +4701,8 @@ function approximateMatch(graph, query, options = {}) {
4662
4701
  }
4663
4702
 
4664
4703
  // src/index.ts
4665
- var VERSION = "0.0.0";
4704
+ var VERSION = "0.0.4";
4666
4705
 
4667
- export { COMPLEX_MAP_SOURCE_SYSTEM, DIAGNOSTIC_CODES, FATAL_CODES, HL7_V2_OID_ROOT, HL7_V2_URL_PREFIX, ICD10CM_ORDER_FILE_FIELDS, ICD10CM_SYSTEM, ICD9CM_SYSTEM, MAP_CATEGORIES, NO_MAP_CATEGORIES, RELA, RELA_INVERSE, RXNORM_SYSTEM, SNOMEDCT_SYSTEM, SYSTEM_IDENTITIES, TERM_TYPES, TerminologyError, VERSION, applyComplexMap, applyGem, approximateMatch, asTermType, brandsFor, buildSubsumption, codeableConcept, coding, consistsOf, defaultStatusMapper, doseFormsOf, expand, genericFor, getArray, getBoolean, getConcept, getNumber, getString, ingredientsOf, invertGem, isA, isJsonObject, isUnknownSystem, loadCodeSystem, loadComplexMap, loadConceptMap, loadGems, loadRxNormGraph, loadUcumEssence, loadValueSet, lookup, makeStatus, matchesFilter, parseCsv, parseCsvSource, parseFhirCodeSystem, parseFixedWidth, parseRrf, parseRrfLine, parseUcum, reduce, relatedByRela, resolveNdc, resolveSystem, sliceField, translate, ucumEqual, unsupportedOps, validateCode, validateCodeInValueSet, validateUcum };
4706
+ export { COMPLEX_MAP_SOURCE_SYSTEM, DIAGNOSTIC_CODES, FATAL_CODES, HL7_V2_OID_ROOT, HL7_V2_URL_PREFIX, ICD10CM_ORDER_FILE_FIELDS, ICD10CM_SYSTEM, ICD9CM_SYSTEM, MAP_CATEGORIES, NO_MAP_CATEGORIES, RELA, RELA_INVERSE, RXNORM_SYSTEM, SNOMEDCT_SYSTEM, SYSTEM_IDENTITIES, TERM_TYPES, TerminologyError, VERSION, applyComplexMap, applyGem, approximateMatch, asTermType, brandsFor, buildSubsumption, codeableConcept, coding, consistsOf, defaultStatusMapper, doseFormsOf, expand, genericFor, getArray, getBoolean, getConcept, getNumber, getString, ingredientsOf, invertGem, isA, isJsonObject, isSynonymTermType, isUnknownSystem, loadCodeSystem, loadComplexMap, loadConceptMap, loadGems, loadRxNormGraph, loadUcumEssence, loadValueSet, lookup, makeStatus, matchesFilter, parseCsv, parseCsvSource, parseFhirCodeSystem, parseFixedWidth, parseRrf, parseRrfLine, parseUcum, reduce, relatedByRela, resolveNdc, resolveSystem, sliceField, translate, ucumEqual, unsupportedOps, validateCode, validateCodeInValueSet, validateUcum };
4668
4707
  //# sourceMappingURL=index.mjs.map
4669
4708
  //# sourceMappingURL=index.mjs.map