fhirsmith 0.9.0 → 0.9.2

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
@@ -48,37 +48,37 @@ class SnomedModule extends BaseTerminologyModule {
48
48
  registerCommands(terminologyCommand, globalOptions) {
49
49
  // Import command
50
50
  terminologyCommand
51
- .command('import')
52
- .description('Import SNOMED CT data from RF2 source directory')
53
- .option('-s, --source <directory>', 'Source directory containing RF2 files')
54
- .option('-b, --base <directory>', 'Base edition directory (for extensions)')
55
- .option('-d, --dest <file>', 'Destination cache file')
56
- .option('-e, --edition <code>', 'Edition code (e.g., 900000000000207008 for International)')
57
- .option('-v, --version <version>', 'Version in YYYYMMDD format (e.g., 20250801)')
58
- .option('-u, --uri <uri>', 'Version URI (overrides edition/version if provided)')
59
- .option('-l, --language <code>', 'Default language code (overrides edition default if provided)')
60
- .option('-y, --yes', 'Skip confirmations')
61
- .action(async (options) => {
62
- await this.handleImportCommand({...globalOptions, ...options});
63
- });
51
+ .command('import')
52
+ .description('Import SNOMED CT data from RF2 source directory')
53
+ .option('-s, --source <directory>', 'Source directory containing RF2 files')
54
+ .option('-b, --base <directory>', 'Base edition directory (for extensions)')
55
+ .option('-d, --dest <file>', 'Destination cache file')
56
+ .option('-e, --edition <code>', 'Edition code (e.g., 900000000000207008 for International)')
57
+ .option('-v, --version <version>', 'Version in YYYYMMDD format (e.g., 20250801)')
58
+ .option('-u, --uri <uri>', 'Version URI (overrides edition/version if provided)')
59
+ .option('-l, --language <code>', 'Default language code (overrides edition default if provided)')
60
+ .option('-y, --yes', 'Skip confirmations')
61
+ .action(async (options) => {
62
+ await this.handleImportCommand({...globalOptions, ...options});
63
+ });
64
64
 
65
65
  // Validate command
66
66
  terminologyCommand
67
- .command('validate')
68
- .description('Validate SNOMED CT RF2 directory structure')
69
- .option('-s, --source <directory>', 'Source directory to validate')
70
- .action(async (options) => {
71
- await this.handleValidateCommand({...globalOptions, ...options});
72
- });
67
+ .command('validate')
68
+ .description('Validate SNOMED CT RF2 directory structure')
69
+ .option('-s, --source <directory>', 'Source directory to validate')
70
+ .action(async (options) => {
71
+ await this.handleValidateCommand({...globalOptions, ...options});
72
+ });
73
73
 
74
74
  // Status command
75
75
  terminologyCommand
76
- .command('status')
77
- .description('Show status of SNOMED CT cache')
78
- .option('-d, --dest <file>', 'Cache file to check')
79
- .action(async (options) => {
80
- await this.handleStatusCommand({...globalOptions, ...options});
81
- });
76
+ .command('status')
77
+ .description('Show status of SNOMED CT cache')
78
+ .option('-d, --dest <file>', 'Cache file to check')
79
+ .action(async (options) => {
80
+ await this.handleStatusCommand({...globalOptions, ...options});
81
+ });
82
82
  }
83
83
 
84
84
  async handleImportCommand(options) {
@@ -633,7 +633,7 @@ class SnomedModule extends BaseTerminologyModule {
633
633
  }
634
634
 
635
635
  const additionalAnswers = additionalQuestions.length > 0 ?
636
- await inquirer.prompt(additionalQuestions) : {};
636
+ await inquirer.prompt(additionalQuestions) : {};
637
637
 
638
638
  // Build the final configuration
639
639
  const config = {
@@ -774,7 +774,7 @@ class SnomedModule extends BaseTerminologyModule {
774
774
  } else if (firstLine.startsWith('id\teffectiveTime\tactive\tmoduleId\tconceptId\tlanguageCode\ttypeId\tterm\tcaseSignificanceId')) {
775
775
  files.descriptions.push(filePath);
776
776
  } else if (firstLine.startsWith('id\teffectiveTime\tactive\tmoduleId\tsourceId\tdestinationId\trelationshipGroup\ttypeId\tcharacteristicTypeId\tmodifierId') &&
777
- !filePath.includes('StatedRelationship')) {
777
+ !filePath.includes('StatedRelationship')) {
778
778
  files.relationships.push(filePath);
779
779
  }
780
780
  } catch (error) {
@@ -1165,6 +1165,19 @@ class SnomedImporter {
1165
1165
  refsetDirectories: []
1166
1166
  };
1167
1167
 
1168
+ // For extensions: load base edition files first so that all International
1169
+ // Edition concepts, descriptions, and relationships are present before the
1170
+ // extension content is layered on top.
1171
+ if (this.config.base) {
1172
+ if (this.config.verbose) {
1173
+ console.log(`Loading base edition from: ${this.config.base}`);
1174
+ }
1175
+ this._scanDirectory(this.config.base, files);
1176
+ }
1177
+
1178
+ // Then load the extension (or standalone edition) source files.
1179
+ // For extensions these come second so that extension rows can override
1180
+ // base rows where the same component has been updated.
1168
1181
  this._scanDirectory(this.config.source, files);
1169
1182
  return files;
1170
1183
  }
@@ -1200,7 +1213,7 @@ class SnomedImporter {
1200
1213
  } else if (firstLine.startsWith('id\teffectiveTime\tactive\tmoduleId\tconceptId\tlanguageCode\ttypeId\tterm\tcaseSignificanceId')) {
1201
1214
  files.descriptions.push(filePath);
1202
1215
  } else if (firstLine.startsWith('id\teffectiveTime\tactive\tmoduleId\tsourceId\tdestinationId\trelationshipGroup\ttypeId\tcharacteristicTypeId\tmodifierId') &&
1203
- !filePath.includes('StatedRelationship')) {
1216
+ !filePath.includes('StatedRelationship')) {
1204
1217
  files.relationships.push(filePath);
1205
1218
  }
1206
1219
  } catch (error) {
@@ -1250,6 +1263,9 @@ class SnomedImporter {
1250
1263
  this.conceptList = [];
1251
1264
  let processedLines = 0;
1252
1265
 
1266
+ // When loading base + extension, track list indices for fast replacement
1267
+ const conceptIdToListIndex = this.config.base ? new Map() : null;
1268
+
1253
1269
  for (let i = 0; i < this.files.concepts.length; i++) {
1254
1270
  const file = this.files.concepts[i];
1255
1271
  const rl = readline.createInterface({
@@ -1275,8 +1291,23 @@ class SnomedImporter {
1275
1291
  };
1276
1292
 
1277
1293
  if (this.conceptMap.has(concept.id)) {
1278
- throw new Error(`Duplicate Concept Id at line ${lineCount}: ${concept.id} - check you are processing the snapshot not the full edition`);
1294
+ // When loading base + extension, the same concept may appear in both.
1295
+ // The extension snapshot row takes precedence (it is loaded second).
1296
+ // If there is no base directory this is a genuine duplicate in a single
1297
+ // snapshot and we should still raise an error.
1298
+ if (!this.config.base) {
1299
+ throw new Error(`Duplicate Concept Id at line ${lineCount}: ${concept.id} - check you are processing the snapshot not the full edition`);
1300
+ }
1301
+ // Replace the base edition row with the extension row
1302
+ const idx = conceptIdToListIndex.get(concept.id);
1303
+ if (idx !== undefined) {
1304
+ this.conceptList[idx] = concept;
1305
+ }
1306
+ this.conceptMap.set(concept.id, concept);
1279
1307
  } else {
1308
+ if (conceptIdToListIndex) {
1309
+ conceptIdToListIndex.set(concept.id, this.conceptList.length);
1310
+ }
1280
1311
  this.conceptList.push(concept);
1281
1312
  this.conceptMap.set(concept.id, concept);
1282
1313
  }
@@ -1347,6 +1378,12 @@ class SnomedImporter {
1347
1378
  const descriptionList = [];
1348
1379
  let processedLines = 0;
1349
1380
 
1381
+ // Build a lookup from description id -> index in descriptionList so that
1382
+ // extension rows can replace base rows for the same description.
1383
+ if (this.config.base) {
1384
+ this._descriptionIdSet = new Map();
1385
+ }
1386
+
1350
1387
  for (const file of this.files.descriptions) {
1351
1388
  const rl = readline.createInterface({
1352
1389
  input: fs.createReadStream(file),
@@ -1372,7 +1409,19 @@ class SnomedImporter {
1372
1409
  caseSignificanceId: BigInt(parts[8])
1373
1410
  };
1374
1411
 
1375
- descriptionList.push(desc);
1412
+ // When loading base + extension, the same description may appear in
1413
+ // both. The extension row (loaded second) takes precedence.
1414
+ if (this.config.base && this._descriptionIdSet) {
1415
+ const existingIdx = this._descriptionIdSet.get(desc.id);
1416
+ if (existingIdx !== undefined) {
1417
+ descriptionList[existingIdx] = desc;
1418
+ } else {
1419
+ this._descriptionIdSet.set(desc.id, descriptionList.length);
1420
+ descriptionList.push(desc);
1421
+ }
1422
+ } else {
1423
+ descriptionList.push(desc);
1424
+ }
1376
1425
  }
1377
1426
 
1378
1427
  processedLines++;
@@ -1417,8 +1466,8 @@ class SnomedImporter {
1417
1466
  const caps = this.conceptMap.get(desc.caseSignificanceId);
1418
1467
 
1419
1468
  const descOffset = this.descriptions.addDescription(
1420
- termOffset, desc.id, effectiveTime, concept.index,
1421
- module.index, kind.index, caps.index, desc.active, lang
1469
+ termOffset, desc.id, effectiveTime, concept.index,
1470
+ module.index, kind.index, caps.index, desc.active, lang
1422
1471
  );
1423
1472
 
1424
1473
  // Track description on concept
@@ -1692,6 +1741,11 @@ class SnomedImporter {
1692
1741
  }
1693
1742
  this.isAIndex = isAConcept.index;
1694
1743
 
1744
+ // Pass 1: collect all relationship rows, deduplicating so that extension
1745
+ // rows (loaded second) override base rows with the same relationship id.
1746
+ const relationshipRows = [];
1747
+ const relationshipIdMap = this.config.base ? new Map() : null; // id -> index in relationshipRows
1748
+
1695
1749
  for (const file of this.files.relationships) {
1696
1750
  const rl = readline.createInterface({
1697
1751
  input: fs.createReadStream(file),
@@ -1718,40 +1772,16 @@ class SnomedImporter {
1718
1772
  modifierId: BigInt(parts[9])
1719
1773
  };
1720
1774
 
1721
- const source = this.conceptMap.get(rel.sourceId);
1722
- const destination = this.conceptMap.get(rel.destinationId);
1723
- const type = this.conceptMap.get(rel.typeId);
1724
-
1725
- if (source && destination && type) {
1726
- const effectiveTime = this.convertDateToSnomedDate(rel.effectiveTime);
1727
-
1728
- // Check if this is a defining relationship
1729
- const defining = rel.characteristicTypeId === RF2_MAGIC_RELN_DEFINING ||
1730
- rel.characteristicTypeId === RF2_MAGIC_RELN_STATED ||
1731
- rel.characteristicTypeId === RF2_MAGIC_RELN_INFERRED;
1732
-
1733
- const relationshipIndex = this.relationships.addRelationship(
1734
- rel.id, source.index, destination.index, type.index,
1735
- 0, 0, 0, effectiveTime, rel.active, defining, rel.relationshipGroup
1736
- );
1737
-
1738
- // Track parent/child relationships for is-a relationships
1739
- if (type.index === this.isAIndex && defining) {
1740
- const sourceTracker = this.getOrCreateConceptTracker(source.index);
1741
- if (rel.active) {
1742
- sourceTracker.addActiveParent(destination.index);
1743
- } else {
1744
- sourceTracker.addInactiveParent(destination.index);
1745
- }
1775
+ if (relationshipIdMap) {
1776
+ const existingIdx = relationshipIdMap.get(rel.id);
1777
+ if (existingIdx !== undefined) {
1778
+ relationshipRows[existingIdx] = rel;
1779
+ } else {
1780
+ relationshipIdMap.set(rel.id, relationshipRows.length);
1781
+ relationshipRows.push(rel);
1746
1782
  }
1747
-
1748
- // Track inbound/outbound relationships
1749
- const sourceTracker = this.getOrCreateConceptTracker(source.index);
1750
- const destTracker = this.getOrCreateConceptTracker(destination.index);
1751
-
1752
- sourceTracker.addOutbound(relationshipIndex);
1753
- destTracker.addInbound(relationshipIndex);
1754
-
1783
+ } else {
1784
+ relationshipRows.push(rel);
1755
1785
  }
1756
1786
  }
1757
1787
 
@@ -1762,10 +1792,62 @@ class SnomedImporter {
1762
1792
  }
1763
1793
  }
1764
1794
 
1795
+ if (this.progressReporter) {
1796
+ this.progressReporter.completeTask('Reading Relationships', processedLines, totalLines);
1797
+ }
1798
+
1799
+ // Pass 2: process the deduplicated relationship rows into the binary
1800
+ // structures and concept trackers.
1801
+ const buildProgressBar = this.progressReporter?.createTaskProgressBar('Building Relationships');
1802
+ buildProgressBar?.start(relationshipRows.length, 0);
1803
+
1804
+ for (let i = 0; i < relationshipRows.length; i++) {
1805
+ const rel = relationshipRows[i];
1806
+
1807
+ const source = this.conceptMap.get(rel.sourceId);
1808
+ const destination = this.conceptMap.get(rel.destinationId);
1809
+ const type = this.conceptMap.get(rel.typeId);
1810
+
1811
+ if (source && destination && type) {
1812
+ const effectiveTime = this.convertDateToSnomedDate(rel.effectiveTime);
1813
+
1814
+ // Check if this is a defining relationship
1815
+ const defining = rel.characteristicTypeId === RF2_MAGIC_RELN_DEFINING ||
1816
+ rel.characteristicTypeId === RF2_MAGIC_RELN_STATED ||
1817
+ rel.characteristicTypeId === RF2_MAGIC_RELN_INFERRED;
1818
+
1819
+ const relationshipIndex = this.relationships.addRelationship(
1820
+ rel.id, source.index, destination.index, type.index,
1821
+ 0, 0, 0, effectiveTime, rel.active, defining, rel.relationshipGroup
1822
+ );
1823
+
1824
+ // Track parent/child relationships for is-a relationships
1825
+ if (type.index === this.isAIndex && defining) {
1826
+ const sourceTracker = this.getOrCreateConceptTracker(source.index);
1827
+ if (rel.active) {
1828
+ sourceTracker.addActiveParent(destination.index);
1829
+ } else {
1830
+ sourceTracker.addInactiveParent(destination.index);
1831
+ }
1832
+ }
1833
+
1834
+ // Track inbound/outbound relationships
1835
+ const sourceTracker = this.getOrCreateConceptTracker(source.index);
1836
+ const destTracker = this.getOrCreateConceptTracker(destination.index);
1837
+
1838
+ sourceTracker.addOutbound(relationshipIndex);
1839
+ destTracker.addInbound(relationshipIndex);
1840
+ }
1841
+
1842
+ if (i % 1000 === 0) {
1843
+ buildProgressBar?.update(i);
1844
+ }
1845
+ }
1846
+
1765
1847
  this.relationships.doneBuild();
1766
1848
 
1767
1849
  if (this.progressReporter) {
1768
- this.progressReporter.completeTask('Reading Relationships', processedLines, totalLines);
1850
+ this.progressReporter.completeTask('Building Relationships', relationshipRows.length, relationshipRows.length);
1769
1851
  }
1770
1852
  }
1771
1853
 
@@ -1800,9 +1882,9 @@ class SnomedImporter {
1800
1882
  // Set parents if concept has any
1801
1883
  if (tracker.activeParents.length > 0 || tracker.inactiveParents.length > 0) {
1802
1884
  const activeParentsRef = tracker.activeParents.length > 0 ?
1803
- this.refs.addReferences(tracker.activeParents) : 0;
1885
+ this.refs.addReferences(tracker.activeParents) : 0;
1804
1886
  const inactiveParentsRef = tracker.inactiveParents.length > 0 ?
1805
- this.refs.addReferences(tracker.inactiveParents) : 0;
1887
+ this.refs.addReferences(tracker.inactiveParents) : 0;
1806
1888
 
1807
1889
  this.concepts.setParents(concept.index, activeParentsRef, inactiveParentsRef);
1808
1890
  } else {
@@ -2104,14 +2186,14 @@ class SnomedImporter {
2104
2186
  // NOTE: This calls addString() so it must happen AFTER strings.reopen()
2105
2187
  for (const refSet of refSetsArray) {
2106
2188
  this.refsetIndex.addReferenceSet(
2107
- this.addString(refSet.title), // This needs strings builder to be active
2108
- refSet.filename,
2109
- refSet.index,
2110
- refSet.membersByRef,
2111
- refSet.membersByName,
2112
- refSet.fieldTypes,
2113
- refSet.fieldNames,
2114
- refSet.langs
2189
+ this.addString(refSet.title), // This needs strings builder to be active
2190
+ refSet.filename,
2191
+ refSet.index,
2192
+ refSet.membersByRef,
2193
+ refSet.membersByName,
2194
+ refSet.fieldTypes,
2195
+ refSet.fieldNames,
2196
+ refSet.langs
2115
2197
  );
2116
2198
  }
2117
2199
  }
@@ -2216,7 +2298,13 @@ class SnomedImporter {
2216
2298
  if (!refSet || currentRefSetId !== refSetId) {
2217
2299
  currentRefSetId = refSetId;
2218
2300
  refSet = this.getOrCreateRefSet(refSetId, displayName, isLangRefset);
2219
- refSet.filename = this.addString(path.relative(this.config.source, filePath));
2301
+ // Compute relative path — the file may live under the base directory
2302
+ // rather than the extension source directory.
2303
+ let relPath = path.relative(this.config.source, filePath);
2304
+ if (this.config.base && relPath.startsWith('..')) {
2305
+ relPath = path.relative(this.config.base, filePath);
2306
+ }
2307
+ refSet.filename = this.addString(relPath);
2220
2308
  refSet.fieldTypes = this.getOrCreateFieldTypes(fieldTypes);
2221
2309
  refSet.fieldNames = this.getOrCreateFieldNames(headers.slice(6), fieldTypes); // Additional fields beyond standard 6
2222
2310
  }
@@ -2577,8 +2665,8 @@ class SnomedImporter {
2577
2665
  };
2578
2666
 
2579
2667
  const services = new SnomedExpressionServices(
2580
- snomedStructures,
2581
- this.isAIndex
2668
+ snomedStructures,
2669
+ this.isAIndex
2582
2670
  );
2583
2671
 
2584
2672
  // Set building flag to true so services will generate normal forms dynamically
@@ -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/library.js CHANGED
@@ -35,6 +35,7 @@ const { OCLCodeSystemProvider, OCLSourceCodeSystemFactory } = require('./ocl/cs-
35
35
  const { OCLValueSetProvider } = require('./ocl/vs-ocl');
36
36
  const { OCLConceptMapProvider } = require('./ocl/cm-ocl');
37
37
  const {UriServicesFactory} = require("./cs/cs-uri");
38
+ const {debugLog} = require("./operation-context");
38
39
 
39
40
  /**
40
41
  * This class holds all the loaded content ready for processing
@@ -185,6 +186,7 @@ class Library {
185
186
  try {
186
187
  await this.processSource(source, this.packageManager, "cs");
187
188
  } catch (error) {
189
+ debugLog(error);
188
190
  console.error(`Failed to load code systems from '${source}': ${error.message}`);
189
191
  throw error;
190
192
  }
@@ -196,6 +198,7 @@ class Library {
196
198
  try {
197
199
  await this.processSource(source, this.packageManager, "npm");
198
200
  } catch (error) {
201
+ debugLog(error);
199
202
  console.error(`Failed to load package '${source}': ${error.message}`);
200
203
  throw error;
201
204
  }