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,774 @@
|
|
|
1
|
+
const { Language } = require("../../library/languages");
|
|
2
|
+
const {CanonicalResource} = require("./canonical-resource");
|
|
3
|
+
const {VersionUtilities} = require("../../library/version-utilities");
|
|
4
|
+
|
|
5
|
+
const CodeSystemContentMode = Object.freeze({
|
|
6
|
+
Complete: 'complete',
|
|
7
|
+
NotPresent: 'not-present',
|
|
8
|
+
Example: 'example',
|
|
9
|
+
Fragment : 'fragment',
|
|
10
|
+
Supplement : 'supplement'
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Represents a FHIR CodeSystem resource with version conversion support
|
|
15
|
+
* @class
|
|
16
|
+
*/
|
|
17
|
+
class CodeSystem extends CanonicalResource {
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Creates a new CodeSystem instance
|
|
21
|
+
* @param {Object} jsonObj - The JSON object containing CodeSystem data
|
|
22
|
+
* @param {string} [version='R5'] - FHIR version ('R3', 'R4', or 'R5')
|
|
23
|
+
* @param {string} jsonObj.resourceType - Must be "CodeSystem"
|
|
24
|
+
* @param {string} jsonObj.url - Canonical URL for the code system
|
|
25
|
+
* @param {string} [jsonObj.version] - Version of the code system
|
|
26
|
+
* @param {string} jsonObj.name - Name for this code system
|
|
27
|
+
* @param {string} jsonObj.status - Publication status (draft|active|retired|unknown)
|
|
28
|
+
* @param {Object[]} [jsonObj.concept] - Array of concept definitions
|
|
29
|
+
*/
|
|
30
|
+
constructor(jsonObj, fhirVersion = 'R5') {
|
|
31
|
+
super(jsonObj, fhirVersion);
|
|
32
|
+
// Convert to R5 format internally (modifies input for performance)
|
|
33
|
+
this.jsonObj = this._convertToR5(this.jsonObj, fhirVersion);
|
|
34
|
+
this.validate();
|
|
35
|
+
this.buildMaps();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Map of code to concept object for fast lookup
|
|
40
|
+
* @type {Map<string, Object>}
|
|
41
|
+
*/
|
|
42
|
+
codeMap = new Map();
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Map of code to concept object for fast lookup - not case sensitive, only for non-case sensitive code systems
|
|
46
|
+
* @type {Map<string, Object>}
|
|
47
|
+
*/
|
|
48
|
+
codeMapNC;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Map of display text to concept object for fast lookup
|
|
52
|
+
* @type {Map<string, Object>}
|
|
53
|
+
*/
|
|
54
|
+
displayMap = new Map();
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Map of parent code to array of child codes
|
|
58
|
+
* @type {Map<string, string[]>}
|
|
59
|
+
*/
|
|
60
|
+
parentToChildrenMap = new Map();
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Map of child code to array of parent codes
|
|
64
|
+
* @type {Map<string, string[]>}
|
|
65
|
+
*/
|
|
66
|
+
childToParentsMap = new Map();
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Static factory method for convenience
|
|
70
|
+
* @param {string} jsonString - JSON string representation of CodeSystem
|
|
71
|
+
* @param {string} [version='R5'] - FHIR version ('R3', 'R4', or 'R5')
|
|
72
|
+
* @returns {CodeSystem} New CodeSystem instance
|
|
73
|
+
*/
|
|
74
|
+
static fromJSON(jsonString, version = 'R5') {
|
|
75
|
+
return new CodeSystem(JSON.parse(jsonString), version);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Returns JSON string representation
|
|
80
|
+
* @param {string} [version='R5'] - Target FHIR version ('R3', 'R4', or 'R5')
|
|
81
|
+
* @returns {string} JSON string
|
|
82
|
+
*/
|
|
83
|
+
toJSONString(version = 'R5') {
|
|
84
|
+
const outputObj = this._convertFromR5(this.jsonObj, version);
|
|
85
|
+
return JSON.stringify(outputObj);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Converts input CodeSystem to R5 format (modifies input object for performance)
|
|
90
|
+
* @param {Object} jsonObj - The input CodeSystem object
|
|
91
|
+
* @param {string} version - Source FHIR version
|
|
92
|
+
* @returns {Object} The same object, potentially modified to R5 format
|
|
93
|
+
* @private
|
|
94
|
+
*/
|
|
95
|
+
_convertToR5(jsonObj, version) {
|
|
96
|
+
if (version === 'R5') {
|
|
97
|
+
return jsonObj; // Already R5, no conversion needed
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (version === 'R3') {
|
|
101
|
+
// R3 to R5: Convert identifier from single object to array
|
|
102
|
+
if (jsonObj.identifier && !Array.isArray(jsonObj.identifier)) {
|
|
103
|
+
jsonObj.identifier = [jsonObj.identifier];
|
|
104
|
+
}
|
|
105
|
+
return jsonObj;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (version === 'R4') {
|
|
109
|
+
// R4 to R5: identifier is already an array, no conversion needed
|
|
110
|
+
return jsonObj;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
throw new Error(`Unsupported FHIR version: ${version}`);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Converts R5 CodeSystem to target version format (clones object first)
|
|
118
|
+
* @param {Object} r5Obj - The R5 format CodeSystem object
|
|
119
|
+
* @param {string} targetVersion - Target FHIR version
|
|
120
|
+
* @returns {Object} New object in target version format
|
|
121
|
+
* @private
|
|
122
|
+
*/
|
|
123
|
+
_convertFromR5(r5Obj, targetVersion) {
|
|
124
|
+
if (VersionUtilities.isR5Ver(targetVersion)) {
|
|
125
|
+
return r5Obj; // No conversion needed
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Clone the object to avoid modifying the original
|
|
129
|
+
const cloned = JSON.parse(JSON.stringify(r5Obj));
|
|
130
|
+
|
|
131
|
+
if (VersionUtilities.isR4Ver(targetVersion)) {
|
|
132
|
+
return this._convertR5ToR4(cloned);
|
|
133
|
+
} else if (VersionUtilities.isR3Ver(targetVersion)) {
|
|
134
|
+
return this._convertR5ToR3(cloned);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
throw new Error(`Unsupported target FHIR version: ${targetVersion}`);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Converts R5 CodeSystem to R4 format
|
|
142
|
+
* @param {Object} r5Obj - Cloned R5 CodeSystem object
|
|
143
|
+
* @returns {Object} R4 format CodeSystem
|
|
144
|
+
* @private
|
|
145
|
+
*/
|
|
146
|
+
_convertR5ToR4(r5Obj) {
|
|
147
|
+
// Remove R5-specific elements that don't exist in R4
|
|
148
|
+
if (r5Obj.versionAlgorithmString) {
|
|
149
|
+
delete r5Obj.versionAlgorithmString;
|
|
150
|
+
}
|
|
151
|
+
if (r5Obj.versionAlgorithmCoding) {
|
|
152
|
+
delete r5Obj.versionAlgorithmCoding;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Filter out R5-only filter operators
|
|
156
|
+
if (r5Obj.filter && Array.isArray(r5Obj.filter)) {
|
|
157
|
+
r5Obj.filter = r5Obj.filter.map(filter => {
|
|
158
|
+
if (filter.operator && Array.isArray(filter.operator)) {
|
|
159
|
+
// Remove R5-only operators like 'generalizes'
|
|
160
|
+
filter.operator = filter.operator.filter(op =>
|
|
161
|
+
!this._isR5OnlyFilterOperator(op)
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
return filter;
|
|
165
|
+
}).filter(filter =>
|
|
166
|
+
// Remove filters that have no valid operators left
|
|
167
|
+
!filter.operator || filter.operator.length > 0
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return r5Obj;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Converts R5 CodeSystem to R3 format
|
|
176
|
+
* @param {Object} r5Obj - Cloned R5 CodeSystem object
|
|
177
|
+
* @returns {Object} R3 format CodeSystem
|
|
178
|
+
* @private
|
|
179
|
+
*/
|
|
180
|
+
_convertR5ToR3(r5Obj) {
|
|
181
|
+
// First apply R4 conversions
|
|
182
|
+
const r4Obj = this._convertR5ToR4(r5Obj);
|
|
183
|
+
|
|
184
|
+
// R5/R4 to R3: Convert identifier from array back to single object
|
|
185
|
+
if (r4Obj.identifier && Array.isArray(r4Obj.identifier)) {
|
|
186
|
+
if (r4Obj.identifier.length > 0) {
|
|
187
|
+
// Take the first identifier if multiple exist
|
|
188
|
+
r4Obj.identifier = r4Obj.identifier[0];
|
|
189
|
+
} else {
|
|
190
|
+
// Remove empty array
|
|
191
|
+
delete r4Obj.identifier;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Remove additional R4-specific elements that don't exist in R3
|
|
196
|
+
if (r4Obj.supplements) {
|
|
197
|
+
delete r4Obj.supplements;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// R3 has more limited filter operator support
|
|
201
|
+
if (r4Obj.filter && Array.isArray(r4Obj.filter)) {
|
|
202
|
+
r4Obj.filter = r4Obj.filter.map(filter => {
|
|
203
|
+
if (filter.operator && Array.isArray(filter.operator)) {
|
|
204
|
+
// Keep only R3-compatible operators
|
|
205
|
+
filter.operator = filter.operator.filter(op =>
|
|
206
|
+
this._isR3CompatibleFilterOperator(op)
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
return filter;
|
|
210
|
+
}).filter(filter =>
|
|
211
|
+
// Remove filters that have no valid operators left
|
|
212
|
+
!filter.operator || filter.operator.length > 0
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return r4Obj;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Checks if a filter operator is R5-only
|
|
221
|
+
* @param {string} operator - Filter operator code
|
|
222
|
+
* @returns {boolean} True if operator is R5-only
|
|
223
|
+
* @private
|
|
224
|
+
*/
|
|
225
|
+
_isR5OnlyFilterOperator(operator) {
|
|
226
|
+
const r5OnlyOperators = [
|
|
227
|
+
'generalizes', // Added in R5
|
|
228
|
+
// Add other R5-only operators as they're identified
|
|
229
|
+
];
|
|
230
|
+
return r5OnlyOperators.includes(operator);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Checks if a filter operator is compatible with R3
|
|
235
|
+
* @param {string} operator - Filter operator code
|
|
236
|
+
* @returns {boolean} True if operator is R3-compatible
|
|
237
|
+
* @private
|
|
238
|
+
*/
|
|
239
|
+
_isR3CompatibleFilterOperator(operator) {
|
|
240
|
+
const r3CompatibleOperators = [
|
|
241
|
+
'=', // Equal
|
|
242
|
+
'is-a', // Is-A relationship
|
|
243
|
+
'descendent-of', // Descendant of (note: R3 spelling)
|
|
244
|
+
'is-not-a', // Is-Not-A relationship
|
|
245
|
+
'regex', // Regular expression
|
|
246
|
+
'in', // In set
|
|
247
|
+
'not-in', // Not in set
|
|
248
|
+
'exists', // Property exists
|
|
249
|
+
];
|
|
250
|
+
return r3CompatibleOperators.includes(operator);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Validates that this is a proper CodeSystem resource
|
|
255
|
+
* @throws {Error} If validation fails
|
|
256
|
+
*/
|
|
257
|
+
/**
|
|
258
|
+
* Enhanced validate method for CodeSystem class
|
|
259
|
+
* Add this to replace the existing validate() method
|
|
260
|
+
*/
|
|
261
|
+
validate() {
|
|
262
|
+
if (!this.jsonObj || typeof this.jsonObj !== 'object') {
|
|
263
|
+
throw new Error('Invalid CodeSystem: expected object');
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (this.jsonObj.resourceType !== 'CodeSystem') {
|
|
267
|
+
throw new Error(`Invalid CodeSystem: resourceType must be "CodeSystem", got "${this.jsonObj.resourceType}"`);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if (!this.jsonObj.url || typeof this.jsonObj.url !== 'string') {
|
|
271
|
+
throw new Error('Invalid CodeSystem: url is required and must be a string');
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
if (this.jsonObj.status && typeof this.jsonObj.status !== 'string') {
|
|
275
|
+
throw new Error('Invalid CodeSystem: status must be a string');
|
|
276
|
+
}
|
|
277
|
+
if (this.jsonObj.status && typeof this.jsonObj.status == 'string') {
|
|
278
|
+
const validStatuses = ['draft', 'active', 'retired', 'unknown'];
|
|
279
|
+
if (!validStatuses.includes(this.jsonObj.status)) {
|
|
280
|
+
throw new Error(`Invalid CodeSystem: status must be one of ${validStatuses.join(', ')}, got "${this.jsonObj.status}"`);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Validate identifier array
|
|
285
|
+
if (this.jsonObj.identifier) {
|
|
286
|
+
// Convert single identifier object to array if needed (for R3)
|
|
287
|
+
if (!Array.isArray(this.jsonObj.identifier)) {
|
|
288
|
+
this.jsonObj.identifier = [this.jsonObj.identifier];
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
this._validateArray(this.jsonObj.identifier, 'identifier', (identifier, index) => {
|
|
292
|
+
if (!identifier || typeof identifier !== 'object') {
|
|
293
|
+
throw new Error(`Invalid CodeSystem: identifier[${index}] must be an object`);
|
|
294
|
+
}
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// Validate jurisdiction array
|
|
299
|
+
if (this.jsonObj.jurisdiction) {
|
|
300
|
+
if (!Array.isArray(this.jsonObj.jurisdiction)) {
|
|
301
|
+
throw new Error('Invalid CodeSystem: jurisdiction must be an array if present');
|
|
302
|
+
}
|
|
303
|
+
this._validateArray(this.jsonObj.jurisdiction, 'jurisdiction', (jurisdiction, index) => {
|
|
304
|
+
if (!jurisdiction || typeof jurisdiction !== 'object') {
|
|
305
|
+
throw new Error(`Invalid CodeSystem: jurisdiction[${index}] must be an object`);
|
|
306
|
+
}
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// Validate useContext array
|
|
311
|
+
if (this.jsonObj.useContext) {
|
|
312
|
+
if (!Array.isArray(this.jsonObj.useContext)) {
|
|
313
|
+
throw new Error('Invalid CodeSystem: useContext must be an array if present');
|
|
314
|
+
}
|
|
315
|
+
this._validateArray(this.jsonObj.useContext, 'useContext', (useContext, index) => {
|
|
316
|
+
if (!useContext || typeof useContext !== 'object') {
|
|
317
|
+
throw new Error(`Invalid CodeSystem: useContext[${index}] must be an object`);
|
|
318
|
+
}
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// Validate filter array
|
|
323
|
+
if (this.jsonObj.filter) {
|
|
324
|
+
if (!Array.isArray(this.jsonObj.filter)) {
|
|
325
|
+
throw new Error('Invalid CodeSystem: filter must be an array if present');
|
|
326
|
+
}
|
|
327
|
+
this._validateArray(this.jsonObj.filter, 'filter', (filter, index) => {
|
|
328
|
+
if (!filter || typeof filter !== 'object') {
|
|
329
|
+
throw new Error(`Invalid CodeSystem: filter[${index}] must be an object`);
|
|
330
|
+
}
|
|
331
|
+
if (filter.operator && !Array.isArray(filter.operator)) {
|
|
332
|
+
throw new Error(`Invalid CodeSystem: filter[${index}].operator must be an array if present`);
|
|
333
|
+
}
|
|
334
|
+
if (filter.operator) {
|
|
335
|
+
this._validateArray(filter.operator, `filter[${index}].operator`, (operator, opIndex) => {
|
|
336
|
+
if (typeof operator !== 'string') {
|
|
337
|
+
throw new Error(`Invalid CodeSystem: filter[${index}].operator[${opIndex}] must be a string`);
|
|
338
|
+
}
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// Validate property array
|
|
345
|
+
if (this.jsonObj.property) {
|
|
346
|
+
if (!Array.isArray(this.jsonObj.property)) {
|
|
347
|
+
throw new Error('Invalid CodeSystem: property must be an array if present');
|
|
348
|
+
}
|
|
349
|
+
this._validateArray(this.jsonObj.property, 'property', (property, index) => {
|
|
350
|
+
if (!property || typeof property !== 'object') {
|
|
351
|
+
throw new Error(`Invalid CodeSystem: property[${index}] must be an object`);
|
|
352
|
+
}
|
|
353
|
+
if (!property.code || typeof property.code !== 'string') {
|
|
354
|
+
throw new Error(`Invalid CodeSystem: property[${index}].code is required and must be a string`);
|
|
355
|
+
}
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// Validate concept array
|
|
360
|
+
if (this.jsonObj.concept) {
|
|
361
|
+
if (!Array.isArray(this.jsonObj.concept)) {
|
|
362
|
+
throw new Error('Invalid CodeSystem: concept must be an array if present');
|
|
363
|
+
}
|
|
364
|
+
this._validateConceptArray(this.jsonObj.concept, 'concept');
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Helper method to validate arrays for null/undefined elements
|
|
370
|
+
* @param {Array} array - The array to validate
|
|
371
|
+
* @param {string} path - Path description for error messages
|
|
372
|
+
* @param {Function} [itemValidator] - Optional function to validate each item
|
|
373
|
+
* @private
|
|
374
|
+
*/
|
|
375
|
+
_validateArray(array, path, itemValidator) {
|
|
376
|
+
if (!Array.isArray(array)) {
|
|
377
|
+
throw new Error(`Invalid CodeSystem: ${path} must be an array`);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
array.forEach((item, index) => {
|
|
381
|
+
if (item === null || item === undefined) {
|
|
382
|
+
throw new Error(`Invalid CodeSystem: ${path}[${index}] is null or undefined`);
|
|
383
|
+
}
|
|
384
|
+
if (itemValidator) {
|
|
385
|
+
itemValidator(item, index);
|
|
386
|
+
}
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Recursively validates concept arrays and their nested structure
|
|
392
|
+
* @param {Array} concepts - Array of concepts to validate
|
|
393
|
+
* @param {string} path - Path description for error messages
|
|
394
|
+
* @private
|
|
395
|
+
*/
|
|
396
|
+
_validateConceptArray(concepts, path) {
|
|
397
|
+
this._validateArray(concepts, path, (concept, index) => {
|
|
398
|
+
const conceptPath = `${path}[${index}]`;
|
|
399
|
+
|
|
400
|
+
if (!concept || typeof concept !== 'object') {
|
|
401
|
+
throw new Error(`Invalid CodeSystem: ${conceptPath} must be an object`);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
if (!concept.code || typeof concept.code !== 'string') {
|
|
405
|
+
throw new Error(`Invalid CodeSystem: ${conceptPath}.code is required and must be a string`);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// Validate designation array
|
|
409
|
+
if (concept.designation) {
|
|
410
|
+
if (!Array.isArray(concept.designation)) {
|
|
411
|
+
throw new Error(`Invalid CodeSystem: ${conceptPath}.designation must be an array if present`);
|
|
412
|
+
}
|
|
413
|
+
this._validateArray(concept.designation, `${conceptPath}.designation`, (designation, desigIndex) => {
|
|
414
|
+
if (!designation || typeof designation !== 'object') {
|
|
415
|
+
throw new Error(`Invalid CodeSystem: ${conceptPath}.designation[${desigIndex}] must be an object`);
|
|
416
|
+
}
|
|
417
|
+
// We could add more specific designation validation here if needed
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// Validate property array
|
|
422
|
+
if (concept.property) {
|
|
423
|
+
if (!Array.isArray(concept.property)) {
|
|
424
|
+
throw new Error(`Invalid CodeSystem: ${conceptPath}.property must be an array if present`);
|
|
425
|
+
}
|
|
426
|
+
this._validateArray(concept.property, `${conceptPath}.property`, (property, propIndex) => {
|
|
427
|
+
if (!property || typeof property !== 'object') {
|
|
428
|
+
throw new Error(`Invalid CodeSystem: ${conceptPath}.property[${propIndex}] must be an object`);
|
|
429
|
+
}
|
|
430
|
+
if (!property.code || typeof property.code !== 'string') {
|
|
431
|
+
throw new Error(`Invalid CodeSystem: ${conceptPath}.property[${propIndex}].code is required and must be a string`);
|
|
432
|
+
}
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// Recursively validate nested concepts
|
|
437
|
+
if (concept.concept) {
|
|
438
|
+
if (!Array.isArray(concept.concept)) {
|
|
439
|
+
throw new Error(`Invalid CodeSystem: ${conceptPath}.concept must be an array if present`);
|
|
440
|
+
}
|
|
441
|
+
this._validateConceptArray(concept.concept, `${conceptPath}.concept`);
|
|
442
|
+
}
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
/**
|
|
447
|
+
* Builds internal maps for fast concept lookup and hierarchy navigation
|
|
448
|
+
* @private
|
|
449
|
+
*/
|
|
450
|
+
buildMaps() {
|
|
451
|
+
this.codeMap.clear();
|
|
452
|
+
if (this.caseInsensitive()) {
|
|
453
|
+
this.codeMapNC = new Map();
|
|
454
|
+
}
|
|
455
|
+
this.displayMap.clear();
|
|
456
|
+
this.parentToChildrenMap.clear();
|
|
457
|
+
this.childToParentsMap.clear();
|
|
458
|
+
|
|
459
|
+
if (!this.jsonObj.concept) {
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
// First pass: build basic maps and collect all concepts (including nested)
|
|
464
|
+
const allConcepts = [];
|
|
465
|
+
this._collectAllConcepts(this.jsonObj.concept, allConcepts);
|
|
466
|
+
|
|
467
|
+
allConcepts.forEach(concept => {
|
|
468
|
+
// Build code and display maps
|
|
469
|
+
this.codeMap.set(concept.code, concept);
|
|
470
|
+
if (this.caseInsensitive()) {
|
|
471
|
+
this.codeMapNC.set(concept.code.toLowerCase(), concept);
|
|
472
|
+
}
|
|
473
|
+
if (concept.display) {
|
|
474
|
+
this.displayMap.set(concept.display, concept);
|
|
475
|
+
}
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
// Second pass: build hierarchy maps
|
|
479
|
+
allConcepts.forEach(concept => {
|
|
480
|
+
this._buildHierarchyMaps(concept);
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
// Third pass: handle nested concept structures
|
|
484
|
+
this._buildNestedHierarchy(this.jsonObj.concept);
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
/**
|
|
488
|
+
* Recursively collects all concepts including nested ones
|
|
489
|
+
* @param {Object[]} concepts - Array of concepts
|
|
490
|
+
* @param {Object[]} allConcepts - Accumulator for all concepts
|
|
491
|
+
* @private
|
|
492
|
+
*/
|
|
493
|
+
_collectAllConcepts(concepts, allConcepts) {
|
|
494
|
+
concepts.forEach(concept => {
|
|
495
|
+
allConcepts.push(concept);
|
|
496
|
+
if (concept.concept && Array.isArray(concept.concept)) {
|
|
497
|
+
this._collectAllConcepts(concept.concept, allConcepts);
|
|
498
|
+
}
|
|
499
|
+
});
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
/**
|
|
503
|
+
* Builds hierarchy maps from concept properties
|
|
504
|
+
* @param {Object} concept - The concept to process
|
|
505
|
+
* @private
|
|
506
|
+
*/
|
|
507
|
+
_buildHierarchyMaps(concept) {
|
|
508
|
+
if (!concept.property || !Array.isArray(concept.property)) {
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
concept.property.forEach(property => {
|
|
513
|
+
if (property.code === 'parent' && property.valueCode) {
|
|
514
|
+
// This concept has a parent
|
|
515
|
+
this._addToChildToParentsMap(concept.code, property.valueCode);
|
|
516
|
+
this._addToParentToChildrenMap(property.valueCode, concept.code);
|
|
517
|
+
} else if (property.code === 'child' && property.valueCode) {
|
|
518
|
+
// This concept has a child
|
|
519
|
+
this._addToParentToChildrenMap(concept.code, property.valueCode);
|
|
520
|
+
this._addToChildToParentsMap(property.valueCode, concept.code);
|
|
521
|
+
}
|
|
522
|
+
});
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
/**
|
|
526
|
+
* Builds hierarchy from nested concept structures
|
|
527
|
+
* @param {Object[]} concepts - Array of concepts
|
|
528
|
+
* @param {string} [parentCode] - Code of parent concept
|
|
529
|
+
* @private
|
|
530
|
+
*/
|
|
531
|
+
_buildNestedHierarchy(concepts, parentCode = null) {
|
|
532
|
+
concepts.forEach(concept => {
|
|
533
|
+
if (parentCode) {
|
|
534
|
+
this._addToChildToParentsMap(concept.code, parentCode);
|
|
535
|
+
this._addToParentToChildrenMap(parentCode, concept.code);
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
if (concept.concept && Array.isArray(concept.concept)) {
|
|
539
|
+
this._buildNestedHierarchy(concept.concept, concept.code);
|
|
540
|
+
}
|
|
541
|
+
});
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
/**
|
|
545
|
+
* Adds a parent-child relationship to the child-to-parents map
|
|
546
|
+
* @param {string} childCode - The child concept code
|
|
547
|
+
* @param {string} parentCode - The parent concept code
|
|
548
|
+
* @private
|
|
549
|
+
*/
|
|
550
|
+
_addToChildToParentsMap(childCode, parentCode) {
|
|
551
|
+
if (!this.childToParentsMap.has(childCode)) {
|
|
552
|
+
this.childToParentsMap.set(childCode, []);
|
|
553
|
+
}
|
|
554
|
+
const parents = this.childToParentsMap.get(childCode);
|
|
555
|
+
if (!parents.includes(parentCode)) {
|
|
556
|
+
parents.push(parentCode);
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
/**
|
|
561
|
+
* Adds a parent-child relationship to the parent-to-children map
|
|
562
|
+
* @param {string} parentCode - The parent concept code
|
|
563
|
+
* @param {string} childCode - The child concept code
|
|
564
|
+
* @private
|
|
565
|
+
*/
|
|
566
|
+
_addToParentToChildrenMap(parentCode, childCode) {
|
|
567
|
+
if (!this.parentToChildrenMap.has(parentCode)) {
|
|
568
|
+
this.parentToChildrenMap.set(parentCode, []);
|
|
569
|
+
}
|
|
570
|
+
const children = this.parentToChildrenMap.get(parentCode);
|
|
571
|
+
if (!children.includes(childCode)) {
|
|
572
|
+
children.push(childCode);
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
/**
|
|
577
|
+
* Gets a concept by its code
|
|
578
|
+
* @param {string} code - The concept code to look up
|
|
579
|
+
* @returns {Object|undefined} The concept object or undefined if not found
|
|
580
|
+
*/
|
|
581
|
+
getConceptByCode(code) {
|
|
582
|
+
return this.caseInsensitive() ? this.codeMapNC.get(code.toLowerCase()) : this.codeMap.get(code);
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
/**
|
|
586
|
+
* Gets a concept by its display text
|
|
587
|
+
* @param {string} display - The display text to look up
|
|
588
|
+
* @returns {Object|undefined} The concept object or undefined if not found
|
|
589
|
+
*/
|
|
590
|
+
getConceptByDisplay(display) {
|
|
591
|
+
return this.displayMap.get(display);
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
/**
|
|
595
|
+
* Gets all child codes for a given parent code
|
|
596
|
+
* @param {string} parentCode - The parent concept code
|
|
597
|
+
* @returns {string[]} Array of child codes (empty array if no children)
|
|
598
|
+
*/
|
|
599
|
+
getChildren(parentCode) {
|
|
600
|
+
return this.parentToChildrenMap.get(parentCode) || [];
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
/**
|
|
604
|
+
* Gets all parent codes for a given child code
|
|
605
|
+
* @param {string} childCode - The child concept code
|
|
606
|
+
* @returns {string[]} Array of parent codes (empty array if no parents)
|
|
607
|
+
*/
|
|
608
|
+
getParents(childCode) {
|
|
609
|
+
return this.childToParentsMap.get(childCode) || [];
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
/**
|
|
613
|
+
* Gets all descendant codes (children, grandchildren, etc.) for a given code
|
|
614
|
+
* @param {string} code - The ancestor concept code
|
|
615
|
+
* @returns {string[]} Array of all descendant codes
|
|
616
|
+
*/
|
|
617
|
+
getDescendants(code) {
|
|
618
|
+
const descendants = [];
|
|
619
|
+
const descSet = new Set();
|
|
620
|
+
this.addDescendents(descendants, descSet, code, false);
|
|
621
|
+
return descendants;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
addDescendents(descendants, descSet, current, add) {
|
|
625
|
+
if (!descSet.has(current)) {
|
|
626
|
+
descSet.add(current);
|
|
627
|
+
if (add) {
|
|
628
|
+
descendants.push(current);
|
|
629
|
+
}
|
|
630
|
+
const children = this.getChildren(current);
|
|
631
|
+
for (let i = 0; i < children.length; i++) {
|
|
632
|
+
const child = children[i];
|
|
633
|
+
this.addDescendents(descendants, descSet, child, true);
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
/**
|
|
638
|
+
* Gets all ancestor codes (parents, grandparents, etc.) for a given code
|
|
639
|
+
* @param {string} code - The descendant concept code
|
|
640
|
+
* @returns {string[]} Array of all ancestor codes
|
|
641
|
+
*/
|
|
642
|
+
getAncestors(code) {
|
|
643
|
+
const ancestors = new Set();
|
|
644
|
+
const visited = new Set([code]); // Track visited codes to handle circular references
|
|
645
|
+
const toProcess = [code];
|
|
646
|
+
|
|
647
|
+
while (toProcess.length > 0) {
|
|
648
|
+
const current = toProcess.pop();
|
|
649
|
+
const parents = this.getParents(current);
|
|
650
|
+
|
|
651
|
+
parents.forEach(parent => {
|
|
652
|
+
if (!visited.has(parent)) {
|
|
653
|
+
visited.add(parent);
|
|
654
|
+
ancestors.add(parent);
|
|
655
|
+
toProcess.push(parent);
|
|
656
|
+
}
|
|
657
|
+
});
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
return Array.from(ancestors);
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
/**
|
|
664
|
+
* Checks if a code is a descendant of another code
|
|
665
|
+
* @param {string} descendantCode - The potential descendant code
|
|
666
|
+
* @param {string} ancestorCode - The potential ancestor code
|
|
667
|
+
* @returns {boolean} True if descendantCode is a descendant of ancestorCode
|
|
668
|
+
*/
|
|
669
|
+
isDescendantOf(descendantCode, ancestorCode) {
|
|
670
|
+
return this.getAncestors(descendantCode).includes(ancestorCode);
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
/**
|
|
674
|
+
* Gets root concepts (concepts with no parents)
|
|
675
|
+
* @returns {string[]} Array of root concept codes
|
|
676
|
+
*/
|
|
677
|
+
getRootConcepts() {
|
|
678
|
+
return Array.from(this.codeMap.keys()).filter(code =>
|
|
679
|
+
this.getParents(code).length === 0
|
|
680
|
+
);
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
/**
|
|
684
|
+
* Gets leaf concepts (concepts with no children)
|
|
685
|
+
* @returns {string[]} Array of leaf concept codes
|
|
686
|
+
*/
|
|
687
|
+
getLeafConcepts() {
|
|
688
|
+
return Array.from(this.codeMap.keys()).filter(code =>
|
|
689
|
+
this.getChildren(code).length === 0
|
|
690
|
+
);
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
/**
|
|
694
|
+
* Checks if a code exists in this code system
|
|
695
|
+
* @param {string} code - The code to check
|
|
696
|
+
* @returns {boolean} True if the code exists
|
|
697
|
+
*/
|
|
698
|
+
hasCode(code) {
|
|
699
|
+
return this.caseInsensitive() ? this.codeMapNC.has(code.toLowerCase()) : this.codeMap.has(code);
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
/**
|
|
703
|
+
* Gets all codes in this code system
|
|
704
|
+
* @returns {string[]} Array of all codes
|
|
705
|
+
*/
|
|
706
|
+
getAllCodes() {
|
|
707
|
+
return Array.from(this.codeMap.keys());
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
/**
|
|
711
|
+
* Gets all concepts in this code system
|
|
712
|
+
* @returns {Object[]} Array of all concept objects
|
|
713
|
+
*/
|
|
714
|
+
getAllConcepts() {
|
|
715
|
+
return Array.from(this.codeMap.values());
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
/**
|
|
719
|
+
* Gets basic info about this code system
|
|
720
|
+
* @returns {Object} Basic information object
|
|
721
|
+
*/
|
|
722
|
+
getInfo() {
|
|
723
|
+
return {
|
|
724
|
+
resourceType: this.jsonObj.resourceType,
|
|
725
|
+
url: this.jsonObj.url,
|
|
726
|
+
version: this.jsonObj.version,
|
|
727
|
+
name: this.jsonObj.name,
|
|
728
|
+
title: this.jsonObj.title,
|
|
729
|
+
status: this.jsonObj.status,
|
|
730
|
+
fhirVersion: this.fhirVersion,
|
|
731
|
+
conceptCount: this.codeMap.size,
|
|
732
|
+
rootConceptCount: this.getRootConcepts().length,
|
|
733
|
+
leafConceptCount: this.getLeafConcepts().length
|
|
734
|
+
};
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
static isUseADisplay(use) {
|
|
738
|
+
return (use != null) || true; // for now
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
static makeUseForDisplay() {
|
|
742
|
+
return null;
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
/**
|
|
746
|
+
* Gets the language for this CodeSystem as a Language object
|
|
747
|
+
* @returns {Language|null} Parsed language or null if not specified
|
|
748
|
+
*/
|
|
749
|
+
langCode() {
|
|
750
|
+
return this.jsonObj.language ? new Language(this.jsonObj.language) : null;
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
get content() {
|
|
754
|
+
return this.jsonObj.content;
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
hasHierarchy() {
|
|
758
|
+
return this.parentToChildrenMap.size > 0 || this.childToParentsMap.size > 0;
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
caseInsensitive() {
|
|
762
|
+
return this.jsonObj.caseSensitive == undefined || this.jsonObj.caseSensitive == false;
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
get id() {
|
|
766
|
+
return this.jsonObj.id;
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
set id(value) {
|
|
770
|
+
this.jsonObj.id = value;
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
module.exports = { CodeSystem, CodeSystemContentMode };
|