fhirsmith 0.8.3 → 0.8.4
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 +25 -3
- package/package.json +1 -1
- package/tx/cs/cs-api.js +19 -4
- package/tx/cs/cs-base.js +13 -0
- package/tx/cs/cs-snomed.js +64 -3
- package/tx/library/codesystem.js +20 -0
- package/tx/library/designations.js +37 -1
- package/tx/provider.js +30 -0
- package/tx/workers/expand.js +19 -12
- package/tx/workers/lookup.js +19 -3
- package/tx/workers/worker.js +8 -2
- package/tx/xversion/xv-codesystem.js +3 -3
- package/tx/xversion/xv-parameters.js +2 -2
- package/tx/xversion/xv-valueset.js +1 -2
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,29 @@ All notable changes to the Health Intersections Node Server will be documented i
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
## [v0.8.4] - 2026-04-01
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- add .npmrc to defend against supply chain attacks
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
|
|
18
|
+
- Rework extension handling to make sure uzbek loinc works - load supplements from store
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
|
|
22
|
+
- tx/expand: fix for bug where filter array is present but empty
|
|
23
|
+
- tx/SCT: support filters generalizes and child-of
|
|
24
|
+
- tx/SCT: fix bug evaluating property filters
|
|
25
|
+
- Fix version conversion issues
|
|
26
|
+
|
|
27
|
+
### Tx Conformance Statement
|
|
28
|
+
|
|
29
|
+
FHIRsmith passed all 1497 HL7 terminology service tests (modes tx.fhir.org+omop+general+snomed, tests v1.9.1, runner v6.9.4)
|
|
30
|
+
|
|
8
31
|
## [v0.8.3] - 2026-03-31
|
|
9
32
|
|
|
10
33
|
### Changed
|
|
@@ -15,15 +38,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
15
38
|
- Publisher: Allow non-admin users to delete non-approved tasks
|
|
16
39
|
|
|
17
40
|
### Fixed
|
|
18
|
-
|
|
41
|
+
|
|
19
42
|
- Publisher: fix task logging
|
|
20
|
-
- SHL: path fixes
|
|
43
|
+
- SHL: path fixes
|
|
21
44
|
|
|
22
45
|
### Tx Conformance Statement
|
|
23
46
|
|
|
24
47
|
FHIRsmith passed all 1497 HL7 terminology service tests (modes tx.fhir.org+omop+general+snomed, tests v1.9.1, runner v6.9.4)
|
|
25
48
|
|
|
26
|
-
|
|
27
49
|
## [v0.8.2] - 2026-03-29
|
|
28
50
|
|
|
29
51
|
### Added
|
package/package.json
CHANGED
package/tx/cs/cs-api.js
CHANGED
|
@@ -221,10 +221,11 @@ class CodeSystemProvider {
|
|
|
221
221
|
}
|
|
222
222
|
|
|
223
223
|
/**
|
|
224
|
+
* @param {boolean} langPacks - whether to include language packs
|
|
224
225
|
* @returns {string[]} all supplements in scope
|
|
225
226
|
*/
|
|
226
|
-
listSupplements() {
|
|
227
|
-
return this.supplements ? this.supplements.map(s => s.vurl) : [];
|
|
227
|
+
listSupplements(langPacks) {
|
|
228
|
+
return this.supplements ? this.supplements.filter(s => langPacks || !s.isLangPack()).map(s => s.vurl) : [];
|
|
228
229
|
}
|
|
229
230
|
|
|
230
231
|
/**
|
|
@@ -385,12 +386,15 @@ class CodeSystemProvider {
|
|
|
385
386
|
const concept= supplement.getConceptByCode(code);
|
|
386
387
|
if (concept) {
|
|
387
388
|
if (concept.display) {
|
|
388
|
-
|
|
389
|
+
// sometimes the display is just repeated from the base code system
|
|
390
|
+
if (!displays.hasAnyDisplay(concept.display)) {
|
|
391
|
+
displays.addDesignation(true, 'active', supplement.jsonObj.language, CodeSystem.makeUseForDisplay(), concept.display).supplement = supplement;
|
|
392
|
+
}
|
|
389
393
|
}
|
|
390
394
|
if (concept.designation) {
|
|
391
395
|
for (const d of concept.designation) {
|
|
392
396
|
let status = Extensions.readString(d, "http://hl7.org/fhir/StructureDefinition/structuredefinition-standards-status");
|
|
393
|
-
displays.addDesignation(false, status || 'active', d.language, d.use, d.value, d.extension?.length > 0 ? d.extension : []);
|
|
397
|
+
displays.addDesignation(false, status || 'active', d.language, d.use, d.value, d.extension?.length > 0 ? d.extension : []).supplement = supplement;
|
|
394
398
|
}
|
|
395
399
|
}
|
|
396
400
|
}
|
|
@@ -854,6 +858,17 @@ class CodeSystemFactoryProvider {
|
|
|
854
858
|
return [];
|
|
855
859
|
}
|
|
856
860
|
|
|
861
|
+
/**
|
|
862
|
+
*
|
|
863
|
+
* @param supplements - the list of supplements to populate - fill with any supplements matching url(+version)
|
|
864
|
+
* @param url - url of code system
|
|
865
|
+
* @param version - version of codesystem
|
|
866
|
+
* @param {Map} statedSupplements - return language packs and supplements that are listed in stated supplements, by versioned URL
|
|
867
|
+
* @returns {Promise<void>}
|
|
868
|
+
*/
|
|
869
|
+
async listSupplements(supplements, url, version, statedSupplements) {
|
|
870
|
+
// do nothing
|
|
871
|
+
}
|
|
857
872
|
/**
|
|
858
873
|
* see comments for registerSupplements()
|
|
859
874
|
*
|
package/tx/cs/cs-base.js
CHANGED
|
@@ -41,6 +41,19 @@ class BaseCSServices extends CodeSystemProvider {
|
|
|
41
41
|
return property;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
+
_addDateTimeProperty(params, type, name, value) {
|
|
45
|
+
|
|
46
|
+
const property = {
|
|
47
|
+
name: type,
|
|
48
|
+
part: [
|
|
49
|
+
{name: 'code', valueCode: name},
|
|
50
|
+
{name: 'value', valueDateTime: value}
|
|
51
|
+
]
|
|
52
|
+
};
|
|
53
|
+
params.push(property);
|
|
54
|
+
return property;
|
|
55
|
+
}
|
|
56
|
+
|
|
44
57
|
_addStringProperty(params, type, name, value, language = null) {
|
|
45
58
|
|
|
46
59
|
const property = {
|
package/tx/cs/cs-snomed.js
CHANGED
|
@@ -355,6 +355,52 @@ class SnomedServices {
|
|
|
355
355
|
return result;
|
|
356
356
|
}
|
|
357
357
|
|
|
358
|
+
filterChildOf(id = true) {
|
|
359
|
+
const result = new SnomedFilterContext();
|
|
360
|
+
const conceptResult = this.concepts.findConcept(id);
|
|
361
|
+
|
|
362
|
+
if (!conceptResult.found) {
|
|
363
|
+
throw new Error(`The SNOMED CT Concept ${id} is not known`);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
const descendants = this.getConceptChildren(conceptResult.index);
|
|
367
|
+
|
|
368
|
+
result.descendants = descendants;
|
|
369
|
+
|
|
370
|
+
return result;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
filterGeneralizes(id = true) {
|
|
375
|
+
const result = new SnomedFilterContext();
|
|
376
|
+
const conceptResult = this.concepts.findConcept(id);
|
|
377
|
+
|
|
378
|
+
if (!conceptResult.found) {
|
|
379
|
+
throw new Error(`The SNOMED CT Concept ${id} is not known`);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
let ancestors = new Set();
|
|
383
|
+
let parents = this.getConceptParents(conceptResult.index);
|
|
384
|
+
let isNew = true;
|
|
385
|
+
while (isNew) {
|
|
386
|
+
isNew = false;
|
|
387
|
+
let np = [];
|
|
388
|
+
for (let parent of parents) {
|
|
389
|
+
if (!ancestors.has(parent)) {
|
|
390
|
+
isNew = true;
|
|
391
|
+
ancestors.add(parent);
|
|
392
|
+
np.push(...this.getConceptParents(parent));
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
parents = np;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
result.descendants = [...ancestors];
|
|
399
|
+
|
|
400
|
+
return result;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
|
|
358
404
|
filterIn(id) {
|
|
359
405
|
const result = new SnomedFilterContext();
|
|
360
406
|
const conceptResult = this.concepts.findConcept(id);
|
|
@@ -775,6 +821,13 @@ class SnomedProvider extends BaseCSServices {
|
|
|
775
821
|
const ctxt = await this.#ensureContext(context);
|
|
776
822
|
if (ctxt) {
|
|
777
823
|
if (!(ctxt instanceof SnomedExpressionContext) || ctxt.expression?.concepts.length == 1) {
|
|
824
|
+
const time = this.sct.concepts.getConcept(ctxt.getReference()).effectiveTime;
|
|
825
|
+
const pascalEpoch = new Date(1899, 11, 30);
|
|
826
|
+
const date = new Date(pascalEpoch.getTime() + time * 86400000);
|
|
827
|
+
const dateStr = date.toISOString().slice(0, 10);
|
|
828
|
+
this._addDateTimeProperty(params, 'property', 'effectiveTime', dateStr);
|
|
829
|
+
|
|
830
|
+
|
|
778
831
|
const parents = this.sct.getConceptParents(ctxt.getReference());
|
|
779
832
|
for (let parentRef of parents) {
|
|
780
833
|
const code = this.sct.getConceptId(parentRef);
|
|
@@ -792,7 +845,7 @@ class SnomedProvider extends BaseCSServices {
|
|
|
792
845
|
const moduleId = this.sct.concepts.getModuleId(ctxt.getReference());
|
|
793
846
|
if (moduleId) {
|
|
794
847
|
const code = this.sct.getConceptId(moduleId);
|
|
795
|
-
this._addCodeProperty(params, 'property', 'module', code, null,
|
|
848
|
+
this._addCodeProperty(params, 'property', 'module', code, null, this.sct.getDisplayName(moduleId));
|
|
796
849
|
}
|
|
797
850
|
|
|
798
851
|
const relationships = this.sct.getConceptRelationships(ctxt.getReference());
|
|
@@ -838,7 +891,7 @@ class SnomedProvider extends BaseCSServices {
|
|
|
838
891
|
async doesFilter(prop, op, value) {
|
|
839
892
|
if (prop === 'concept') {
|
|
840
893
|
const id = this.sct.stringToIdOrZero(value);
|
|
841
|
-
if (id !== 0n && ['=', 'is-a', 'descendent-of', 'in'].includes(op)) {
|
|
894
|
+
if (id !== 0n && ['=', 'is-a', 'descendent-of', 'in', 'generalizes', 'child-of'].includes(op)) {
|
|
842
895
|
return this.sct.conceptExists(value);
|
|
843
896
|
}
|
|
844
897
|
}
|
|
@@ -891,6 +944,14 @@ class SnomedProvider extends BaseCSServices {
|
|
|
891
944
|
filterContext.filters.push(this.sct.filterIsA(id, false));
|
|
892
945
|
return null;
|
|
893
946
|
}
|
|
947
|
+
case 'child-of': {
|
|
948
|
+
filterContext.filters.push(this.sct.filterChildOf(id));
|
|
949
|
+
return null;
|
|
950
|
+
}
|
|
951
|
+
case 'generalizes': {
|
|
952
|
+
filterContext.filters.push(this.sct.filterGeneralizes(id, false));
|
|
953
|
+
return null;
|
|
954
|
+
}
|
|
894
955
|
case 'in': {
|
|
895
956
|
filterContext.filters.push(this.sct.filterIn(id));
|
|
896
957
|
return null;
|
|
@@ -1121,7 +1182,7 @@ class SnomedProvider extends BaseCSServices {
|
|
|
1121
1182
|
if (set.propProp || set.propValue) {
|
|
1122
1183
|
for (let i = 0; i < this.sct.concepts.count(); i++) {
|
|
1123
1184
|
let concept = this.sct.concepts.getConceptByCount(i);
|
|
1124
|
-
const relationships = this.sct.getConceptRelationships(concept.
|
|
1185
|
+
const relationships = this.sct.getConceptRelationships(concept.index);
|
|
1125
1186
|
for (let relationshipRef of relationships) {
|
|
1126
1187
|
const relationship = this.sct.relationships.getRelationship(relationshipRef);
|
|
1127
1188
|
if (set.propProp === relationship.relType && set.propValue === relationship.target) {
|
package/tx/library/codesystem.js
CHANGED
|
@@ -2,6 +2,7 @@ const { Language } = require("../../library/languages");
|
|
|
2
2
|
const {CanonicalResource} = require("./canonical-resource");
|
|
3
3
|
const {codeSystemFromR5, codeSystemToR5} = require("../xversion/xv-codesystem");
|
|
4
4
|
const {getValuePrimitive} = require("../../library/utilities");
|
|
5
|
+
const {VersionUtilities} = require("../../library/version-utilities");
|
|
5
6
|
|
|
6
7
|
const CodeSystemContentMode = Object.freeze({
|
|
7
8
|
Complete: 'complete',
|
|
@@ -614,12 +615,31 @@ class CodeSystem extends CanonicalResource {
|
|
|
614
615
|
}
|
|
615
616
|
|
|
616
617
|
isLangPack() {
|
|
618
|
+
// todo: this is a temporary work around until fhir.tx.support.r4#0.37.0 is released that properly marks this as a language pack
|
|
619
|
+
if (this.jsonObj.url === 'https://terminology.dhp.uz/fhir/CodeSystem/loinc-supplement-uz') {
|
|
620
|
+
return true;
|
|
621
|
+
}
|
|
617
622
|
return (this.jsonObj.extension || []).find(x => x.url == 'http://hl7.org/fhir/StructureDefinition/codesystem-supplement-type' && getValuePrimitive(x) == 'lang-pack');
|
|
618
623
|
}
|
|
619
624
|
|
|
620
625
|
isPropUri(code, uri) {
|
|
621
626
|
return (this.jsonObj.property || []).find(x => x.code == code && x.uri == uri);
|
|
622
627
|
}
|
|
628
|
+
|
|
629
|
+
isSupplementFor(url, version) {
|
|
630
|
+
if (this.jsonObj.content !== 'supplement') {
|
|
631
|
+
return false;
|
|
632
|
+
}
|
|
633
|
+
let suppU = this.jsonObj.supplements;
|
|
634
|
+
if (suppU == url) {
|
|
635
|
+
return true;
|
|
636
|
+
}
|
|
637
|
+
if (suppU.startsWith(url + '|')) {
|
|
638
|
+
let suppV = suppU.substring(suppU.indexOf('|') + 1);
|
|
639
|
+
return VersionUtilities.versionMatchesByAlgorithm(suppV, version, VersionUtilities.guessVersionAlgorithmFromVersion(version));
|
|
640
|
+
}
|
|
641
|
+
return false;
|
|
642
|
+
}
|
|
623
643
|
}
|
|
624
644
|
|
|
625
645
|
module.exports = { CodeSystem, CodeSystemContentMode };
|
|
@@ -404,6 +404,15 @@ class Designations {
|
|
|
404
404
|
// }
|
|
405
405
|
}
|
|
406
406
|
|
|
407
|
+
hasAnyDisplay(value) {
|
|
408
|
+
for (let designation of this.designations) {
|
|
409
|
+
if (designation.value === value) {
|
|
410
|
+
return true;
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
return false;
|
|
414
|
+
}
|
|
415
|
+
|
|
407
416
|
/**
|
|
408
417
|
* Check if a display value exists with specified matching criteria
|
|
409
418
|
*/
|
|
@@ -551,7 +560,7 @@ class Designations {
|
|
|
551
560
|
/**
|
|
552
561
|
* Find the preferred designation for given language preferences
|
|
553
562
|
*/
|
|
554
|
-
preferredDesignation(langList = null) {
|
|
563
|
+
preferredDesignation(langList = null, supplements = null) {
|
|
555
564
|
if (this.designations.length === 0) {
|
|
556
565
|
return null;
|
|
557
566
|
}
|
|
@@ -560,24 +569,39 @@ class Designations {
|
|
|
560
569
|
// No language list, prefer base designations
|
|
561
570
|
for (const cd of this.designations) {
|
|
562
571
|
if (this._isPreferred(cd) && cd.isActive()) {
|
|
572
|
+
if (supplements && cd.source) {
|
|
573
|
+
supplements.add(cd.source);
|
|
574
|
+
}
|
|
563
575
|
return cd;
|
|
564
576
|
}
|
|
565
577
|
}
|
|
566
578
|
for (const cd of this.designations) {
|
|
567
579
|
if (this.isDisplay(cd) && cd.isActive()) {
|
|
580
|
+
if (supplements && cd.source) {
|
|
581
|
+
supplements.add(cd.source);
|
|
582
|
+
}
|
|
568
583
|
return cd;
|
|
569
584
|
}
|
|
570
585
|
}
|
|
571
586
|
for (const cd of this.designations) {
|
|
572
587
|
if (this.isDisplay(cd)) {
|
|
588
|
+
if (supplements && cd.source) {
|
|
589
|
+
supplements.add(cd.source);
|
|
590
|
+
}
|
|
573
591
|
return cd;
|
|
574
592
|
}
|
|
575
593
|
}
|
|
576
594
|
for (const cd of this.designations) {
|
|
577
595
|
if (this._isPreferred(cd)) {
|
|
596
|
+
if (supplements && cd.source) {
|
|
597
|
+
supplements.add(cd.source);
|
|
598
|
+
}
|
|
578
599
|
return cd;
|
|
579
600
|
}
|
|
580
601
|
}
|
|
602
|
+
if (supplements && this.designations[0].source) {
|
|
603
|
+
supplements.add(this.designations[0].source);
|
|
604
|
+
}
|
|
581
605
|
return this.designations[0];
|
|
582
606
|
}
|
|
583
607
|
|
|
@@ -589,16 +613,25 @@ class Designations {
|
|
|
589
613
|
for (const matchType of matchTypes) {
|
|
590
614
|
for (const cd of this.designations) {
|
|
591
615
|
if (this._langMatches(lang, cd.language, matchType) && this.isDisplay(cd)) {
|
|
616
|
+
if (supplements && cd.source) {
|
|
617
|
+
supplements.add(cd.source);
|
|
618
|
+
}
|
|
592
619
|
return cd;
|
|
593
620
|
}
|
|
594
621
|
}
|
|
595
622
|
for (const cd of this.designations) {
|
|
596
623
|
if (this._langMatches(lang, cd.language, matchType) && this._isPreferred(cd)) {
|
|
624
|
+
if (supplements && cd.source) {
|
|
625
|
+
supplements.add(cd.source);
|
|
626
|
+
}
|
|
597
627
|
return cd;
|
|
598
628
|
}
|
|
599
629
|
}
|
|
600
630
|
for (const cd of this.designations) {
|
|
601
631
|
if (this._langMatches(lang, cd.language, matchType)) {
|
|
632
|
+
if (supplements && cd.source) {
|
|
633
|
+
supplements.add(cd.source);
|
|
634
|
+
}
|
|
602
635
|
return cd;
|
|
603
636
|
}
|
|
604
637
|
}
|
|
@@ -606,6 +639,9 @@ class Designations {
|
|
|
606
639
|
}
|
|
607
640
|
for (const cd of this.designations) {
|
|
608
641
|
if (!cd.language && this.isDisplay(cd)) {
|
|
642
|
+
if (supplements && cd.source) {
|
|
643
|
+
supplements.add(cd.source);
|
|
644
|
+
}
|
|
609
645
|
return cd;
|
|
610
646
|
}
|
|
611
647
|
}
|
package/tx/provider.js
CHANGED
|
@@ -107,6 +107,36 @@ class Provider {
|
|
|
107
107
|
return null;
|
|
108
108
|
}
|
|
109
109
|
|
|
110
|
+
loadSupplements(url, version, statedSupplements) {
|
|
111
|
+
let supplements = new Map();
|
|
112
|
+
for (let csp of this.codeSystemFactories.values()) {
|
|
113
|
+
csp.listSupplements(supplements, url, version, statedSupplements);
|
|
114
|
+
}
|
|
115
|
+
for (let cs of this.codeSystems.values()) {
|
|
116
|
+
if (cs.isSupplementFor(url, version)) {
|
|
117
|
+
if (cs.isLangPack() || this.isStatedSupplement(cs, statedSupplements)) {
|
|
118
|
+
supplements.set(cs.vurl, cs);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return [...supplements.values()];
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
isStatedSupplement(cs, statedSupplements) {
|
|
126
|
+
if (statedSupplements == null) {
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
for (let suppU of statedSupplements) {
|
|
130
|
+
if (suppU === cs.url) {
|
|
131
|
+
return true;
|
|
132
|
+
}
|
|
133
|
+
if (suppU.startsWith(cs.url+"|")) {
|
|
134
|
+
let suppV = suppU.substring(suppU.indexOf('|') + 1);
|
|
135
|
+
return VersionUtilities.versionMatchesByAlgorithm(suppV, cs.version, VersionUtilities.guessVersionAlgorithmFromVersion(cs.version));
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
110
140
|
/**
|
|
111
141
|
* Create a code system provider from a CodeSystem resource
|
|
112
142
|
* @param {OperationContext} opContext - The code system resource
|
package/tx/workers/expand.js
CHANGED
|
@@ -204,6 +204,7 @@ class ValueSetExpander {
|
|
|
204
204
|
hasExclusions = false;
|
|
205
205
|
requiredSupplements = new Set();
|
|
206
206
|
usedSupplements = new Set();
|
|
207
|
+
reportedSupplements = new Set();
|
|
207
208
|
internalLimit = INTERNAL_DEFAULT_LIMIT;
|
|
208
209
|
externalLimit = EXTERNAL_DEFAULT_LIMIT;
|
|
209
210
|
|
|
@@ -339,9 +340,9 @@ class ValueSetExpander {
|
|
|
339
340
|
const s = this.canonical(system, version);
|
|
340
341
|
this.addParamUri(expansion, 'used-codesystem', s);
|
|
341
342
|
if (cs != null) {
|
|
342
|
-
const ts = cs.listSupplements();
|
|
343
|
+
const ts = cs.listSupplements(false);
|
|
343
344
|
for (const vs of ts) {
|
|
344
|
-
this.
|
|
345
|
+
this.reportedSupplements.add(vs);
|
|
345
346
|
}
|
|
346
347
|
}
|
|
347
348
|
}
|
|
@@ -415,7 +416,7 @@ class ValueSetExpander {
|
|
|
415
416
|
}
|
|
416
417
|
|
|
417
418
|
// display and designations
|
|
418
|
-
const pref = displays.preferredDesignation(this.params.workingLanguages());
|
|
419
|
+
const pref = displays.preferredDesignation(this.params.workingLanguages(), this.reportedSupplements);
|
|
419
420
|
if (pref && pref.value) {
|
|
420
421
|
n.display = pref.value;
|
|
421
422
|
}
|
|
@@ -426,6 +427,9 @@ class ValueSetExpander {
|
|
|
426
427
|
if (!n.designation) {
|
|
427
428
|
n.designation = [];
|
|
428
429
|
}
|
|
430
|
+
if (t.source) {
|
|
431
|
+
this.reportedSupplements.add(t.source);
|
|
432
|
+
}
|
|
429
433
|
n.designation.push(t.asObject());
|
|
430
434
|
}
|
|
431
435
|
}
|
|
@@ -490,9 +494,9 @@ class ValueSetExpander {
|
|
|
490
494
|
const s = this.canonical(system, version);
|
|
491
495
|
this.addParamUri(expansion, 'used-codesystem', s);
|
|
492
496
|
if (cs) {
|
|
493
|
-
const ts= cs.listSupplements();
|
|
497
|
+
const ts= cs.listSupplements(false);
|
|
494
498
|
for (const vs of ts) {
|
|
495
|
-
this.
|
|
499
|
+
this.reportedSupplements.add(vs);
|
|
496
500
|
}
|
|
497
501
|
}
|
|
498
502
|
}
|
|
@@ -813,7 +817,7 @@ class ValueSetExpander {
|
|
|
813
817
|
this.worker.opContext.log('iterate concepts done');
|
|
814
818
|
}
|
|
815
819
|
|
|
816
|
-
if (cset.filter) {
|
|
820
|
+
if (cset.filter && cset.filter.length > 0) {
|
|
817
821
|
this.worker.opContext.log('prepare filters');
|
|
818
822
|
const fcl = cset.filter;
|
|
819
823
|
const prep = await cs.getPrepContext(true);
|
|
@@ -1040,10 +1044,9 @@ class ValueSetExpander {
|
|
|
1040
1044
|
if (expansion) {
|
|
1041
1045
|
const vs = this.canonical(await cs.system(), await cs.version());
|
|
1042
1046
|
this.addParamUri(expansion, 'used-codesystem', vs);
|
|
1043
|
-
const ts = cs.listSupplements();
|
|
1047
|
+
const ts = cs.listSupplements(false);
|
|
1044
1048
|
for (const v of ts) {
|
|
1045
|
-
this.
|
|
1046
|
-
this.addParamUri(expansion, 'used-supplement', v);
|
|
1049
|
+
this.reportedSupplements.add(v);
|
|
1047
1050
|
}
|
|
1048
1051
|
}
|
|
1049
1052
|
|
|
@@ -1081,10 +1084,9 @@ class ValueSetExpander {
|
|
|
1081
1084
|
if (expansion) {
|
|
1082
1085
|
const vs = this.canonical(await cs.system(), await cs.version());
|
|
1083
1086
|
this.addParamUri(expansion, 'used-codesystem', vs);
|
|
1084
|
-
const ts= cs.listSupplements();
|
|
1087
|
+
const ts= cs.listSupplements(false);
|
|
1085
1088
|
for (const v of ts) {
|
|
1086
|
-
this.
|
|
1087
|
-
this.addParamUri(expansion, 'used-supplement', v);
|
|
1089
|
+
this.reportedSupplements.add(v);
|
|
1088
1090
|
}
|
|
1089
1091
|
}
|
|
1090
1092
|
|
|
@@ -1303,6 +1305,11 @@ class ValueSetExpander {
|
|
|
1303
1305
|
}
|
|
1304
1306
|
}
|
|
1305
1307
|
|
|
1308
|
+
const ts = this.reportedSupplements;
|
|
1309
|
+
for (const v of ts) {
|
|
1310
|
+
this.addParamUri(exp, 'used-supplement', v);
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1306
1313
|
this.worker.opContext.log('finish up');
|
|
1307
1314
|
|
|
1308
1315
|
let list;
|
package/tx/workers/lookup.js
CHANGED
|
@@ -149,14 +149,15 @@ class LookupWorker extends TerminologyWorker {
|
|
|
149
149
|
|
|
150
150
|
// check supplements
|
|
151
151
|
const used = new Set();
|
|
152
|
-
|
|
152
|
+
const reported = new Set();
|
|
153
|
+
this.checkSupplements(csProvider, null, txp.supplements, used, reported);
|
|
153
154
|
const unused = new Set([...txp.supplements].filter(s => !used.has(s)));
|
|
154
155
|
if (unused.size > 0) {
|
|
155
156
|
throw new Issue('error', 'not-found', null, 'VALUESET_SUPPLEMENT_MISSING', this.i18n.translatePlural(unused.size, 'VALUESET_SUPPLEMENT_MISSING', txp.HTTPLanguages, [[...unused].join(',')]), 'not-found').handleAsOO(400);
|
|
156
157
|
}
|
|
157
158
|
|
|
158
159
|
// Perform the lookup
|
|
159
|
-
const result = await this.doLookup(csProvider, code, txp);
|
|
160
|
+
const result = await this.doLookup(csProvider, code, txp, reported);
|
|
160
161
|
return res.status(200).json(result);
|
|
161
162
|
} catch (error) {
|
|
162
163
|
this.log.error(error);
|
|
@@ -242,9 +243,10 @@ class LookupWorker extends TerminologyWorker {
|
|
|
242
243
|
* @param {CodeSystemProvider} csProvider - CodeSystem provider
|
|
243
244
|
* @param {string} code - Code to look up
|
|
244
245
|
* @param {Object} params - Parsed parameters
|
|
246
|
+
* @param {Set} reportedSupplements - Set of supplements that are to be reported
|
|
245
247
|
* @returns {Object} Parameters resource with lookup result
|
|
246
248
|
*/
|
|
247
|
-
async doLookup(csProvider, code, params) {
|
|
249
|
+
async doLookup(csProvider, code, params, reportedSupplements) {
|
|
248
250
|
this.deadCheck('doLookup');
|
|
249
251
|
|
|
250
252
|
await this.checkSupplements(csProvider, null, params.supplements);
|
|
@@ -352,6 +354,12 @@ class LookupWorker extends TerminologyWorker {
|
|
|
352
354
|
this.deadCheck('doLookup-designations');
|
|
353
355
|
const designationParts = [];
|
|
354
356
|
|
|
357
|
+
if (designation.supplement) {
|
|
358
|
+
designationParts.push({
|
|
359
|
+
name: 'source',
|
|
360
|
+
valueCanonical: designation.supplement.vurl
|
|
361
|
+
});
|
|
362
|
+
}
|
|
355
363
|
if (designation.language) {
|
|
356
364
|
designationParts.push({
|
|
357
365
|
name: 'language',
|
|
@@ -382,6 +390,14 @@ class LookupWorker extends TerminologyWorker {
|
|
|
382
390
|
// Let the provider add additional properties
|
|
383
391
|
await csProvider.extendLookup(ctxt, params.properties || [], responseParams);
|
|
384
392
|
|
|
393
|
+
if (reportedSupplements) {
|
|
394
|
+
for (const supplement of reportedSupplements) {
|
|
395
|
+
responseParams.push({
|
|
396
|
+
name: 'used-supplement',
|
|
397
|
+
valueCanonical: supplement
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
}
|
|
385
401
|
return {
|
|
386
402
|
resourceType: 'Parameters',
|
|
387
403
|
parameter: responseParams
|
package/tx/workers/worker.js
CHANGED
|
@@ -252,6 +252,7 @@ class TerminologyWorker {
|
|
|
252
252
|
loadSupplements(url, version = '', statedSupplements) {
|
|
253
253
|
const supplements = [];
|
|
254
254
|
|
|
255
|
+
supplements.push(...this.provider.loadSupplements(url, version, statedSupplements));
|
|
255
256
|
// todo: look in provider for supplements
|
|
256
257
|
|
|
257
258
|
if (!this.additionalResources) {
|
|
@@ -306,7 +307,7 @@ class TerminologyWorker {
|
|
|
306
307
|
* @param {CodeSystemProvider} cs - Code system provider
|
|
307
308
|
* @param {Object} src - Source element (for extensions)
|
|
308
309
|
*/
|
|
309
|
-
checkSupplements(cs, src, requiredSupplements, usedSupplements = null) {
|
|
310
|
+
checkSupplements(cs, src, requiredSupplements, usedSupplements = null, reportedSupplements = null) {
|
|
310
311
|
// Check for required supplements in extensions
|
|
311
312
|
if (src && src.extension) {
|
|
312
313
|
const supplementExtensions = src.extension.filter(x => x.url == 'http://hl7.org/fhir/StructureDefinition/valueset-supplement');
|
|
@@ -318,7 +319,12 @@ class TerminologyWorker {
|
|
|
318
319
|
}
|
|
319
320
|
}
|
|
320
321
|
|
|
321
|
-
|
|
322
|
+
if (reportedSupplements) {
|
|
323
|
+
for (let s of cs.listSupplements(true)) {
|
|
324
|
+
reportedSupplements.add(s);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
// Note required supplements that are satisfied - and might be version independent
|
|
322
328
|
if (usedSupplements) {
|
|
323
329
|
for (const s of requiredSupplements) {
|
|
324
330
|
if (cs.hasSupplement(s)) {
|
|
@@ -9,11 +9,11 @@ const {VersionUtilities} = require("../../library/version-utilities");
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
function codeSystemToR5(jsonObj, version) {
|
|
12
|
-
if (version
|
|
12
|
+
if (VersionUtilities.isR5Ver(version)) {
|
|
13
13
|
return jsonObj; // Already R5, no conversion needed
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
if (version
|
|
16
|
+
if (VersionUtilities.isR3Ver(version)) {
|
|
17
17
|
// R3 to R5: Convert identifier from single object to array
|
|
18
18
|
if (jsonObj.identifier && !Array.isArray(jsonObj.identifier)) {
|
|
19
19
|
jsonObj.identifier = [jsonObj.identifier];
|
|
@@ -21,7 +21,7 @@ function codeSystemToR5(jsonObj, version) {
|
|
|
21
21
|
return jsonObj;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
if (version
|
|
24
|
+
if (VersionUtilities.isR4Ver(version)) {
|
|
25
25
|
// R4 to R5: identifier is already an array, no conversion needed
|
|
26
26
|
return jsonObj;
|
|
27
27
|
}
|
|
@@ -17,10 +17,10 @@ function parametersToR5(jsonObj, sourceVersion) {
|
|
|
17
17
|
}
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
const {
|
|
20
|
+
const {convertResourceToR5} = require("./xv-resource");
|
|
21
21
|
for (let p of jsonObj.parameter) {
|
|
22
22
|
if (p.resource) {
|
|
23
|
-
p.resource =
|
|
23
|
+
p.resource = convertResourceToR5(p.resource, sourceVersion);
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
26
|
return jsonObj;
|
|
@@ -203,8 +203,7 @@ function convertContainsPropertyR5ToR4(containsList) {
|
|
|
203
203
|
*/
|
|
204
204
|
function isR5OnlyFilterOperator(operator) {
|
|
205
205
|
const r5OnlyOperators = [
|
|
206
|
-
'
|
|
207
|
-
// Add other R5-only operators as they're identified
|
|
206
|
+
'child-of', ' descendent-leaf' // Added in R5
|
|
208
207
|
];
|
|
209
208
|
return r5OnlyOperators.includes(operator);
|
|
210
209
|
}
|