fhirsmith 0.10.0 → 0.11.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.
- package/CHANGELOG.md +48 -1
- package/changelog-template.md +22 -0
- package/library/logger.js +8 -1
- package/library/package-manager.js +40 -2
- package/npmprojector/watcher.js +1 -1
- package/package.json +3 -2
- package/packages/packages.js +28 -15
- package/publisher/publisher.js +8 -8
- package/registry/api.js +92 -31
- package/registry/crawler.js +15 -1
- package/registry/model.js +158 -8
- package/registry/readme.md +5 -13
- package/registry/registry.js +87 -46
- package/registry/test-runner.js +1 -1
- package/server.js +8 -1
- package/token/token.js +1 -1
- package/translations/Messages.properties +3 -1
- package/translations/rendering-phrases.properties +1227 -1218
- package/translations/rendering-phrases_ar.properties +1097 -1091
- package/translations/rendering-phrases_fr.properties +626 -624
- package/translations/rendering-phrases_nl.properties +976 -970
- package/translations/rendering-phrases_pt.properties +1026 -1020
- package/translations/rendering-phrases_ru.properties +1100 -1094
- package/tsconfig.eslint.json +9 -0
- package/tx/cs/cs-api.js +57 -4
- package/tx/cs/cs-cs.js +98 -25
- package/tx/cs/cs-hgvs.js +4 -0
- package/tx/cs/cs-lang.js +8 -0
- package/tx/cs/cs-mimetypes.js +4 -0
- package/tx/cs/cs-snomed.js +530 -75
- package/tx/cs/cs-uri.js +4 -0
- package/tx/data/snomed-subset.txt +40 -3
- package/tx/importers/import-sct.module.js +18 -0
- package/tx/library/designations.js +3 -5
- package/tx/library/renderer.js +235 -38
- package/tx/library.js +5 -1
- package/tx/operation-context.js +40 -3
- package/tx/provider.js +41 -12
- package/tx/sct/ecl.js +30 -11
- package/tx/sct/expressions.js +28 -1
- package/tx/sct/structures.js +5 -5
- package/tx/tx-html.js +24 -15
- package/tx/tx.fhir.org.yml +1 -4
- package/tx/vs/vs-database.js +41 -8
- package/tx/vs/vs-package.js +55 -0
- package/tx/vs/vs-vsac.js +4 -0
- package/tx/workers/batch-validate.js +125 -18
- package/tx/workers/cache-control.js +31 -2
- package/tx/workers/expand.js +50 -32
- package/tx/workers/metadata.js +27 -6
- package/tx/workers/search.js +7 -1
- package/tx/workers/validate.js +30 -18
- package/tx/workers/worker.js +30 -7
- package/tx/xversion/xv-valueset.js +5 -2
- package/xig/xig.js +3 -3
package/tx/cs/cs-uri.js
CHANGED
|
@@ -162,6 +162,10 @@ class UriServices extends CodeSystemProvider {
|
|
|
162
162
|
return true;
|
|
163
163
|
}
|
|
164
164
|
|
|
165
|
+
canBeExpanded() {
|
|
166
|
+
return false; // URIs cannot be iterated / enumerated
|
|
167
|
+
}
|
|
168
|
+
|
|
165
169
|
// ============================================================================
|
|
166
170
|
// Filtering (not supported for URIs)
|
|
167
171
|
// ============================================================================
|
|
@@ -22,6 +22,8 @@
|
|
|
22
22
|
74400008 |Appendicitis (disorder)|
|
|
23
23
|
155729003 |Appendicitis (disorder)|
|
|
24
24
|
307530000 |Appendicitis NOS (disorder)|
|
|
25
|
+
42463004 |Genus Opisthorchis (organism)|
|
|
26
|
+
71650008 |Opisthorchis viverrini (organism)|
|
|
25
27
|
116676008 |Associated morphology (attribute)|
|
|
26
28
|
128045006 |Cellulitis (disorder)|
|
|
27
29
|
20946005 |Fracture, closed (morphologic abnormality)|
|
|
@@ -34,14 +36,49 @@
|
|
|
34
36
|
6990005 |Fracture of shaft of tibia (disorder)|
|
|
35
37
|
<<128241005 |Inflammatory disease of liver (disorder)|
|
|
36
38
|
<<10200004 |Liver structure (body structure)|
|
|
39
|
+
<<90140006 |Structure of intrahepatic biliary tree (body structure)|
|
|
37
40
|
<<364159005 |Liver observable (observable entity)|
|
|
38
41
|
<<227014008 |Liver pate (substance)|
|
|
39
42
|
<<249565005 |Liver finding (finding)|
|
|
40
43
|
<<406459008 |Product containing halibut liver oil (medicinal product)|
|
|
41
44
|
<<776168003 |Product containing only halibut liver oil (medicinal product)|
|
|
42
|
-
<<776168003 |Product containing only halibut liver oil (medicinal product)|
|
|
43
45
|
<<309399009 |Hospital-based dietitian (occupation)|
|
|
44
46
|
<<421813008 |Class Hepaticopsida (organism)|
|
|
45
|
-
<<441802002 |
|
|
47
|
+
<<441802002 |Computed tomography of liver (procedure)|
|
|
46
48
|
<<119383005 |Specimen from liver (specimen)|
|
|
47
|
-
129433002 |Inspection|
|
|
49
|
+
129433002 |Inspection|
|
|
50
|
+
367430006 |Repair of tendon of hand (procedure)|
|
|
51
|
+
119657005 |Hand repair (procedure)|
|
|
52
|
+
274059009 |Hand tendon operation (procedure)|
|
|
53
|
+
281760001 |Repair of tendon of upper limb (procedure)|
|
|
54
|
+
18701002 |Repair of tendon of hand with graft (procedure)|
|
|
55
|
+
214433003 |Repair of ruptured tendon of hand (procedure)|
|
|
56
|
+
243234005 |Repair of tendon of hand by transfer or transplantation (procedure)|
|
|
57
|
+
26731003 |Repair of tendon of hand by suture (procedure)|
|
|
58
|
+
27106001 |Reconstruction of tendon pulley of hand (procedure)|
|
|
59
|
+
45810006 |Reattachment of tendon of hand (procedure)|
|
|
60
|
+
709291000 |Repair of flexor tendon of hand (procedure)|
|
|
61
|
+
712638006 |Repair of extensor tendon of hand (procedure)|
|
|
62
|
+
76340004 |Plication of tendon of hand (procedure)|
|
|
63
|
+
90650008 |Lengthening of tendon of hand (procedure)|
|
|
64
|
+
90907001 |Shortening of tendon of hand (procedure)|
|
|
65
|
+
91092007 |Myotenontoplasty of hand (procedure)|
|
|
66
|
+
118632007 |Structure of tendon within hand (body structure)|
|
|
67
|
+
13024002 |Tendon structure (body structure)|
|
|
68
|
+
245098002 |Structure of tendon within upper limb (body structure)|
|
|
69
|
+
85562004 |Hand structure (body structure)|
|
|
70
|
+
#Referenced in request
|
|
71
|
+
734139008 |Anatomy structure and part association reference set (foundation metadata concept)|
|
|
72
|
+
#For snomed-procedure-in-display test
|
|
73
|
+
#Required by: validate-code-isa-in-response.json
|
|
74
|
+
41271000119108 |Infective arthritis caused by virus and co-occurrent with hepatitis (disorder)|
|
|
75
|
+
#Required by: expand-ecl-ancOrSelf-response.json, expand-ecl-ancestors-response.json, expand-ecl-parents-response.json, expand-ecl-parentsOrSelf-response.json
|
|
76
|
+
303270005 |Liver and biliary structure (body structure)|
|
|
77
|
+
699602007 |Structure of viscus of abdomen proper (body structure)|
|
|
78
|
+
#Required by: expand-ecl-ancOrSelf-response.json, expand-ecl-ancestors-response.json
|
|
79
|
+
420473002 |Structure of subregion of trunk (body structure)|
|
|
80
|
+
49596003 |Digestive organ (body structure)|
|
|
81
|
+
86762007 |Digestive system (body structure)|
|
|
82
|
+
1197039003 |Structure of lymphatic vessel of liver (body structure)|
|
|
83
|
+
#Required by: lookup-procedure-pc-response.json
|
|
84
|
+
24028007 |Right (qualifier value)|
|
|
@@ -2407,6 +2407,16 @@ class SnomedImporter {
|
|
|
2407
2407
|
const refSet = new RefSet(refSetId);
|
|
2408
2408
|
refSet.title = displayName;
|
|
2409
2409
|
refSet.isLangRefset = isLangRefset;
|
|
2410
|
+
// Set the reference set's defining-concept index at creation time, using
|
|
2411
|
+
// this refset's own id. Doing it here (rather than once after the member
|
|
2412
|
+
// loop) is essential when a single RF2 file contains members for more than
|
|
2413
|
+
// one reference set (e.g. the association file carries both 734139008 and
|
|
2414
|
+
// 900000000000526001) — otherwise only the last refset in the file gets a
|
|
2415
|
+
// valid index and the others collapse to index 0.
|
|
2416
|
+
const refSetConcept = this.conceptMap.get(BigInt(refSetId));
|
|
2417
|
+
if (refSetConcept) {
|
|
2418
|
+
refSet.index = refSetConcept.index;
|
|
2419
|
+
}
|
|
2410
2420
|
this.refSets.set(refSetId, refSet);
|
|
2411
2421
|
}
|
|
2412
2422
|
return this.refSets.get(refSetId);
|
|
@@ -2585,6 +2595,14 @@ class SnomedImporter {
|
|
|
2585
2595
|
|
|
2586
2596
|
// Index concepts - optimized version
|
|
2587
2597
|
for (const concept of this.conceptList) {
|
|
2598
|
+
// Store the concept's module (resolved to its concept index) so that
|
|
2599
|
+
// $lookup can report the 'module' property. Modules whose concept is not
|
|
2600
|
+
// part of the subset are skipped (moduleId stays 0 / property omitted).
|
|
2601
|
+
const moduleConcept = this.conceptMap.get(concept.moduleId);
|
|
2602
|
+
if (moduleConcept && moduleConcept.index != null) {
|
|
2603
|
+
this.concepts.setModuleId(concept.index, moduleConcept.index);
|
|
2604
|
+
}
|
|
2605
|
+
|
|
2588
2606
|
const refSetRefs = [];
|
|
2589
2607
|
const refSetValues = [];
|
|
2590
2608
|
|
|
@@ -214,7 +214,8 @@ const DisplayDifference = {
|
|
|
214
214
|
const DesignationUse = {
|
|
215
215
|
DISPLAY: {
|
|
216
216
|
system: 'http://terminology.hl7.org/CodeSystem/hl7TermMaintInfra', // http://terminology.hl7.org/CodeSystem/designation-use',
|
|
217
|
-
code: 'preferredForLanguage' // 'display'
|
|
217
|
+
code: 'preferredForLanguage', // 'display'
|
|
218
|
+
display: 'Preferred For Language'
|
|
218
219
|
},
|
|
219
220
|
FSN: {
|
|
220
221
|
system: 'http://snomed.info/sct',
|
|
@@ -356,10 +357,7 @@ class Designations {
|
|
|
356
357
|
designation.status = status;
|
|
357
358
|
|
|
358
359
|
if (isDisplay) {
|
|
359
|
-
designation.use = {
|
|
360
|
-
system: DesignationUse.DISPLAY.system,
|
|
361
|
-
code: DesignationUse.DISPLAY.code
|
|
362
|
-
};
|
|
360
|
+
designation.use = { ...DesignationUse.DISPLAY };
|
|
363
361
|
} else if (use) {
|
|
364
362
|
designation.use = use;
|
|
365
363
|
}
|
package/tx/library/renderer.js
CHANGED
|
@@ -157,8 +157,8 @@ class Renderer {
|
|
|
157
157
|
async renderMetadataTable(res, tbl, sourcePackage) {
|
|
158
158
|
this.renderMetadataVersion(res, tbl);
|
|
159
159
|
await this.renderMetadataProfiles(res, tbl);
|
|
160
|
-
this.renderMetadataTags(res, tbl);
|
|
161
|
-
this.renderMetadataLabels(res, tbl);
|
|
160
|
+
await this.renderMetadataTags(res, tbl);
|
|
161
|
+
await this.renderMetadataLabels(res, tbl);
|
|
162
162
|
this.renderMetadataLastUpdated(res, tbl);
|
|
163
163
|
this.renderMetadataSource(res, tbl);
|
|
164
164
|
this.renderProperty(tbl, 'TEST_PLAN_LANG', res.language);
|
|
@@ -173,6 +173,7 @@ class Renderer {
|
|
|
173
173
|
this.renderProperty(tbl, 'CANON_REND_COMMITTEE', Extensions.readString(res, 'http://hl7.org/fhir/StructureDefinition/structuredefinition-wg'));
|
|
174
174
|
this.renderPropertyCopy(tbl, 'GENERAL_COPYRIGHT', res.copyright);
|
|
175
175
|
this.renderProperty(tbl, 'EXT_FMM_LEVEL', Extensions.readString(res, 'http://hl7.org/fhir/StructureDefinition/structuredefinition-fmm'));
|
|
176
|
+
this.renderProperty(tbl, 'EXT_STANDARDS_STATUS', Extensions.readString(res, 'http://hl7.org/fhir/StructureDefinition/structuredefinition-standards-status'));
|
|
176
177
|
this.renderProperty(tbl, 'PAT_PERIOD', res.effectivePeriod);
|
|
177
178
|
this.renderPropertyLink(tbl, 'WEB_SOURCE', Extensions.readString(res, 'http://hl7.org/fhir/StructureDefinition/web-source'));
|
|
178
179
|
this.renderProperty(tbl, 'GENERAL_SOURCE', sourcePackage);
|
|
@@ -221,41 +222,45 @@ class Renderer {
|
|
|
221
222
|
}
|
|
222
223
|
}
|
|
223
224
|
|
|
224
|
-
renderMetadataTags(res, tbl) {
|
|
225
|
+
async renderMetadataTags(res, tbl) {
|
|
225
226
|
if (res.meta?.tag) {
|
|
226
227
|
let tr = tbl.tr();
|
|
227
228
|
tr.td().b().tx(this.translate('GENERAL_PROF'));
|
|
228
229
|
if (res.meta.tag.length > 1) {
|
|
229
230
|
let ul = tr.td();
|
|
230
231
|
for (let u of res.meta.tag) {
|
|
231
|
-
this.renderCoding(ul.li(), u);
|
|
232
|
+
await this.renderCoding(ul.li(), u);
|
|
232
233
|
}
|
|
233
234
|
} else {
|
|
234
|
-
this.renderCoding(tr.td(), res.meta.tag[0]);
|
|
235
|
+
await this.renderCoding(tr.td(), res.meta.tag[0]);
|
|
235
236
|
}
|
|
236
237
|
}
|
|
237
238
|
}
|
|
238
239
|
|
|
239
|
-
renderMetadataLabels(res, tbl) {
|
|
240
|
+
async renderMetadataLabels(res, tbl) {
|
|
240
241
|
if (res.meta?.label) {
|
|
241
242
|
let tr = tbl.tr();
|
|
242
243
|
tr.td().b().tx(this.translate('GENERAL_PROF'));
|
|
243
244
|
if (res.meta.label.length > 1) {
|
|
244
245
|
let ul = tr.td();
|
|
245
246
|
for (let u of res.meta.label) {
|
|
246
|
-
this.
|
|
247
|
+
await this.renderCoding(ul.li(), u);
|
|
247
248
|
}
|
|
248
249
|
} else {
|
|
249
|
-
this.renderCoding(tr.td(), res.meta.label[0]);
|
|
250
|
+
await this.renderCoding(tr.td(), res.meta.label[0]);
|
|
250
251
|
}
|
|
251
252
|
}
|
|
252
253
|
}
|
|
253
254
|
|
|
255
|
+
// Inline phrases inherited from the Java core translations (RES_REND_VER = "version: {0}"
|
|
256
|
+
// etc) are not table labels; when rendering into a name/value table we use the dedicated
|
|
257
|
+
// *_NAME / *_VALUE phrases instead, and leave the inline phrases to the Java code.
|
|
258
|
+
|
|
254
259
|
renderMetadataVersion(res, tbl) {
|
|
255
260
|
if (res.meta?.version) {
|
|
256
261
|
let tr = tbl.tr();
|
|
257
|
-
tr.td().b().tx(this.translate('
|
|
258
|
-
tr.td().tx(res.meta.version);
|
|
262
|
+
tr.td().b().tx(this.translate('RES_REND_VER_NAME'));
|
|
263
|
+
tr.td().tx(this.translate('RES_REND_VER_VALUE', [res.meta.version]));
|
|
259
264
|
}
|
|
260
265
|
}
|
|
261
266
|
|
|
@@ -263,9 +268,8 @@ class Renderer {
|
|
|
263
268
|
if (res.meta?.lastUpdated) {
|
|
264
269
|
this._requireValidDate(res.meta.lastUpdated, 'meta.lastUpdated');
|
|
265
270
|
let tr = tbl.tr();
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
tr.td().b().tx(this.translate('RES_REND_UPDATED', [this.displayDate(res.meta.lastUpdated)]));
|
|
271
|
+
tr.td().b().tx(this.translate('RES_REND_UPDATED_NAME'));
|
|
272
|
+
tr.td().tx(this.translate('RES_REND_UPDATED_VALUE', [this.displayDate(res.meta.lastUpdated)]));
|
|
269
273
|
}
|
|
270
274
|
}
|
|
271
275
|
|
|
@@ -326,8 +330,8 @@ class Renderer {
|
|
|
326
330
|
renderMetadataSource(res, tbl) {
|
|
327
331
|
if (res.meta?.source) {
|
|
328
332
|
let tr = tbl.tr();
|
|
329
|
-
tr.td().b().tx(this.translate('
|
|
330
|
-
tr.td().tx(res.meta.source);
|
|
333
|
+
tr.td().b().tx(this.translate('RES_REND_INFO_SOURCE_NAME'));
|
|
334
|
+
tr.td().tx(this.translate('RES_REND_INFO_SOURCE_VALUE', [res.meta.source]));
|
|
331
335
|
}
|
|
332
336
|
}
|
|
333
337
|
|
|
@@ -340,12 +344,12 @@ class Renderer {
|
|
|
340
344
|
}
|
|
341
345
|
}
|
|
342
346
|
|
|
343
|
-
renderLinkComma(x, uri) {
|
|
344
|
-
let {
|
|
345
|
-
if (
|
|
346
|
-
x.commaItem(
|
|
347
|
+
async renderLinkComma(x, uri) {
|
|
348
|
+
let {description, link} = await (this.linkResolver ? this.linkResolver.resolveURL(this.opContext, uri) : null) || {};
|
|
349
|
+
if (link) {
|
|
350
|
+
x.commaItem(description, link);
|
|
347
351
|
} else {
|
|
348
|
-
x.commaItem(
|
|
352
|
+
x.commaItem(link);
|
|
349
353
|
}
|
|
350
354
|
}
|
|
351
355
|
|
|
@@ -654,7 +658,7 @@ class Renderer {
|
|
|
654
658
|
await this.renderMetadataTable(cs, div_.table("grid"), sourcePackage);
|
|
655
659
|
|
|
656
660
|
// Code system properties
|
|
657
|
-
const hasProps = this.generateProperties(div_, cs);
|
|
661
|
+
const hasProps = await this.generateProperties(div_, cs);
|
|
658
662
|
|
|
659
663
|
// Filters
|
|
660
664
|
this.generateFilters(div_, cs);
|
|
@@ -673,7 +677,7 @@ class Renderer {
|
|
|
673
677
|
p.tx(" ");
|
|
674
678
|
p.startCommaList("and");
|
|
675
679
|
for (let ext of supplements) {
|
|
676
|
-
this.renderLinkComma(p, getValuePrimitive(ext));
|
|
680
|
+
await this.renderLinkComma(p, getValuePrimitive(ext));
|
|
677
681
|
}
|
|
678
682
|
p.stopCommaList();
|
|
679
683
|
p.tx(".");
|
|
@@ -762,10 +766,10 @@ class Renderer {
|
|
|
762
766
|
li.stopCommaList();
|
|
763
767
|
}
|
|
764
768
|
} else if (inc.valueSet && inc.valueSet.length > 0) {
|
|
765
|
-
li.tx(this.translatePlural(inc.valueSet.length, '
|
|
769
|
+
li.tx(this.translatePlural(inc.valueSet.length, 'VALUE_SET_IMPORT')+" ");
|
|
766
770
|
li.startCommaList("and");
|
|
767
771
|
for (let vs of inc.valueSet) {
|
|
768
|
-
this.renderLinkComma(li, vs);
|
|
772
|
+
await this.renderLinkComma(li, vs);
|
|
769
773
|
}
|
|
770
774
|
li.stopCommaList();
|
|
771
775
|
} else {
|
|
@@ -773,18 +777,22 @@ class Renderer {
|
|
|
773
777
|
}
|
|
774
778
|
}
|
|
775
779
|
|
|
776
|
-
generateProperties(x, cs) {
|
|
780
|
+
async generateProperties(x, cs) {
|
|
777
781
|
if (!cs.property || cs.property.length === 0) {
|
|
778
782
|
return false;
|
|
779
783
|
}
|
|
780
784
|
|
|
785
|
+
const EXT_PROPERTY_VALUESET = 'http://hl7.org/fhir/StructureDefinition/codesystem-property-valueset';
|
|
786
|
+
|
|
781
787
|
// Check what columns we need
|
|
782
788
|
let hasURI = false;
|
|
783
789
|
let hasDescription = false;
|
|
790
|
+
let hasValueSet = false;
|
|
784
791
|
|
|
785
792
|
for (const p of cs.property) {
|
|
786
793
|
hasURI = hasURI || !!p.uri;
|
|
787
794
|
hasDescription = hasDescription || !!p.description;
|
|
795
|
+
hasValueSet = hasValueSet || !!Extensions.has(p, EXT_PROPERTY_VALUESET);
|
|
788
796
|
}
|
|
789
797
|
|
|
790
798
|
x.para().b().tx(this.translate('GENERAL_PROPS'));
|
|
@@ -800,6 +808,9 @@ class Renderer {
|
|
|
800
808
|
if (hasDescription) {
|
|
801
809
|
tr.th().tx(this.translate('GENERAL_DESC'));
|
|
802
810
|
}
|
|
811
|
+
if (hasValueSet) {
|
|
812
|
+
tr.th().tx(this.translate('GENERAL_VALUESET'));
|
|
813
|
+
}
|
|
803
814
|
|
|
804
815
|
for (const p of cs.property) {
|
|
805
816
|
const row = tbl.tr();
|
|
@@ -811,6 +822,13 @@ class Renderer {
|
|
|
811
822
|
if (hasDescription) {
|
|
812
823
|
row.td().tx(p.description || '');
|
|
813
824
|
}
|
|
825
|
+
if (hasValueSet) {
|
|
826
|
+
const td = row.td();
|
|
827
|
+
const vsUrl = Extensions.readString(p, EXT_PROPERTY_VALUESET);
|
|
828
|
+
if (vsUrl) {
|
|
829
|
+
await this.renderLink(td, vsUrl);
|
|
830
|
+
}
|
|
831
|
+
}
|
|
814
832
|
}
|
|
815
833
|
|
|
816
834
|
return true;
|
|
@@ -879,6 +897,9 @@ class Renderer {
|
|
|
879
897
|
if (columnInfo.hasDefinition) {
|
|
880
898
|
headerRow.th().tx(this.translate('GENERAL_DEFINITION'));
|
|
881
899
|
}
|
|
900
|
+
if (columnInfo.hasComment) {
|
|
901
|
+
headerRow.th().tx(this.translate('GENERAL_COMMENTS'));
|
|
902
|
+
}
|
|
882
903
|
if (columnInfo.hasDeprecated) {
|
|
883
904
|
headerRow.th().tx(this.translate('CODESYSTEM_DEPRECATED'));
|
|
884
905
|
}
|
|
@@ -888,6 +909,11 @@ class Renderer {
|
|
|
888
909
|
headerRow.th().tx(this.getDisplayForProperty(prop) || prop.code);
|
|
889
910
|
}
|
|
890
911
|
|
|
912
|
+
// Designation columns (by use + language)
|
|
913
|
+
for (const desig of columnInfo.designations) {
|
|
914
|
+
headerRow.th().tx(this.formatDesignationHeader(desig));
|
|
915
|
+
}
|
|
916
|
+
|
|
891
917
|
// Render concepts recursively
|
|
892
918
|
for (const concept of cs.concept) {
|
|
893
919
|
await this.addConceptRow(tbl, concept, 0, cs, columnInfo);
|
|
@@ -913,17 +939,23 @@ class Renderer {
|
|
|
913
939
|
}
|
|
914
940
|
|
|
915
941
|
analyzeConceptColumns(cs) {
|
|
942
|
+
const EXT_CS_COMMENT = 'http://hl7.org/fhir/StructureDefinition/codesystem-concept-comments';
|
|
943
|
+
const EXT_DISPLAY_HINT = 'http://hl7.org/fhir/StructureDefinition/structuredefinition-display-hint';
|
|
916
944
|
const info = {
|
|
917
945
|
hasHierarchy: false,
|
|
918
946
|
hasDisplay: false,
|
|
919
947
|
hasDefinition: false,
|
|
920
948
|
hasDeprecated: false,
|
|
921
949
|
hasComment: false,
|
|
922
|
-
properties: []
|
|
950
|
+
properties: [],
|
|
951
|
+
designations: [],
|
|
952
|
+
useMarkdown: this.hasMarkdownInDefinitions(cs)
|
|
923
953
|
};
|
|
924
954
|
|
|
925
955
|
// Check which properties are actually used
|
|
926
956
|
const usedProperties = new Set();
|
|
957
|
+
// Track distinct designations, keyed by use + language.
|
|
958
|
+
const usedDesignations = new Map();
|
|
927
959
|
|
|
928
960
|
const analyzeConceptList = (concepts) => {
|
|
929
961
|
for (const c of concepts) {
|
|
@@ -943,30 +975,59 @@ class Renderer {
|
|
|
943
975
|
info.hasDeprecated = true;
|
|
944
976
|
}
|
|
945
977
|
|
|
978
|
+
// Check for concept comments
|
|
979
|
+
if (Extensions.has(c, EXT_CS_COMMENT)) {
|
|
980
|
+
info.hasComment = true;
|
|
981
|
+
}
|
|
982
|
+
|
|
946
983
|
// Track used properties
|
|
947
984
|
if (c.property) {
|
|
948
985
|
for (const prop of c.property) {
|
|
949
986
|
usedProperties.add(prop.code);
|
|
950
987
|
}
|
|
951
988
|
}
|
|
989
|
+
|
|
990
|
+
// Track distinct designations (by use + language)
|
|
991
|
+
if (c.designation) {
|
|
992
|
+
for (const desig of c.designation) {
|
|
993
|
+
const key = this.getDesignationKey(desig);
|
|
994
|
+
if (!usedDesignations.has(key)) {
|
|
995
|
+
usedDesignations.set(key, { use: desig.use, language: desig.language });
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
}
|
|
952
999
|
}
|
|
953
1000
|
};
|
|
954
1001
|
|
|
955
1002
|
analyzeConceptList(cs.concept || []);
|
|
956
1003
|
|
|
1004
|
+
// If any property carries a display-hint extension, switch to "manual"
|
|
1005
|
+
// column selection: only properties explicitly hinted display/no-link show.
|
|
1006
|
+
const isManual = (cs.property || []).some(p => Extensions.has(p, EXT_DISPLAY_HINT));
|
|
1007
|
+
|
|
957
1008
|
// Filter to properties that are actually used
|
|
958
1009
|
if (cs.property) {
|
|
959
1010
|
for (const prop of cs.property) {
|
|
960
|
-
if (usedProperties.has(prop.code) && this.showPropertyInTable(prop)) {
|
|
1011
|
+
if (usedProperties.has(prop.code) && this.showPropertyInTable(prop, isManual)) {
|
|
961
1012
|
info.properties.push(prop);
|
|
962
1013
|
}
|
|
963
1014
|
}
|
|
964
1015
|
}
|
|
965
1016
|
|
|
1017
|
+
// Convert designation map to array, sorted for stable ordering
|
|
1018
|
+
info.designations = Array.from(usedDesignations.values()).sort((a, b) => {
|
|
1019
|
+
return this.getDesignationKey(a).localeCompare(this.getDesignationKey(b));
|
|
1020
|
+
});
|
|
1021
|
+
|
|
966
1022
|
return info;
|
|
967
1023
|
}
|
|
968
1024
|
|
|
969
|
-
showPropertyInTable(prop) {
|
|
1025
|
+
showPropertyInTable(prop, isManual = false) {
|
|
1026
|
+
if (isManual) {
|
|
1027
|
+
// Manual mode: only show properties explicitly hinted display/no-link.
|
|
1028
|
+
const hint = Extensions.readString(prop, 'http://hl7.org/fhir/StructureDefinition/structuredefinition-display-hint');
|
|
1029
|
+
return hint === 'display' || hint === 'no-link';
|
|
1030
|
+
}
|
|
970
1031
|
// Skip certain internal properties
|
|
971
1032
|
const skipCodes = ['status', 'inactive', 'deprecated', 'notSelectable'];
|
|
972
1033
|
return !skipCodes.includes(prop.code);
|
|
@@ -977,6 +1038,60 @@ class Renderer {
|
|
|
977
1038
|
return prop.description || prop.code;
|
|
978
1039
|
}
|
|
979
1040
|
|
|
1041
|
+
/**
|
|
1042
|
+
* Translations of a concept's definition, carried as translation extensions
|
|
1043
|
+
* on the definition primitive (the sibling _definition element in JSON).
|
|
1044
|
+
* Returns [{lang, value}]. Display translations are intentionally NOT read
|
|
1045
|
+
* here - display variants belong in designations.
|
|
1046
|
+
*/
|
|
1047
|
+
definitionTranslations(concept) {
|
|
1048
|
+
const el = concept._definition;
|
|
1049
|
+
if (!el || !el.extension) {
|
|
1050
|
+
return [];
|
|
1051
|
+
}
|
|
1052
|
+
const out = [];
|
|
1053
|
+
for (const ext of el.extension) {
|
|
1054
|
+
if (ext.url === 'http://hl7.org/fhir/StructureDefinition/translation') {
|
|
1055
|
+
const lang = Extensions.readString(ext, 'lang');
|
|
1056
|
+
const value = Extensions.readString(ext, 'content');
|
|
1057
|
+
if (value) {
|
|
1058
|
+
out.push({ lang: lang || '', value });
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
return out;
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
/**
|
|
1066
|
+
* Whether concept definitions should be rendered as markdown. Honours the
|
|
1067
|
+
* codesystem-use-markdown extension; otherwise auto-detects common markdown
|
|
1068
|
+
* constructs in any definition.
|
|
1069
|
+
*/
|
|
1070
|
+
hasMarkdownInDefinitions(cs) {
|
|
1071
|
+
const flag = Extensions.readValue(cs, 'http://hl7.org/fhir/StructureDefinition/codesystem-use-markdown');
|
|
1072
|
+
if (flag && typeof flag.valueBoolean === 'boolean') {
|
|
1073
|
+
return flag.valueBoolean;
|
|
1074
|
+
}
|
|
1075
|
+
const looksMarkdown = (str) => {
|
|
1076
|
+
if (!str) {
|
|
1077
|
+
return false;
|
|
1078
|
+
}
|
|
1079
|
+
return /\]\([^)]*\)|^\s*#{1,6}\s+|^\s*[-*+]\s+|^\s*\d+\.\s+/m.test(str);
|
|
1080
|
+
};
|
|
1081
|
+
const walk = (concepts) => {
|
|
1082
|
+
for (const c of concepts || []) {
|
|
1083
|
+
if (looksMarkdown(c.definition)) {
|
|
1084
|
+
return true;
|
|
1085
|
+
}
|
|
1086
|
+
if (c.concept && walk(c.concept)) {
|
|
1087
|
+
return true;
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
return false;
|
|
1091
|
+
};
|
|
1092
|
+
return walk(cs.concept || []);
|
|
1093
|
+
}
|
|
1094
|
+
|
|
980
1095
|
isDeprecated(concept) {
|
|
981
1096
|
if (concept.property) {
|
|
982
1097
|
for (const prop of concept.property) {
|
|
@@ -987,6 +1102,12 @@ class Renderer {
|
|
|
987
1102
|
}
|
|
988
1103
|
}
|
|
989
1104
|
}
|
|
1105
|
+
// The valueset-deprecated extension (on compose.include.concept or
|
|
1106
|
+
// expansion.contains) marks a concept as deprecated within this value set.
|
|
1107
|
+
const ext = Extensions.has(concept, 'http://hl7.org/fhir/StructureDefinition/valueset-deprecated');
|
|
1108
|
+
if (ext && ext.valueBoolean === true) {
|
|
1109
|
+
return true;
|
|
1110
|
+
}
|
|
990
1111
|
return false;
|
|
991
1112
|
}
|
|
992
1113
|
|
|
@@ -1031,7 +1152,35 @@ class Renderer {
|
|
|
1031
1152
|
|
|
1032
1153
|
// Definition column
|
|
1033
1154
|
if (columnInfo.hasDefinition) {
|
|
1034
|
-
tr.td()
|
|
1155
|
+
const td = tr.td();
|
|
1156
|
+
const useMarkdown = columnInfo.useMarkdown;
|
|
1157
|
+
if (concept.definition) {
|
|
1158
|
+
if (useMarkdown) {
|
|
1159
|
+
td.markdown(concept.definition);
|
|
1160
|
+
} else {
|
|
1161
|
+
td.tx(concept.definition);
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
// Translations of the definition (translation extension on the
|
|
1165
|
+
// definition primitive) are rendered below the definition, same cell.
|
|
1166
|
+
for (const t of this.definitionTranslations(concept)) {
|
|
1167
|
+
td.br();
|
|
1168
|
+
td.i().tx('(' + t.lang + ') ');
|
|
1169
|
+
if (useMarkdown) {
|
|
1170
|
+
td.markdown(t.value);
|
|
1171
|
+
} else {
|
|
1172
|
+
td.tx(t.value);
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
// Comment column
|
|
1178
|
+
if (columnInfo.hasComment) {
|
|
1179
|
+
const td = tr.td();
|
|
1180
|
+
const comment = Extensions.readString(concept, 'http://hl7.org/fhir/StructureDefinition/codesystem-concept-comments');
|
|
1181
|
+
if (comment) {
|
|
1182
|
+
td.tx(comment);
|
|
1183
|
+
}
|
|
1035
1184
|
}
|
|
1036
1185
|
|
|
1037
1186
|
// Deprecated column
|
|
@@ -1040,11 +1189,36 @@ class Renderer {
|
|
|
1040
1189
|
if (this.isDeprecated(concept)) {
|
|
1041
1190
|
td.tx(this.translate('CODESYSTEM_DEPRECATED_TRUE'));
|
|
1042
1191
|
|
|
1043
|
-
//
|
|
1044
|
-
|
|
1045
|
-
|
|
1192
|
+
// Replacement: prefer the codesystem-replacedby extension (a Coding),
|
|
1193
|
+
// then fall back to a 'replacedBy' property value.
|
|
1194
|
+
const replacedByExt = Extensions.readValue(concept, 'http://hl7.org/fhir/StructureDefinition/codesystem-replacedby');
|
|
1195
|
+
const replacedByCoding = replacedByExt && replacedByExt.valueCoding;
|
|
1196
|
+
const replacedByProp = this.getPropertyValue(concept, 'replacedBy');
|
|
1197
|
+
if (replacedByCoding) {
|
|
1198
|
+
td.tx(' ' + this.translate('CODESYSTEM_REPLACED_BY') + ' ');
|
|
1199
|
+
const link = this.linkResolver ?
|
|
1200
|
+
await this.linkResolver.resolveCode(this.opContext, replacedByCoding.system, replacedByCoding.version, replacedByCoding.code) : null;
|
|
1201
|
+
if (link) {
|
|
1202
|
+
td.ah(link.link).tx(replacedByCoding.code);
|
|
1203
|
+
} else {
|
|
1204
|
+
td.code().tx(replacedByCoding.code);
|
|
1205
|
+
}
|
|
1206
|
+
if (replacedByCoding.display) {
|
|
1207
|
+
td.tx(' "' + replacedByCoding.display + '"');
|
|
1208
|
+
}
|
|
1209
|
+
} else if (replacedByProp) {
|
|
1046
1210
|
td.tx(' ' + this.translate('CODESYSTEM_REPLACED_BY') + ' ');
|
|
1047
|
-
td.code().tx(
|
|
1211
|
+
td.code().tx(replacedByProp.value);
|
|
1212
|
+
} else {
|
|
1213
|
+
// No replacement: show the concept-level standards-status reason.
|
|
1214
|
+
const ssExt = Extensions.readValue(concept, 'http://hl7.org/fhir/StructureDefinition/structuredefinition-standards-status');
|
|
1215
|
+
if (ssExt) {
|
|
1216
|
+
const reason = Extensions.readString(ssExt, 'http://hl7.org/fhir/StructureDefinition/structuredefinition-standards-status-reason');
|
|
1217
|
+
if (reason) {
|
|
1218
|
+
td.tx(' ');
|
|
1219
|
+
td.markdown(reason);
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1048
1222
|
}
|
|
1049
1223
|
}
|
|
1050
1224
|
}
|
|
@@ -1052,6 +1226,7 @@ class Renderer {
|
|
|
1052
1226
|
// Property columns
|
|
1053
1227
|
for (const prop of columnInfo.properties) {
|
|
1054
1228
|
const td = tr.td();
|
|
1229
|
+
const noLink = Extensions.readString(prop, 'http://hl7.org/fhir/StructureDefinition/structuredefinition-display-hint') === 'no-link';
|
|
1055
1230
|
const values = this.getPropertyValues(concept, prop.code);
|
|
1056
1231
|
|
|
1057
1232
|
let first = true;
|
|
@@ -1061,7 +1236,16 @@ class Renderer {
|
|
|
1061
1236
|
}
|
|
1062
1237
|
first = false;
|
|
1063
1238
|
|
|
1064
|
-
await this.renderPropertyValue(td, val, prop, cs);
|
|
1239
|
+
await this.renderPropertyValue(td, val, prop, cs, noLink);
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
// Designation columns (by use + language)
|
|
1244
|
+
for (const desigDef of columnInfo.designations) {
|
|
1245
|
+
const td = tr.td();
|
|
1246
|
+
const value = this.getDesignationValue(concept, desigDef);
|
|
1247
|
+
if (value) {
|
|
1248
|
+
td.tx(value);
|
|
1065
1249
|
}
|
|
1066
1250
|
}
|
|
1067
1251
|
|
|
@@ -1098,13 +1282,13 @@ class Renderer {
|
|
|
1098
1282
|
return null;
|
|
1099
1283
|
}
|
|
1100
1284
|
|
|
1101
|
-
async renderPropertyValue(td, val, propDef, cs) {
|
|
1285
|
+
async renderPropertyValue(td, val, propDef, cs, noLink = false) {
|
|
1102
1286
|
if (!val) return;
|
|
1103
1287
|
|
|
1104
1288
|
switch (val.type) {
|
|
1105
1289
|
case 'code': {
|
|
1106
1290
|
// If it's a parent reference, link to it
|
|
1107
|
-
if (propDef.code === 'parent' || propDef.code === 'child') {
|
|
1291
|
+
if ((propDef.code === 'parent' || propDef.code === 'child') && !noLink) {
|
|
1108
1292
|
td.ah('#' + cs.id + '-' + val.value).tx(val.value);
|
|
1109
1293
|
} else {
|
|
1110
1294
|
td.code().tx(val.value);
|
|
@@ -1113,7 +1297,7 @@ class Renderer {
|
|
|
1113
1297
|
}
|
|
1114
1298
|
case 'coding': {
|
|
1115
1299
|
const coding = val.value;
|
|
1116
|
-
const link = this.linkResolver ?
|
|
1300
|
+
const link = (!noLink && this.linkResolver) ?
|
|
1117
1301
|
await this.linkResolver.resolveCode(this.opContext, coding.system, coding.version, coding.code) : null;
|
|
1118
1302
|
if (link) {
|
|
1119
1303
|
td.ah(link.link).tx(coding.code);
|
|
@@ -1131,7 +1315,7 @@ class Renderer {
|
|
|
1131
1315
|
}
|
|
1132
1316
|
case 'string': {
|
|
1133
1317
|
// Check if it's a URL
|
|
1134
|
-
if (val.value.startsWith('http://') || val.value.startsWith('https://')) {
|
|
1318
|
+
if (!noLink && (val.value.startsWith('http://') || val.value.startsWith('https://'))) {
|
|
1135
1319
|
td.ah(val.value).tx(val.value);
|
|
1136
1320
|
} else {
|
|
1137
1321
|
td.tx(val.value);
|
|
@@ -1215,6 +1399,19 @@ class Renderer {
|
|
|
1215
1399
|
}
|
|
1216
1400
|
|
|
1217
1401
|
async renderExpansion(x, vs, tbl) {
|
|
1402
|
+
// Surface expansion-level status extensions as prominent warnings: the
|
|
1403
|
+
// expansion may not contain every code in the value set.
|
|
1404
|
+
if (Extensions.has(vs.expansion, 'http://hl7.org/fhir/StructureDefinition/valueset-unclosed')) {
|
|
1405
|
+
const reason = Extensions.readString(vs.expansion, 'http://hl7.org/fhir/StructureDefinition/valueset-unclosed-reason');
|
|
1406
|
+
const p = x.para();
|
|
1407
|
+
p.b().tx('Warning: ');
|
|
1408
|
+
p.tx('This expansion is not closed - it may not contain all the codes in the value set.' + (reason ? ' ' + reason : ''));
|
|
1409
|
+
}
|
|
1410
|
+
if (Extensions.has(vs.expansion, 'http://hl7.org/fhir/StructureDefinition/valueset-toocostly')) {
|
|
1411
|
+
const p = x.para();
|
|
1412
|
+
p.b().tx('Warning: ');
|
|
1413
|
+
p.tx('This expansion is incomplete - a complete expansion was too costly to produce.');
|
|
1414
|
+
}
|
|
1218
1415
|
this.renderProperty(tbl, 'Expansion Identifier', vs.expansion.identifier);
|
|
1219
1416
|
this.renderProperty(tbl, 'Expansion Timestamp', vs.expansion.timestamp);
|
|
1220
1417
|
this.renderProperty(tbl, 'Expansion Total', vs.expansion.total);
|
package/tx/library.js
CHANGED
|
@@ -481,7 +481,11 @@ class Library {
|
|
|
481
481
|
throw new Error("Unable to load VSAC provider unless vsacCfg is provided in the configuration");
|
|
482
482
|
}
|
|
483
483
|
let vsac = new VSACValueSetProvider(this.vsacCfg, this.stats);
|
|
484
|
-
|
|
484
|
+
// Intentionally not awaited: initialize() might run a full VSAC sync on first load,
|
|
485
|
+
// which can take minutes - we don't want to block startup. But attach a handler
|
|
486
|
+
// so a failed background sync surfaces as a log error rather than an unhandled
|
|
487
|
+
// promise rejection.
|
|
488
|
+
vsac.initialize().catch((err) => this.log.error(`VSAC initialization failed: ${err.message}`));
|
|
485
489
|
this.valueSetProviders.push(vsac);
|
|
486
490
|
this.externalSources.push(vsac);
|
|
487
491
|
//const mem = process.memoryUsage();
|