fhirsmith 0.7.5 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +50 -0
- package/README.md +8 -0
- package/library/html.js +4 -0
- package/library/languages.js +10 -0
- package/package.json +1 -1
- package/packages/package-crawler.js +106 -51
- package/packages/packages.js +14 -0
- package/publisher/publisher.js +118 -28
- package/registry/registry.js +99 -91
- package/root-bare-template.html +92 -0
- package/security.md +32 -0
- package/server.js +99 -22
- package/stats.js +43 -10
- package/tx/README.md +6 -6
- package/tx/cs/cs-api.js +3 -0
- package/tx/cs/cs-api.md +285 -0
- package/tx/cs/cs-loinc.js +14 -2
- package/tx/cs/cs-rxnorm.js +14 -10
- package/tx/cs/cs-snomed.js +166 -5
- package/tx/html/dash-metrics.liquid +147 -0
- package/tx/importers/import-rxnorm.module.js +4 -30
- package/tx/importers/readme.md +3 -1
- package/tx/library/canonical-resource.js +8 -0
- package/tx/library/conceptmap.js +3 -1
- package/tx/library/designations.js +4 -8
- package/tx/library/renderer.js +9 -9
- package/tx/library.js +10 -4
- package/tx/ocl/cm-ocl.cjs +185 -65
- package/tx/ocl/cs-ocl.cjs +69 -50
- package/tx/ocl/jobs/background-queue.cjs +0 -8
- package/tx/ocl/mappers/concept-mapper.cjs +13 -3
- package/tx/ocl/shared/patches.cjs +1 -0
- package/tx/ocl/vs-ocl.cjs +137 -157
- package/tx/operation-context.js +3 -3
- package/tx/provider.js +4 -3
- package/tx/sct/structures.js +5 -0
- package/tx/tx-html.js +36 -9
- package/tx/tx.fhir.org.yml +1 -1
- package/tx/tx.js +34 -11
- package/tx/vs/vs-database.js +127 -6
- package/tx/vs/vs-vsac.js +98 -3
- package/tx/workers/search.js +2 -1
- package/tx/workers/translate.js +39 -14
- package/tx/workers/validate.js +3 -3
- package/utilities/dashboard.html +274 -0
- package/xig/xig.js +171 -9
package/tx/cs/cs-api.md
ADDED
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
# CodeSystem Provider API
|
|
2
|
+
|
|
3
|
+
## Introduction
|
|
4
|
+
|
|
5
|
+
The CodeSystem Provider API defines the contract between the FHIR terminology server and the
|
|
6
|
+
individual code system implementations that back it. It consists of two abstract base classes:
|
|
7
|
+
|
|
8
|
+
- **`CodeSystemProvider`** — represents a live, per-request provider for a specific code system
|
|
9
|
+
(bound to an `OperationContext`). One instance is created per request.
|
|
10
|
+
- **`CodeSystemFactoryProvider`** — a long-lived factory that the server holds in memory and
|
|
11
|
+
uses to construct `CodeSystemProvider` instances on demand.
|
|
12
|
+
|
|
13
|
+
Implementors subclass `CodeSystemProvider` and override the methods marked *must override*.
|
|
14
|
+
The remaining methods have sensible defaults that can optionally be overridden to improve
|
|
15
|
+
fidelity or performance.
|
|
16
|
+
|
|
17
|
+
### OperationContext
|
|
18
|
+
|
|
19
|
+
Every `CodeSystemProvider` is constructed with an `OperationContext` that carries
|
|
20
|
+
per-request state: the requested languages (`opContext.langs`), a usage tracker, and other
|
|
21
|
+
request-scoped information. Providers must store and consult this context when making
|
|
22
|
+
language-sensitive decisions.
|
|
23
|
+
|
|
24
|
+
### Supplements
|
|
25
|
+
|
|
26
|
+
A provider may be constructed with an array of `CodeSystem` supplement resources. These are
|
|
27
|
+
FHIR CodeSystem resources with `content = supplement`, and they supply additional displays and
|
|
28
|
+
designations for codes that exist in the base code system. The base class provides helper
|
|
29
|
+
methods (`_displayFromSupplements`, `_listSupplementDesignations`, `_hasAnySupplementDisplays`)
|
|
30
|
+
that implementors can call from their `display()` and `designations()` overrides.
|
|
31
|
+
|
|
32
|
+
### Codes and Contexts
|
|
33
|
+
|
|
34
|
+
Many methods accept either a raw `string` code or a `CodeSystemProviderContext` — an opaque
|
|
35
|
+
handle returned by `locate()` and the iteration/filter machinery. Passing a context instead
|
|
36
|
+
of a string allows implementations to avoid redundant lookups; the pattern is to locate a
|
|
37
|
+
concept once, obtain a context, and then pass that context to subsequent property queries.
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## Metadata
|
|
42
|
+
|
|
43
|
+
These methods describe the code system as a whole. All are synchronous.
|
|
44
|
+
|
|
45
|
+
### Required overrides
|
|
46
|
+
|
|
47
|
+
| Method | Returns | Description |
|
|
48
|
+
|---|---|---|
|
|
49
|
+
| `system()` | `string` | The canonical URI of the code system (e.g. `http://loinc.org`). |
|
|
50
|
+
| `version()` | `string` | The version of the code system. |
|
|
51
|
+
| `description()` | `string` | A human-readable description of the code system. |
|
|
52
|
+
| `totalCount()` | `integer` | The total number of concepts in the code system. |
|
|
53
|
+
|
|
54
|
+
### Provided (with defaults)
|
|
55
|
+
|
|
56
|
+
| Method | Default | Description |
|
|
57
|
+
|---|---|---|
|
|
58
|
+
| `name()` | `system()` + `\|` + `version()` | The versioned URI for the code system. Returns `system()` alone if there is no version. |
|
|
59
|
+
| `vurl()` | Same as `name()` | Alias for the versioned URI. |
|
|
60
|
+
| `defLang()` | `'en'` | The default language for displays in this code system. |
|
|
61
|
+
| `contentMode()` | `CodeSystemContentMode.Complete` | Whether the code system is complete, a fragment, etc. |
|
|
62
|
+
| `expandLimitation()` | `0` | A cap on expansion size (e.g. for CPT). `0` means no limit. |
|
|
63
|
+
| `sourcePackage()` | `null` | The NPM package that contributed this code system, if known. |
|
|
64
|
+
| `propertyDefinitions()` | `null` | The set of defined properties; override to expose them. |
|
|
65
|
+
| `isNotClosed()` | `false` | Return `true` for grammar-based systems (e.g. UCUM) that cannot be fully enumerated. |
|
|
66
|
+
| `isCaseSensitive()` | `true` | Whether code comparisons are case-sensitive. |
|
|
67
|
+
| `hasParents()` | `false` | Whether the code system has a concept hierarchy. |
|
|
68
|
+
| `specialEnumeration()` | `null` | Override to nominate a substitute enumeration (used by UCUM). |
|
|
69
|
+
| `listFeatures()` | `null` | Return applicable server features. |
|
|
70
|
+
| `status()` | `{}` | Return status metadata: `{ status, standardsStatus, experimental }`. |
|
|
71
|
+
| `versionAlgorithm()` | `null` | The algorithm used for version comparison (e.g. `'semver'`, `'date'`). |
|
|
72
|
+
| `versionNeeded()` | `false` | Whether a version must be specified when using this code system. |
|
|
73
|
+
| `valueSet()` | `null` | The implicit value set URI for this code system, if any. |
|
|
74
|
+
| `versionIsMoreDetailed(check, actual)` | `false` | Return `true` if `actual` is a more specific version than `check` (used by SCT edition handling). |
|
|
75
|
+
| `hasSupplement(url)` | — | Returns `true` if the named supplement is in scope. |
|
|
76
|
+
| `listSupplements()` | — | Returns the versioned URIs of all supplements in scope. |
|
|
77
|
+
| `hasAnyDisplays(languages)` | — | Returns `true` if there are displays available in the requested languages. |
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## Code Properties
|
|
82
|
+
|
|
83
|
+
These methods return information about an individual concept. Each accepts either a raw
|
|
84
|
+
`string` code or a `CodeSystemProviderContext` obtained from `locate()`.
|
|
85
|
+
All are `async`.
|
|
86
|
+
|
|
87
|
+
### Required overrides
|
|
88
|
+
|
|
89
|
+
| Method | Returns | Description |
|
|
90
|
+
|---|---|---|
|
|
91
|
+
| `code(code)` | `string` | Returns the canonical form of the code (may normalise case, whitespace, etc.). |
|
|
92
|
+
| `display(code)` | `string` | The best available display for the concept given the languages in `opContext.langs`. Should consult supplements via `_displayFromSupplements()`. |
|
|
93
|
+
| `definition(code)` | `string` | The definition for the concept, or `null`. |
|
|
94
|
+
| `designations(code, displays)` | — | Populates the `ConceptDesignations` object with all available designations across all languages. Should call `_listSupplementDesignations()` to include supplement designations. |
|
|
95
|
+
|
|
96
|
+
### Provided (with defaults)
|
|
97
|
+
|
|
98
|
+
| Method | Default | Description |
|
|
99
|
+
|---|---|---|
|
|
100
|
+
| `isAbstract(code)` | `false` | Whether the concept is abstract (cannot be used in instances). |
|
|
101
|
+
| `isInactive(code)` | `false` | Whether the concept is inactive. |
|
|
102
|
+
| `isDeprecated(code)` | `false` | Whether the concept is deprecated. |
|
|
103
|
+
| `getStatus(code)` | `null` | The status string for the concept. |
|
|
104
|
+
| `itemWeight(code)` | `null` | The assigned item weight (used in questionnaire scoring). |
|
|
105
|
+
| `parent(code)` | `null` | The parent concept, for hierarchical code systems. |
|
|
106
|
+
| `extensions(code)` | `null` | FHIR extensions on the concept, if any. |
|
|
107
|
+
| `properties(code)` | `[]` | The defined properties for the concept. |
|
|
108
|
+
| `incompleteValidationMessage(code)` | `null` | A message explaining why validation may be incomplete (used by SCT). |
|
|
109
|
+
| `sameConcept(a, b)` | `false` | Returns `true` if `a` and `b` refer to the same concept (e.g. different expression forms). |
|
|
110
|
+
| `isDisplay(designation)` | `false` | Called for designations not already marked with a standard display use code; return `true` to treat the designation as a display. |
|
|
111
|
+
| `subsumesTest(codeA, codeB)` | `'not-subsumed'` | Returns one of `equivalent`, `subsumes`, `subsumed-by`, or `not-subsumed`. |
|
|
112
|
+
| `extendLookup(ctxt, props, params)` | — | Called during `$lookup`; add any additional properties to `params`. |
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## Iteration
|
|
117
|
+
|
|
118
|
+
Iteration allows the server to enumerate all concepts in a code system, for use in value set
|
|
119
|
+
expansion. The pattern is:
|
|
120
|
+
|
|
121
|
+
```js
|
|
122
|
+
const iter = await provider.iteratorAll();
|
|
123
|
+
let ctxt;
|
|
124
|
+
while ((ctxt = await provider.nextContext(iter)) !== null) {
|
|
125
|
+
const c = await provider.code(ctxt);
|
|
126
|
+
const d = await provider.display(ctxt);
|
|
127
|
+
// ...
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
For hierarchical code systems, `iterator(code)` returns an iterator over the *children* of
|
|
132
|
+
the given concept. Pass `null` to iterate the root concepts. `iteratorAll()` iterates
|
|
133
|
+
everything regardless of hierarchy.
|
|
134
|
+
|
|
135
|
+
| Method | Returns | Description |
|
|
136
|
+
|---|---|---|
|
|
137
|
+
| `iterator(code)` | `CodeSystemIterator \| null` | Returns an iterator over the children of the given concept, or over the root concepts if `code` is `null`. Return `null` if iteration is not supported. |
|
|
138
|
+
| `iteratorAll()` | `CodeSystemIterator \| null` | Returns an iterator over all concepts. For flat code systems this delegates to `iterator(null)`; for hierarchical systems this must be overridden. |
|
|
139
|
+
| `nextContext(iterator)` | `CodeSystemProviderContext \| null` | Advances the iterator and returns the next concept, or `null` when exhausted. |
|
|
140
|
+
|
|
141
|
+
The default `iterator()` returns `null` (iteration not supported). The default `nextContext()`
|
|
142
|
+
returns `null`. Override both to support expansion.
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## Filters
|
|
147
|
+
|
|
148
|
+
Filters are used when expanding value sets that use `include.filter` clauses. The filter
|
|
149
|
+
workflow has two phases:
|
|
150
|
+
|
|
151
|
+
**Phase 1 — preparation.** The server calls `getPrepContext(iterate)` to obtain a
|
|
152
|
+
`FilterExecutionContext`, then calls `filter()` once for each filter clause in the include.
|
|
153
|
+
After all filters have been registered, `executeFilters()` is called to obtain one or more
|
|
154
|
+
`FilterConceptSet` objects.
|
|
155
|
+
|
|
156
|
+
**Phase 2 — evaluation.** The server either iterates the result set
|
|
157
|
+
(`filterMore` / `filterConcept`) or checks membership (`filterLocate` / `filterCheck`).
|
|
158
|
+
|
|
159
|
+
```js
|
|
160
|
+
const ctx = await provider.getPrepContext(true);
|
|
161
|
+
await provider.filter(ctx, 'concept', 'is-a', 'some-code');
|
|
162
|
+
const sets = await provider.executeFilters(ctx);
|
|
163
|
+
const set = sets[0];
|
|
164
|
+
while (await provider.filterMore(ctx, set)) {
|
|
165
|
+
const concept = await provider.filterConcept(ctx, set);
|
|
166
|
+
// ...
|
|
167
|
+
}
|
|
168
|
+
await provider.filterFinish(ctx);
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
| Method | Default | Description |
|
|
172
|
+
|---|---|---|
|
|
173
|
+
| `doesFilter(prop, op, value)` | `false` | Return `true` if the given filter property/operator/value combination is supported. |
|
|
174
|
+
| `getPrepContext(iterate)` | Returns a `FilterExecutionContext` | Returns the shared context object that will be passed to all subsequent filter calls. |
|
|
175
|
+
| `filter(ctx, prop, op, value)` | *must override if filters supported* | Registers a single filter clause. |
|
|
176
|
+
| `searchFilter(ctx, text, sort)` | throws | Registers a free-text search filter. |
|
|
177
|
+
| `specialFilter(ctx, sort)` | throws if `specialEnumeration()` set | Registers a special enumeration filter (UCUM). |
|
|
178
|
+
| `executeFilters(ctx)` | *must override if filters supported* | Finalises filter setup and returns `FilterConceptSet[]` for iteration or membership testing. |
|
|
179
|
+
| `filterSize(ctx, set)` | *must override* | Returns the number of concepts in the filter set. |
|
|
180
|
+
| `filtersNotClosed(ctx)` | `false` | Return `true` if the filter set is open-ended (grammar-based system). |
|
|
181
|
+
| `filterMore(ctx, set)` | *must override* | Advances the iterator; returns `true` if there is a current concept. |
|
|
182
|
+
| `filterConcept(ctx, set)` | *must override* | Returns the current concept as a `CodeSystemProviderContext`. |
|
|
183
|
+
| `filterLocate(ctx, set, code)` | *must override* | Finds a specific code in the filter set. Returns a `CodeSystemProviderContext` if found, or an error string if not. |
|
|
184
|
+
| `filterCheck(ctx, set, concept)` | *must override* | Returns `true` if the concept is in the filter set, or an error string if not. |
|
|
185
|
+
| `filterFinish(ctx)` | no-op | Called when the filter session is complete; release any resources held. |
|
|
186
|
+
|
|
187
|
+
### Alternative: `handlesSelecting` / `processSelection`
|
|
188
|
+
|
|
189
|
+
For large code systems where a value set selects codes from only that system, the server can
|
|
190
|
+
hand the entire include/exclude list to the provider in one call rather than using the filter
|
|
191
|
+
workflow above.
|
|
192
|
+
|
|
193
|
+
Override `handlesSelecting()` to return `true` and then implement `processSelection()`.
|
|
194
|
+
The method receives the full lists of includes and excludes, along with pagination parameters
|
|
195
|
+
if applicable, and returns `FilterConceptSet[]`.
|
|
196
|
+
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
## Lookup
|
|
200
|
+
|
|
201
|
+
Lookup supports the `$lookup` and `$validate-code` operations.
|
|
202
|
+
|
|
203
|
+
| Method | Description |
|
|
204
|
+
|---|---|
|
|
205
|
+
| `locate(code)` | Look up a code by string. Returns `{ context, message }`. If found, `context` is a `CodeSystemProviderContext` and `message` is `null`. If not found, `context` is `null` and `message` explains why. **Must override.** |
|
|
206
|
+
| `locateIsA(code)` | Look up a code within the scope of a parent concept. Required only if `hasParents()` returns `true`. |
|
|
207
|
+
| `extendLookup(ctxt, props, params)` | Called during `$lookup` to allow the provider to add extra properties to the response `Parameters`. The `props` array lists the property names requested by the client. |
|
|
208
|
+
|
|
209
|
+
The server calls `locate()` first to confirm the code exists, then uses the returned context
|
|
210
|
+
to call `display()`, `definition()`, `designations()`, `properties()`, `extensions()`, and
|
|
211
|
+
`extendLookup()` as needed to build the lookup response.
|
|
212
|
+
|
|
213
|
+
---
|
|
214
|
+
|
|
215
|
+
## Translations
|
|
216
|
+
|
|
217
|
+
Code systems that define implicit concept maps (for example, cross-version extension mappings)
|
|
218
|
+
can surface translations via the `$translate` operation.
|
|
219
|
+
|
|
220
|
+
These methods are on **`CodeSystemProvider`**:
|
|
221
|
+
|
|
222
|
+
| Method | Default | Description |
|
|
223
|
+
|---|---|---|
|
|
224
|
+
| `registerConceptMaps(list)` | no-op | Called at startup; the provider should add any implicit `ConceptMap` resources it owns to `list`. |
|
|
225
|
+
| `getTranslations(coding, target)` | `null` | Returns `CodeTranslation[]` mapping the given `coding` to the `target` system, or `null` if no translation is available. |
|
|
226
|
+
|
|
227
|
+
These methods are on **`CodeSystemFactoryProvider`**:
|
|
228
|
+
|
|
229
|
+
| Method | Default | Description |
|
|
230
|
+
|---|---|---|
|
|
231
|
+
| `findImplicitConceptMaps(conceptMaps, source, dest)` | `null` | Returns concept maps between `source` and `dest` that are implicitly defined by this code system. |
|
|
232
|
+
| `findImplicitConceptMap(url, version)` | `null` | Returns a single implicit concept map by URL and version. |
|
|
233
|
+
|
|
234
|
+
---
|
|
235
|
+
|
|
236
|
+
## Known Value Sets and Concept Maps
|
|
237
|
+
|
|
238
|
+
Code system providers may have built-in knowledge of value sets and concept maps that are
|
|
239
|
+
defined as part of the code system's specification (for example, the FHIR core code systems
|
|
240
|
+
ship with a number of defined value sets).
|
|
241
|
+
|
|
242
|
+
These methods are on **`CodeSystemFactoryProvider`**:
|
|
243
|
+
|
|
244
|
+
| Method | Default | Description |
|
|
245
|
+
|---|---|---|
|
|
246
|
+
| `buildKnownValueSet(url, version)` | `null` | If the factory knows of a value set at `url` (optionally at `version`), build and return it as a `ValueSet` resource. Return `null` if not known. |
|
|
247
|
+
| `registerSupplements()` | `[]` | Returns a list of `CodeSystem` supplement resources whose metadata is known to the factory. Content may be omitted; the server will call `fillOutSupplement()` if it needs the full content. |
|
|
248
|
+
| `fillOutSupplement(supplement)` | no-op | Called by the server when it needs the full content of a supplement that was registered with partial content. The factory should populate `supplement` in-place. |
|
|
249
|
+
|
|
250
|
+
---
|
|
251
|
+
|
|
252
|
+
## CodeSystemFactoryProvider
|
|
253
|
+
|
|
254
|
+
The factory is a long-lived singleton that the server registers at startup and holds in
|
|
255
|
+
memory for the lifetime of the server process. Its responsibilities are:
|
|
256
|
+
|
|
257
|
+
- Holding data loaded from disk or a database (call `load()` at startup).
|
|
258
|
+
- Creating per-request `CodeSystemProvider` instances via `build()`.
|
|
259
|
+
- Declaring implicit value sets, concept maps, and supplements.
|
|
260
|
+
|
|
261
|
+
### Required overrides
|
|
262
|
+
|
|
263
|
+
| Method | Description |
|
|
264
|
+
|---|---|
|
|
265
|
+
| `system()` | The canonical URI of the code system. |
|
|
266
|
+
| `name()` | A human-readable name including version information. |
|
|
267
|
+
| `version()` | The version of the code system data held by this factory. |
|
|
268
|
+
| `defaultVersion()` | The latest known version, used when no version is requested. |
|
|
269
|
+
| `id()` | A short identifier for this provider (used in logging and diagnostics). |
|
|
270
|
+
| `build(opContext, supplements)` | Construct and return a `CodeSystemProvider` bound to the given `OperationContext` and supplements. |
|
|
271
|
+
|
|
272
|
+
### Provided (with defaults)
|
|
273
|
+
|
|
274
|
+
| Method | Default | Description |
|
|
275
|
+
|---|---|---|
|
|
276
|
+
| `load()` | no-op | Called once at startup; load data from disk, database, or network here. |
|
|
277
|
+
| `nameBase()` | Delegates to `name()` | The name without version information. |
|
|
278
|
+
| `content()` | `'complete'` | The content mode of the code system (`complete`, `fragment`, etc.). |
|
|
279
|
+
| `getPartialVersion()` | Major.minor of `version()` if semver | A partial version string for flexible version matching. |
|
|
280
|
+
| `describeVersion(version)` | `'v' + version` | A display string for a version. |
|
|
281
|
+
| `useCount()` | — | The number of times `build()` has been called. |
|
|
282
|
+
| `recordUse()` | — | Increments the use count. |
|
|
283
|
+
| `iteratable()` | `false` | Return `true` if the code system supports iteration. |
|
|
284
|
+
| `codeLink(code)` | `undefined` | Return a URL linking to documentation for the given code, if available. |
|
|
285
|
+
| `close()` | no-op | Called at server shutdown; release database connections and other resources here. |
|
package/tx/cs/cs-loinc.js
CHANGED
|
@@ -5,6 +5,7 @@ const { Language, Languages} = require('../../library/languages');
|
|
|
5
5
|
const { CodeSystemFactoryProvider} = require('./cs-api');
|
|
6
6
|
const { validateOptionalParameter, validateArrayParameter} = require("../../library/utilities");
|
|
7
7
|
const {BaseCSServices} = require("./cs-base");
|
|
8
|
+
const {sqlEscapeString} = require("../../xig/xig");
|
|
8
9
|
|
|
9
10
|
// Context kinds matching Pascal enum
|
|
10
11
|
const LoincProviderContextKind = {
|
|
@@ -657,13 +658,17 @@ class LoincServices extends BaseCSServices {
|
|
|
657
658
|
}
|
|
658
659
|
|
|
659
660
|
async filter(filterContext, prop, op, value) {
|
|
660
|
-
|
|
661
|
-
|
|
662
661
|
const filter = new LoincFilterHolder();
|
|
663
662
|
await this.#executeFilterQuery(prop, op, value, filter);
|
|
664
663
|
filterContext.filters.push(filter);
|
|
665
664
|
}
|
|
666
665
|
|
|
666
|
+
async searchFilter(filterContext, filterText, sort) {
|
|
667
|
+
const filter = new LoincFilterHolder();
|
|
668
|
+
await this.#executeFilterQuery('$text', (sort ? '>' : '<'), filterText.filter, filter);
|
|
669
|
+
filterContext.filters.push(filter);
|
|
670
|
+
}
|
|
671
|
+
|
|
667
672
|
async #executeFilterQuery(prop, op, value, filter) {
|
|
668
673
|
let sql = '';
|
|
669
674
|
let lsql = '';
|
|
@@ -913,6 +918,13 @@ class LoincServices extends BaseCSServices {
|
|
|
913
918
|
WHERE CodeKey IN (SELECT CodeKey FROM Properties WHERE PropertyTypeKey = 9)
|
|
914
919
|
AND CodeKey = `;
|
|
915
920
|
}
|
|
921
|
+
} else if (prop === '$text' && (op === '>' || op === '<')) {
|
|
922
|
+
sql = `SELECT CodeKey as Key FROM Codes
|
|
923
|
+
WHERE Description like '%${sqlEscapeString(value)}%'
|
|
924
|
+
ORDER BY Description `+(op === '<' ? 'ASC' : 'DESC');
|
|
925
|
+
lsql = `SELECT COUNT(CodeKey) as Key FROM Codes
|
|
926
|
+
WHERE Codes.Description like '%${sqlEscapeString(value)}%'
|
|
927
|
+
AND TargetKey = `;
|
|
916
928
|
}
|
|
917
929
|
|
|
918
930
|
if (sql) {
|
package/tx/cs/cs-rxnorm.js
CHANGED
|
@@ -331,20 +331,20 @@ class RxNormServices extends CodeSystemProvider {
|
|
|
331
331
|
async doesFilter(prop, op, value) {
|
|
332
332
|
|
|
333
333
|
|
|
334
|
-
|
|
334
|
+
let propUC = prop.toUpperCase();
|
|
335
335
|
|
|
336
336
|
// TTY filters
|
|
337
|
-
if (
|
|
337
|
+
if (propUC === 'TTY' && ['=', 'in'].includes(op)) {
|
|
338
338
|
return true;
|
|
339
339
|
}
|
|
340
340
|
|
|
341
341
|
// STY filter
|
|
342
|
-
if (
|
|
342
|
+
if (propUC === 'STY' && op === '=') {
|
|
343
343
|
return true;
|
|
344
344
|
}
|
|
345
345
|
|
|
346
346
|
// SAB filter
|
|
347
|
-
if (
|
|
347
|
+
if (propUC === 'SAB' && op === '=') {
|
|
348
348
|
return true;
|
|
349
349
|
}
|
|
350
350
|
|
|
@@ -370,12 +370,12 @@ class RxNormServices extends CodeSystemProvider {
|
|
|
370
370
|
|
|
371
371
|
|
|
372
372
|
const filter = new RxNormFilterHolder();
|
|
373
|
-
|
|
373
|
+
let propUC = prop.toUpperCase();
|
|
374
374
|
|
|
375
375
|
let sql = '';
|
|
376
376
|
let params = {};
|
|
377
377
|
|
|
378
|
-
if (op === 'in' &&
|
|
378
|
+
if (op === 'in' && propUC === 'TTY') {
|
|
379
379
|
const values = value.split(',').map(v => v.trim()).filter(v => v);
|
|
380
380
|
const placeholders = values.map((_, i) => `$tty${i}`).join(',');
|
|
381
381
|
sql = `AND TTY IN (${placeholders})`;
|
|
@@ -383,13 +383,13 @@ class RxNormServices extends CodeSystemProvider {
|
|
|
383
383
|
params[`tty${i}`] = this.#sqlWrapString(val);
|
|
384
384
|
});
|
|
385
385
|
} else if (op === '=') {
|
|
386
|
-
if (
|
|
386
|
+
if (propUC === 'STY') {
|
|
387
387
|
sql = `AND ${this.getCodeField()} IN (SELECT RXCUI FROM rxnsty WHERE TUI = $sty)`;
|
|
388
388
|
params.sty = this.#sqlWrapString(value);
|
|
389
|
-
} else if (
|
|
389
|
+
} else if (propUC === 'SAB') {
|
|
390
390
|
sql = `AND ${this.getCodeField()} IN (SELECT ${this.getCodeField()} FROM rxnconso WHERE SAB = $sab)`;
|
|
391
391
|
params.sab = this.#sqlWrapString(value);
|
|
392
|
-
} else if (
|
|
392
|
+
} else if (propUC === 'TTY') {
|
|
393
393
|
sql = `AND TTY = $tty`;
|
|
394
394
|
params.tty = this.#sqlWrapString(value);
|
|
395
395
|
} else if (this.rels.includes(prop)) {
|
|
@@ -429,7 +429,6 @@ class RxNormServices extends CodeSystemProvider {
|
|
|
429
429
|
}
|
|
430
430
|
|
|
431
431
|
async searchFilter(filterContext, filter, sort) {
|
|
432
|
-
|
|
433
432
|
|
|
434
433
|
if (!filter || !filter.stems || filter.stems.length === 0) {
|
|
435
434
|
throw new Error('Invalid search filter');
|
|
@@ -709,6 +708,11 @@ class RxNormTypeServicesFactory extends CodeSystemFactoryProvider {
|
|
|
709
708
|
const db = new sqlite3.Database(this.dbPath);
|
|
710
709
|
|
|
711
710
|
try {
|
|
711
|
+
await new Promise((resolve, reject) => {
|
|
712
|
+
db.run(`CREATE INDEX IF NOT EXISTS idx_rxnstems_cui_stem ON RXNSTEMS(CUI, stem)`,
|
|
713
|
+
err => err ? reject(err) : resolve());
|
|
714
|
+
});
|
|
715
|
+
|
|
712
716
|
this._sharedData = {
|
|
713
717
|
version: '',
|
|
714
718
|
rels: [],
|
package/tx/cs/cs-snomed.js
CHANGED
|
@@ -371,6 +371,29 @@ class SnomedServices {
|
|
|
371
371
|
return result;
|
|
372
372
|
}
|
|
373
373
|
|
|
374
|
+
filterInactive(state) {
|
|
375
|
+
const result = new SnomedFilterContext();
|
|
376
|
+
result.inactive = state;
|
|
377
|
+
return result;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
filterModuleId(id) {
|
|
381
|
+
const result = new SnomedFilterContext();
|
|
382
|
+
let concept = this.concepts.findConcept(id);
|
|
383
|
+
result.moduleId = concept.index;
|
|
384
|
+
return result;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
filterByProperty(prop, value) {
|
|
388
|
+
const result = new SnomedFilterContext();
|
|
389
|
+
let p = this.concepts.findConcept(prop);
|
|
390
|
+
let v = this.concepts.findConcept(value);
|
|
391
|
+
result.propProp = p.index;
|
|
392
|
+
result.propValue = v.index;
|
|
393
|
+
return result;
|
|
394
|
+
|
|
395
|
+
}
|
|
396
|
+
|
|
374
397
|
searchFilter(searchText, includeInactive = false, exactMatch = false) {
|
|
375
398
|
const result = new SnomedFilterContext();
|
|
376
399
|
|
|
@@ -765,6 +788,12 @@ class SnomedProvider extends BaseCSServices {
|
|
|
765
788
|
this._addCodeProperty(params, 'property', 'child', code, null, description);
|
|
766
789
|
}
|
|
767
790
|
|
|
791
|
+
const moduleId = this.sct.concepts.getModuleId(ctxt.getReference());
|
|
792
|
+
if (moduleId) {
|
|
793
|
+
const code = this.sct.getConceptId(moduleId);
|
|
794
|
+
this._addCodeProperty(params, 'property', 'module', code, null, null);
|
|
795
|
+
}
|
|
796
|
+
|
|
768
797
|
const relationships = this.sct.getConceptRelationships(ctxt.getReference());
|
|
769
798
|
let set = new Set();
|
|
770
799
|
for (let relationshipRef of relationships) {
|
|
@@ -812,10 +841,25 @@ class SnomedProvider extends BaseCSServices {
|
|
|
812
841
|
return this.sct.conceptExists(value);
|
|
813
842
|
}
|
|
814
843
|
}
|
|
844
|
+
if (prop === 'inactive') {
|
|
845
|
+
return op === '=' && ['true', 'false'].includes(value);
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
if (prop === 'moduleId') {
|
|
849
|
+
const id = this.sct.stringToIdOrZero(value);
|
|
850
|
+
return id !== 0n && op === '=';
|
|
851
|
+
}
|
|
852
|
+
|
|
815
853
|
if (prop == 'expressions' && op == '=' && ['true', 'false'].includes(value)) {
|
|
816
854
|
return true;
|
|
817
855
|
}
|
|
818
856
|
|
|
857
|
+
const cid = this.sct.stringToIdOrZero(prop);
|
|
858
|
+
if (cid != 0) {
|
|
859
|
+
const id = this.sct.stringToIdOrZero(value);
|
|
860
|
+
return id !== 0n && op === '=';
|
|
861
|
+
}
|
|
862
|
+
|
|
819
863
|
return false;
|
|
820
864
|
}
|
|
821
865
|
|
|
@@ -851,7 +895,38 @@ class SnomedProvider extends BaseCSServices {
|
|
|
851
895
|
return null;
|
|
852
896
|
}
|
|
853
897
|
default:
|
|
854
|
-
throw new Error(`Unsupported filter operation: ${op}`);
|
|
898
|
+
throw new Error(`Unsupported filter operation: concept ${op} ${value}`);
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
if (prop === 'inactive') {
|
|
903
|
+
if (value !== 'true' && value !== 'false') {
|
|
904
|
+
throw new Error(`Invalid filter value: ${value}`);
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
switch (op) {
|
|
908
|
+
case '=': {
|
|
909
|
+
filterContext.filters.push(this.sct.filterInactive(value === 'true'));
|
|
910
|
+
return null;
|
|
911
|
+
}
|
|
912
|
+
default:
|
|
913
|
+
throw new Error(`Unsupported filter operation: inactive ${op} ${value}`);
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
if (prop === 'moduleId') {
|
|
918
|
+
const id = this.sct.stringToIdOrZero(value);
|
|
919
|
+
if (id === 0n) {
|
|
920
|
+
throw new Error(`Invalid concept ID: ${value}`);
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
switch (op) {
|
|
924
|
+
case '=': {
|
|
925
|
+
filterContext.filters.push(this.sct.filterModuleId(id));
|
|
926
|
+
return null;
|
|
927
|
+
}
|
|
928
|
+
default:
|
|
929
|
+
throw new Error(`Unsupported filter operation: moduleId ${op} ${value}`);
|
|
855
930
|
}
|
|
856
931
|
}
|
|
857
932
|
|
|
@@ -862,6 +937,24 @@ class SnomedProvider extends BaseCSServices {
|
|
|
862
937
|
return null;
|
|
863
938
|
}
|
|
864
939
|
|
|
940
|
+
const cid = this.sct.stringToIdOrZero(prop);
|
|
941
|
+
if (cid != 0) {
|
|
942
|
+
|
|
943
|
+
const id = this.sct.stringToIdOrZero(value);
|
|
944
|
+
if (id === 0n) {
|
|
945
|
+
throw new Error(`Invalid concept ID: ${value}`);
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
switch (op) {
|
|
949
|
+
case '=': {
|
|
950
|
+
filterContext.filters.push(this.sct.filterByProperty(cid, id));
|
|
951
|
+
return null;
|
|
952
|
+
}
|
|
953
|
+
default:
|
|
954
|
+
throw new Error(`Unsupported filter operation: ${prop} ${op} ${value}`);
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
|
|
865
958
|
throw new Error(`Unsupported filter property: ${prop}`);
|
|
866
959
|
}
|
|
867
960
|
|
|
@@ -893,7 +986,7 @@ class SnomedProvider extends BaseCSServices {
|
|
|
893
986
|
|
|
894
987
|
async filterMore(filterContext, set) {
|
|
895
988
|
set.cursor = set.cursor || 0;
|
|
896
|
-
|
|
989
|
+
this.#ensurePopulated(set);
|
|
897
990
|
const size = await this.filterSize(filterContext, set);
|
|
898
991
|
return set.cursor < size;
|
|
899
992
|
}
|
|
@@ -927,12 +1020,27 @@ class SnomedProvider extends BaseCSServices {
|
|
|
927
1020
|
}
|
|
928
1021
|
|
|
929
1022
|
const ctxt = conceptResult.context;
|
|
930
|
-
|
|
931
|
-
|
|
932
1023
|
const reference = ctxt.getReference();
|
|
933
1024
|
let found = false;
|
|
934
1025
|
|
|
935
|
-
if (set.
|
|
1026
|
+
if (set.inactive !== undefined) {
|
|
1027
|
+
let concept = this.sct.concepts.getConcept(reference);
|
|
1028
|
+
let active = (concept.flags & 0x0F) === 0;
|
|
1029
|
+
found = active !== set.inactive
|
|
1030
|
+
} else if (set.moduleId) {
|
|
1031
|
+
let concept = this.sct.concepts.getConcept(reference);
|
|
1032
|
+
let moduleId = this.sct.concepts.getModuleId(concept.index);
|
|
1033
|
+
found = moduleId === set.moduleId;
|
|
1034
|
+
} else if (set.propProp || set.propValue) {
|
|
1035
|
+
found = false;
|
|
1036
|
+
const relationships = this.sct.getConceptRelationships(reference);
|
|
1037
|
+
for (let relationshipRef of relationships) {
|
|
1038
|
+
const relationship = this.sct.relationships.getRelationship(relationshipRef);
|
|
1039
|
+
if (set.propProp === relationship.relType && set.propValue === relationship.target) {
|
|
1040
|
+
found = true;
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
} else if (set.matches && set.matches.length > 0) {
|
|
936
1044
|
found = set.matches.some(m => m.index === reference);
|
|
937
1045
|
} else if (set.members && set.members.length > 0) {
|
|
938
1046
|
found = set.members.some(m => m.ref === reference);
|
|
@@ -958,6 +1066,23 @@ class SnomedProvider extends BaseCSServices {
|
|
|
958
1066
|
}
|
|
959
1067
|
|
|
960
1068
|
const reference = concept.getReference();
|
|
1069
|
+
if (set.inactive !== undefined) {
|
|
1070
|
+
return this.sct.isActive(reference) !== set.inactive;
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
if (set.moduleId) {
|
|
1074
|
+
return this.sct.concepts.getModuleId(reference) === set.moduleId;
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
if (set.propProp || set.propValue) {
|
|
1078
|
+
const relationships = this.sct.getConceptRelationships(reference);
|
|
1079
|
+
for (let relationshipRef of relationships) {
|
|
1080
|
+
const relationship = this.sct.relationships.getRelationship(relationshipRef);
|
|
1081
|
+
if (set.propProp === relationship.relType && set.propValue === relationship.target) {
|
|
1082
|
+
return true;
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
961
1086
|
|
|
962
1087
|
if (set.matches && set.matches.length > 0) {
|
|
963
1088
|
return set.matches.some(m => m.index === reference);
|
|
@@ -970,6 +1095,42 @@ class SnomedProvider extends BaseCSServices {
|
|
|
970
1095
|
return false;
|
|
971
1096
|
}
|
|
972
1097
|
|
|
1098
|
+
#ensurePopulated(set) {
|
|
1099
|
+
if (set.populationDone) {
|
|
1100
|
+
return;
|
|
1101
|
+
}
|
|
1102
|
+
if (set.inactive !== undefined && set.descendants.length === 0) {
|
|
1103
|
+
for (let i = 0; i < this.sct.concepts.count(); i++) {
|
|
1104
|
+
let concept = this.sct.concepts.getConceptByCount(i);
|
|
1105
|
+
let active = (concept.flags & 0x0F) === 0;
|
|
1106
|
+
if (active !== set.inactive) {
|
|
1107
|
+
set.descendants.push(concept.index);
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
if (set.moduleId) {
|
|
1112
|
+
for (let i = 0; i < this.sct.concepts.count(); i++) {
|
|
1113
|
+
let concept = this.sct.concepts.getConceptByCount(i);
|
|
1114
|
+
let moduleId = this.sct.concepts.getModuleId(concept.index);
|
|
1115
|
+
if (moduleId === set.moduleId) {
|
|
1116
|
+
set.descendants.push(concept.index);
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
if (set.propProp || set.propValue) {
|
|
1121
|
+
for (let i = 0; i < this.sct.concepts.count(); i++) {
|
|
1122
|
+
let concept = this.sct.concepts.getConceptByCount(i);
|
|
1123
|
+
const relationships = this.sct.getConceptRelationships(concept.getReference());
|
|
1124
|
+
for (let relationshipRef of relationships) {
|
|
1125
|
+
const relationship = this.sct.relationships.getRelationship(relationshipRef);
|
|
1126
|
+
if (set.propProp === relationship.relType && set.propValue === relationship.target) {
|
|
1127
|
+
set.descendants.push(concept.index);
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
set.populationDone = true;
|
|
1133
|
+
}
|
|
973
1134
|
|
|
974
1135
|
// Search filter
|
|
975
1136
|
async searchFilter(filterContext, filter, sort) {
|