@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 +14 -0
- package/LICENSE +1 -1
- package/README.md +170 -120
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +14 -11
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
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
|
|
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
|
|
11
|
-
|
|
10
|
+
> A zero-dependency, FHIR-shaped terminology engine for US healthcare code systems that never fabricates a code.
|
|
11
|
+
|
|
12
|
+
[](https://www.npmjs.com/package/@cosyte/terminology)
|
|
13
|
+
[](https://github.com/cosyte/terminology/actions/workflows/ci.yml)
|
|
14
|
+
[](https://github.com/cosyte/terminology/blob/main/LICENSE)
|
|
15
|
+
[](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
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
-
|
|
62
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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` =
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
353
|
-
|
|
354
|
-
third-party notices section naming each of
|
|
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.
|
|
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;
|