fhirsmith 0.7.5 → 0.7.6

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/stats.js CHANGED
@@ -5,6 +5,7 @@ const escape = require('escape-html');
5
5
  class ServerStats {
6
6
  started = false;
7
7
  requestCount = 0;
8
+ staticRequestCount = 0;
8
9
  requestTime = 0;
9
10
  // Collect metrics every 10 minutes
10
11
  intervalMs = 10 * 60 * 1000;
@@ -27,7 +28,8 @@ class ServerStats {
27
28
  const now = Date.now();
28
29
 
29
30
  const currentMem = process.memoryUsage().heapUsed;
30
- const requestsDelta = this.requestCount - this.requestCountSnapshot;
31
+ const combinedCount = this.requestCount + this.staticRequestCount;
32
+ const requestsDelta = combinedCount - this.requestCountSnapshot;
31
33
  const requestsTat = requestsDelta > 0 ? this.requestTime / requestsDelta : 0;
32
34
  const minutesSinceStart = this.history.length > 1
33
35
  ? this.intervalMs / 60000
@@ -38,7 +40,7 @@ class ServerStats {
38
40
  const idleDelta = currentCpu.idle - this.lastUsage.idle;
39
41
  const totalDelta = currentCpu.total - this.lastUsage.total;
40
42
  const percent = totalDelta > 0 ? 100 * (1 - idleDelta / totalDelta) : 0;
41
-
43
+
42
44
  const loopDelay = this.eventLoopMonitor.mean / 1e6;
43
45
  let cacheCount = 0;
44
46
  for (let m of this.cachingModules) {
@@ -48,11 +50,11 @@ class ServerStats {
48
50
  this.history.push({time: now, mem: currentMem - this.startMem, rpm: requestsPerMin, tat: requestsTat, cpu: percent, block: loopDelay, cache : cacheCount});
49
51
 
50
52
  this.eventLoopMonitor.reset();
51
- this.requestCountSnapshot = this.requestCount;
53
+ this.requestCountSnapshot = combinedCount;
52
54
  this.requestTime = 0;
53
55
  this.lastTime = now;
54
56
  this.lastUsage = currentCpu;
55
-
57
+
56
58
  // Prune old data (keep 24 hours)
57
59
  const cutoff = now - (24 * 60 * 60 * 1000); // 24 hours ago
58
60
  this.history = this.history.filter(m => m.time > cutoff);
package/tx/README.md CHANGED
@@ -201,7 +201,7 @@ Loads LOINC from a SQLite database file.
201
201
  ```
202
202
 
203
203
  The filename is downloaded from the base URL if not cached. Database files must be in the server's proprietary format.
204
- The file is built by importing LOINC (to be documented)
204
+ The file is built by importing LOINC (see [documentation](importers/readme.md))
205
205
 
206
206
  #### `rxnorm` - RxNorm
207
207
 
@@ -211,7 +211,7 @@ Loads RxNorm drug terminology from a SQLite database file.
211
211
  - rxnorm:rxnorm_02032025-a.db
212
212
  ```
213
213
 
214
- The file is built by importing RxNorm (to be documented)
214
+ The file is built by importing RxNorm (see [documentation](importers/readme.md))
215
215
 
216
216
  #### `ndc` - NDC (National Drug Code)
217
217
 
@@ -228,7 +228,7 @@ Loads FDA UNII codes from a SQLite database file.
228
228
  ```yaml
229
229
  - unii:unii_20240622.db
230
230
  ```
231
- The file is built by importing UNII (to be documented)
231
+ The file is built by importing UNII (see [documentation](importers/readme.md))
232
232
 
233
233
  #### `snomed` - SNOMED CT
234
234
 
@@ -254,7 +254,7 @@ Common edition identifiers:
254
254
  - `nl` - Netherlands
255
255
  - `ips` - IPS (International Patient Summary) Free Set
256
256
 
257
- The file is built by importing SNOMED CT (to be documented)
257
+ The file is built by importing SNOMED CT (see [documentation](importers/readme.md))
258
258
 
259
259
 
260
260
  #### `cpt` - CPT (Current Procedural Terminology)
@@ -267,7 +267,7 @@ Loads CPT codes from a SQLite database file.
267
267
 
268
268
  **Note:** CPT is copyrighted by the American Medical Association. Ensure you have appropriate licensing.
269
269
 
270
- The file is built by importing CPT (to be documented)
270
+ The file is built by importing CPT (see [documentation](importers/readme.md))
271
271
 
272
272
  #### `omop` - OMOP Vocabularies
273
273
 
@@ -276,7 +276,7 @@ Loads OMOP (Observational Medical Outcomes Partnership) vocabulary mappings from
276
276
  ```yaml
277
277
  - omop:omop_v20250227.db
278
278
  ```
279
- The file is built by importing OMOP (to be documented)
279
+ The file is built by importing OMOP (see [documentation](importers/readme.md))
280
280
 
281
281
  #### `npm` - FHIR NPM Packages
282
282
 
package/tx/cs/cs-api.js CHANGED
@@ -9,6 +9,9 @@ const {validateParameter, validateArrayParameter} = require("../../library/utili
9
9
  const {I18nSupport} = require("../../library/i18nsupport");
10
10
  const {VersionUtilities} = require("../../library/version-utilities");
11
11
 
12
+ /**
13
+ * For documentation, see cs-api.md
14
+ */
12
15
  class FilterExecutionContext {
13
16
  filters = [];
14
17
  forIterate = false;
@@ -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. |
@@ -1,6 +1,8 @@
1
1
  # TX-Import: Medical Terminology Import Tool
2
2
 
3
- A comprehensive CLI tool for importing various medical terminology standards into SQLite databases and other formats. The tool supports LOINC, SNOMED CT, UNII, NDC, and provides extensible architecture for additional terminologies.
3
+ A comprehensive CLI tool for importing various medical terminology standards into SQLite databases and other formats
4
+ for use by FHIRsmith. The tool supports LOINC, SNOMED CT, UNII, NDC, and provides extensible architecture for
5
+ additional terminologies.
4
6
 
5
7
  ## Table of Contents
6
8
 
package/tx/library.js CHANGED
@@ -65,7 +65,8 @@ class Library {
65
65
  */
66
66
  conceptMapProviders;
67
67
 
68
- contentSources = [];
68
+ packageSources = [];
69
+ externalSources = [];
69
70
 
70
71
  baseUrl = null;
71
72
  cacheFolder = null;
@@ -360,6 +361,9 @@ class Library {
360
361
  const codeSystemProvider = new OCLCodeSystemProvider(config);
361
362
  const valueSetProvider = new OCLValueSetProvider(config);
362
363
  const conceptMapProvider = new OCLConceptMapProvider(config);
364
+ this.externalSources.push(codeSystemProvider);
365
+ this.externalSources.push(valueSetProvider);
366
+ this.externalSources.push(conceptMapProvider);
363
367
  providerSet = {
364
368
  config,
365
369
  codeSystemProvider,
@@ -470,6 +474,7 @@ class Library {
470
474
  let vsac = new VSACValueSetProvider(this.vsacCfg, this.stats);
471
475
  vsac.initialize();
472
476
  this.valueSetProviders.push(vsac);
477
+ this.externalSources.push(vsac);
473
478
  //const mem = process.memoryUsage();
474
479
  let time = Math.floor(Date.now() - this.lastTime).toString().padStart(5)+" ";
475
480
  let system = "vsac".padEnd(50);
@@ -597,7 +602,7 @@ class Library {
597
602
  const contentLoader = new PackageContentLoader(fullPackagePath);
598
603
  await contentLoader.initialize();
599
604
 
600
- this.contentSources.push(contentLoader.id()+"#"+contentLoader.version());
605
+ this.packageSources.push(contentLoader.id()+"#"+contentLoader.version());
601
606
 
602
607
  let cp = new ListCodeSystemProvider();
603
608
  const resources = await contentLoader.getResourcesByType("CodeSystem");
@@ -635,7 +640,7 @@ class Library {
635
640
  const contentLoader = new PackageContentLoader(fullPackagePath);
636
641
  await contentLoader.initialize();
637
642
 
638
- this.contentSources.push(contentLoader.id()+"#"+contentLoader.version());
643
+ this.packageSources.push(contentLoader.id()+"#"+contentLoader.version());
639
644
 
640
645
  let cp = new ListCodeSystemProvider();
641
646
  const resources = await contentLoader.getResourcesByType("CodeSystem");
@@ -847,7 +852,8 @@ class Library {
847
852
  provider.lastTime = this.lastTime;
848
853
  provider.lastMemory = this.lastMemory;
849
854
  provider.totalDownloaded = this.totalDownloaded;
850
- provider.contentSources = this.contentSources;
855
+ provider.packageSources = this.packageSources;
856
+ provider.externalSources = this.externalSources;
851
857
 
852
858
 
853
859
  // Now add the existing value set providers after the FHIR core packages
package/tx/provider.js CHANGED
@@ -49,7 +49,8 @@ class Provider {
49
49
  */
50
50
  conceptMapProviders;
51
51
 
52
- contentSources;
52
+ packageSources;
53
+ externalSources;
53
54
 
54
55
  baseUrl = null;
55
56
  path;
package/tx/tx-html.js CHANGED
@@ -234,24 +234,44 @@ class TxHtmlRenderer {
234
234
  html += await this.buildSearchForm(req);
235
235
 
236
236
  // ===== Packages and Factories Section =====
237
- html += '<hr/><h3>Content Sources &amp; Code System Factories</h3>';
237
+ html += '<hr/><h3>Source Content</h3>';
238
238
 
239
- // List content sources
240
- html += '<h6>Content Sources</h6>';
241
- if (provider.contentSources && provider.contentSources.length > 0) {
242
- const sorted = [...provider.contentSources].sort();
239
+ // List Packages
240
+ html += '<h6>FHIR Packages</h6>';
241
+ if (provider.packageSources && provider.packageSources.length > 0) {
242
+ const sorted = [...provider.packageSources].sort();
243
243
  html += '<ul>';
244
244
  for (const source of sorted) {
245
245
  html += `<li>${escape(source)}</li>`;
246
246
  }
247
247
  html += '</ul>';
248
248
  } else {
249
- html += '<p><em>No content sources available</em></p>';
249
+ html += '<p><em>No FHIR Packages Loaded</em></p>';
250
250
  }
251
251
 
252
- // Code System Factories table
253
- // Code System Factories table
254
- html += '<h6 class="mt-4">External CodeSystems</h6>';
252
+ // List Packages
253
+ html += '<h6>External Sources</h6>';
254
+ if (provider.externalSources && provider.externalSources.length > 0) {
255
+ const sorted = [...provider.externalSources].sort();
256
+ html += '<ul>';
257
+ for (const source of sorted) {
258
+ let n = source.name();
259
+ if (!n) {
260
+ n = source.sourcePackage();
261
+ }
262
+ let ii = source.infoName();
263
+ if (ii) {
264
+ html += `<li>${escape(n)} (<a href="info/${source.id()}">${ii}</a>)</li>`;
265
+ } else {
266
+ html += `<li>${escape(n)}</li>`;
267
+ }
268
+ }
269
+ html += '</ul>';
270
+ } else {
271
+ html += '<p><em>No External Sources Configured</em></p>';
272
+ }
273
+
274
+ html += '<h6 class="mt-4">Special CodeSystems</h6>';
255
275
  html += '<table class="grid">';
256
276
  html += '<thead><tr><th>Name</th><th>URI</th><th>Version</th><th>Use Count</th></tr></thead>';
257
277
  html += '<tbody>';
@@ -1290,6 +1310,13 @@ class TxHtmlRenderer {
1290
1310
  });
1291
1311
  }
1292
1312
 
1313
+ async buildInfoPage(source, req) {
1314
+ let html = '';
1315
+ const infoContent = await source.info(req);
1316
+ html += infoContent;
1317
+ return html;
1318
+ }
1319
+
1293
1320
  buildSourceOptions(provider) {
1294
1321
  let result = '<option value=""></option>';
1295
1322
  result += `<option value="internal">internal</option>`;
package/tx/tx.js CHANGED
@@ -9,7 +9,7 @@ const express = require('express');
9
9
  const path = require('path');
10
10
  const Logger = require('../library/logger');
11
11
  const { Library } = require('./library');
12
- const { OperationContext, ResourceCache, ExpansionCache } = require('./operation-context');
12
+ const { OperationContext, ResourceCache, ExpansionCache, debugLog} = require('./operation-context');
13
13
  const { LanguageDefinitions } = require('../library/languages');
14
14
  const { I18nSupport } = require('../library/i18nsupport');
15
15
  const { CodeSystemXML } = require('./xml/codesystem-xml');
@@ -965,6 +965,29 @@ class TXModule {
965
965
  this.countRequest('home', Date.now() - start);
966
966
  }
967
967
  });
968
+
969
+ // External source info pages
970
+ router.get('/info/:id', async (req, res) => {
971
+ const start = Date.now();
972
+ try {
973
+ const source = req.txEndpoint.provider.externalSources.find(s => s.id() === req.params.id);
974
+ if (!source) {
975
+ res.status(404).send('Not found');
976
+ return;
977
+ }
978
+ let txhtml = new TxHtmlRenderer(new Renderer(req.txOpContext, req.txEndpoint.provider), this.liquid, this.languages, this.i18n, req.txEndpoint.path);
979
+ const content = await txhtml.buildInfoPage(source, req);
980
+ const html = await txhtml.renderPage(source.name(), content, req.txEndpoint, start);
981
+ res.setHeader('Content-Type', 'text/html');
982
+ res.send(html);
983
+ } catch (error) {
984
+ debugLog(error);
985
+ this.log.error(`Error rendering info page for ${req.params.id}: ${error.message}`);
986
+ res.status(500).send('Internal server error');
987
+ } finally {
988
+ this.countRequest('info', Date.now() - start);
989
+ }
990
+ });
968
991
  }
969
992
 
970
993
  /**
@@ -1153,16 +1176,16 @@ class TXModule {
1153
1176
  ec = 0;
1154
1177
 
1155
1178
  checkProperJson() { // jsonStr) {
1156
- // const errors = [];
1157
- // if (jsonStr.includes("[]")) errors.push("Found [] in json");
1158
- // if (jsonStr.includes('""')) errors.push('Found "" in json');
1159
- //
1160
- // if (errors.length > 0) {
1161
- // this.ec++;
1162
- // const filename = `/Users/grahamegrieve/temp/tx-err-log/err${this.ec}.json`;
1163
- // writeFileSync(filename, jsonStr);
1164
- // throw new Error(errors.join('; '));
1165
- // }
1179
+ // const errors = [];
1180
+ // if (jsonStr.includes("[]")) errors.push("Found [] in json");
1181
+ // if (jsonStr.includes('""')) errors.push('Found "" in json');
1182
+ //
1183
+ // if (errors.length > 0) {
1184
+ // this.ec++;
1185
+ // const filename = `/Users/grahamegrieve/temp/tx-err-log/err${this.ec}.json`;
1186
+ // writeFileSync(filename, jsonStr);
1187
+ // throw new Error(errors.join('; '));
1188
+ // }
1166
1189
  }
1167
1190
 
1168
1191
  transformResourceForVersion(data, fhirVersion) {