fhirsmith 0.5.1 → 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 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
@@ -17,6 +29,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
17
29
 
18
30
  FHIRsmith 0.5.1 passed all 1288 HL7 terminology service tests (modes tx.fhir.org,omop,general,snomed, tests v1.9.1-SNAPSHOT, runner v6.8.0)
19
31
 
32
+ ## [v0.5.2] - 2026-02-20
33
+
34
+ ### Changed
35
+ - Fixed bad count reference in XIG
36
+
37
+ ### Tx Conformance Statement
38
+
39
+ FHIRsmith 0.5.2 passed all 1288 HL7 terminology service tests (modes tx.fhir.org,omop,general,snomed, tests v1.9.1-SNAPSHOT, runner v6.8.0)
40
+
20
41
  ## [v0.5.0] - 2026-02-19
21
42
 
22
43
  ### Added
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fhirsmith",
3
- "version": "0.5.1",
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
- const elapsedMs = (now - this.lastTime);
37
- const usage = process.cpuUsage(this.lastUsage);
38
- const cpuMs = (usage.user + usage.system) / 1000; // microseconds → milliseconds
39
- const percent = elapsedMs > 0 ? 100 * cpuMs / elapsedMs : 0;
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 = process.cpuUsage();
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 | Type | Required | Description |
61
- |--------|------|----------|-------------|
62
- | `enabled` | boolean | Yes | Whether the module is enabled |
63
- | `cacheTimeout` | integer | No | How many minutes to keep client side caches (for cache-id parameter). Default: 30 |
64
- | `expansionCacheSize` | integer | No | Maximum number of expanded ValueSets to cache. Default: 1000 |
65
- | `expansionCacheMemoryThreshold` | integer | No | Heap memory usage in MB that triggers evicting oldest half of expansion cache. 0 = disabled. Default: 0 |
66
- | `librarySource` | string | Yes | Path to the YAML file that defines the terminology sources to load |
67
- | `endpoints` | array | Yes | List of endpoint configurations (at least one required) |
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 application doesn't make use of this context;
503
- * it's only use is to be passed back to the CodeSystem provider so it can make use of it - if it wants
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 (or null, it no use for this)
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("Must override"); } // ? must override?
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
- * Get a FilterConceptSet for a value set filter
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
  *
@@ -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');
@@ -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
- // Search filter - not implemented
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();
@@ -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) {
@@ -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
  }
@@ -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
- if (!this.referenceSetExists(refsetId)) {
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(refsetId, ''),
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
- if (!this.conceptExists(conceptId)) {
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(conceptId, ''),
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');
@@ -89,6 +89,7 @@
89
89
  <a href="[%endpoint-path%]/metadata" style="color: gold">Capability Statement</a> &nbsp;|&nbsp;
90
90
  <a href="[%endpoint-path%]/metadata?mode=terminology" style="color: gold">Terminology Capabilities</a> &nbsp;|&nbsp;
91
91
  <a href="[%endpoint-path%]/op.html" style="color: gold">Operations</a> &nbsp;|&nbsp;
92
+ <a href="[%endpoint-path%]/problems.html" style="color: gold">Problems</a> &nbsp;|&nbsp;
92
93
  &nbsp;
93
94
  </div>
94
95
  </div>
@@ -608,7 +608,11 @@ class Designations {
608
608
  }
609
609
  }
610
610
  }
611
-
611
+ for (const cd of this.designations) {
612
+ if (!cd.language && this.isDisplay(cd)) {
613
+ return cd;
614
+ }
615
+ }
612
616
  return null;
613
617
  }
614
618
 
@@ -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
- await this.renderPropertyLink(tbl, "Parameter: " + p.name, getValuePrimitive(p));
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
@@ -758,6 +758,7 @@ class Library {
758
758
  cmp.close();
759
759
  }
760
760
  }
761
+
761
762
  }
762
763
 
763
764
  module.exports = { Library };
@@ -447,6 +447,7 @@ class OperationContext {
447
447
  newContext.timeTracker = this.timeTracker.link();
448
448
  newContext.logEntries = [...this.logEntries];
449
449
  newContext.debugging = this.debugging;
450
+ newContext.usageTracker = this.usageTracker;
450
451
  return newContext;
451
452
  }
452
453
 
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];