fhirsmith 0.9.0 → 0.9.1

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.
@@ -1298,7 +1298,7 @@ VIEWDEFINITION_SHOULD_HAVE_NAME = No name provided. A name is required in many c
1298
1298
  VIEWDEFINITION_TYPE_MISMATCH = The path expression ''{0}'' does not return a value of the type ''{1}'' - found ''{2}''{3}
1299
1299
  VIEWDEFINITION_UNABLE_TO_TYPE = Unable to determine a type (found ''{0}''){1}
1300
1300
  VIEWDEFINITION_UNKNOWN_RESOURCE = The name ''{0}'' is not a valid resource{1}
1301
- VS_EXP_FILTER_UNK = ValueSet ''{0}'' Filter by property ''{1}'' and op ''{2}'' is not supported yet
1301
+ VS_EXP_FILTER_UNK = ValueSet ''{0}'' Filter by property ''{1}'' and op ''{2}'' is not supported (yet?)
1302
1302
  VS_EXP_IMPORT_CS = Cannot include value set ''{0}'' because it''s actually a code system
1303
1303
  VS_EXP_IMPORT_CS_PINNED = Cannot include value set ''{0}'' version ''{1}'' because it''s actually a code system
1304
1304
  VS_EXP_IMPORT_CS_PINNED_X = Cannot exclude value set ''{0}'' version ''{1}'' because it''s actually a code system
@@ -1400,7 +1400,7 @@ TEXT_LINK_NO_DATA = No data element was found in the textLink extension
1400
1400
  TEXT_LINK_SELECTOR_INVALID = The textLink selector ''{0}'' is invalid: {1}
1401
1401
  SD_CONTEXT_SHOULD_ELEMENT_NOT_FOUND = The element {0} is not valid
1402
1402
  SD_CONTEXT_SHOULD_ELEMENT_NOT_FOUND_VER = The element {0} is not valid in version {1}
1403
- FILTER_NOT_UNDERSTOOD = The filter "{0} {1} {2}" from the value set {3} was not understood in the context of {4}
1403
+ FILTER_NOT_UNDERSTOOD = The filter "{0} {1} {2}" is not understood or supported
1404
1404
  XHTML_CONTROL_NO_SOURCE = The xhtml node at ''{0}'' does not have a class attribute to indicate its source (boilerplate, generated, or original) and this is required by the profile {1}
1405
1405
  XHTML_XHTML_MIXED_LANG = The xhtml has some language sections ({0}), and also has content that is not in a language section
1406
1406
  XHTML_CONTROL_NO_LANGS = The xhtml has some language sections ({0}), but language sections are prohibited in this context by the profile {1}
package/tx/cs/cs-cs.js CHANGED
@@ -1224,7 +1224,7 @@ class FhirCodeSystemProvider extends BaseCSServices {
1224
1224
 
1225
1225
  // Handle concept/code hierarchy filters
1226
1226
  if ((prop === 'concept' || prop === 'code')) {
1227
- results = await this._handleConceptFilter(filterContext, op, value);
1227
+ results = await this._handleConceptFilter(filterContext, prop, op, value);
1228
1228
  }
1229
1229
 
1230
1230
  // Handle child existence filter
@@ -1248,7 +1248,8 @@ class FhirCodeSystemProvider extends BaseCSServices {
1248
1248
  }
1249
1249
 
1250
1250
  if (!results) {
1251
- throw new Error(`The filter ${prop} ${op} ${value} was not understood`)
1251
+ throw new Issue('error', 'exception', null, 'FILTER_NOT_UNDERSTOOD',
1252
+ this.opContext.i18n.translate('FILTER_NOT_UNDERSTOOD', this.opContext.langs, [prop, op, value]), 'vs-invalid', 422);
1252
1253
  }
1253
1254
  // Add to filter context
1254
1255
  if (!filterContext.filters) {
@@ -1267,15 +1268,17 @@ class FhirCodeSystemProvider extends BaseCSServices {
1267
1268
  * @returns {Promise<FhirCodeSystemProviderFilterContext>} Filter results
1268
1269
  * @private
1269
1270
  */
1270
- async _handleConceptFilter(filterContext, op, value) {
1271
+ async _handleConceptFilter(filterContext, prop, op, value) {
1271
1272
  const results = new FhirCodeSystemProviderFilterContext();
1272
1273
 
1273
1274
  if (op === 'is-a' || op === 'descendent-of') {
1274
1275
  // Find all descendants of the specified code
1275
1276
  const includeRoot = (op === 'is-a');
1276
1277
  await this._addDescendants(results, value, includeRoot);
1277
- }
1278
- else if (op === 'is-not-a') {
1278
+ } else if (op === 'child-of') {
1279
+ // Find all descendants of the specified code
1280
+ await this._addChildren(results, value);
1281
+ } else if (op === 'is-not-a') {
1279
1282
  // Find all concepts that are NOT descendants of the specified code
1280
1283
  const excludeDescendants = this.codeSystem.getDescendants(value);
1281
1284
  const excludeSet = new Set([value, ...excludeDescendants]);
@@ -1323,6 +1326,9 @@ class FhirCodeSystemProvider extends BaseCSServices {
1323
1326
  } catch (error) {
1324
1327
  throw new Issue('error', 'exception', null, 'INVALID_REGEX', this.opContext.i18n.translate('INVALID_REGEX', this.opContext.langs, [value, error.message]), 'vs-invalid', 422);
1325
1328
  }
1329
+ } else {
1330
+ throw new Issue('error', 'exception', null, 'FILTER_NOT_UNDERSTOOD',
1331
+ this.opContext.i18n.translate('FILTER_NOT_UNDERSTOOD', this.opContext.langs, [prop, op, value]), 'vs-invalid', 422);
1326
1332
  }
1327
1333
 
1328
1334
  return results;
@@ -1353,6 +1359,27 @@ class FhirCodeSystemProvider extends BaseCSServices {
1353
1359
  }
1354
1360
  }
1355
1361
 
1362
+ /**
1363
+ * Add immediate children of a code to the results
1364
+ * @param {FhirCodeSystemProviderFilterContext} results - Results to add to
1365
+ * @param {string} ancestorCode - The parent code
1366
+ * @private
1367
+ */
1368
+ async _addChildren(results, parentCode) {
1369
+ const concept = this.codeSystem.getConceptByCode(parentCode);
1370
+ if (concept) {
1371
+ const descendants = this.codeSystem.getChildren(parentCode);
1372
+ for (const code of descendants) {
1373
+ if (code !== parentCode) { // should not be
1374
+ const concept = this.codeSystem.getConceptByCode(code);
1375
+ if (concept) {
1376
+ results.add(concept, 0);
1377
+ }
1378
+ }
1379
+ }
1380
+ }
1381
+ }
1382
+
1356
1383
  /**
1357
1384
  * Handle child exists filter
1358
1385
  * @param {FilterExecutionContext} filterContext - Filter context
@@ -56,7 +56,13 @@ const Extensions = {
56
56
  if (!resource) {
57
57
  return undefined;
58
58
  }
59
- const extensions = Array.isArray(resource) ? resource : (resource.extension || []);
59
+ let extensions = Array.isArray(resource) ? resource : (resource.extension || []);
60
+ for (let ext of extensions || []) {
61
+ if (ext.url === url) {
62
+ return getValuePrimitive(ext);
63
+ }
64
+ }
65
+ extensions = Array.isArray(resource) ? resource : (resource.modifierExtension || []);
60
66
  for (let ext of extensions || []) {
61
67
  if (ext.url === url) {
62
68
  return getValuePrimitive(ext);
@@ -466,14 +466,15 @@ class Renderer {
466
466
  li.tx(" "+ this.translate('VALUE_SET_WHERE')+" ");
467
467
  li.startCommaList("and");
468
468
  for (let f of inc.filter) {
469
- if (f.op == 'exists') {
469
+ let op = this.readFilterOp(f);
470
+ if (op == 'exists') {
470
471
  if (f.value == "true") {
471
472
  li.commaItem(f.property+" "+ this.translate('VALUE_SET_EXISTS'));
472
473
  } else {
473
474
  li.commaItem(f.property+" "+ this.translate('VALUE_SET_DOESNT_EXIST'));
474
475
  }
475
476
  } else {
476
- li.commaItem(f.property + " " + f.op + " ");
477
+ li.commaItem(f.property + " " + op + " ");
477
478
  const loc = this.linkResolver ? await this.linkResolver.resolveCode(this.opContext, inc.system, inc.version, f.value) : null;
478
479
  if (loc) {
479
480
  li.ah(loc.link).tx(loc.description);
@@ -2243,6 +2244,14 @@ class Renderer {
2243
2244
  return defn+' = ' + value;
2244
2245
  }
2245
2246
  }
2247
+
2248
+ readFilterOp(f) {
2249
+ if (f._op) {
2250
+ return Extensions.readString(f._op, 'http://hl7.org/fhir/5.0/StructureDefinition/extension-ValueSet.compose.include.filter.op');
2251
+ } else {
2252
+ return f.op;
2253
+ }
2254
+ }
2246
2255
  }
2247
2256
 
2248
2257
  module.exports = { Renderer };
package/tx/vs/vs-vsac.js CHANGED
@@ -17,6 +17,7 @@ class VSACValueSetProvider extends AbstractValueSetProvider {
17
17
  * @param {string} config.cacheFolder - Local folder for cached database
18
18
  * @param {number} [config.refreshIntervalHours=24] - Hours between refresh scans
19
19
  * @param {string} [config.baseUrl='http://cts.nlm.nih.gov/fhir'] - Base URL for VSAC FHIR server
20
+ * @param {number} [config.timeoutMs=120000] - HTTP request timeout in milliseconds
20
21
  */
21
22
  constructor(config, stats) {
22
23
  super();
@@ -43,7 +44,7 @@ class VSACValueSetProvider extends AbstractValueSetProvider {
43
44
  const authString = Buffer.from(`apikey:${this.apiKey}`).toString('base64');
44
45
  this.httpClient = axios.create({
45
46
  baseURL: this.baseUrl,
46
- timeout: 30000,
47
+ timeout: config.timeoutMs || 120000,
47
48
  headers: {
48
49
  'Accept': 'application/fhir+json',
49
50
  'User-Agent': 'FHIR-ValueSet-Provider/1.0',
@@ -839,7 +839,7 @@ class ValueSetChecker {
839
839
  } else {
840
840
  bAdd = !unknownSystems.has(system + '|' + version);
841
841
  if (bAdd) {
842
- let vl = await this.listVersions(system);
842
+ let vl = await this.worker.listVersions(system);
843
843
  if (vl.length == 0) {
844
844
  mid = 'UNKNOWN_CODESYSTEM_VERSION_NONE';
845
845
  vn = system;
@@ -1,5 +1,6 @@
1
1
  const {VersionUtilities} = require("../../library/version-utilities");
2
2
  const {getValueName} = require("../../library/utilities");
3
+ const {Extensions} = require("../library/extensions");
3
4
 
4
5
  /**
5
6
  * Converts input ValueSet to R5 format (modifies input object for performance)
@@ -13,6 +14,12 @@ function valueSetToR5(jsonObj, sourceVersion) {
13
14
  if (VersionUtilities.isR5Ver(sourceVersion)) {
14
15
  return jsonObj; // No conversion needed
15
16
  }
17
+ for (const inc of jsonObj.compose.include || []) {
18
+ valueSetIncludeToR5(inc);
19
+ }
20
+ for (const inc of jsonObj.compose.exclude || []) {
21
+ valueSetIncludeToR5(inc);
22
+ }
16
23
  if (VersionUtilities.isR4Ver(sourceVersion)) {
17
24
  return jsonObj; // No conversion needed
18
25
  }
@@ -26,6 +33,19 @@ function valueSetToR5(jsonObj, sourceVersion) {
26
33
  throw new Error(`Unsupported FHIR version: ${sourceVersion}`);
27
34
  }
28
35
 
36
+ function valueSetIncludeToR5(inc) {
37
+ for (const filter of inc.filter || []) {
38
+ if (filter._op) {
39
+ let code = Extensions.readString(filter._op, 'http://hl7.org/fhir/5.0/StructureDefinition/extension-ValueSet.compose.include.filter.op');
40
+ if (code) {
41
+ filter.op = code;
42
+ delete filter._op;
43
+ }
44
+ }
45
+ }
46
+ }
47
+
48
+
29
49
  /**
30
50
  * Converts R5 ValueSet to target version format (clones object first)
31
51
  * @param {Object} r5Obj - The R5 format ValueSet object
@@ -70,8 +90,8 @@ function valueSetR5ToR4(r5Obj) {
70
90
  if (include.filter && Array.isArray(include.filter)) {
71
91
  include.filter = include.filter.map(filter => {
72
92
  if (filter.op && isR5OnlyFilterOperator(filter.op)) {
73
- // Remove R5-only operators
74
- return null;
93
+ filter._op = { "extension": "http://hl7.org/fhir/5.0/StructureDefinition/extension-ValueSet.compose.include.filter.op", "valueCode": filter.op}
94
+ delete filter.op;
75
95
  }
76
96
  return filter;
77
97
  }).filter(filter => filter !== null);
@@ -85,8 +105,8 @@ function valueSetR5ToR4(r5Obj) {
85
105
  if (exclude.filter && Array.isArray(exclude.filter)) {
86
106
  exclude.filter = exclude.filter.map(filter => {
87
107
  if (filter.op && isR5OnlyFilterOperator(filter.op)) {
88
- // Remove R5-only operators
89
- return null;
108
+ filter._op = { "extension": "http://hl7.org/fhir/5.0/StructureDefinition/extension-ValueSet.compose.include.filter.op", "valueCode": filter.op}
109
+ delete filter.op;
90
110
  }
91
111
  return filter;
92
112
  }).filter(filter => filter !== null);
@@ -135,8 +155,8 @@ function valueSetR5ToR3(r5Obj) {
135
155
  if (include.filter && Array.isArray(include.filter)) {
136
156
  include.filter = include.filter.map(filter => {
137
157
  if (filter.op && !isR3CompatibleFilterOperator(filter.op)) {
138
- // Remove non-R3-compatible operators
139
- return null;
158
+ filter._op = { "extension": "http://hl7.org/fhir/5.0/StructureDefinition/extension-ValueSet.compose.include.filter.op", "valueCode": filter.op}
159
+ delete filter.op;
140
160
  }
141
161
  return filter;
142
162
  }).filter(filter => filter !== null);
@@ -150,8 +170,8 @@ function valueSetR5ToR3(r5Obj) {
150
170
  if (exclude.filter && Array.isArray(exclude.filter)) {
151
171
  exclude.filter = exclude.filter.map(filter => {
152
172
  if (filter.op && !isR3CompatibleFilterOperator(filter.op)) {
153
- // Remove non-R3-compatible operators
154
- return null;
173
+ filter._op = { "extension": "http://hl7.org/fhir/5.0/StructureDefinition/extension-ValueSet.compose.include.filter.op", "valueCode": filter.op}
174
+ delete filter.op;
155
175
  }
156
176
  return filter;
157
177
  }).filter(filter => filter !== null);