@cosyte/terminology 0.1.1 → 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,19 @@
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
+
9
+ ## 0.1.2
10
+
11
+ ### Patch Changes
12
+
13
+ - b9772b6: The `resolveSystem` documentation in the type declarations no longer says `@cosyte/transform` uses it, which was never true.
14
+
15
+ No code system, map, value set, result, or diagnostic changes. The README now names the package that does use this engine, `@cosyte/cli`, for its `map-codes` command, and the `LICENSE` names the copyright holder by its legal name, Cosyte LLC.
16
+
3
17
  ## 0.1.1
4
18
 
5
19
  ### Patch Changes
package/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2026 Cosyte
3
+ Copyright (c) 2026 Cosyte LLC
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -1,67 +1,88 @@
1
1
  <a href="https://cosyte.com">
2
2
  <picture>
3
3
  <source media="(prefers-color-scheme: dark)" srcset="https://cosyte.com/tile/cosyte-lockup-tile-on-dark-1200x300.png">
4
- <img alt="Cosyte: a plus mark set in two overlapping rounded squares, one solid and one outlined, beside the Cosyte wordmark" src="https://cosyte.com/tile/cosyte-lockup-tile-on-light-1200x300.png">
4
+ <img alt="The Cosyte logo on its own white ground: the icon beside the word Cosyte." src="https://cosyte.com/tile/cosyte-lockup-tile-on-light-1200x300.png">
5
5
  </picture>
6
6
  </a>
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
15
- resources. It is the sibling engine `@cosyte/transform` and, later, the parsers' code-system
16
- recognition consume. It ships the **engine, and no code-system release**: SNOMED CT, CPT, LOINC,
17
- UMLS/RxNorm, and VSAC value sets are strictly bring-your-own. It is not content-free, though: what it
18
- _does_ bundle, including the UCUM unit table, the code-system _identities_ (OID ↔ canonical URI), the
19
- SNOMED CT concepts the crosswalk resolver names and RxNorm's relationship and term-type names, is
20
- listed with its copyright under [What is bundled](#what-is-bundled).
21
-
22
- > **Status:** `0.1.0`, **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).
29
+ resources, and `@cosyte/cli` uses it to translate codes in its `map-codes` command. It ships the
30
+ **engine, and no code-system release**: SNOMED CT, CPT, LOINC, UMLS/RxNorm, and VSAC value sets are
31
+ strictly bring-your-own. It is not content-free, though: what it _does_ bundle, including the UCUM
32
+ unit table, the code-system _identities_ (OID ↔ canonical URI), the SNOMED CT concepts the crosswalk
33
+ resolver names, and RxNorm's relationship and term-type names, is listed with its copyright under
34
+ [What is bundled](#what-is-bundled).
35
+
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.x` 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
- Runnable programs for the main jobs are in
62
- [`examples/`](https://github.com/cosyte/terminology/tree/main/examples).
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
80
+
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).
63
84
 
64
- ## Resolve a code system to its canonical URI
85
+ ### Resolve a code system to its canonical URI
65
86
 
66
87
  ```ts
67
88
  import { resolveSystem, isUnknownSystem } from "@cosyte/terminology";
@@ -70,7 +91,7 @@ const r = resolveSystem("2.16.840.1.113883.6.1"); // OID, mnemonic, or URI
70
91
  console.log(isUnknownSystem(r) ? "unknown" : r.url); // http://loinc.org
71
92
  ```
72
93
 
73
- ## Translate a code through a ConceptMap
94
+ ### Translate a code through a ConceptMap
74
95
 
75
96
  ```ts
76
97
  import { loadConceptMap, translate } from "@cosyte/terminology";
@@ -112,7 +133,7 @@ if (result.unmapped) {
112
133
  When at least one target does assert a relationship, the source is translated exactly as before and
113
134
  any `disjoint` row rides along in `result.matches`, in its declared position.
114
135
 
115
- ## Look up a code in a code system
136
+ ### Look up a code in a code system
116
137
 
117
138
  ```ts
118
139
  import { loadCodeSystem, lookup, validateCode } from "@cosyte/terminology";
@@ -132,7 +153,7 @@ if (hit.found) {
132
153
  validateCode(cs, "2160-0").valid; // $validate-code: true iff present; never a guessed true
133
154
  ```
134
155
 
135
- ## Validate and compare UCUM units
156
+ ### Validate and compare UCUM units
136
157
 
137
158
  Recognition and canonicalization only: **no magnitude conversion** (`5 mg/dL` → `mmol/L` needs the
138
159
  analyte's molar mass, a clinical computation this engine refuses).
@@ -159,11 +180,70 @@ functional-test cases it claims, and claims no more than it runs: see
159
180
  `vendor/ucum/NOTICE.md`: see [What is bundled](#what-is-bundled).
160
181
 
161
182
  > **Case-sensitive (c/s) mode only.** UCUM's normative interchange mode (the one FHIR/HL7 bind to)
162
- > is case-sensitive (`m` = metre, `M` = mega, `Pa` = pascal, `pA` = picoampere). The
183
+ > is case-sensitive (`m` = meter, `M` = mega, `Pa` = pascal, `pA` = picoampere). The
163
184
  > case-**insensitive** (c/i) spelling variant is **not** supported; a c/i-only string returns a typed
164
185
  > `TERM_UCUM_INVALID` (fail-safe, never silently reinterpreted). Magnitude conversion is likewise a
165
186
  > deliberate non-goal (recognition and canonicalization only).
166
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
+
167
247
  ### UCUM conformance
168
248
 
169
249
  The claim in full, so a reader can tell what was run from what was not. UCUM permits a qualified
@@ -187,7 +267,7 @@ here, if the suite's most recent history date stops matching the date named here
187
267
  here is not one the tests execute, if a named kind's section carries no case, or if this page and
188
268
  `vendor/ucum/NOTICE.md` state different suite dates.
189
269
 
190
- ## Terminology ecosystem conformance
270
+ ### Terminology ecosystem conformance
191
271
 
192
272
  The ValueSet layer is measured against **HL7's own FHIR terminology ecosystem test cases**
193
273
  (`HL7/fhir-tx-ecosystem-ig`) as well as against tests written here. The claim in full, so a reader
@@ -195,18 +275,18 @@ can tell what was run from what was not:
195
275
 
196
276
  - **Snapshot: upstream commit `33d37fc0f8efaed5832032f3a73956f66d4d4f23`.** The suite publishes no
197
277
  version field, so the commit is the version, and it is the version these counts were produced
198
- against. A pinned copy of the cases sits in the repository with their origin and licence recorded
278
+ against. A pinned copy of the cases sits in the repository with their origin and license recorded
199
279
  beside them; they are third-party material and are not part of the published package.
200
280
  - **Suites in scope: `simple-cases`, `parameters`, `validation`. Operations in scope: `$expand` and
201
281
  ValueSet `$validate-code`.** The suite's other suites and its other operations are not run, and no
202
282
  conformance to them is claimed.
203
283
  - **Cases ran: 101. Passed: 20. Declined: 81.** The three add up: every case that ran is exactly one
204
284
  of them. Those three suites declare 112 cases in total; the rest declare a different operation,
205
- are marked in the suite for one server's own behaviour, or are held out of the measurement, and
285
+ are marked in the suite for one server's own behavior, or are held out of the measurement, and
206
286
  none of those is run or counted anywhere above.
207
287
  - **A declined case is one this library has no answer for, named rather than counted as a pass.** It
208
288
  asks for a request parameter the public API takes no argument for (`activeOnly`,
209
- `displayLanguage`, `includeDesignations` and others), for HTTP or language negotiation that a
289
+ `displayLanguage`, `includeDesignations`, and others), for HTTP or language negotiation that a
210
290
  library call cannot perform, or it is met by this engine's own typed refusal. Every decline is
211
291
  recorded with its case and its reason.
212
292
  - **A case that answers differently fails the run, and is never recorded as a decline.** The counts
@@ -214,73 +294,18 @@ can tell what was run from what was not:
214
294
 
215
295
  The cases are written for a **server** answering over HTTP, and this package is a library, so they
216
296
  are driven in process through the public API and the comparison is narrower than a server's would
217
- be: an expansion is compared on its membership (system, code and display), a `$validate-code` case
297
+ be: an expansion is compared on its membership (system, code, and display), a `$validate-code` case
218
298
  on its recorded `result`. The recorded responses declare their own tolerances and those are
219
- honoured as declared; every tolerance applied beyond them is written down beside the counts, on the
299
+ honored as declared; every tolerance applied beyond them is written down beside the counts, on the
220
300
  ground that a tolerance nobody can see is indistinguishable from a pass that was not earned.
221
301
 
222
302
  The package's own test suite reproduces the run and holds these figures to it, so they cannot go
223
303
  stale in silence.
224
304
 
225
305
  **This is a measurement, not an endorsement.** HL7 publishes these cases and reviews outcomes under
226
- its own process. Nothing here is an approval, a certification or an endorsement of this package by
306
+ its own process. Nothing here is an approval, a certification, or an endorsement of this package by
227
307
  HL7 or by anyone else.
228
308
 
229
- ## Navigate the RxNorm drug graph
230
-
231
- Load a **bring-your-own** RxNorm RRF release and walk the ingredient / brand / clinical-drug /
232
- dose-form graph. Relationships are read in RxNorm's documented direction (`RELA` is the relationship
233
- the **second** `RXCUI` has to the **first**); navigation follows only authored edges: the engine
234
- never synthesizes an inverse, and never fabricates a concept, edge, or NDC.
235
-
236
- Authored edges only means the graph you get is RxNorm's, including where its shape surprises you.
237
- `has_ingredient` is authored from the **clinical** side (`SCDC`/`SCDF`/`SCDG`) to the ingredient
238
- `IN`, and from the **branded** side (`SBD`/`SBDC`/`SBDF`/`SBDG`) to the **brand name** `BN` rather
239
- than to the active ingredient. RxNorm authors no ingredient edge on a clinical drug (`SCD`) at all,
240
- so that query answers with an honest empty set rather than a guess. To reach an active ingredient,
241
- walk to the **clinical** component and take its edge: `consistsOf` then `ingredientsOf`. From an
242
- `SBD`, `consistsOf` returns both its branded component (`SBDC`, whose ingredient edge is the brand
243
- name again) and the clinical `SCDC`; it is the `SCDC` that leads to the `IN`. The **precise** forms
244
- (`SCDFP`, `SBDFP`, `SCDGP`) carry no ingredient edge at all: an `SCDFP` instead reaches its
245
- basis-of-strength substances by `has_boss`, a one-to-many relation with a different meaning whose
246
- targets can be a `PIN` or a plain `IN`, and which `ingredientsOf` does not follow. Read the `tty` of
247
- what comes back rather than assuming it.
248
-
249
- A concept is typed by its **defining** atom, whichever line of `RXNCONSO` that sits on. `PSN`, `SY`
250
- and `TMSY` type a _name_ rather than a concept, so they never establish an `RXCUI`'s term type. An
251
- `RXCUI` no defining atom could type is left out of the graph and reported as
252
- `TERM_RXNORM_UNTYPED_CONCEPT` on `graph.warnings`, rather than loaded under a synonym's term type:
253
- a concept that answers with a term type it does not have is a fabricated claim about a drug, and
254
- this engine refuses instead.
255
-
256
- ```ts
257
- import {
258
- loadRxNormGraph,
259
- ingredientsOf,
260
- consistsOf,
261
- genericFor,
262
- resolveNdc,
263
- } from "@cosyte/terminology";
264
-
265
- const graph = loadRxNormGraph({
266
- conso: rxnconsoRrf,
267
- rel: rxnrelRrf,
268
- sat: rxnsatRrf,
269
- version: "RXNORM_2026AA",
270
- });
271
-
272
- ingredientsOf(graph, "316151"); // an SCDC → { found: true, targets: [ { rxcui: "29046", tty: "IN", name: "lisinopril", … } ] }
273
- ingredientsOf(graph, "314076"); // an SCD → { found: true, targets: [] }, because no such edge is authored
274
- consistsOf(graph, "314076"); // → the SCDC, the first of the two hops to the ingredient
275
- genericFor(graph, "104377"); // an SBD → its SCD, via the authored tradename_of edge
276
- resolveNdc(graph, "00000000001"); // { resolved: true, rxcui: "314076", status: "active", asOf: "RXNORM_2026AA" }
277
- ingredientsOf(graph, "99999999"); // { found: false, code: "TERM_RXNORM_UNKNOWN_RXCUI" }, never a guess
278
- ```
279
-
280
- The engine ships **no** RxNorm release; you supply it (the public-domain Current Prescribable
281
- Content subset, or the full BYO release). What it does ship is RxNorm's relationship and term-type
282
- _names_, listed under [What is bundled](#what-is-bundled).
283
-
284
309
  ## The invariants this engine is built on
285
310
 
286
311
  - **Never fabricate.** An unmapped source is a typed `unmapped`, never a guessed target. Every target
@@ -294,21 +319,21 @@ _names_, listed under [What is bundled](#what-is-bundled).
294
319
  - **Liberal load, conservative assertion.** Malformed input degrades to a typed diagnostic; a 1:many
295
320
  mapping returns the full candidate set, never collapsed to one.
296
321
  - **Value-free diagnostic messages.** Nothing you configured and nothing your release or resource
297
- contained reaches a `message`, `detail`, `reason` or `err.stack` (not a column name, not a
322
+ contained reaches a `message`, `detail`, `reason`, or `err.stack` (not a column name, not a
298
323
  canonical URI, not a unit atom) at any length. What sits beside a message is a locus: a line
299
324
  number, a column count, an index path into your own resource (`compose.include[2]`), or a fixed
300
325
  token naming which file of a release it came from. The
301
- **objects** are a different matter: a typed `unknown`/`unmapped` outcome names the code, unit or
326
+ **objects** are a different matter: a typed `unknown`/`unmapped` outcome names the code, unit, or
302
327
  NDC you asked about, so log the `code` and the locus rather than the whole result: a code _in
303
328
  patient context_ can be PHI.
304
329
  - **Zero runtime dependencies. Dual ESM + CJS.** Node stdlib only; built with `tsup`, validated with
305
330
  `attw`. A release you load is frozen, its indexes included: a code system's concepts, a GEM's or a
306
- complex map's entries and a drug graph's concepts, edges and NDCs are each a read-only **view** of a
307
- map rather than the map itself, so nothing holding one can add, delete or clear an entry, whichever
331
+ complex map's entries, and a drug graph's concepts, edges, and NDCs are each a read-only **view** of a
332
+ map rather than the map itself, so nothing holding one can add, delete, or clear an entry, whichever
308
333
  way it tries. A view is not a `Map` instance, so a model carrying one cannot be `structuredClone`d
309
334
  or posted to a worker: copy out what you need (`new Map(cs.concepts)`). The bundled unit table is frozen deeply, atom by atom and definition by
310
335
  definition, because it is shared with the engine's own reducer; its two lookup maps refuse `set`,
311
- `delete` and `clear` by name, and an entry forced past that through `Map.prototype` changes nothing
336
+ `delete`, and `clear` by name, and an entry forced past that through `Map.prototype` changes nothing
312
337
  the engine reads, because `parseUcum` resolves an atom by scanning the frozen atom array rather than
313
338
  through a lookup map. The AST and
314
339
  the reduction that `parseUcum` and `reduce` hand back are per-call values you own, and mutating one
@@ -335,7 +360,7 @@ with its copyright:
335
360
  ICD-10-CM codes, appear in the API documentation examples, which are distributed in the compiled
336
361
  declaration files and in the build's sourcemaps. SNOMED CT is copyright
337
362
  © International Health Terminology Standards Development Organisation. **No SNOMED CT release or
338
- refset is bundled**: you supply the map rows under your own SNOMED CT licence.
363
+ refset is bundled**: you supply the map rows under your own SNOMED CT license.
339
364
  - **The RxNorm relationship and term-type names** the drug-graph API is written against: the
340
365
  relationship names in `RELA` / `RELA_INVERSE` (`has_ingredient`, `tradename_of`, …), and the
341
366
  term-type codes in `TERM_TYPES`, each with its name as published by the U.S. National Library of
@@ -347,8 +372,33 @@ with its copyright:
347
372
  This section states what is distributed and under whose copyright. It is not legal advice, and makes
348
373
  no claim about whether any particular use of these materials is permitted to you.
349
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
+
350
398
  ## License
351
399
 
352
- MIT © Cosyte: this package's own code. The bundled third-party materials above stay under their own
353
- copyright and licences; the `LICENSE` file that ships with this package carries the MIT text and a
354
- third-party notices section naming each of them.
400
+ [MIT](https://github.com/cosyte/terminology/blob/main/LICENSE) © Cosyte LLC: this package's own code.
401
+ The bundled third-party materials above stay under their own copyright and licenses; the `LICENSE` file
402
+ that ships with this package carries the MIT text and a third-party notices section naming each of
403
+ them. The code-system and standards names this package uses belong to their owners: see
404
+ [TRADEMARKS.md](https://github.com/cosyte/terminology/blob/main/TRADEMARKS.md).
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.1";
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;