@cosyte/terminology 0.1.2 → 0.1.3

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/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.3
4
+
5
+ ### Patch Changes
6
+
7
+ - 8d1e983: The README follows the house layout, and the package description gains two serial commas. The README now opens with why the engine exists and its status, then install and usage with a link to the guides on docs.cosyte.com, and ends with support, security, contributing, and license. It names the Node.js lines CI tests (22, 24, and 26); its conformance statements and its list of bundled material are unchanged. No code changed.
8
+
3
9
  ## 0.1.2
4
10
 
5
11
  ### Patch Changes
package/README.md CHANGED
@@ -7,8 +7,22 @@
7
7
 
8
8
  # @cosyte/terminology
9
9
 
10
- > A **zero-dependency terminology engine** for US healthcare code systems: FHIR-shaped, BYO-data,
11
- > and it **never fabricates a code**.
10
+ > A zero-dependency, FHIR-shaped terminology engine for US healthcare code systems that never fabricates a code.
11
+
12
+ [![npm version](https://img.shields.io/npm/v/@cosyte/terminology.svg)](https://www.npmjs.com/package/@cosyte/terminology)
13
+ [![CI](https://img.shields.io/github/actions/workflow/status/cosyte/terminology/ci.yml?branch=main&label=CI)](https://github.com/cosyte/terminology/actions/workflows/ci.yml)
14
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/cosyte/terminology/blob/main/LICENSE)
15
+ [![Node](https://img.shields.io/badge/node-%3E%3D22.0.0-brightgreen.svg)](https://nodejs.org)
16
+
17
+ Terminology engine for US healthcare code systems in Node.js and TypeScript: FHIR `$translate`,
18
+ `$lookup`, `$validate-code`, and `$expand` over code systems, value sets, and maps you supply.
19
+
20
+ ## Why this exists
21
+
22
+ A wrong code, map, or value-set binding does not fail loudly: it becomes a different clinical
23
+ meaning in whatever consumes the answer, and the output still looks valid. So this engine never
24
+ fabricates and never inverts. An unknown or unmapped code comes back as a typed result rather than a
25
+ guess, and a directional map is read only in its authored direction.
12
26
 
13
27
  `@cosyte/terminology` is **not a wire parser**. It mirrors the FHIR **Terminology Module**
14
28
  (`$translate`, `$lookup`, `$validate-code`, `$expand`, …), operating over **consumer-supplied** FHIR
@@ -19,51 +33,56 @@ unit table, the code-system _identities_ (OID ↔ canonical URI), the SNOMED CT
19
33
  resolver names, and RxNorm's relationship and term-type names, is listed with its copyright under
20
34
  [What is bundled](#what-is-bundled).
21
35
 
22
- > **Status:** the 0.1 line, **published on npm.** The public API is settled: below `1.0.0` a breaking
23
- > change ships in a new minor version, never a patch, and the changelog says what broke. The
24
- > **engine is complete**, every operation below ships today. The surface:
25
- >
26
- > - the code-system **identity resolver** (`resolveSystem`) and the ConceptMap **`$translate`** engine
27
- > (`loadConceptMap` / `translate`);
28
- > - the **CodeSystem load layer** (RRF / CSV / fixed-width / FHIR JSON) with **`$lookup`** and
29
- > **`$validate-code`**;
30
- > - **ValueSet** binding (`compose` / **`$expand`** / **`$validate-code`**), subsumption read from the
31
- > release's own hierarchy, measured against HL7's own terminology ecosystem test cases
32
- > ([terminology ecosystem conformance](#terminology-ecosystem-conformance));
33
- > - **UCUM** unit validation and canonicalization (`validateUcum` / `ucumEqual`, recognition only, **no
34
- > magnitude conversion**), gated on the official UCUM functional tests: the **validation** and
35
- > **conversion** case kinds, against the suite dated 3-Feb 2021, and no other kind
36
- > ([UCUM conformance](#ucum-conformance));
37
- > - the **crosswalk resolvers**: the CMS **ICD-9↔ICD-10 GEMs** (`loadGems` / `applyGem`,
38
- > public-domain) and the NLM **SNOMED CT → ICD-10-CM complex map** (`loadComplexMap` /
39
- > `applyComplexMap`, BYO: no SNOMED CT refset bundled), never inverted;
40
- > - the **RxNorm drug graph** (`loadRxNormGraph` + `ingredientsOf` / `genericFor` / `brandsFor` /
41
- > `doseFormsOf` / `consistsOf` / `resolveNdc` / `approximateMatch`) over a **BYO** RxNorm RRF release,
42
- > edges read in RxNorm's documented direction and never inverted, an absent `RXCUI`/NDC typed, never
43
- > fabricated.
44
- >
45
- > **Bring your own data.** The engine is whole; **no code-system release is bundled.** It runs over
46
- > the FHIR resources and standard releases _you_ supply: SNOMED CT, CPT, LOINC, UMLS/RxNorm, the CMS
47
- > GEM files, the NLM complex-map refset, and VSAC value sets are all bring-your-own, under whatever
48
- > terms their steward sets. The content packs that would change that (RxNorm Prescribable, ICD-10-CM)
49
- > are not bundled either. For the things that **are** bundled, and under whose copyright, see
50
- > [What is bundled](#what-is-bundled).
36
+ ## Status
37
+
38
+ **The 0.1 line, published on npm.** The public API is settled: below `1.0.0` a breaking change ships
39
+ in a new minor version, never a patch, and the changelog says what broke. The **engine is
40
+ complete**: every operation below ships today. The surface:
41
+
42
+ - the code-system **identity resolver** (`resolveSystem`) and the ConceptMap **`$translate`** engine
43
+ (`loadConceptMap` / `translate`);
44
+ - the **CodeSystem load layer** (RRF / CSV / fixed-width / FHIR JSON) with **`$lookup`** and
45
+ **`$validate-code`**;
46
+ - **ValueSet** binding (`compose` / **`$expand`** / **`$validate-code`**), subsumption read from the
47
+ release's own hierarchy, measured against HL7's own terminology ecosystem test cases
48
+ ([terminology ecosystem conformance](#terminology-ecosystem-conformance));
49
+ - **UCUM** unit validation and canonicalization (`validateUcum` / `ucumEqual`, recognition only, **no
50
+ magnitude conversion**), gated on the official UCUM functional tests: the **validation** and
51
+ **conversion** case kinds, against the suite dated 3-Feb 2021, and no other kind
52
+ ([UCUM conformance](#ucum-conformance));
53
+ - the **crosswalk resolvers**: the CMS **ICD-9↔ICD-10 GEMs** (`loadGems` / `applyGem`,
54
+ public-domain) and the NLM **SNOMED CT → ICD-10-CM complex map** (`loadComplexMap` /
55
+ `applyComplexMap`, BYO: no SNOMED CT refset bundled), never inverted;
56
+ - the **RxNorm drug graph** (`loadRxNormGraph` + `ingredientsOf` / `genericFor` / `brandsFor` /
57
+ `doseFormsOf` / `consistsOf` / `resolveNdc` / `approximateMatch`) over a **BYO** RxNorm RRF release,
58
+ edges read in RxNorm's documented direction and never inverted, an absent `RXCUI`/NDC typed, never
59
+ fabricated.
60
+
61
+ **Bring your own data.** The engine is whole; **no code-system release is bundled.** It runs over the
62
+ FHIR resources and standard releases _you_ supply: SNOMED CT, CPT, LOINC, UMLS/RxNorm, the CMS GEM
63
+ files, the NLM complex-map refset, and VSAC value sets are all bring-your-own, under whatever terms
64
+ their steward sets. The content packs that would change that (RxNorm Prescribable, ICD-10-CM) are not
65
+ bundled either. For the things that **are** bundled, and under whose copyright, see
66
+ [What is bundled](#what-is-bundled).
51
67
 
52
68
  ## Install
53
69
 
54
- > **On npm.** `@cosyte/terminology` is **published**, on the 0.1 line. The command below
55
- > installs the real package; the registry, not this page, is the authority on the exact version.
56
-
57
70
  ```bash
58
71
  npm install @cosyte/terminology
59
72
  ```
60
73
 
61
- Node.js 22 or later, tested on 22 and 24.
74
+ Node.js 22 or later, tested on 22, 24, and 26.
75
+
76
+ The package is published on the 0.1 line, and the command above installs the real package; the
77
+ registry, not this page, is the authority on the exact version.
78
+
79
+ ## Usage
62
80
 
63
- Runnable programs for the main jobs are in
64
- [`examples/`](https://github.com/cosyte/terminology/tree/main/examples).
81
+ The guides and the full API reference are at
82
+ [docs.cosyte.com/terminology](https://docs.cosyte.com/terminology). Runnable programs for the main
83
+ jobs are in [`examples/`](https://github.com/cosyte/terminology/tree/main/examples).
65
84
 
66
- ## Resolve a code system to its canonical URI
85
+ ### Resolve a code system to its canonical URI
67
86
 
68
87
  ```ts
69
88
  import { resolveSystem, isUnknownSystem } from "@cosyte/terminology";
@@ -72,7 +91,7 @@ const r = resolveSystem("2.16.840.1.113883.6.1"); // OID, mnemonic, or URI
72
91
  console.log(isUnknownSystem(r) ? "unknown" : r.url); // http://loinc.org
73
92
  ```
74
93
 
75
- ## Translate a code through a ConceptMap
94
+ ### Translate a code through a ConceptMap
76
95
 
77
96
  ```ts
78
97
  import { loadConceptMap, translate } from "@cosyte/terminology";
@@ -114,7 +133,7 @@ if (result.unmapped) {
114
133
  When at least one target does assert a relationship, the source is translated exactly as before and
115
134
  any `disjoint` row rides along in `result.matches`, in its declared position.
116
135
 
117
- ## Look up a code in a code system
136
+ ### Look up a code in a code system
118
137
 
119
138
  ```ts
120
139
  import { loadCodeSystem, lookup, validateCode } from "@cosyte/terminology";
@@ -134,7 +153,7 @@ if (hit.found) {
134
153
  validateCode(cs, "2160-0").valid; // $validate-code: true iff present; never a guessed true
135
154
  ```
136
155
 
137
- ## Validate and compare UCUM units
156
+ ### Validate and compare UCUM units
138
157
 
139
158
  Recognition and canonicalization only: **no magnitude conversion** (`5 mg/dL` → `mmol/L` needs the
140
159
  analyte's molar mass, a clinical computation this engine refuses).
@@ -166,6 +185,65 @@ functional-test cases it claims, and claims no more than it runs: see
166
185
  > `TERM_UCUM_INVALID` (fail-safe, never silently reinterpreted). Magnitude conversion is likewise a
167
186
  > deliberate non-goal (recognition and canonicalization only).
168
187
 
188
+ ### Navigate the RxNorm drug graph
189
+
190
+ Load a **bring-your-own** RxNorm RRF release and walk the ingredient / brand / clinical-drug /
191
+ dose-form graph. Relationships are read in RxNorm's documented direction (`RELA` is the relationship
192
+ the **second** `RXCUI` has to the **first**); navigation follows only authored edges: the engine
193
+ never synthesizes an inverse, and never fabricates a concept, edge, or NDC.
194
+
195
+ Authored edges only means the graph you get is RxNorm's, including where its shape surprises you.
196
+ `has_ingredient` is authored from the **clinical** side (`SCDC`/`SCDF`/`SCDG`) to the ingredient
197
+ `IN`, and from the **branded** side (`SBD`/`SBDC`/`SBDF`/`SBDG`) to the **brand name** `BN` rather
198
+ than to the active ingredient. RxNorm authors no ingredient edge on a clinical drug (`SCD`) at all,
199
+ so that query answers with an honest empty set rather than a guess. To reach an active ingredient,
200
+ walk to the **clinical** component and take its edge: `consistsOf` then `ingredientsOf`. From an
201
+ `SBD`, `consistsOf` returns both its branded component (`SBDC`, whose ingredient edge is the brand
202
+ name again) and the clinical `SCDC`; it is the `SCDC` that leads to the `IN`. The **precise** forms
203
+ (`SCDFP`, `SBDFP`, `SCDGP`) carry no ingredient edge at all: an `SCDFP` instead reaches its
204
+ basis-of-strength substances by `has_boss`, a one-to-many relation with a different meaning whose
205
+ targets can be a `PIN` or a plain `IN`, and which `ingredientsOf` does not follow. Read the `tty` of
206
+ what comes back rather than assuming it.
207
+
208
+ A concept is typed by its **defining** atom, whichever line of `RXNCONSO` that sits on. `PSN`, `SY`,
209
+ and `TMSY` type a _name_ rather than a concept, so they never establish an `RXCUI`'s term type. An
210
+ `RXCUI` no defining atom could type is left out of the graph and reported as
211
+ `TERM_RXNORM_UNTYPED_CONCEPT` on `graph.warnings`, rather than loaded under a synonym's term type:
212
+ a concept that answers with a term type it does not have is a fabricated claim about a drug, and
213
+ this engine refuses instead.
214
+
215
+ ```ts
216
+ import {
217
+ loadRxNormGraph,
218
+ ingredientsOf,
219
+ consistsOf,
220
+ genericFor,
221
+ resolveNdc,
222
+ } from "@cosyte/terminology";
223
+
224
+ const graph = loadRxNormGraph({
225
+ conso: rxnconsoRrf,
226
+ rel: rxnrelRrf,
227
+ sat: rxnsatRrf,
228
+ version: "RXNORM_2026AA",
229
+ });
230
+
231
+ ingredientsOf(graph, "316151"); // an SCDC → { found: true, targets: [ { rxcui: "29046", tty: "IN", name: "lisinopril", … } ] }
232
+ ingredientsOf(graph, "314076"); // an SCD → { found: true, targets: [] }, because no such edge is authored
233
+ consistsOf(graph, "314076"); // → the SCDC, the first of the two hops to the ingredient
234
+ genericFor(graph, "104377"); // an SBD → its SCD, via the authored tradename_of edge
235
+ resolveNdc(graph, "00000000001"); // { resolved: true, rxcui: "314076", status: "active", asOf: "RXNORM_2026AA" }
236
+ ingredientsOf(graph, "99999999"); // { found: false, code: "TERM_RXNORM_UNKNOWN_RXCUI" }, never a guess
237
+ ```
238
+
239
+ The engine ships **no** RxNorm release; you supply it (the public-domain Current Prescribable
240
+ Content subset, or the full BYO release). What it does ship is RxNorm's relationship and term-type
241
+ _names_, listed under [What is bundled](#what-is-bundled).
242
+
243
+ ## Conformance
244
+
245
+ Each claim below is stated in full and held to a fresh run by the package's own test suite.
246
+
169
247
  ### UCUM conformance
170
248
 
171
249
  The claim in full, so a reader can tell what was run from what was not. UCUM permits a qualified
@@ -189,7 +267,7 @@ here, if the suite's most recent history date stops matching the date named here
189
267
  here is not one the tests execute, if a named kind's section carries no case, or if this page and
190
268
  `vendor/ucum/NOTICE.md` state different suite dates.
191
269
 
192
- ## Terminology ecosystem conformance
270
+ ### Terminology ecosystem conformance
193
271
 
194
272
  The ValueSet layer is measured against **HL7's own FHIR terminology ecosystem test cases**
195
273
  (`HL7/fhir-tx-ecosystem-ig`) as well as against tests written here. The claim in full, so a reader
@@ -228,61 +306,6 @@ stale in silence.
228
306
  its own process. Nothing here is an approval, a certification, or an endorsement of this package by
229
307
  HL7 or by anyone else.
230
308
 
231
- ## Navigate the RxNorm drug graph
232
-
233
- Load a **bring-your-own** RxNorm RRF release and walk the ingredient / brand / clinical-drug /
234
- dose-form graph. Relationships are read in RxNorm's documented direction (`RELA` is the relationship
235
- the **second** `RXCUI` has to the **first**); navigation follows only authored edges: the engine
236
- never synthesizes an inverse, and never fabricates a concept, edge, or NDC.
237
-
238
- Authored edges only means the graph you get is RxNorm's, including where its shape surprises you.
239
- `has_ingredient` is authored from the **clinical** side (`SCDC`/`SCDF`/`SCDG`) to the ingredient
240
- `IN`, and from the **branded** side (`SBD`/`SBDC`/`SBDF`/`SBDG`) to the **brand name** `BN` rather
241
- than to the active ingredient. RxNorm authors no ingredient edge on a clinical drug (`SCD`) at all,
242
- so that query answers with an honest empty set rather than a guess. To reach an active ingredient,
243
- walk to the **clinical** component and take its edge: `consistsOf` then `ingredientsOf`. From an
244
- `SBD`, `consistsOf` returns both its branded component (`SBDC`, whose ingredient edge is the brand
245
- name again) and the clinical `SCDC`; it is the `SCDC` that leads to the `IN`. The **precise** forms
246
- (`SCDFP`, `SBDFP`, `SCDGP`) carry no ingredient edge at all: an `SCDFP` instead reaches its
247
- basis-of-strength substances by `has_boss`, a one-to-many relation with a different meaning whose
248
- targets can be a `PIN` or a plain `IN`, and which `ingredientsOf` does not follow. Read the `tty` of
249
- what comes back rather than assuming it.
250
-
251
- A concept is typed by its **defining** atom, whichever line of `RXNCONSO` that sits on. `PSN`, `SY`,
252
- and `TMSY` type a _name_ rather than a concept, so they never establish an `RXCUI`'s term type. An
253
- `RXCUI` no defining atom could type is left out of the graph and reported as
254
- `TERM_RXNORM_UNTYPED_CONCEPT` on `graph.warnings`, rather than loaded under a synonym's term type:
255
- a concept that answers with a term type it does not have is a fabricated claim about a drug, and
256
- this engine refuses instead.
257
-
258
- ```ts
259
- import {
260
- loadRxNormGraph,
261
- ingredientsOf,
262
- consistsOf,
263
- genericFor,
264
- resolveNdc,
265
- } from "@cosyte/terminology";
266
-
267
- const graph = loadRxNormGraph({
268
- conso: rxnconsoRrf,
269
- rel: rxnrelRrf,
270
- sat: rxnsatRrf,
271
- version: "RXNORM_2026AA",
272
- });
273
-
274
- ingredientsOf(graph, "316151"); // an SCDC → { found: true, targets: [ { rxcui: "29046", tty: "IN", name: "lisinopril", … } ] }
275
- ingredientsOf(graph, "314076"); // an SCD → { found: true, targets: [] }, because no such edge is authored
276
- consistsOf(graph, "314076"); // → the SCDC, the first of the two hops to the ingredient
277
- genericFor(graph, "104377"); // an SBD → its SCD, via the authored tradename_of edge
278
- resolveNdc(graph, "00000000001"); // { resolved: true, rxcui: "314076", status: "active", asOf: "RXNORM_2026AA" }
279
- ingredientsOf(graph, "99999999"); // { found: false, code: "TERM_RXNORM_UNKNOWN_RXCUI" }, never a guess
280
- ```
281
-
282
- The engine ships **no** RxNorm release; you supply it (the public-domain Current Prescribable
283
- Content subset, or the full BYO release). What it does ship is RxNorm's relationship and term-type
284
- _names_, listed under [What is bundled](#what-is-bundled).
285
-
286
309
  ## The invariants this engine is built on
287
310
 
288
311
  - **Never fabricate.** An unmapped source is a typed `unmapped`, never a guessed target. Every target
@@ -349,6 +372,29 @@ with its copyright:
349
372
  This section states what is distributed and under whose copyright. It is not legal advice, and makes
350
373
  no claim about whether any particular use of these materials is permitted to you.
351
374
 
375
+ ## Support
376
+
377
+ Questions, bug reports, and an answer the engine got wrong go in
378
+ [GitHub issues](https://github.com/cosyte/terminology/issues), with the smallest synthetic code
379
+ system, map, or value set that reproduces it. Use synthetic data only: a code in patient context can
380
+ be PHI. [SUPPORT.md](https://github.com/cosyte/.github/blob/main/SUPPORT.md) says where to ask what.
381
+
382
+ ## Security
383
+
384
+ Report a vulnerability privately, never in a public issue, and with no real patient data in it:
385
+ through GitHub private vulnerability reporting on this repository, or by email to
386
+ [security@cosyte.com](mailto:security@cosyte.com). Our
387
+ [security policy](https://github.com/cosyte/.github/blob/main/SECURITY.md) says how.
388
+
389
+ ## Contributing
390
+
391
+ For anything that changes what the engine answers, open an issue first, so the behavior can be agreed
392
+ before it is written; typo, link, and test fixes can go straight to a pull request. A contribution
393
+ clears the gate CI runs, listed in
394
+ [AGENTS.md](https://github.com/cosyte/terminology/blob/main/AGENTS.md), and a change a consumer would
395
+ notice adds a changeset (`pnpm changeset`), whose summary becomes the changelog entry. See
396
+ [CONTRIBUTING](https://github.com/cosyte/.github/blob/main/CONTRIBUTING.md).
397
+
352
398
  ## License
353
399
 
354
400
  [MIT](https://github.com/cosyte/terminology/blob/main/LICENSE) © Cosyte LLC: this package's own code.
package/dist/index.cjs CHANGED
@@ -4979,7 +4979,7 @@ function approximateMatch(graph, query, options = {}) {
4979
4979
  }
4980
4980
 
4981
4981
  // src/index.ts
4982
- var VERSION = "0.1.2";
4982
+ var VERSION = "0.1.3";
4983
4983
 
4984
4984
  exports.COMPLEX_MAP_SOURCE_SYSTEM = COMPLEX_MAP_SOURCE_SYSTEM;
4985
4985
  exports.DIAGNOSTIC_CODES = DIAGNOSTIC_CODES;