fhirsmith 0.3.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 +42 -0
- package/FHIRsmith.png +0 -0
- package/README.md +277 -0
- package/config-template.json +144 -0
- package/library/folder-setup.js +58 -0
- package/library/html-server.js +166 -0
- package/library/html.js +835 -0
- package/library/i18nsupport.js +259 -0
- package/library/languages.js +779 -0
- package/library/logger-telnet.js +205 -0
- package/library/logger.js +279 -0
- package/library/package-manager.js +876 -0
- package/library/utilities.js +196 -0
- package/library/version-utilities.js +1056 -0
- package/npmprojector/config-example.json +13 -0
- package/npmprojector/indexer.js +394 -0
- package/npmprojector/npmprojector.js +395 -0
- package/npmprojector/readme.md +174 -0
- package/npmprojector/watcher.js +335 -0
- package/package.json +119 -0
- package/packages/package-crawler.js +846 -0
- package/packages/packages-template.html +126 -0
- package/packages/packages.js +2838 -0
- package/passwords.ini +2 -0
- package/publisher/publisher-template.html +208 -0
- package/publisher/publisher.js +2167 -0
- package/publisher/task-draft.js +458 -0
- package/registry/api.js +735 -0
- package/registry/crawler.js +637 -0
- package/registry/model.js +513 -0
- package/registry/readme.md +243 -0
- package/registry/registry-data.json +121015 -0
- package/registry/registry-template.html +126 -0
- package/registry/registry.js +1395 -0
- package/registry/test-runner.js +237 -0
- package/root-template.html +124 -0
- package/server.js +524 -0
- package/shl/private-key.pem +5 -0
- package/shl/public-key.pem +18 -0
- package/shl/shl.js +1125 -0
- package/shl/vhl.js +69 -0
- package/static/FHIRsmith128.png +0 -0
- package/static/FHIRsmith16.png +0 -0
- package/static/FHIRsmith32.png +0 -0
- package/static/FHIRsmith64.png +0 -0
- package/static/assets/css/bootstrap-fhir.css +5302 -0
- package/static/assets/css/bootstrap-glyphicons.css +2 -0
- package/static/assets/css/bootstrap.css +4097 -0
- package/static/assets/css/jquery-ui.css +523 -0
- package/static/assets/css/jquery-ui.structure.css +863 -0
- package/static/assets/css/jquery-ui.structure.min.css +5 -0
- package/static/assets/css/jquery-ui.theme.css +439 -0
- package/static/assets/css/jquery-ui.theme.min.css +5 -0
- package/static/assets/css/jquery.ui.all.css +7 -0
- package/static/assets/css/modules.css +18 -0
- package/static/assets/css/project.css +367 -0
- package/static/assets/css/pygments-manni.css +66 -0
- package/static/assets/css/tags.css +74 -0
- package/static/assets/css/xml.css +2 -0
- package/static/assets/fonts/glyphiconshalflings-regular.eot +0 -0
- package/static/assets/fonts/glyphiconshalflings-regular.otf +0 -0
- package/static/assets/fonts/glyphiconshalflings-regular.svg +175 -0
- package/static/assets/fonts/glyphiconshalflings-regular.ttf +0 -0
- package/static/assets/fonts/glyphiconshalflings-regular.woff +0 -0
- package/static/assets/ico/apple-touch-icon-114-precomposed.png +0 -0
- package/static/assets/ico/apple-touch-icon-144-precomposed.png +0 -0
- package/static/assets/ico/apple-touch-icon-57-precomposed.png +0 -0
- package/static/assets/ico/apple-touch-icon-72-precomposed.png +0 -0
- package/static/assets/ico/favicon.ico +0 -0
- package/static/assets/ico/favicon.png +0 -0
- package/static/assets/images/fhir-logo-www.png +0 -0
- package/static/assets/images/fhir-logo.png +0 -0
- package/static/assets/images/hl7-logo.png +0 -0
- package/static/assets/images/logo_ansinew.jpg +0 -0
- package/static/assets/images/search.png +0 -0
- package/static/assets/images/stripe.png +0 -0
- package/static/assets/images/target.png +0 -0
- package/static/assets/images/tx-registry-root.gif +0 -0
- package/static/assets/images/tx-registry.png +0 -0
- package/static/assets/images/tx-server.png +0 -0
- package/static/assets/images/tx-version.png +0 -0
- package/static/assets/js/bootstrap.min.js +6 -0
- package/static/assets/js/fhir-gw.js +259 -0
- package/static/assets/js/fhir.js +2 -0
- package/static/assets/js/html5shiv.js +8 -0
- package/static/assets/js/jcookie.js +96 -0
- package/static/assets/js/jquery-ui.min.js +6 -0
- package/static/assets/js/jquery.js +10716 -0
- package/static/assets/js/jquery.min.js +2 -0
- package/static/assets/js/jquery.ui.core.js +314 -0
- package/static/assets/js/jquery.ui.draggable.js +825 -0
- package/static/assets/js/jquery.ui.mouse.js +162 -0
- package/static/assets/js/jquery.ui.resizable.js +842 -0
- package/static/assets/js/jquery.ui.widget.js +268 -0
- package/static/assets/js/json2.js +487 -0
- package/static/assets/js/jtip.js +97 -0
- package/static/assets/js/respond.min.js +6 -0
- package/static/assets/js/statuspage.js +70 -0
- package/static/assets/js/xml.js +2 -0
- package/static/dist/js/bootstrap.js +1964 -0
- package/static/favicon.png +0 -0
- package/static/fhir.css +626 -0
- package/static/icon-fhir-16.png +0 -0
- package/static/images/ui-bg_diagonals-thick_18_b81900_40x40.png +0 -0
- package/static/images/ui-bg_diagonals-thick_20_666666_40x40.png +0 -0
- package/static/images/ui-bg_flat_10_000000_40x100.png +0 -0
- package/static/images/ui-bg_glass_100_f6f6f6_1x400.png +0 -0
- package/static/images/ui-bg_glass_100_fdf5ce_1x400.png +0 -0
- package/static/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
- package/static/images/ui-bg_gloss-wave_35_f6a828_500x100.png +0 -0
- package/static/images/ui-bg_highlight-soft_100_eeeeee_1x100.png +0 -0
- package/static/images/ui-bg_highlight-soft_75_ffe45c_1x100.png +0 -0
- package/static/images/ui-icons_222222_256x240.png +0 -0
- package/static/images/ui-icons_228ef1_256x240.png +0 -0
- package/static/images/ui-icons_ef8c08_256x240.png +0 -0
- package/static/images/ui-icons_ffd27a_256x240.png +0 -0
- package/static/images/ui-icons_ffffff_256x240.png +0 -0
- package/static/js/jquery.effects.blind.js +49 -0
- package/static/js/jquery.effects.bounce.js +78 -0
- package/static/js/jquery.effects.clip.js +54 -0
- package/static/js/jquery.effects.core.js +763 -0
- package/static/js/jquery.effects.drop.js +50 -0
- package/static/js/jquery.effects.explode.js +79 -0
- package/static/js/jquery.effects.fade.js +32 -0
- package/static/js/jquery.effects.fold.js +56 -0
- package/static/js/jquery.effects.highlight.js +50 -0
- package/static/js/jquery.effects.pulsate.js +51 -0
- package/static/js/jquery.effects.scale.js +178 -0
- package/static/js/jquery.effects.shake.js +57 -0
- package/static/js/jquery.effects.slide.js +50 -0
- package/static/js/jquery.effects.transfer.js +45 -0
- package/static/js/jquery.ui.accordion.js +611 -0
- package/static/js/jquery.ui.autocomplete.js +612 -0
- package/static/js/jquery.ui.button.js +416 -0
- package/static/js/jquery.ui.datepicker.js +1823 -0
- package/static/js/jquery.ui.dialog.js +878 -0
- package/static/js/jquery.ui.droppable.js +296 -0
- package/static/js/jquery.ui.position.js +252 -0
- package/static/js/jquery.ui.progressbar.js +109 -0
- package/static/js/jquery.ui.selectable.js +266 -0
- package/static/js/jquery.ui.slider.js +666 -0
- package/static/js/jquery.ui.sortable.js +1077 -0
- package/static/js/jquery.ui.tabs.js +758 -0
- package/stats.js +80 -0
- package/test-cache/vsac/vsac-valuesets.db +0 -0
- package/token/nginx_passport_setup.md +383 -0
- package/token/security_guide.md +294 -0
- package/token/token-template.html +330 -0
- package/token/token.js +1300 -0
- package/translations/Messages.properties +1510 -0
- package/translations/Messages_ar.properties +1399 -0
- package/translations/Messages_de.properties +836 -0
- package/translations/Messages_es.properties +737 -0
- package/translations/Messages_fr.properties +1 -0
- package/translations/Messages_ja.properties +893 -0
- package/translations/Messages_nl.properties +1357 -0
- package/translations/Messages_pt.properties +1302 -0
- package/translations/Messages_ru.properties +1 -0
- package/translations/Messages_uz.properties +1 -0
- package/translations/Messages_zh.properties +1 -0
- package/translations/rendering-phrases.properties +1128 -0
- package/translations/rendering-phrases_ar.properties +1091 -0
- package/translations/rendering-phrases_de.properties +6 -0
- package/translations/rendering-phrases_es.properties +6 -0
- package/translations/rendering-phrases_fr.properties +624 -0
- package/translations/rendering-phrases_ja.properties +21 -0
- package/translations/rendering-phrases_nl.properties +970 -0
- package/translations/rendering-phrases_pt.properties +1020 -0
- package/translations/rendering-phrases_ru.properties +1094 -0
- package/translations/rendering-phrases_uz.properties +1 -0
- package/translations/rendering-phrases_zh.properties +1 -0
- package/tx/README.md +418 -0
- package/tx/cm/cm-api.js +110 -0
- package/tx/cm/cm-database.js +735 -0
- package/tx/cm/cm-package.js +325 -0
- package/tx/cs/cs-api.js +789 -0
- package/tx/cs/cs-areacode.js +615 -0
- package/tx/cs/cs-country.js +1110 -0
- package/tx/cs/cs-cpt.js +785 -0
- package/tx/cs/cs-cs.js +1579 -0
- package/tx/cs/cs-currency.js +539 -0
- package/tx/cs/cs-db.js +1321 -0
- package/tx/cs/cs-hgvs.js +329 -0
- package/tx/cs/cs-lang.js +465 -0
- package/tx/cs/cs-loinc.js +1485 -0
- package/tx/cs/cs-mimetypes.js +238 -0
- package/tx/cs/cs-ndc.js +704 -0
- package/tx/cs/cs-omop.js +1025 -0
- package/tx/cs/cs-provider-api.js +43 -0
- package/tx/cs/cs-provider-list.js +37 -0
- package/tx/cs/cs-rxnorm.js +808 -0
- package/tx/cs/cs-snomed.js +1102 -0
- package/tx/cs/cs-ucum.js +514 -0
- package/tx/cs/cs-unii.js +271 -0
- package/tx/cs/cs-uri.js +218 -0
- package/tx/cs/cs-usstates.js +305 -0
- package/tx/dev.fhir.org.yml +14 -0
- package/tx/fixtures/test-cases-setup.json +18 -0
- package/tx/fixtures/test-cases.yml +16 -0
- package/tx/html/codesystem-operations.liquid +25 -0
- package/tx/html/home-metrics.liquid +247 -0
- package/tx/html/operations-form.liquid +148 -0
- package/tx/html/search-form.liquid +62 -0
- package/tx/html/tx-template.html +133 -0
- package/tx/html/valueset-operations.liquid +54 -0
- package/tx/importers/atc-to-fhir.js +316 -0
- package/tx/importers/import-loinc.module.js +1536 -0
- package/tx/importers/import-ndc.module.js +1088 -0
- package/tx/importers/import-rxnorm.module.js +898 -0
- package/tx/importers/import-sct.module.js +2457 -0
- package/tx/importers/import-unii.module.js +601 -0
- package/tx/importers/readme.md +453 -0
- package/tx/importers/subset-loinc.module.js +1081 -0
- package/tx/importers/subset-rxnorm.module.js +938 -0
- package/tx/importers/tx-import-base.js +351 -0
- package/tx/importers/tx-import-settings.js +310 -0
- package/tx/importers/tx-import.js +357 -0
- package/tx/library/canonical-resource.js +88 -0
- package/tx/library/capabilitystatement.js +292 -0
- package/tx/library/codesystem.js +774 -0
- package/tx/library/conceptmap.js +568 -0
- package/tx/library/designations.js +932 -0
- package/tx/library/errors.js +77 -0
- package/tx/library/extensions.js +117 -0
- package/tx/library/namingsystem.js +322 -0
- package/tx/library/operation-outcome.js +127 -0
- package/tx/library/parameters.js +105 -0
- package/tx/library/renderer.js +1559 -0
- package/tx/library/terminologycapabilities.js +418 -0
- package/tx/library/ucum-parsers.js +1029 -0
- package/tx/library/ucum-service.js +370 -0
- package/tx/library/ucum-types.js +1099 -0
- package/tx/library/valueset.js +543 -0
- package/tx/library.js +676 -0
- package/tx/ocl/cm-ocl.js +106 -0
- package/tx/ocl/cs-ocl.js +39 -0
- package/tx/ocl/vs-ocl.js +105 -0
- package/tx/operation-context.js +568 -0
- package/tx/params.js +613 -0
- package/tx/provider.js +403 -0
- package/tx/sct/ecl.js +1560 -0
- package/tx/sct/expressions.js +2077 -0
- package/tx/sct/structures.js +1396 -0
- package/tx/tx-html.js +1063 -0
- package/tx/tx.fhir.org.yml +39 -0
- package/tx/tx.js +927 -0
- package/tx/vs/vs-api.js +112 -0
- package/tx/vs/vs-database.js +786 -0
- package/tx/vs/vs-package.js +358 -0
- package/tx/vs/vs-vsac.js +366 -0
- package/tx/workers/batch-validate.js +129 -0
- package/tx/workers/batch.js +361 -0
- package/tx/workers/closure.js +32 -0
- package/tx/workers/expand.js +1845 -0
- package/tx/workers/lookup.js +407 -0
- package/tx/workers/metadata.js +467 -0
- package/tx/workers/operations.js +34 -0
- package/tx/workers/read.js +164 -0
- package/tx/workers/search.js +384 -0
- package/tx/workers/subsumes.js +334 -0
- package/tx/workers/translate.js +492 -0
- package/tx/workers/validate.js +2504 -0
- package/tx/workers/worker.js +904 -0
- package/tx/xml/capabilitystatement-xml.js +63 -0
- package/tx/xml/codesystem-xml.js +62 -0
- package/tx/xml/conceptmap-xml.js +65 -0
- package/tx/xml/namingsystem-xml.js +65 -0
- package/tx/xml/operationoutcome-xml.js +127 -0
- package/tx/xml/parameters-xml.js +312 -0
- package/tx/xml/terminologycapabilities-xml.js +64 -0
- package/tx/xml/valueset-xml.js +64 -0
- package/tx/xml/xml-base.js +603 -0
- package/vcl/vcl-parser.js +1098 -0
- package/vcl/vcl.js +253 -0
- package/windows-install.js +19 -0
- package/xig/xig-template.html +124 -0
- package/xig/xig.js +3049 -0
|
@@ -0,0 +1,543 @@
|
|
|
1
|
+
const {CanonicalResource} = require("./canonical-resource");
|
|
2
|
+
const {getValueName} = require("../../library/utilities");
|
|
3
|
+
const {VersionUtilities} = require("../../library/version-utilities");
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Represents a FHIR ValueSet resource with version conversion support
|
|
7
|
+
* @class
|
|
8
|
+
*/
|
|
9
|
+
class ValueSet extends CanonicalResource {
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Creates a new ValueSet instance
|
|
13
|
+
* @param {Object} jsonObj - The JSON object containing ValueSet data
|
|
14
|
+
* @param {string} [fhirVersion='R5'] - FHIR version ('R3', 'R4', or 'R5')
|
|
15
|
+
* @param {string} jsonObj.resourceType - Must be "ValueSet"
|
|
16
|
+
* @param {string} jsonObj.url - Canonical URL for the value set
|
|
17
|
+
* @param {string} [jsonObj.version] - Version of the value set
|
|
18
|
+
* @param {string} jsonObj.name - Name for this value set
|
|
19
|
+
* @param {string} jsonObj.status - Publication status (draft|active|retired|unknown)
|
|
20
|
+
* @param {Object} [jsonObj.compose] - Content logical definition of the value set
|
|
21
|
+
* @param {Object} [jsonObj.expansion] - Used when the value set is "expanded"
|
|
22
|
+
*/
|
|
23
|
+
constructor(jsonObj, fhirVersion = 'R5') {
|
|
24
|
+
super(jsonObj, fhirVersion);
|
|
25
|
+
// Convert to R5 format internally (modifies input for performance)
|
|
26
|
+
this.jsonObj = this._convertToR5(jsonObj, fhirVersion);
|
|
27
|
+
this.validate();
|
|
28
|
+
this.buildMaps();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Map of system(#version)|code to expansion contains item for fast lookup
|
|
33
|
+
* @type {Map<string, Object>}
|
|
34
|
+
*/
|
|
35
|
+
codeMap = new Map();
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Static factory method for convenience
|
|
39
|
+
* @param {string} jsonString - JSON string representation of ValueSet
|
|
40
|
+
* @param {string} [version='R5'] - FHIR version ('R3', 'R4', or 'R5')
|
|
41
|
+
* @returns {ValueSet} New ValueSet instance
|
|
42
|
+
*/
|
|
43
|
+
static fromJSON(jsonString, version = 'R5') {
|
|
44
|
+
return new ValueSet(JSON.parse(jsonString), version);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Returns JSON string representation
|
|
50
|
+
* @param {string} [version='R5'] - Target FHIR version ('R3', 'R4', or 'R5')
|
|
51
|
+
* @returns {string} JSON string
|
|
52
|
+
*/
|
|
53
|
+
toJSONString(version = 'R5') {
|
|
54
|
+
const outputObj = this.convertFromR5(this.jsonObj, version);
|
|
55
|
+
return JSON.stringify(outputObj);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Converts input ValueSet to R5 format (modifies input object for performance)
|
|
60
|
+
* @param {Object} jsonObj - The input ValueSet object
|
|
61
|
+
* @param {string} version - Source FHIR version
|
|
62
|
+
* @returns {Object} The same object, potentially modified to R5 format
|
|
63
|
+
* @private
|
|
64
|
+
*/
|
|
65
|
+
_convertToR5(jsonObj, version) {
|
|
66
|
+
if (version === 'R5') {
|
|
67
|
+
return jsonObj; // Already R5, no conversion needed
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (version === 'R3') {
|
|
71
|
+
// R3 to R5: Remove extensible field (we ignore it completely)
|
|
72
|
+
if (jsonObj.extensible !== undefined) {
|
|
73
|
+
delete jsonObj.extensible;
|
|
74
|
+
}
|
|
75
|
+
return jsonObj;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (version === 'R4') {
|
|
79
|
+
// R4 to R5: No structural conversion needed
|
|
80
|
+
// R5 is backward compatible for the structural elements we care about
|
|
81
|
+
return jsonObj;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
throw new Error(`Unsupported FHIR version: ${version}`);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Converts R5 ValueSet to target version format (clones object first)
|
|
89
|
+
* @param {Object} r5Obj - The R5 format ValueSet object
|
|
90
|
+
* @param {string} targetVersion - Target FHIR version
|
|
91
|
+
* @returns {Object} New object in target version format
|
|
92
|
+
* @private
|
|
93
|
+
*/
|
|
94
|
+
convertFromR5(r5Obj, targetVersion) {
|
|
95
|
+
if (VersionUtilities.isR5Ver(targetVersion)) {
|
|
96
|
+
return r5Obj; // No conversion needed
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Clone the object to avoid modifying the original
|
|
100
|
+
const cloned = JSON.parse(JSON.stringify(r5Obj));
|
|
101
|
+
|
|
102
|
+
if (VersionUtilities.isR4Ver(targetVersion)) {
|
|
103
|
+
return this._convertR5ToR4(cloned);
|
|
104
|
+
} else if (VersionUtilities.isR3Ver(targetVersion)) {
|
|
105
|
+
return this._convertR5ToR3(cloned);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
throw new Error(`Unsupported target FHIR version: ${targetVersion}`);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Converts R5 ValueSet to R4 format
|
|
113
|
+
* @param {Object} r5Obj - Cloned R5 ValueSet object
|
|
114
|
+
* @returns {Object} R4 format ValueSet
|
|
115
|
+
* @private
|
|
116
|
+
*/
|
|
117
|
+
_convertR5ToR4(r5Obj) {
|
|
118
|
+
// Remove R5-specific elements that don't exist in R4
|
|
119
|
+
if (r5Obj.versionAlgorithmString) {
|
|
120
|
+
delete r5Obj.versionAlgorithmString;
|
|
121
|
+
}
|
|
122
|
+
if (r5Obj.versionAlgorithmCoding) {
|
|
123
|
+
delete r5Obj.versionAlgorithmCoding;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Filter out R5-only filter operators in compose
|
|
127
|
+
if (r5Obj.compose && r5Obj.compose.include) {
|
|
128
|
+
r5Obj.compose.include = r5Obj.compose.include.map(include => {
|
|
129
|
+
if (include.filter && Array.isArray(include.filter)) {
|
|
130
|
+
include.filter = include.filter.map(filter => {
|
|
131
|
+
if (filter.op && this._isR5OnlyFilterOperator(filter.op)) {
|
|
132
|
+
// Remove R5-only operators
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
return filter;
|
|
136
|
+
}).filter(filter => filter !== null);
|
|
137
|
+
}
|
|
138
|
+
return include;
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (r5Obj.compose && r5Obj.compose.exclude) {
|
|
143
|
+
r5Obj.compose.exclude = r5Obj.compose.exclude.map(exclude => {
|
|
144
|
+
if (exclude.filter && Array.isArray(exclude.filter)) {
|
|
145
|
+
exclude.filter = exclude.filter.map(filter => {
|
|
146
|
+
if (filter.op && this._isR5OnlyFilterOperator(filter.op)) {
|
|
147
|
+
// Remove R5-only operators
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
return filter;
|
|
151
|
+
}).filter(filter => filter !== null);
|
|
152
|
+
}
|
|
153
|
+
return exclude;
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (r5Obj.expansion) {
|
|
158
|
+
let exp = r5Obj.expansion;
|
|
159
|
+
|
|
160
|
+
// Convert ValueSet.expansion.property to extensions
|
|
161
|
+
if (exp.property && exp.property.length > 0) {
|
|
162
|
+
exp.extension = exp.extension || [];
|
|
163
|
+
for (let prop of exp.property) {
|
|
164
|
+
exp.extension.push({
|
|
165
|
+
url: "http://hl7.org/fhir/5.0/StructureDefinition/extension-ValueSet.expansion.property",
|
|
166
|
+
extension: [
|
|
167
|
+
{ url: "code", valueCode: prop.code },
|
|
168
|
+
{ url: "uri", valueUri: prop.uri }
|
|
169
|
+
]
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
delete exp.property;
|
|
173
|
+
this.convertContainsPropertyR5ToR4(exp.contains);
|
|
174
|
+
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return r5Obj;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Recursive function to convert contains.property
|
|
182
|
+
convertContainsPropertyR5ToR4(containsList) {
|
|
183
|
+
if (!containsList) return;
|
|
184
|
+
|
|
185
|
+
for (let item of containsList) {
|
|
186
|
+
if (item.property && item.property.length > 0) {
|
|
187
|
+
item.extension = item.extension || [];
|
|
188
|
+
for (let prop of item.property) {
|
|
189
|
+
let ext = {
|
|
190
|
+
url: "http://hl7.org/fhir/5.0/StructureDefinition/extension-ValueSet.expansion.contains.property",
|
|
191
|
+
extension: [
|
|
192
|
+
{ url: "code", valueCode: prop.code }
|
|
193
|
+
]
|
|
194
|
+
};
|
|
195
|
+
let pn = getValueName(prop);
|
|
196
|
+
let subExt = { url: "value" };
|
|
197
|
+
subExt[pn] = prop[pn];
|
|
198
|
+
ext.extension.push(subExt);
|
|
199
|
+
item.extension.push(ext);
|
|
200
|
+
}
|
|
201
|
+
delete item.property;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Recurse into nested contains
|
|
205
|
+
if (item.contains) {
|
|
206
|
+
this.convertContainsPropertyR5ToR4(item.contains);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Converts R5 ValueSet to R3 format
|
|
213
|
+
* @param {Object} r5Obj - Cloned R5 ValueSet object
|
|
214
|
+
* @returns {Object} R3 format ValueSet
|
|
215
|
+
* @private
|
|
216
|
+
*/
|
|
217
|
+
_convertR5ToR3(r5Obj) {
|
|
218
|
+
// First apply R4 conversions
|
|
219
|
+
const r4Obj = this._convertR5ToR4(r5Obj);
|
|
220
|
+
|
|
221
|
+
// R3 has more limited filter operator support
|
|
222
|
+
if (r4Obj.compose && r4Obj.compose.include) {
|
|
223
|
+
r4Obj.compose.include = r4Obj.compose.include.map(include => {
|
|
224
|
+
if (include.filter && Array.isArray(include.filter)) {
|
|
225
|
+
include.filter = include.filter.map(filter => {
|
|
226
|
+
if (filter.op && !this._isR3CompatibleFilterOperator(filter.op)) {
|
|
227
|
+
// Remove non-R3-compatible operators
|
|
228
|
+
return null;
|
|
229
|
+
}
|
|
230
|
+
return filter;
|
|
231
|
+
}).filter(filter => filter !== null);
|
|
232
|
+
}
|
|
233
|
+
return include;
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (r4Obj.compose && r4Obj.compose.exclude) {
|
|
238
|
+
r4Obj.compose.exclude = r4Obj.compose.exclude.map(exclude => {
|
|
239
|
+
if (exclude.filter && Array.isArray(exclude.filter)) {
|
|
240
|
+
exclude.filter = exclude.filter.map(filter => {
|
|
241
|
+
if (filter.op && !this._isR3CompatibleFilterOperator(filter.op)) {
|
|
242
|
+
// Remove non-R3-compatible operators
|
|
243
|
+
return null;
|
|
244
|
+
}
|
|
245
|
+
return filter;
|
|
246
|
+
}).filter(filter => filter !== null);
|
|
247
|
+
}
|
|
248
|
+
return exclude;
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return r4Obj;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Checks if a filter operator is R5-only
|
|
257
|
+
* @param {string} operator - Filter operator code
|
|
258
|
+
* @returns {boolean} True if operator is R5-only
|
|
259
|
+
* @private
|
|
260
|
+
*/
|
|
261
|
+
_isR5OnlyFilterOperator(operator) {
|
|
262
|
+
const r5OnlyOperators = [
|
|
263
|
+
'generalizes', // Added in R5
|
|
264
|
+
// Add other R5-only operators as they're identified
|
|
265
|
+
];
|
|
266
|
+
return r5OnlyOperators.includes(operator);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Checks if a filter operator is compatible with R3
|
|
271
|
+
* @param {string} operator - Filter operator code
|
|
272
|
+
* @returns {boolean} True if operator is R3-compatible
|
|
273
|
+
* @private
|
|
274
|
+
*/
|
|
275
|
+
_isR3CompatibleFilterOperator(operator) {
|
|
276
|
+
const r3CompatibleOperators = [
|
|
277
|
+
'=', // Equal
|
|
278
|
+
'is-a', // Is-A relationship
|
|
279
|
+
'descendent-of', // Descendant of (note: R3 spelling)
|
|
280
|
+
'is-not-a', // Is-Not-A relationship
|
|
281
|
+
'regex', // Regular expression
|
|
282
|
+
'in', // In set
|
|
283
|
+
'not-in', // Not in set
|
|
284
|
+
'exists', // Property exists
|
|
285
|
+
];
|
|
286
|
+
return r3CompatibleOperators.includes(operator);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Gets the FHIR version this ValueSet was loaded from
|
|
291
|
+
* @returns {string} FHIR version ('R3', 'R4', or 'R5')
|
|
292
|
+
*/
|
|
293
|
+
getFHIRVersion() {
|
|
294
|
+
return this.version;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Validates that this is a proper ValueSet resource
|
|
299
|
+
* @throws {Error} If validation fails
|
|
300
|
+
*/
|
|
301
|
+
validate() {
|
|
302
|
+
if (!this.jsonObj || typeof this.jsonObj !== 'object') {
|
|
303
|
+
throw new Error('Invalid ValueSet: expected object');
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
if (this.jsonObj.resourceType !== 'ValueSet') {
|
|
307
|
+
throw new Error(`Invalid ValueSet: resourceType must be "ValueSet", got "${this.jsonObj.resourceType}"`);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
if (this.jsonObj.url && typeof this.jsonObj.url !== 'string') {
|
|
311
|
+
throw new Error('Invalid ValueSet: url must be a string if present');
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
if (this.jsonObj.name && typeof this.jsonObj.name !== 'string') {
|
|
315
|
+
throw new Error('Invalid ValueSet: name must be a string if present');
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
if (this.jsonObj.status && typeof this.jsonObj.status !== 'string') {
|
|
319
|
+
throw new Error('Invalid ValueSet: status must be a string if present');
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
const validStatuses = ['draft', 'active', 'retired', 'unknown'];
|
|
323
|
+
if (this.jsonObj.status && !validStatuses.includes(this.jsonObj.status)) {
|
|
324
|
+
throw new Error(`Invalid ValueSet: status must be one of ${validStatuses.join(', ')}, got "${this.jsonObj.status}"`);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Validate identifier - should always be array (no conversion needed for ValueSet)
|
|
328
|
+
if (this.jsonObj.identifier && !Array.isArray(this.jsonObj.identifier)) {
|
|
329
|
+
throw new Error('Invalid ValueSet: identifier should be an array');
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// Validate compose structure if present
|
|
333
|
+
if (this.jsonObj.compose) {
|
|
334
|
+
if (this.jsonObj.compose.include && !Array.isArray(this.jsonObj.compose.include)) {
|
|
335
|
+
throw new Error('Invalid ValueSet: compose.include must be an array if present');
|
|
336
|
+
}
|
|
337
|
+
if (this.jsonObj.compose.exclude && !Array.isArray(this.jsonObj.compose.exclude)) {
|
|
338
|
+
throw new Error('Invalid ValueSet: compose.exclude must be an array if present');
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// Validate expansion structure if present
|
|
343
|
+
if (this.jsonObj.expansion) {
|
|
344
|
+
if (this.jsonObj.expansion.contains && !Array.isArray(this.jsonObj.expansion.contains)) {
|
|
345
|
+
throw new Error('Invalid ValueSet: expansion.contains must be an array if present');
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Builds internal maps for fast expansion lookup
|
|
352
|
+
* @private
|
|
353
|
+
*/
|
|
354
|
+
buildMaps() {
|
|
355
|
+
this.id = this.jsonObj.id;
|
|
356
|
+
this.codeMap.clear();
|
|
357
|
+
|
|
358
|
+
if (!this.jsonObj.expansion || !this.jsonObj.expansion.contains) {
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Build map of system(#version)|code -> expansion contains item
|
|
363
|
+
this._buildExpansionMap(this.jsonObj.expansion.contains);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Recursively builds expansion map from contains items
|
|
368
|
+
* @param {Object[]} contains - Array of expansion contains items
|
|
369
|
+
* @private
|
|
370
|
+
*/
|
|
371
|
+
_buildExpansionMap(contains) {
|
|
372
|
+
contains.forEach(item => {
|
|
373
|
+
if (item.system && item.code) {
|
|
374
|
+
const key = this._buildCodeKey(item.system, item.version, item.code);
|
|
375
|
+
this.codeMap.set(key, item);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// Handle nested contains (hierarchical expansions)
|
|
379
|
+
if (item.contains && Array.isArray(item.contains)) {
|
|
380
|
+
this._buildExpansionMap(item.contains);
|
|
381
|
+
}
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* Builds a lookup key for system(#version)|code
|
|
387
|
+
* @param {string} system - Code system URL
|
|
388
|
+
* @param {string} [version] - Code system version
|
|
389
|
+
* @param {string} code - Code value
|
|
390
|
+
* @returns {string} Lookup key
|
|
391
|
+
* @private
|
|
392
|
+
*/
|
|
393
|
+
_buildCodeKey(system, version, code) {
|
|
394
|
+
if (version) {
|
|
395
|
+
return `${system}#${version}|${code}`;
|
|
396
|
+
}
|
|
397
|
+
return `${system}|${code}`;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* Gets an expansion item by system and code
|
|
402
|
+
* @param {string} system - Code system URL
|
|
403
|
+
* @param {string} code - Code value
|
|
404
|
+
* @param {string} [version] - Code system version
|
|
405
|
+
* @returns {Object|undefined} The expansion contains item or undefined if not found
|
|
406
|
+
*/
|
|
407
|
+
getCode(system, code, version = null) {
|
|
408
|
+
const key = this._buildCodeKey(system, version, code);
|
|
409
|
+
return this.codeMap.get(key);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* Checks if a code exists in this value set expansion
|
|
414
|
+
* @param {string} system - Code system URL
|
|
415
|
+
* @param {string} code - Code value
|
|
416
|
+
* @param {string} [version] - Code system version
|
|
417
|
+
* @returns {boolean} True if the code exists in expansion
|
|
418
|
+
*/
|
|
419
|
+
hasCode(system, code, version = null) {
|
|
420
|
+
const key = this._buildCodeKey(system, version, code);
|
|
421
|
+
return this.codeMap.has(key);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* Finds a contains entry in the expansion by system, version, and code
|
|
426
|
+
* Searches recursively through nested contains
|
|
427
|
+
* @param {string} systemUri - Code system URL
|
|
428
|
+
* @param {string} version - Code system version (can be empty string)
|
|
429
|
+
* @param {string} code - Code value
|
|
430
|
+
* @returns {Object|null} The contains entry or null if not found
|
|
431
|
+
*/
|
|
432
|
+
findContains(systemUri, version, code) {
|
|
433
|
+
if (!this.jsonObj.expansion || !this.jsonObj.expansion.contains) {
|
|
434
|
+
return null;
|
|
435
|
+
}
|
|
436
|
+
return this._findContainsInList(this.jsonObj.expansion.contains, systemUri, version, code);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/**
|
|
440
|
+
* Recursively searches for a contains entry in a list
|
|
441
|
+
* @param {Object[]} list - Array of contains entries
|
|
442
|
+
* @param {string} systemUri - Code system URL
|
|
443
|
+
* @param {string} version - Code system version
|
|
444
|
+
* @param {string} code - Code value
|
|
445
|
+
* @returns {Object|null} The contains entry or null
|
|
446
|
+
* @private
|
|
447
|
+
*/
|
|
448
|
+
_findContainsInList(list, systemUri, version, code) {
|
|
449
|
+
for (const cc of list) {
|
|
450
|
+
if (systemUri === cc.system && code === cc.code &&
|
|
451
|
+
(!version || version === cc.version)) {
|
|
452
|
+
return cc;
|
|
453
|
+
}
|
|
454
|
+
if (cc.contains && cc.contains.length > 0) {
|
|
455
|
+
const found = this._findContainsInList(cc.contains, systemUri, version, code);
|
|
456
|
+
if (found) {
|
|
457
|
+
return found;
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
return null;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/**
|
|
465
|
+
* Gets all codes in this value set expansion
|
|
466
|
+
* @returns {Object[]} Array of {system, version, code, display} objects
|
|
467
|
+
*/
|
|
468
|
+
getAllCodes() {
|
|
469
|
+
return Array.from(this.codeMap.values()).map(item => ({
|
|
470
|
+
system: item.system,
|
|
471
|
+
version: item.version,
|
|
472
|
+
code: item.code,
|
|
473
|
+
display: item.display
|
|
474
|
+
}));
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
/**
|
|
478
|
+
* Gets all codes from a specific system
|
|
479
|
+
* @param {string} system - Code system URL
|
|
480
|
+
* @param {string} [version] - Code system version
|
|
481
|
+
* @returns {Object[]} Array of expansion contains items from the system
|
|
482
|
+
*/
|
|
483
|
+
getCodesFromSystem(system, version = null) {
|
|
484
|
+
return Array.from(this.codeMap.values()).filter(item => {
|
|
485
|
+
if (version) {
|
|
486
|
+
return item.system === system && item.version === version;
|
|
487
|
+
}
|
|
488
|
+
return item.system === system;
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
/**
|
|
493
|
+
* Gets all unique systems in this value set expansion
|
|
494
|
+
* @returns {string[]} Array of system URLs
|
|
495
|
+
*/
|
|
496
|
+
getSystems() {
|
|
497
|
+
const systems = new Set();
|
|
498
|
+
this.codeMap.forEach(item => {
|
|
499
|
+
if (item.system) {
|
|
500
|
+
systems.add(item.system);
|
|
501
|
+
}
|
|
502
|
+
});
|
|
503
|
+
return Array.from(systems);
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
/**
|
|
507
|
+
* Checks if the value set is expanded (has expansion.contains)
|
|
508
|
+
* @returns {boolean} True if the value set has an expansion
|
|
509
|
+
*/
|
|
510
|
+
isExpanded() {
|
|
511
|
+
return !!(this.jsonObj.expansion && this.jsonObj.expansion.contains && this.jsonObj.expansion.contains.length > 0);
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
/**
|
|
515
|
+
* Gets the total count from expansion, if available
|
|
516
|
+
* @returns {number|undefined} Total count or undefined if not available
|
|
517
|
+
*/
|
|
518
|
+
getExpansionTotal() {
|
|
519
|
+
return this.jsonObj.expansion?.total;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
/**
|
|
523
|
+
* Gets basic info about this value set
|
|
524
|
+
* @returns {Object} Basic information object
|
|
525
|
+
*/
|
|
526
|
+
getInfo() {
|
|
527
|
+
return {
|
|
528
|
+
resourceType: this.jsonObj.resourceType,
|
|
529
|
+
url: this.jsonObj.url,
|
|
530
|
+
version: this.jsonObj.version,
|
|
531
|
+
name: this.jsonObj.name,
|
|
532
|
+
title: this.jsonObj.title,
|
|
533
|
+
status: this.jsonObj.status,
|
|
534
|
+
fhirVersion: this.version,
|
|
535
|
+
isExpanded: this.isExpanded(),
|
|
536
|
+
expansionTotal: this.getExpansionTotal(),
|
|
537
|
+
codeCount: this.codeMap.size,
|
|
538
|
+
systemCount: this.getSystems().length
|
|
539
|
+
};
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
module.exports = ValueSet;
|