fhirsmith 0.5.5 → 0.6.0

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.
@@ -1,25 +1,18 @@
1
- <div class="operations" style="margin-top: 20px;">
2
- <button type="button" class="btn btn-sm btn-outline-secondary" onclick="toggleOperations('{{ opsId }}')">Show Operations</button>
3
- <div id="{{ opsId }}" style="display: none; margin-top: 10px;">
1
+ <div class="operation-form" style="margin-bottom: 15px;">
2
+ <strong>Lookup</strong>
3
+ <form method="get" action="$lookup" style="display: inline-block; margin-left: 10px;">
4
+ <input type="hidden" name="system" value="{{ url }}"/>
5
+ <input type="text" name="code" placeholder="Code" required size="20"/>
6
+ <button type="submit" class="btn btn-sm btn-primary">Lookup</button>
7
+ </form>
8
+ </div>
4
9
 
5
- <div class="operation-form" style="margin-bottom: 15px;">
6
- <strong>Lookup</strong>
7
- <form method="get" action="$lookup" style="display: inline-block; margin-left: 10px;">
8
- <input type="hidden" name="system" value="{{ url }}"/>
9
- <input type="text" name="code" placeholder="Code" required size="20"/>
10
- <button type="submit" class="btn btn-sm btn-primary">Lookup</button>
11
- </form>
12
- </div>
13
-
14
- <div class="operation-form" style="margin-bottom: 15px;">
15
- <strong>Subsumes</strong>
16
- <form method="get" action="$subsumes" style="display: inline-block; margin-left: 10px;">
17
- <input type="hidden" name="system" value="{{ url }}"/>
18
- <input type="text" name="codeA" placeholder="Code A" required size="15"/>
19
- <input type="text" name="codeB" placeholder="Code B" required size="15"/>
20
- <button type="submit" class="btn btn-sm btn-primary">Subsumes</button>
21
- </form>
22
- </div>
23
-
24
- </div>
25
- </div>
10
+ <div class="operation-form" style="margin-bottom: 15px;">
11
+ <strong>Subsumes</strong>
12
+ <form method="get" action="$subsumes" style="display: inline-block; margin-left: 10px;">
13
+ <input type="hidden" name="system" value="{{ url }}"/>
14
+ <input type="text" name="codeA" placeholder="Code A" required size="15"/>
15
+ <input type="text" name="codeB" placeholder="Code B" required size="15"/>
16
+ <button type="submit" class="btn btn-sm btn-primary">Subsumes</button>
17
+ </form>
18
+ </div>
@@ -1,54 +1,48 @@
1
- <div class="operations" style="margin-top: 20px;">
2
- <button type="button" class="btn btn-sm btn-outline-secondary" onclick="toggleOperations('{{ opsId }}')">Show Operations</button>
3
- <div id="{{ opsId }}" style="display: none; margin-top: 10px;">
4
1
 
5
- <div class="operation-form" style="margin-bottom: 15px;">
6
- <strong>Expand</strong>
7
- <form method="get" action="$expand" style="margin-left: 10px; margin-top: 5px;">
8
- <input type="hidden" name="url" value="{{ url }}"/>
9
- <table class="grid" cellpadding="0" cellspacing="0">
10
- <tr>
11
- <td>Filter: <input type="text" name="filter" size="20"/></td>
12
- <td>Language: <input type="text" name="displayLanguage" size="10"/></td>
13
- </tr>
14
- <tr>
15
- <td colspan="2">
16
- <input type="checkbox" name="includeDesignations" id="expand_desig" value="true"/>
17
- <label for="expand_desig">Include Designations</label>
18
- <input type="checkbox" name="activeOnly" id="expand_active" value="true"/>
19
- <label for="expand_active">Active Only</label>
20
- </td>
21
- </tr>
22
- </table>
23
- <button type="submit" class="btn btn-sm btn-primary">Expand</button>
24
- </form>
25
- </div>
2
+ <div class="operation-form" style="margin-bottom: 15px;">
3
+ <strong>Expand</strong>
4
+ <form method="get" action="$expand" style="margin-left: 10px; margin-top: 5px;">
5
+ <input type="hidden" name="url" value="{{ url }}"/>
6
+ <table class="grid" cellpadding="0" cellspacing="0">
7
+ <tr>
8
+ <td>Filter: <input type="text" name="filter" size="20"/></td>
9
+ <td>Language: <input type="text" name="displayLanguage" size="10"/></td>
10
+ </tr>
11
+ <tr>
12
+ <td colspan="2">
13
+ <input type="checkbox" name="includeDesignations" id="expand_desig" value="true"/>
14
+ <label for="expand_desig">Include Designations</label>
15
+ <input type="checkbox" name="activeOnly" id="expand_active" value="true"/>
16
+ <label for="expand_active">Active Only</label>
17
+ </td>
18
+ </tr>
19
+ </table>
20
+ <button type="submit" class="btn btn-sm btn-primary">Expand</button>
21
+ </form>
22
+ </div>
26
23
 
27
- <div class="operation-form" style="margin-bottom: 15px;">
28
- <strong>Validate Code (ValueSet)</strong>
29
- <form method="get" action="$validate-code" style="margin-left: 10px; margin-top: 5px;">
30
- <input type="hidden" name="url" value="{{ url }}"/>
31
- <input type="hidden" name="inferSystem" id="{{ inferSystemId }}" value="true"/>
32
- <table class="grid" cellpadding="0" cellspacing="0">
33
- <tr>
34
- <td>System: <input type="text" name="system" id="{{ vcSystemId }}" size="30" onchange="updateInferSystem('{{ vcSystemId }}', '{{ inferSystemId }}')"/></td>
35
- <td>Version: <input type="text" name="version" size="10"/></td>
36
- </tr>
37
- <tr>
38
- <td>Code: <input type="text" name="code" size="20" required/></td>
39
- <td>Display: <input type="text" name="display" size="20"/></td>
40
- </tr>
41
- <tr>
42
- <td>Language: <input type="text" name="displayLanguage" size="10"/></td>
43
- <td>
44
- <input type="checkbox" name="abstract" value="true"/>
45
- <label>Abstract</label>
46
- </td>
47
- </tr>
48
- </table>
49
- <button type="submit" class="btn btn-sm btn-primary">Validate Code</button>
50
- </form>
51
- </div>
52
-
53
- </div>
54
- </div>
24
+ <div class="operation-form" style="margin-bottom: 15px;">
25
+ <strong>Validate Code (ValueSet)</strong>
26
+ <form method="get" action="$validate-code" style="margin-left: 10px; margin-top: 5px;">
27
+ <input type="hidden" name="url" value="{{ url }}"/>
28
+ <input type="hidden" name="inferSystem" id="{{ inferSystemId }}" value="true"/>
29
+ <table class="grid" cellpadding="0" cellspacing="0">
30
+ <tr>
31
+ <td>System: <input type="text" name="system" id="{{ vcSystemId }}" size="30" onchange="updateInferSystem('{{ vcSystemId }}', '{{ inferSystemId }}')"/></td>
32
+ <td>Version: <input type="text" name="version" size="10"/></td>
33
+ </tr>
34
+ <tr>
35
+ <td>Code: <input type="text" name="code" size="20" required/></td>
36
+ <td>Display: <input type="text" name="display" size="20"/></td>
37
+ </tr>
38
+ <tr>
39
+ <td>Language: <input type="text" name="displayLanguage" size="10"/></td>
40
+ <td>
41
+ <input type="checkbox" name="abstract" value="true"/>
42
+ <label>Abstract</label>
43
+ </td>
44
+ </tr>
45
+ </table>
46
+ <button type="submit" class="btn btn-sm btn-primary">Validate Code</button>
47
+ </form>
48
+ </div>
@@ -33,7 +33,12 @@ class CodeSystem extends CanonicalResource {
33
33
  // Convert to R5 format internally (modifies input for performance)
34
34
  this.jsonObj = codeSystemToR5(this.jsonObj, fhirVersion);
35
35
  if (!noMaps) {
36
- this.validate();
36
+ try {
37
+ this.validate();
38
+ } catch (e) {
39
+ const id = this.jsonObj?.url ? `${this.jsonObj.url}|${this.jsonObj.version || ''}` : this.jsonObj?.name || 'unknown';
40
+ throw new Error(`${e.message} (in ${id})`);
41
+ }
37
42
  this.buildMaps();
38
43
  }
39
44
  }
@@ -22,18 +22,17 @@ class Renderer {
22
22
  displayCoded(...args) {
23
23
  if (args.length === 1) {
24
24
  const arg = args[0];
25
- if (arg.systemUri !== undefined && arg.version !== undefined && arg.code !== undefined && arg.display !== undefined) {
25
+ if (arg instanceof CodeSystemProvider) {
26
+ return arg.system() + "|" + arg.version();
27
+ } else if (arg.system !== undefined && arg.version !== undefined && arg.code !== undefined && arg.display !== undefined) {
26
28
  // It's a Coding
27
29
  return this.displayCodedCoding(arg);
28
30
  } else if (arg.coding !== undefined || arg.text) {
29
31
  // It's a CodeableConcept
30
32
  return this.displayCodedCodeableConcept(arg);
31
- } else if (arg.systemUri !== undefined && arg.version !== undefined) {
33
+ } else if (arg.system !== undefined && arg.version !== undefined) {
32
34
  // It's a CodeSystemProvider
33
35
  return this.displayCodedProvider(arg);
34
- } else if (arg instanceof CodeSystemProvider) {
35
- let cs = arg;
36
- return cs.system() + "|" + cs.version();
37
36
  }
38
37
  } else if (args.length === 2) {
39
38
  return this.displayCodedSystemVersion(args[0], args[1]);
@@ -46,7 +45,7 @@ class Renderer {
46
45
  }
47
46
 
48
47
  displayCodedProvider(system) {
49
- let result = system.systemUri + '|' + system.version;
48
+ let result = system.system + '|' + system.version;
50
49
  if (system.sourcePackage) {
51
50
  result = result + ' (from ' + system.sourcePackage + ')';
52
51
  }
@@ -70,7 +69,7 @@ class Renderer {
70
69
  }
71
70
 
72
71
  displayCodedCoding(code) {
73
- return this.displayCodedSystemVersionCodeDisplay(code.systemUri, code.version, code.code, code.display);
72
+ return this.displayCodedSystemVersionCodeDisplay(code.system, code.version, code.code, code.display);
74
73
  }
75
74
 
76
75
  displayCodedCodeableConcept(code) {
@@ -90,12 +89,12 @@ class Renderer {
90
89
 
91
90
  displayValueSetInclude(inc) {
92
91
  let result;
93
- if (inc.systemUri) {
94
- result = '(' + inc.systemUri + ')';
95
- if (inc.hasConcepts) {
92
+ if (inc.system) {
93
+ result = '(' + inc.system + ')';
94
+ if (inc.concept) {
96
95
  result = result + '(';
97
96
  let first = true;
98
- for (const cc of inc.concepts) {
97
+ for (const cc of inc.concept) {
99
98
  if (first) {
100
99
  first = false;
101
100
  } else {
@@ -105,23 +104,23 @@ class Renderer {
105
104
  }
106
105
  result = result + ')';
107
106
  }
108
- if (inc.hasFilters) {
107
+ if (inc.filter) {
109
108
  result = result + '(';
110
109
  let first = true;
111
- for (const ci of inc.filters) {
110
+ for (const ci of inc.filter) {
112
111
  if (first) {
113
112
  first = false;
114
113
  } else {
115
114
  result = result + ',';
116
115
  }
117
- result = result + ci.prop + ci.op + ci.value;
116
+ result = result + ci.property + ci.op + ci.value;
118
117
  }
119
118
  result = result + ')';
120
119
  }
121
120
  } else {
122
121
  result = '(';
123
122
  let first = true;
124
- for (const s of inc.valueSets || []) {
123
+ for (const s of inc.valueSet || []) {
125
124
  if (first) {
126
125
  first = false;
127
126
  } else {
@@ -148,6 +147,7 @@ class Renderer {
148
147
  this.renderProperty(tbl, 'GENERAL_TITLE', res.title);
149
148
  this.renderProperty(tbl, 'GENERAL_STATUS', res.status);
150
149
  this.renderPropertyMD(tbl, 'GENERAL_DEFINITION', res.description);
150
+ this.renderPropertyMD(tbl, 'GENERAL_PURPOSE', res.purpose);
151
151
  this.renderProperty(tbl, 'CANON_REND_PUBLISHER', res.publisher);
152
152
  this.renderProperty(tbl, 'CANON_REND_COMMITTEE', Extensions.readString(res, 'http://hl7.org/fhir/StructureDefinition/structuredefinition-wg'));
153
153
  this.renderProperty(tbl, 'GENERAL_COPYRIGHT', res.copyright);
@@ -340,7 +340,7 @@ class Renderer {
340
340
  }
341
341
  if (vs.expansion) {
342
342
  div_.h2().tx("Expansion");
343
- await this.renderExpansion(div_.table("grid"), vs, tbl);
343
+ await this.renderExpansion(div_, vs, tbl);
344
344
  }
345
345
 
346
346
  return div_.toString();
@@ -420,7 +420,7 @@ class Renderer {
420
420
  li.tx(this.translate('VALUE_SET_ALL_CODES_DEF')+" ");
421
421
  await this.renderLink(li,inc.system+(inc.version ? "|"+inc.version : ""));
422
422
  } else if (inc.concept) {
423
- li.tx(this.translate('VALUE_SET_THESE_CODES_DEF'));
423
+ li.tx(this.translate('VALUE_SET_THESE_CODES_DEF')+" ");
424
424
  await this.renderLink(li,inc.system+(inc.version ? "|"+inc.version : ""));
425
425
  li.tx(":");
426
426
  const ul = li.ul();
@@ -899,18 +899,45 @@ class Renderer {
899
899
  return count;
900
900
  }
901
901
 
902
+ async renderVSExpansion(vs, showProps) {
903
+ let div_ = div();
904
+ let tbl;
905
+ if (showProps) {
906
+ div_.h2().tx("Expansion Properties");
907
+ tbl = div_.table("grid");
908
+ } else {
909
+ tbl = div(); // dummy
910
+ }
911
+ await this.renderExpansion(div_.table("grid"), vs, tbl);
912
+ return div_.toString();
913
+ }
914
+
902
915
  async renderExpansion(x, vs, tbl) {
903
916
  this.renderProperty(tbl, 'Expansion Identifier', vs.expansion.identifier);
904
917
  this.renderProperty(tbl, 'Expansion Timestamp', vs.expansion.timestamp);
905
918
  this.renderProperty(tbl, 'Expansion Total', vs.expansion.total);
906
919
  this.renderProperty(tbl, 'Expansion Offset', vs.expansion.offset);
920
+ const warnings = [];
921
+ const warningNames = new Set(['deprecated', 'withdrawn', 'retired', 'experimental', 'draft']);
922
+ const useds = [];
923
+ const usedNames = new Set(['codesystem', 'valueset', 'supplement']);
907
924
  for (let p of vs.expansion.parameter || []) {
908
- if( getValueName(p) === 'valueUri' || getValueName(p) === 'valueCanonical') {
925
+ if (p.name.startsWith('warning-') && warningNames.has(p.name.substring(8))) {
926
+ warnings.push(p);
927
+ } else if (p.name.startsWith('used-') && usedNames.has(p.name.substring(5))) {
928
+ useds.push(p);
929
+ } else if( getValueName(p) === 'valueUri' || getValueName(p) === 'valueCanonical') {
909
930
  await this.renderPropertyLink(tbl, "Parameter: " + p.name, getValuePrimitive(p));
910
931
  } else {
911
932
  this.renderProperty(tbl, "Parameter: " + p.name, getValuePrimitive(p));
912
933
  }
913
934
  }
935
+ if (useds.length > 0) {
936
+ await this.renderUsed(x, useds);
937
+ }
938
+ if (warnings.length > 0) {
939
+ await this.renderWarnings(x, warnings);
940
+ }
914
941
 
915
942
  if (!vs.expansion.contains || vs.expansion.contains.length === 0) {
916
943
  x.para().i().tx('No concepts in expansion');
@@ -936,10 +963,10 @@ class Renderer {
936
963
  }
937
964
  headerRow.th().tx(this.translate('TX_DISPLAY'));
938
965
  if (columnInfo.hasAbstract) {
939
- headerRow.th().tx('Abstract');
966
+ headerRow.th().tx(this.translate('Abstract'));
940
967
  }
941
968
  if (columnInfo.hasInactive) {
942
- headerRow.th().tx('Inactive');
969
+ headerRow.th().tx(this.translate('VALUE_SET_INACTIVE'));
943
970
  }
944
971
 
945
972
  // Property columns (from expansion.property definitions)
@@ -1118,12 +1145,12 @@ class Renderer {
1118
1145
 
1119
1146
  // Abstract column
1120
1147
  if (columnInfo.hasAbstract) {
1121
- tr.td().tx(contains.abstract === true ? 'true' : '');
1148
+ tr.td().tx(contains.abstract === true ? 'abstract' : '');
1122
1149
  }
1123
1150
 
1124
1151
  // Inactive column
1125
1152
  if (columnInfo.hasInactive) {
1126
- tr.td().tx(contains.inactive === true ? 'true' : '');
1153
+ tr.td().tx(contains.inactive === true ? this.translate('VALUE_SET_INACT') : '');
1127
1154
  }
1128
1155
 
1129
1156
  // Property columns
@@ -1557,12 +1584,62 @@ class Renderer {
1557
1584
  // No versions specified
1558
1585
  await this.renderLink(li, cs.uri);
1559
1586
  }
1587
+ let content = cs.content || Extensions.readString(cs, "http://hl7.org/fhir/4.0/StructureDefinition/extension-TerminologyCapabilities.codeSystem.content");
1588
+ if (content && content != "complete") {
1589
+ li.tx(" (" + content + ")");
1590
+ }
1560
1591
  }
1561
1592
  }
1562
1593
  }
1563
1594
 
1564
1595
  return div_.toString();
1565
1596
  }
1597
+
1598
+ async renderWarnings(x, warnings) {
1599
+ await this.renderWarningsForStatus(x, 'deprecated', warnings);
1600
+ await this.renderWarningsForStatus(x, 'withdrawn', warnings);
1601
+ await this.renderWarningsForStatus(x, 'retired', warnings);
1602
+ await this.renderWarningsForStatus(x, 'experimental', warnings);
1603
+ await this.renderWarningsForStatus(x, 'draft', warnings);
1604
+ }
1605
+
1606
+ async renderWarningsForStatus(x, name, warnings) {
1607
+ const wl = warnings.filter(item => item.name == 'warning-'+name);
1608
+ if (wl && wl.length > 0) {
1609
+ x.para().tx(`This ValueSet depends on the following ${name} ValueSets: `);
1610
+ let ul = x.ul();
1611
+ for (const w of wl) {
1612
+ const linkinfo = await this.linkResolver.resolveURL(this.opContext, getValuePrimitive(w));
1613
+ if (linkinfo) {
1614
+ ul.li().ah(linkinfo.link).tx(linkinfo.description);
1615
+ } else {
1616
+ ul.li().code().tx(getValuePrimitive(w));
1617
+ }
1618
+ }
1619
+ }
1620
+ }
1621
+
1622
+ async renderUsed(x, list) {
1623
+ x.para().tx(`This ValueSet depends on the following items:`);
1624
+ let ul = x.ul();
1625
+ await this.renderUsedForType(ul, 'codesystem', 'CodeSystem', list);
1626
+ await this.renderUsedForType(ul, 'valueset', 'ValueSet', list);
1627
+ await this.renderUsedForType(ul, 'supplement', 'Supplement', list);
1628
+ }
1629
+
1630
+ async renderUsedForType(ul, name, title, list) {
1631
+ const wl = list.filter(item => item.name == 'used-' + name);
1632
+ for (const w of wl) {
1633
+ const li = ul.li();
1634
+ li.tx(title+": ");
1635
+ const linkinfo = await this.linkResolver.resolveURL(this.opContext, getValuePrimitive(w));
1636
+ if (linkinfo) {
1637
+ li.ah(linkinfo.link).tx(linkinfo.description);
1638
+ } else {
1639
+ li.code().tx(getValuePrimitive(w));
1640
+ }
1641
+ }
1642
+ }
1566
1643
  }
1567
1644
 
1568
1645
  module.exports = { Renderer };
package/tx/library.js CHANGED
@@ -158,7 +158,12 @@ class Library {
158
158
  this.log.info('Fetching Data from '+this.baseUrl);
159
159
 
160
160
  for (const source of config.sources) {
161
- await this.processSource(source, this.packageManager, "fetch");
161
+ try {
162
+ await this.processSource(source, this.packageManager, "fetch");
163
+ } catch (error) {
164
+ console.error(`Failed to fetch source '${source}': ${error.message}`);
165
+ throw error;
166
+ }
162
167
  }
163
168
 
164
169
  this.log.info("Downloaded "+((this.totalDownloaded + this.packageManager.totalDownloaded)/ 1024)+" kB");
@@ -167,13 +172,23 @@ class Library {
167
172
  this.#logSystemHeader();
168
173
 
169
174
  for (const source of config.sources) {
170
- await this.processSource(source, this.packageManager, "cs");
175
+ try {
176
+ await this.processSource(source, this.packageManager, "cs");
177
+ } catch (error) {
178
+ console.error(`Failed to load code systems from '${source}': ${error.message}`);
179
+ throw error;
180
+ }
171
181
  }
172
182
  this.log.info('Loading Packages');
173
183
  this.#logPackagesHeader();
174
184
 
175
185
  for (const source of config.sources) {
176
- await this.processSource(source, this.packageManager, "npm");
186
+ try {
187
+ await this.processSource(source, this.packageManager, "npm");
188
+ } catch (error) {
189
+ console.error(`Failed to load package '${source}': ${error.message}`);
190
+ throw error;
191
+ }
177
192
  }
178
193
 
179
194
  const endMemory = process.memoryUsage();
package/tx/provider.js CHANGED
@@ -153,6 +153,7 @@ class Provider {
153
153
  }
154
154
 
155
155
  getCodeSystemById(opContext, id) {
156
+
156
157
  // Search through codeSystems map for matching id
157
158
  for (const cs of this.codeSystems.values()) {
158
159
  if (opContext) opContext.deadCheck('getCodeSystemById');
@@ -318,9 +319,10 @@ class Provider {
318
319
  factory = this.codeSystemFactories.get(vurlMM);
319
320
  }
320
321
  if (factory != null) {
322
+ let vdesc = version == null ? "" : factory.describeVersion(version);
321
323
  return {
322
- link: this.path+"/CodeSystem/"+factory.id(),
323
- description: factory.name()+(version ? " v"+version : "")
324
+ link: this.path+"/CodeSystem/x-"+factory.id(),
325
+ description: factory.nameBase()+' '+vdesc
324
326
  };
325
327
  }
326
328
  let cs = this.codeSystems.get(vurl);
@@ -1469,7 +1469,7 @@ class SnomedExpressionServices {
1469
1469
  /**
1470
1470
  * Validate concept reference
1471
1471
  */
1472
- checkConcept(concept, limit) {
1472
+ checkConcept(concept, limits) {
1473
1473
  if (concept.code) {
1474
1474
  const conceptId = BigInt(concept.code);
1475
1475
  const result = this.concepts.findConcept(conceptId);
@@ -1480,13 +1480,24 @@ class SnomedExpressionServices {
1480
1480
  throw new Error(`Concept ${concept.code} not found`);
1481
1481
  }
1482
1482
  }
1483
- if (limit && concept.reference) {
1484
- // if a limit is specified, then the concept has to be a specialization of that.
1485
- let parentRef = this.concepts.findConcept(limit);
1486
- let descendentsRef = this.concepts.getAllDesc(parentRef.index);
1487
- const descendants = this.refs.getReferences(descendentsRef);
1488
- if (descendants && !descendants.includes(concept.reference)) {
1489
- throw new Error(`Concept ${concept.code} is not valid in this context (must be a ${limit})`);
1483
+ if (limits && concept.reference) {
1484
+ // if a limit is specified, then the concept has to be a specialization of one of them.
1485
+ let ok = false;
1486
+ for (const limit of limits) {
1487
+ let parentRef = this.concepts.findConcept(limit);
1488
+ let descendentsRef = this.concepts.getAllDesc(parentRef.index);
1489
+ const descendants = this.refs.getReferences(descendentsRef);
1490
+ if (descendants && descendants.includes(concept.reference)) {
1491
+ ok = true;
1492
+ break;
1493
+ }
1494
+ }
1495
+ if (!ok) {
1496
+ if (limits.length == 1) {
1497
+ throw new Error(`Concept ${concept.code} is not valid in this context (must be a ${limits[0]})`);
1498
+ } else {
1499
+ throw new Error(`Concept ${concept.code} is not valid in this context (must be a descendent of one of ${limits})`);
1500
+ }
1490
1501
  }
1491
1502
  }
1492
1503
 
@@ -1615,7 +1626,7 @@ class SnomedExpressionServices {
1615
1626
  * Validate refinement
1616
1627
  */
1617
1628
  checkRefinement(refinement) {
1618
- this.checkConcept(refinement.name, '410662002');
1629
+ this.checkConcept(refinement.name, ['410662002', '106237007']);
1619
1630
  this.checkExpression(refinement.value);
1620
1631
  }
1621
1632