fhirsmith 0.5.2 → 0.5.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 +12 -0
- package/package.json +2 -2
- package/server.js +13 -0
- package/stats.js +21 -7
- package/tx/README.md +12 -8
- package/tx/cs/cs-api.js +87 -8
- package/tx/cs/cs-areacode.js +0 -9
- package/tx/cs/cs-country.js +0 -10
- package/tx/cs/cs-cpt.js +1 -6
- package/tx/cs/cs-cs.js +1 -2
- package/tx/cs/cs-currency.js +0 -9
- package/tx/cs/cs-hgvs.js +0 -5
- package/tx/cs/cs-lang.js +0 -10
- package/tx/cs/cs-loinc.js +0 -7
- package/tx/cs/cs-omop.js +0 -6
- package/tx/cs/cs-rxnorm.js +3 -0
- package/tx/cs/cs-snomed.js +10 -4
- package/tx/cs/cs-ucum.js +0 -9
- package/tx/html/tx-template.html +1 -0
- package/tx/library/designations.js +5 -1
- package/tx/library/renderer.js +6 -1
- package/tx/library.js +1 -0
- package/tx/operation-context.js +1 -0
- package/tx/params.js +7 -1
- package/tx/problems.js +68 -0
- package/tx/provider.js +14 -0
- package/tx/tx.fhir.org.yml +1 -0
- package/tx/tx.js +41 -5
- package/tx/usage-tracker.js +70 -0
- package/tx/vs/vs-vsac.js +12 -12
- package/tx/workers/expand.js +227 -135
- package/tx/workers/validate.js +4 -1
- package/tx/workers/worker.js +14 -0
- package/tx/xversion/xv-parameters.js +8 -1
- package/tx/xversion/xv-resource.js +16 -16
- package/tx/cs/cs-db.js +0 -1308
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,18 @@ All notable changes to the Health Intersections Node Server will be documented i
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [v0.5.2] - 2026-02-24
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- Page listing logical problems in terminology definitions
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
- Fixed many bugs identified by usage
|
|
15
|
+
|
|
16
|
+
### Tx Conformance Statement
|
|
17
|
+
|
|
18
|
+
FHIRsmith 0.5.1 passed all 1382 HL7 terminology service tests (modes tx.fhir.org,omop,general,snomed, tests v1.9.0, runner v6.8.1)
|
|
19
|
+
|
|
8
20
|
## [v0.5.1] - 2026-02-20
|
|
9
21
|
|
|
10
22
|
### Added
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fhirsmith",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.3",
|
|
4
4
|
"description": "A Node.js server that provides a collection of tools to serve the FHIR ecosystem",
|
|
5
5
|
"main": "server.js",
|
|
6
6
|
"engines": {
|
|
@@ -116,4 +116,4 @@
|
|
|
116
116
|
"url": "https://github.com/HealthIntersections/fhirsmith/issues"
|
|
117
117
|
},
|
|
118
118
|
"homepage": "https://github.com/HealthIntersections/fhirsmith#readme"
|
|
119
|
-
}
|
|
119
|
+
}
|
package/server.js
CHANGED
|
@@ -568,6 +568,19 @@ async function startServer() {
|
|
|
568
568
|
if (modules.packages && config.modules.packages.enabled) {
|
|
569
569
|
modules.packages.startInitialCrawler();
|
|
570
570
|
}
|
|
571
|
+
// Run usage tracker in background after startup
|
|
572
|
+
if (modules.tx && modules.tx.library) {
|
|
573
|
+
setImmediate(async () => {
|
|
574
|
+
try {
|
|
575
|
+
serverLog.info('Starting ConceptUsageTracker scan...');
|
|
576
|
+
let count = await modules.tx.usageTracker.scanValueSets(modules.tx.library);
|
|
577
|
+
serverLog.info(`ConceptUsageTracker scan complete: ${count} valuesets list codes`);
|
|
578
|
+
} catch (err) {
|
|
579
|
+
console.log(err);
|
|
580
|
+
serverLog.error('ConceptUsageTracker scan failed:', err);
|
|
581
|
+
}
|
|
582
|
+
});
|
|
583
|
+
}
|
|
571
584
|
} catch (error) {
|
|
572
585
|
console.error('FATAL - Failed to start server:', error);
|
|
573
586
|
serverLog.error('FATAL - Failed to start server:', error);
|
package/stats.js
CHANGED
|
@@ -33,10 +33,12 @@ class ServerStats {
|
|
|
33
33
|
? this.intervalMs / 60000
|
|
34
34
|
: (now - this.startTime) / 60000;
|
|
35
35
|
const requestsPerMin = minutesSinceStart > 0 ? requestsDelta / minutesSinceStart : 0;
|
|
36
|
-
|
|
37
|
-
const
|
|
38
|
-
const
|
|
39
|
-
const
|
|
36
|
+
|
|
37
|
+
const currentCpu = this.readSystemCpu();
|
|
38
|
+
const idleDelta = currentCpu.idle - this.lastUsage.idle;
|
|
39
|
+
const totalDelta = currentCpu.total - this.lastUsage.total;
|
|
40
|
+
const percent = totalDelta > 0 ? 100 * (1 - idleDelta / totalDelta) : 0;
|
|
41
|
+
|
|
40
42
|
const loopDelay = this.eventLoopMonitor.mean / 1e6;
|
|
41
43
|
let cacheCount = 0;
|
|
42
44
|
for (let m of this.cachingModules) {
|
|
@@ -48,9 +50,9 @@ class ServerStats {
|
|
|
48
50
|
this.eventLoopMonitor.reset();
|
|
49
51
|
this.requestCountSnapshot = this.requestCount;
|
|
50
52
|
this.requestTime = 0;
|
|
51
|
-
this.lastUsage = process.cpuUsage();
|
|
52
53
|
this.lastTime = now;
|
|
53
|
-
|
|
54
|
+
this.lastUsage = currentCpu;
|
|
55
|
+
|
|
54
56
|
// Prune old data (keep 24 hours)
|
|
55
57
|
const cutoff = now - (24 * 60 * 60 * 1000); // 24 hours ago
|
|
56
58
|
this.history = this.history.filter(m => m.time > cutoff);
|
|
@@ -61,7 +63,7 @@ class ServerStats {
|
|
|
61
63
|
this.started = true;
|
|
62
64
|
this.startMem = process.memoryUsage().heapUsed;
|
|
63
65
|
this.startTime = Date.now();
|
|
64
|
-
this.lastUsage =
|
|
66
|
+
this.lastUsage = this.readSystemCpu();
|
|
65
67
|
this.lastTime = this.startTime;
|
|
66
68
|
this.eventLoopMonitor = monitorEventLoopDelay({ resolution: 20 });
|
|
67
69
|
this.eventLoopMonitor.enable();
|
|
@@ -114,5 +116,17 @@ class ServerStats {
|
|
|
114
116
|
finishStats() {
|
|
115
117
|
clearInterval(this.timer);
|
|
116
118
|
}
|
|
119
|
+
|
|
120
|
+
readSystemCpu() {
|
|
121
|
+
const os = require('os');
|
|
122
|
+
const cpus = os.cpus();
|
|
123
|
+
let idle = 0, total = 0;
|
|
124
|
+
for (const cpu of cpus) {
|
|
125
|
+
idle += cpu.times.idle;
|
|
126
|
+
total += cpu.times.user + cpu.times.nice + cpu.times.sys + cpu.times.idle + cpu.times.irq;
|
|
127
|
+
}
|
|
128
|
+
return { idle, total };
|
|
129
|
+
}
|
|
130
|
+
|
|
117
131
|
}
|
|
118
132
|
module.exports = ServerStats;
|
package/tx/README.md
CHANGED
|
@@ -26,6 +26,8 @@ Add the `tx` section to your `config.json`:
|
|
|
26
26
|
"enabled": true,
|
|
27
27
|
"librarySource": "/path/to/library.yml",
|
|
28
28
|
"cacheTimeout": 30,
|
|
29
|
+
"internalLimit" : 10000,
|
|
30
|
+
"externalLimit" : 1000,
|
|
29
31
|
"expansionCacheSize": 1000,
|
|
30
32
|
"expansionCacheMemoryThreshold": 0,
|
|
31
33
|
"endpoints": [
|
|
@@ -57,14 +59,16 @@ Add the `tx` section to your `config.json`:
|
|
|
57
59
|
|
|
58
60
|
### Configuration Options
|
|
59
61
|
|
|
60
|
-
| Option
|
|
61
|
-
|
|
62
|
-
| `enabled`
|
|
63
|
-
| `cacheTimeout`
|
|
64
|
-
| `expansionCacheSize`
|
|
65
|
-
| `expansionCacheMemoryThreshold` | integer | No
|
|
66
|
-
| `librarySource`
|
|
67
|
-
| `
|
|
62
|
+
| Option | Type | Required | Description |
|
|
63
|
+
|---------------------------------|---------|----------|---------------------------------------------------------------------------------------------------------|
|
|
64
|
+
| `enabled` | boolean | Yes | Whether the module is enabled |
|
|
65
|
+
| `cacheTimeout` | integer | No | How many minutes to keep client side caches (for cache-id parameter). Default: 30 |
|
|
66
|
+
| `expansionCacheSize` | integer | No | Maximum number of expanded ValueSets to cache. Default: 1000 |
|
|
67
|
+
| `expansionCacheMemoryThreshold` | integer | No | Heap memory usage in MB that triggers evicting oldest half of expansion cache. 0 = disabled. Default: 0 |
|
|
68
|
+
| `librarySource` | string | Yes | Path to the YAML file that defines the terminology sources to load |
|
|
69
|
+
| `internalLimit` | integer | No | Largest number of codes in internal expansions |
|
|
70
|
+
| `externalLimit` | integer | No | Largest number of codes the server will return in an expansion |
|
|
71
|
+
| `endpoints` | array | Yes | List of endpoint configurations (at least one required) |
|
|
68
72
|
|
|
69
73
|
### Endpoint Configuration
|
|
70
74
|
|
package/tx/cs/cs-api.js
CHANGED
|
@@ -30,7 +30,7 @@ class CodeSystemProvider {
|
|
|
30
30
|
*/
|
|
31
31
|
supplements;
|
|
32
32
|
|
|
33
|
-
constructor(opContext, supplements) {
|
|
33
|
+
constructor(opContext, supplements = null) {
|
|
34
34
|
this.opContext = opContext;
|
|
35
35
|
this.supplements = supplements;
|
|
36
36
|
this._ensureOpContext(opContext);
|
|
@@ -488,6 +488,39 @@ class CodeSystemProvider {
|
|
|
488
488
|
|
|
489
489
|
// procedure getCDSInfo(card : TCDSHookCard; langList : THTTPLanguageList; baseURL, code, display : String); virtual;
|
|
490
490
|
|
|
491
|
+
/**
|
|
492
|
+
* There are two models for handling concepts and filters. The first is where the logic is entirely
|
|
493
|
+
* handled by worker classes; this is needed for value sets that select codes across systems, and
|
|
494
|
+
* with references to other value sets. This workflow consists of calling GetPrepContext, followed
|
|
495
|
+
* by some combination of filter+searchFilter, and then executeFilters
|
|
496
|
+
*
|
|
497
|
+
* followed by filterMore/filterConcept. All code system providers have to support this workflow
|
|
498
|
+
*
|
|
499
|
+
* But an important subset of value sets simply select codes from one codeSystem, from large
|
|
500
|
+
* code systems. Such processing can be done much more efficiently by the code system provider.
|
|
501
|
+
* providers that do this should return handlesSelecting() = true, and then for suitable valuesets,
|
|
502
|
+
* the method processSelection() will be called
|
|
503
|
+
*/
|
|
504
|
+
handlesSelecting() {
|
|
505
|
+
return false;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
/**
|
|
509
|
+
* Process a set of includes and excludes for the code system
|
|
510
|
+
*
|
|
511
|
+
* @param {TxParameters} params: information from the request that the user made, to help optimise loading
|
|
512
|
+
* @param {Object[]} includes - a list of includes from the code system. Each include may contain just the system(+version), concepts and/or filters (but won't contain value sets)
|
|
513
|
+
* @param {Object[]} excludes - a list of excludes from the code system. Each include may contain just the system(+version), concepts and/or filters (but won't contain value sets)
|
|
514
|
+
* @param {boolean} excludeInactive: whether the server will use inactive codes or not
|
|
515
|
+
* @param {int} offset if handlesOffset() and !iterate, and if the value set is a simple one that only uses this provider, then this is the applicable offset. -1 if not applicable
|
|
516
|
+
* @param {int} count if handlesOffset() and !iterate, and if the value set is a simple one that only uses this provider, then this is the applicable count. -1 if not applicable
|
|
517
|
+
* @returns {FilterConceptSet[]} filter sets. In general, it wouldn't make sense to return more than one, but providers can do if they want to. See futher comments on executeFilters
|
|
518
|
+
*/
|
|
519
|
+
processSelection(params, includes, excludes, excludeInactive, offset, count) {
|
|
520
|
+
// well, you only need to override if handlesSelecting=true, but that's the only time this will be called
|
|
521
|
+
throw new Error("Must override");
|
|
522
|
+
}
|
|
523
|
+
|
|
491
524
|
/**
|
|
492
525
|
* returns true if a filter is supported
|
|
493
526
|
*
|
|
@@ -499,14 +532,16 @@ class CodeSystemProvider {
|
|
|
499
532
|
async doesFilter(prop, op, value) { return false; }
|
|
500
533
|
|
|
501
534
|
/**
|
|
502
|
-
* gets a single context in which filters will be evaluated. The
|
|
503
|
-
* it's only use is to be passed back to the CodeSystem provider so it can make use of it
|
|
535
|
+
* gets a single context in which filters will be evaluated. The server doesn't doesn't make use of this context;
|
|
536
|
+
* it's only use is to be passed back to the CodeSystem provider so it can make use of it to organise the filter process
|
|
504
537
|
*
|
|
505
538
|
* @param {boolean} iterate true if the conceptSets that result from this will be iterated, and false if they'll be used to locate a single code
|
|
506
|
-
* @returns {FilterExecutionContext} filter
|
|
507
|
-
*
|
|
539
|
+
* @returns {FilterExecutionContext} filter
|
|
540
|
+
*
|
|
541
|
+
**/
|
|
508
542
|
async getPrepContext(iterate) { return new FilterExecutionContext(iterate); }
|
|
509
543
|
|
|
544
|
+
|
|
510
545
|
/**
|
|
511
546
|
* executes a text search filter (whatever that means) and returns a FilterConceptSet
|
|
512
547
|
*
|
|
@@ -516,7 +551,7 @@ class CodeSystemProvider {
|
|
|
516
551
|
* @param {String} filter user entered text search
|
|
517
552
|
* @param {boolean} sort ?
|
|
518
553
|
**/
|
|
519
|
-
async searchFilter(filterContext, filter, sort) { throw new Error("
|
|
554
|
+
async searchFilter(filterContext, filter, sort) { throw new Error("Text Search is not supported"); } // ? must override?
|
|
520
555
|
|
|
521
556
|
/**
|
|
522
557
|
* Used for searching ucum (see specialEnumeration)
|
|
@@ -532,7 +567,7 @@ class CodeSystemProvider {
|
|
|
532
567
|
} // ? must override?
|
|
533
568
|
|
|
534
569
|
/**
|
|
535
|
-
*
|
|
570
|
+
* inform the CS provider about a filter
|
|
536
571
|
*
|
|
537
572
|
* throws an exception if the search filter can't be handled
|
|
538
573
|
*
|
|
@@ -549,6 +584,9 @@ class CodeSystemProvider {
|
|
|
549
584
|
* one FilterConceptSet, then the code system provider has done the join across the
|
|
550
585
|
* filters, otherwise the engine will do so as required
|
|
551
586
|
*
|
|
587
|
+
* The first in the set of returned FilterConceptSet is used for iterating; other
|
|
588
|
+
* FilterConceptSets are used for filterCheck();
|
|
589
|
+
*
|
|
552
590
|
* @param {FilterExecutionContext} filterContext filtering context
|
|
553
591
|
* @returns {FilterConceptSet[]} filter sets
|
|
554
592
|
**/
|
|
@@ -679,7 +717,22 @@ class CodeSystemProvider {
|
|
|
679
717
|
return null;
|
|
680
718
|
}
|
|
681
719
|
|
|
682
|
-
|
|
720
|
+
/**
|
|
721
|
+
* a record of observed usages of codes from this code system
|
|
722
|
+
* - a map of code and object which has count, an integer count
|
|
723
|
+
* of frequency of use (this server iteration, for now)
|
|
724
|
+
*
|
|
725
|
+
* Only populated when expanding, and read-only to the CS Provider
|
|
726
|
+
*
|
|
727
|
+
* @type {Map<String, Object>}
|
|
728
|
+
*/
|
|
729
|
+
usages() {
|
|
730
|
+
if (this.usagesObj == undefined) {
|
|
731
|
+
this.usagesObj = this.opContext.usageTracker ? this.opContext.usageTracker.usages(this.system()) : null;
|
|
732
|
+
}
|
|
733
|
+
return this.usagesObj;
|
|
734
|
+
}
|
|
735
|
+
usagesObj = undefined;
|
|
683
736
|
}
|
|
684
737
|
|
|
685
738
|
class CodeSystemFactoryProvider {
|
|
@@ -759,6 +812,32 @@ class CodeSystemFactoryProvider {
|
|
|
759
812
|
return null;
|
|
760
813
|
}
|
|
761
814
|
|
|
815
|
+
/**
|
|
816
|
+
* If the data available to the provider includes the definition of some supplements,
|
|
817
|
+
* then the provider has to declare them to the server by overriding this method. The
|
|
818
|
+
* method returns a list of CodeSystem resources, with jsonObj provided. The jsonObj
|
|
819
|
+
* in this case must include the correct metadata, with content = supplement, but need
|
|
820
|
+
* not include any actual content (which might be anticipated to be large). If the
|
|
821
|
+
* server sees a CodeSystem supplement with no content that comes from a provider
|
|
822
|
+
* then the server will use fillOutSupplement to ask for the details to be populated
|
|
823
|
+
* if a client has done something that means the server needs it (mostly, it doesn't)
|
|
824
|
+
*
|
|
825
|
+
* @returns {CodeSystem[]}
|
|
826
|
+
*/
|
|
827
|
+
async registerSupplements() {
|
|
828
|
+
return [];
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
/**
|
|
832
|
+
* see comemnts for registerSupplements()
|
|
833
|
+
*
|
|
834
|
+
* @param {CodeSystem} supplement - the supplement to flesh out
|
|
835
|
+
* @returns void
|
|
836
|
+
*/
|
|
837
|
+
async fillOutSupplement(supplement) {
|
|
838
|
+
// nothing
|
|
839
|
+
}
|
|
840
|
+
|
|
762
841
|
/**
|
|
763
842
|
* build and return a known concept map from the URL, if there is one.
|
|
764
843
|
*
|
package/tx/cs/cs-areacode.js
CHANGED
|
@@ -169,15 +169,6 @@ class AreaCodeServices extends CodeSystemProvider {
|
|
|
169
169
|
return (prop === 'type' || prop === 'class') && op === '=';
|
|
170
170
|
}
|
|
171
171
|
|
|
172
|
-
async searchFilter(filterContext, filter, sort) {
|
|
173
|
-
|
|
174
|
-
assert(filterContext && filterContext instanceof FilterExecutionContext, 'filterContext must be a FilterExecutionContext');
|
|
175
|
-
assert(filter && typeof filter === 'string', 'filter must be a non-null string');
|
|
176
|
-
assert(typeof sort === 'boolean', 'sort must be a boolean');
|
|
177
|
-
|
|
178
|
-
throw new Error('Search filter not implemented for AreaCode');
|
|
179
|
-
}
|
|
180
|
-
|
|
181
172
|
async filter(filterContext, prop, op, value) {
|
|
182
173
|
|
|
183
174
|
assert(filterContext && filterContext instanceof FilterExecutionContext, 'filterContext must be a FilterExecutionContext');
|
package/tx/cs/cs-country.js
CHANGED
|
@@ -22,7 +22,6 @@ class CountryCodeServices extends CodeSystemProvider {
|
|
|
22
22
|
super(opContext, supplements);
|
|
23
23
|
this.codes = codes || [];
|
|
24
24
|
this.codeMap = codeMap || new Map();
|
|
25
|
-
this.supplements = [];
|
|
26
25
|
}
|
|
27
26
|
|
|
28
27
|
// Metadata methods
|
|
@@ -179,15 +178,6 @@ class CountryCodeServices extends CodeSystemProvider {
|
|
|
179
178
|
}
|
|
180
179
|
|
|
181
180
|
|
|
182
|
-
async searchFilter(filterContext, filter, sort) {
|
|
183
|
-
|
|
184
|
-
assert(filterContext && filterContext instanceof FilterExecutionContext, 'filterContext must be a FilterExecutionContext');
|
|
185
|
-
assert(filter && typeof filter === 'string', 'filter must be a non-null string');
|
|
186
|
-
assert(typeof sort === 'boolean', 'sort must be a boolean');
|
|
187
|
-
|
|
188
|
-
throw new Error('Search filter not implemented for CountryCode');
|
|
189
|
-
}
|
|
190
|
-
|
|
191
181
|
async filter(filterContext, prop, op, value) {
|
|
192
182
|
|
|
193
183
|
assert(filterContext && filterContext instanceof FilterExecutionContext, 'filterContext must be a FilterExecutionContext');
|
package/tx/cs/cs-cpt.js
CHANGED
|
@@ -569,12 +569,7 @@ class CPTServices extends BaseCSServices {
|
|
|
569
569
|
return filterContext.filters.some(f => !f.closed);
|
|
570
570
|
}
|
|
571
571
|
|
|
572
|
-
|
|
573
|
-
// eslint-disable-next-line no-unused-vars
|
|
574
|
-
async searchFilter(filterContext, filter, sort) {
|
|
575
|
-
|
|
576
|
-
throw new Error('Text search not implemented yet');
|
|
577
|
-
}
|
|
572
|
+
|
|
578
573
|
|
|
579
574
|
// Subsumption testing - not implemented
|
|
580
575
|
async subsumesTest(codeA, codeB) {
|
package/tx/cs/cs-cs.js
CHANGED
|
@@ -109,7 +109,6 @@ class FhirCodeSystemProvider extends BaseCSServices {
|
|
|
109
109
|
*/
|
|
110
110
|
constructor(opContext, codeSystem, supplements) {
|
|
111
111
|
super(opContext, supplements);
|
|
112
|
-
|
|
113
112
|
if (codeSystem.content == 'supplements') {
|
|
114
113
|
throw new Issue('error', 'invalid', null, 'CODESYSTEM_CS_NO_SUPPLEMENT', opContext.i18n.translate('CODESYSTEM_CS_NO_SUPPLEMENT', opContext.langs, codeSystem.vurl));
|
|
115
114
|
}
|
|
@@ -1134,7 +1133,7 @@ class FhirCodeSystemProvider extends BaseCSServices {
|
|
|
1134
1133
|
|
|
1135
1134
|
|
|
1136
1135
|
const results = new FhirCodeSystemProviderFilterContext();
|
|
1137
|
-
const searchTerm = filter.toLowerCase();
|
|
1136
|
+
const searchTerm = filter.filter.toLowerCase();
|
|
1138
1137
|
|
|
1139
1138
|
// Search through all concepts
|
|
1140
1139
|
const allConcepts = this.codeSystem.getAllConcepts();
|
package/tx/cs/cs-currency.js
CHANGED
|
@@ -176,15 +176,6 @@ class Iso4217Services extends CodeSystemProvider {
|
|
|
176
176
|
return prop === 'decimals' && op === 'equals';
|
|
177
177
|
}
|
|
178
178
|
|
|
179
|
-
async searchFilter(filterContext, filter, sort) {
|
|
180
|
-
|
|
181
|
-
assert(filterContext && filterContext instanceof FilterExecutionContext, 'filterContext must be a FilterExecutionContext');
|
|
182
|
-
assert(filter && typeof filter === 'string', 'filter must be a non-null string');
|
|
183
|
-
assert(typeof sort === 'boolean', 'sort must be a boolean');
|
|
184
|
-
|
|
185
|
-
throw new Error('Search filter not implemented for ISO 4217');
|
|
186
|
-
}
|
|
187
|
-
|
|
188
179
|
async filter(filterContext, prop, op, value) {
|
|
189
180
|
|
|
190
181
|
assert(filterContext && filterContext instanceof FilterExecutionContext, 'filterContext must be a FilterExecutionContext');
|
package/tx/cs/cs-hgvs.js
CHANGED
|
@@ -200,11 +200,6 @@ class HGVSServices extends CodeSystemProvider {
|
|
|
200
200
|
throw new Error('Filters are not supported for HGVS');
|
|
201
201
|
}
|
|
202
202
|
|
|
203
|
-
async searchFilter(filterContext, filter, sort) {
|
|
204
|
-
|
|
205
|
-
throw new Error('Filters are not supported for HGVS');
|
|
206
|
-
}
|
|
207
|
-
|
|
208
203
|
async filter(filterContext, prop, op, value) {
|
|
209
204
|
|
|
210
205
|
throw new Error('Filters are not supported for HGVS');
|
package/tx/cs/cs-lang.js
CHANGED
|
@@ -259,16 +259,6 @@ class IETFLanguageCodeProvider extends CodeSystemProvider {
|
|
|
259
259
|
return false;
|
|
260
260
|
}
|
|
261
261
|
|
|
262
|
-
async searchFilter(filterContext, filter, sort) {
|
|
263
|
-
|
|
264
|
-
assert(filterContext && filterContext instanceof FilterExecutionContext, 'filterContext must be a FilterExecutionContext');
|
|
265
|
-
assert(filter && typeof filter === 'string', 'filter must be a non-null string');
|
|
266
|
-
assert(typeof sort === 'boolean', 'sort must be a boolean');
|
|
267
|
-
|
|
268
|
-
throw new Error('Text search not supported');
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
|
|
272
262
|
async filter(filterContext, prop, op, value) {
|
|
273
263
|
|
|
274
264
|
assert(filterContext && filterContext instanceof FilterExecutionContext, 'filterContext must be a FilterExecutionContext');
|
package/tx/cs/cs-loinc.js
CHANGED
|
@@ -1020,13 +1020,6 @@ class LoincServices extends BaseCSServices {
|
|
|
1020
1020
|
return set.hasKey(concept.key);
|
|
1021
1021
|
}
|
|
1022
1022
|
|
|
1023
|
-
// Search filter - placeholder for text search
|
|
1024
|
-
// eslint-disable-next-line no-unused-vars
|
|
1025
|
-
async searchFilter(filterContext, filter, sort) {
|
|
1026
|
-
|
|
1027
|
-
throw new Error('Text search not implemented yet');
|
|
1028
|
-
}
|
|
1029
|
-
|
|
1030
1023
|
// Subsumption testing
|
|
1031
1024
|
async subsumesTest(codeA, codeB) {
|
|
1032
1025
|
await this.#ensureContext(codeA);
|
package/tx/cs/cs-omop.js
CHANGED
|
@@ -650,12 +650,6 @@ class OMOPServices extends BaseCSServices {
|
|
|
650
650
|
return false; // OMOP filters are closed
|
|
651
651
|
}
|
|
652
652
|
|
|
653
|
-
// Search filter - not implemented
|
|
654
|
-
// eslint-disable-next-line no-unused-vars
|
|
655
|
-
async searchFilter(filterContext, filter, sort) {
|
|
656
|
-
|
|
657
|
-
throw new Error('Search filter not implemented yet');
|
|
658
|
-
}
|
|
659
653
|
|
|
660
654
|
// Subsumption testing - not implemented
|
|
661
655
|
async subsumesTest(codeA, codeB) {
|
package/tx/cs/cs-rxnorm.js
CHANGED
|
@@ -748,6 +748,9 @@ class RxNormTypeServicesFactory extends CodeSystemFactoryProvider {
|
|
|
748
748
|
if (d.includes('_')) {
|
|
749
749
|
d = d.substring(d.lastIndexOf('_') + 1);
|
|
750
750
|
}
|
|
751
|
+
if (d.includes('-')) {
|
|
752
|
+
d = d.substring(0, d.lastIndexOf('-'));
|
|
753
|
+
}
|
|
751
754
|
if (/^\d+$/.test(d)) {
|
|
752
755
|
version = d;
|
|
753
756
|
}
|
package/tx/cs/cs-snomed.js
CHANGED
|
@@ -1166,7 +1166,12 @@ class SnomedServicesFactory extends CodeSystemFactoryProvider {
|
|
|
1166
1166
|
|
|
1167
1167
|
if (id.startsWith('?fhir_vs=refset/')) {
|
|
1168
1168
|
const refsetId = id.substring(16);
|
|
1169
|
-
|
|
1169
|
+
let ref = this.snomedServices.concepts.findConcept(refsetId);
|
|
1170
|
+
if (!ref.found) {
|
|
1171
|
+
return null;
|
|
1172
|
+
}
|
|
1173
|
+
let rref = this.snomedServices.refSetIndex.getRefSetByConcept(ref.index);
|
|
1174
|
+
if (rref == 0) {
|
|
1170
1175
|
return null;
|
|
1171
1176
|
}
|
|
1172
1177
|
return {
|
|
@@ -1176,7 +1181,7 @@ class SnomedServicesFactory extends CodeSystemFactoryProvider {
|
|
|
1176
1181
|
version: this.versionDate,
|
|
1177
1182
|
name: 'SNOMEDCTRefSet' + refsetId,
|
|
1178
1183
|
title: 'SNOMED CT Reference Set ' + refsetId,
|
|
1179
|
-
description: this.getDisplayName(
|
|
1184
|
+
description: this.snomedServices.getDisplayName(ref.index),
|
|
1180
1185
|
date: now,
|
|
1181
1186
|
compose: {
|
|
1182
1187
|
include: [{
|
|
@@ -1193,7 +1198,8 @@ class SnomedServicesFactory extends CodeSystemFactoryProvider {
|
|
|
1193
1198
|
|
|
1194
1199
|
if (id.startsWith('?fhir_vs=isa/')) {
|
|
1195
1200
|
const conceptId = id.substring(13);
|
|
1196
|
-
|
|
1201
|
+
let ref = this.snomedServices.concepts.findConcept(conceptId);
|
|
1202
|
+
if (!ref.found) {
|
|
1197
1203
|
return null;
|
|
1198
1204
|
}
|
|
1199
1205
|
return {
|
|
@@ -1203,7 +1209,7 @@ class SnomedServicesFactory extends CodeSystemFactoryProvider {
|
|
|
1203
1209
|
version: this.versionDate,
|
|
1204
1210
|
name: 'SNOMEDCTConcept' + conceptId,
|
|
1205
1211
|
title: 'SNOMED CT Concept ' + conceptId + ' and descendants',
|
|
1206
|
-
description: 'All Snomed CT concepts for ' + this.getDisplayName(
|
|
1212
|
+
description: 'All Snomed CT concepts for ' + this.snomedServices.getDisplayName(ref.index),
|
|
1207
1213
|
date: now,
|
|
1208
1214
|
compose: {
|
|
1209
1215
|
include: [{
|
package/tx/cs/cs-ucum.js
CHANGED
|
@@ -245,15 +245,6 @@ class UcumCodeSystemProvider extends BaseCSServices {
|
|
|
245
245
|
return (prop === 'canonical' && op === '=');
|
|
246
246
|
}
|
|
247
247
|
|
|
248
|
-
async searchFilter(filterContext, filter, sort) {
|
|
249
|
-
|
|
250
|
-
assert(filterContext && filterContext instanceof FilterExecutionContext, 'filterContext must be a FilterExecutionContext');
|
|
251
|
-
assert(filter && typeof filter === 'string', 'filter must be a non-null string');
|
|
252
|
-
assert(typeof sort === 'boolean', 'sort must be a boolean');
|
|
253
|
-
|
|
254
|
-
throw new Error('Search filter not implemented for UCUM');
|
|
255
|
-
}
|
|
256
|
-
|
|
257
248
|
async specialFilter(filterContext, filter, sort) {
|
|
258
249
|
|
|
259
250
|
assert(filterContext && filterContext instanceof FilterExecutionContext, 'filterContext must be a FilterExecutionContext');
|
package/tx/html/tx-template.html
CHANGED
|
@@ -89,6 +89,7 @@
|
|
|
89
89
|
<a href="[%endpoint-path%]/metadata" style="color: gold">Capability Statement</a> |
|
|
90
90
|
<a href="[%endpoint-path%]/metadata?mode=terminology" style="color: gold">Terminology Capabilities</a> |
|
|
91
91
|
<a href="[%endpoint-path%]/op.html" style="color: gold">Operations</a> |
|
|
92
|
+
<a href="[%endpoint-path%]/problems.html" style="color: gold">Problems</a> |
|
|
92
93
|
|
|
93
94
|
</div>
|
|
94
95
|
</div>
|
package/tx/library/renderer.js
CHANGED
|
@@ -2,6 +2,7 @@ const {CodeSystemProvider} = require("../cs/cs-api");
|
|
|
2
2
|
const {Extensions} = require("./extensions");
|
|
3
3
|
const {div} = require("../../library/html");
|
|
4
4
|
const {getValuePrimitive} = require("../../library/utilities");
|
|
5
|
+
const {getValueName} = require("../../library/utilities");
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* @typedef {Object} TerminologyLinkResolver
|
|
@@ -904,7 +905,11 @@ class Renderer {
|
|
|
904
905
|
this.renderProperty(tbl, 'Expansion Total', vs.expansion.total);
|
|
905
906
|
this.renderProperty(tbl, 'Expansion Offset', vs.expansion.offset);
|
|
906
907
|
for (let p of vs.expansion.parameter || []) {
|
|
907
|
-
|
|
908
|
+
if( getValueName(p) === 'valueUri' || getValueName(p) === 'valueCanonical') {
|
|
909
|
+
await this.renderPropertyLink(tbl, "Parameter: " + p.name, getValuePrimitive(p));
|
|
910
|
+
} else {
|
|
911
|
+
this.renderProperty(tbl, "Parameter: " + p.name, getValuePrimitive(p));
|
|
912
|
+
}
|
|
908
913
|
}
|
|
909
914
|
|
|
910
915
|
if (!vs.expansion.contains || vs.expansion.contains.length === 0) {
|
package/tx/library.js
CHANGED
package/tx/operation-context.js
CHANGED
package/tx/params.js
CHANGED
|
@@ -36,6 +36,7 @@ class TxParameters {
|
|
|
36
36
|
validating = false;
|
|
37
37
|
abstractOk = true; // note true!
|
|
38
38
|
inferSystem = false;
|
|
39
|
+
sort = 'design';
|
|
39
40
|
|
|
40
41
|
constructor(languages, i18n, validating) {
|
|
41
42
|
validateParameter(languages, 'languages', LanguageDefinitions);
|
|
@@ -242,6 +243,10 @@ class TxParameters {
|
|
|
242
243
|
if (getValuePrimitive(p) == true) this.inferSystem = true;
|
|
243
244
|
break;
|
|
244
245
|
}
|
|
246
|
+
case 'sort': {
|
|
247
|
+
this.sort = getValuePrimitive(p);
|
|
248
|
+
break;
|
|
249
|
+
}
|
|
245
250
|
case "exclude-system": {
|
|
246
251
|
throw new Issue('error', 'not-supported', null, null, "The parameter 'exclude-system' is not supported by this system", null, 400);
|
|
247
252
|
}
|
|
@@ -524,7 +529,7 @@ class TxParameters {
|
|
|
524
529
|
this.FUid + '|' + b(this.FMembershipOnly) + '|' + this.FProperties.join(',') + '|' +
|
|
525
530
|
b(this.FActiveOnly) + b(this.FDisplayWarning) + b(this.FExcludeNested) + b(this.FGenerateNarrative) + b(this.FExcludeNotForUI) + b(this.FExcludePostCoordinated) +
|
|
526
531
|
b(this.FIncludeDesignations) + b(this.FIncludeDefinition) + b(this.hasActiveOnly) + b(this.hasExcludeNested) + b(this.hasGenerateNarrative) +
|
|
527
|
-
b(this.hasExcludeNotForUI) + b(this.hasExcludePostCoordinated) + b(this.hasIncludeDesignations) +
|
|
532
|
+
b(this.hasExcludeNotForUI) + b(this.hasExcludePostCoordinated) + b(this.hasIncludeDesignations) + this.sort+'|'+
|
|
528
533
|
b(this.hasIncludeDefinition) + b(this.hasDefaultToLatestVersion) + b(this.hasDisplayWarning) + b(this.hasExcludeNotForUI) + b(this.hasMembershipOnly) + b(this.FDefaultToLatestVersion);
|
|
529
534
|
|
|
530
535
|
if (this.hasHTTPLanguages) {
|
|
@@ -585,6 +590,7 @@ class TxParameters {
|
|
|
585
590
|
this.hasDefaultToLatestVersion = other.hasDefaultToLatestVersion;
|
|
586
591
|
this.hasMembershipOnly = other.hasMembershipOnly;
|
|
587
592
|
this.hasDisplayWarning = other.hasDisplayWarning;
|
|
593
|
+
this.sort = other.sort;
|
|
588
594
|
|
|
589
595
|
if (other.FProperties) {
|
|
590
596
|
this.FProperties = [...other.FProperties];
|