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,568 @@
|
|
|
1
|
+
const {CanonicalResource} = require("./canonical-resource");
|
|
2
|
+
const {VersionUtilities} = require("../../library/version-utilities");
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Represents a FHIR ConceptMap resource with version conversion support
|
|
6
|
+
* @class
|
|
7
|
+
*/
|
|
8
|
+
class ConceptMap extends CanonicalResource {
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Creates a new ConceptMap instance
|
|
13
|
+
* @param {Object} jsonObj - The JSON object containing ConceptMap data
|
|
14
|
+
* @param {string} [version='R5'] - FHIR version ('R3', 'R4', or 'R5')
|
|
15
|
+
*/
|
|
16
|
+
constructor(jsonObj, fhirVersion = 'R5') {
|
|
17
|
+
super(jsonObj, fhirVersion);
|
|
18
|
+
// Convert to R5 format internally (modifies input for performance)
|
|
19
|
+
this.jsonObj = this._convertToR5(jsonObj, fhirVersion);
|
|
20
|
+
this.validate();
|
|
21
|
+
this.id = this.jsonObj.id;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Static factory method for convenience
|
|
26
|
+
* @param {string} jsonString - JSON string representation of ValueSet
|
|
27
|
+
* @param {string} [version='R5'] - FHIR version ('R3', 'R4', or 'R5')
|
|
28
|
+
* @returns {ValueSet} New ValueSet instance
|
|
29
|
+
*/
|
|
30
|
+
static fromJSON(jsonString, version = 'R5') {
|
|
31
|
+
return new ConceptMap(JSON.parse(jsonString), version);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Returns JSON string representation
|
|
36
|
+
* @param {string} [version='R5'] - Target FHIR version ('R3', 'R4', or 'R5')
|
|
37
|
+
* @returns {string} JSON string
|
|
38
|
+
*/
|
|
39
|
+
toJSONString(version = 'R5') {
|
|
40
|
+
const outputObj = this._convertFromR5(this.jsonObj, version);
|
|
41
|
+
return JSON.stringify(outputObj);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Converts input ConceptMap to R5 format (modifies input object for performance)
|
|
46
|
+
* @param {Object} jsonObj - The input ConceptMap object
|
|
47
|
+
* @param {string} version - Source FHIR version
|
|
48
|
+
* @returns {Object} The same object, potentially modified to R5 format
|
|
49
|
+
* @private
|
|
50
|
+
*/
|
|
51
|
+
_convertToR5(jsonObj, version) {
|
|
52
|
+
if (VersionUtilities.isR5Ver(version)) {
|
|
53
|
+
return jsonObj; // Already R5, no conversion needed
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (VersionUtilities.isR3Ver(version) || VersionUtilities.isR4Ver(version)) {
|
|
57
|
+
// Convert identifier from single object to array
|
|
58
|
+
if (jsonObj.identifier && !Array.isArray(jsonObj.identifier)) {
|
|
59
|
+
jsonObj.identifier = [jsonObj.identifier];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Convert source/target to sourceScope/targetScope
|
|
63
|
+
if (jsonObj.source !== undefined) {
|
|
64
|
+
// Combine source + sourceVersion if both exist
|
|
65
|
+
if (jsonObj.sourceVersion) {
|
|
66
|
+
jsonObj.sourceScope = `${jsonObj.source}|${jsonObj.sourceVersion}`;
|
|
67
|
+
delete jsonObj.sourceVersion;
|
|
68
|
+
} else {
|
|
69
|
+
jsonObj.sourceScope = jsonObj.source;
|
|
70
|
+
}
|
|
71
|
+
delete jsonObj.source;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (jsonObj.target !== undefined) {
|
|
75
|
+
// Combine target + targetVersion if both exist
|
|
76
|
+
if (jsonObj.targetVersion) {
|
|
77
|
+
jsonObj.targetScope = `${jsonObj.target}|${jsonObj.targetVersion}`;
|
|
78
|
+
delete jsonObj.targetVersion;
|
|
79
|
+
} else {
|
|
80
|
+
jsonObj.targetScope = jsonObj.target;
|
|
81
|
+
}
|
|
82
|
+
delete jsonObj.target;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Convert equivalence to relationship in group.element.target
|
|
86
|
+
if (jsonObj.group && Array.isArray(jsonObj.group)) {
|
|
87
|
+
jsonObj.group.forEach(group => {
|
|
88
|
+
if (group.element && Array.isArray(group.element)) {
|
|
89
|
+
group.element.forEach(element => {
|
|
90
|
+
if (element.target && Array.isArray(element.target)) {
|
|
91
|
+
element.target.forEach(target => {
|
|
92
|
+
if (target.equivalence && !target.relationship) {
|
|
93
|
+
// Convert equivalence to relationship and keep both
|
|
94
|
+
target.relationship = this._convertEquivalenceToRelationship(target.equivalence);
|
|
95
|
+
// Keep equivalence for backward compatibility
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return jsonObj;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
throw new Error(`Unsupported FHIR version: ${version}`);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Converts R5 ConceptMap to target version format (clones object first)
|
|
112
|
+
* @param {Object} r5Obj - The R5 format ConceptMap object
|
|
113
|
+
* @param {string} targetVersion - Target FHIR version
|
|
114
|
+
* @returns {Object} New object in target version format
|
|
115
|
+
* @private
|
|
116
|
+
*/
|
|
117
|
+
_convertFromR5(r5Obj, targetVersion) {
|
|
118
|
+
if (VersionUtilities.isR5Ver(targetVersion)) {
|
|
119
|
+
return r5Obj; // No conversion needed
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Clone the object to avoid modifying the original
|
|
123
|
+
const cloned = JSON.parse(JSON.stringify(r5Obj));
|
|
124
|
+
|
|
125
|
+
if (VersionUtilities.isR4Ver(targetVersion)) {
|
|
126
|
+
return this._convertR5ToR4(cloned);
|
|
127
|
+
} else if (VersionUtilities.isR3Ver(targetVersion)) {
|
|
128
|
+
return this._convertR5ToR3(cloned);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
throw new Error(`Unsupported target FHIR version: ${targetVersion}`);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Converts R5 ConceptMap to R4 format
|
|
136
|
+
* @param {Object} r5Obj - Cloned R5 ConceptMap object
|
|
137
|
+
* @returns {Object} R4 format ConceptMap
|
|
138
|
+
* @private
|
|
139
|
+
*/
|
|
140
|
+
_convertR5ToR4(r5Obj) {
|
|
141
|
+
// Remove R5-specific elements
|
|
142
|
+
if (r5Obj.versionAlgorithmString) {
|
|
143
|
+
delete r5Obj.versionAlgorithmString;
|
|
144
|
+
}
|
|
145
|
+
if (r5Obj.versionAlgorithmCoding) {
|
|
146
|
+
delete r5Obj.versionAlgorithmCoding;
|
|
147
|
+
}
|
|
148
|
+
if (r5Obj.property) {
|
|
149
|
+
delete r5Obj.property;
|
|
150
|
+
}
|
|
151
|
+
if (r5Obj.additionalAttribute) {
|
|
152
|
+
delete r5Obj.additionalAttribute;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Convert identifier array back to single object
|
|
156
|
+
if (r5Obj.identifier && Array.isArray(r5Obj.identifier)) {
|
|
157
|
+
if (r5Obj.identifier.length > 0) {
|
|
158
|
+
r5Obj.identifier = r5Obj.identifier[0]; // Take first identifier
|
|
159
|
+
} else {
|
|
160
|
+
delete r5Obj.identifier;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Convert sourceScope/targetScope back to source/target + version
|
|
165
|
+
if (r5Obj.sourceScope) {
|
|
166
|
+
const parts = r5Obj.sourceScope.split('|');
|
|
167
|
+
r5Obj.source = parts[0];
|
|
168
|
+
if (parts.length > 1) {
|
|
169
|
+
r5Obj.sourceVersion = parts[1];
|
|
170
|
+
}
|
|
171
|
+
delete r5Obj.sourceScope;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (r5Obj.targetScope) {
|
|
175
|
+
const parts = r5Obj.targetScope.split('|');
|
|
176
|
+
r5Obj.target = parts[0];
|
|
177
|
+
if (parts.length > 1) {
|
|
178
|
+
r5Obj.targetVersion = parts[1];
|
|
179
|
+
}
|
|
180
|
+
delete r5Obj.targetScope;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Convert relationship back to equivalence in group.element.target
|
|
184
|
+
if (r5Obj.group && Array.isArray(r5Obj.group)) {
|
|
185
|
+
r5Obj.group.forEach(group => {
|
|
186
|
+
if (group.element && Array.isArray(group.element)) {
|
|
187
|
+
group.element.forEach(element => {
|
|
188
|
+
if (element.target && Array.isArray(element.target)) {
|
|
189
|
+
element.target.forEach(target => {
|
|
190
|
+
// If we have both equivalence and relationship, prefer equivalence for R4
|
|
191
|
+
if (target.relationship && !target.equivalence) {
|
|
192
|
+
target.equivalence = this._convertRelationshipToEquivalence(target.relationship);
|
|
193
|
+
}
|
|
194
|
+
// Remove R5-only relationship field
|
|
195
|
+
delete target.relationship;
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return r5Obj;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Converts R5 ConceptMap to R3 format
|
|
208
|
+
* @param {Object} r5Obj - Cloned R5 ConceptMap object
|
|
209
|
+
* @returns {Object} R3 format ConceptMap
|
|
210
|
+
* @private
|
|
211
|
+
*/
|
|
212
|
+
_convertR5ToR3(r5Obj) {
|
|
213
|
+
// First apply R4 conversions
|
|
214
|
+
const r4Obj = this._convertR5ToR4(r5Obj);
|
|
215
|
+
|
|
216
|
+
// R3 has the same structure as R4 for the elements we care about
|
|
217
|
+
return r4Obj;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Converts R3/R4 equivalence to R5 relationship
|
|
222
|
+
* @param {string} equivalence - R3/R4 equivalence value
|
|
223
|
+
* @returns {string} R5 relationship value
|
|
224
|
+
* @private
|
|
225
|
+
*/
|
|
226
|
+
_convertEquivalenceToRelationship(equivalence) {
|
|
227
|
+
const equivalenceToRelationship = {
|
|
228
|
+
'relatedto': 'related-to',
|
|
229
|
+
'equivalent': 'equivalent',
|
|
230
|
+
'equal': 'equivalent',
|
|
231
|
+
'wider': 'source-is-broader-than-target',
|
|
232
|
+
'subsumes': 'source-is-broader-than-target',
|
|
233
|
+
'narrower': 'source-is-narrower-than-target',
|
|
234
|
+
'specializes': 'source-is-narrower-than-target',
|
|
235
|
+
'inexact': 'not-related-to',
|
|
236
|
+
'unmatched': 'not-related-to',
|
|
237
|
+
'disjoint': 'not-related-to'
|
|
238
|
+
};
|
|
239
|
+
return equivalenceToRelationship[equivalence] || 'related-to';
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Converts R5 relationship back to R3/R4 equivalence
|
|
244
|
+
* @param {string} relationship - R5 relationship value
|
|
245
|
+
* @returns {string} R3/R4 equivalence value
|
|
246
|
+
* @private
|
|
247
|
+
*/
|
|
248
|
+
_convertRelationshipToEquivalence(relationship) {
|
|
249
|
+
const relationshipToEquivalence = {
|
|
250
|
+
'related-to': 'relatedto',
|
|
251
|
+
'equivalent': 'equivalent',
|
|
252
|
+
'source-is-broader-than-target': 'wider',
|
|
253
|
+
'source-is-narrower-than-target': 'narrower',
|
|
254
|
+
'not-related-to': 'unmatched'
|
|
255
|
+
};
|
|
256
|
+
return relationshipToEquivalence[relationship] || 'relatedto';
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Gets the FHIR version this ConceptMap was loaded from
|
|
261
|
+
* @returns {string} FHIR version ('R3', 'R4', or 'R5')
|
|
262
|
+
*/
|
|
263
|
+
getFHIRVersion() {
|
|
264
|
+
return this.version;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Validates that this is a proper ConceptMap resource
|
|
269
|
+
* @throws {Error} If validation fails
|
|
270
|
+
*/
|
|
271
|
+
validate() {
|
|
272
|
+
if (!this.jsonObj || typeof this.jsonObj !== 'object') {
|
|
273
|
+
throw new Error('Invalid ConceptMap: expected object');
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if (this.jsonObj.resourceType !== 'ConceptMap') {
|
|
277
|
+
throw new Error(`Invalid ConceptMap: resourceType must be "ConceptMap", got "${this.jsonObj.resourceType}"`);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (!this.jsonObj.url || typeof this.jsonObj.url !== 'string') {
|
|
281
|
+
throw new Error('Invalid ConceptMap: url is required and must be a string');
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
if (this.jsonObj.name && typeof this.jsonObj.name !== 'string') {
|
|
285
|
+
throw new Error('Invalid ConceptMap: name must be a string if present');
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
if (!this.jsonObj.status || typeof this.jsonObj.status !== 'string') {
|
|
289
|
+
throw new Error('Invalid ConceptMap: status is required and must be a string');
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const validStatuses = ['draft', 'active', 'retired', 'unknown'];
|
|
293
|
+
if (!validStatuses.includes(this.jsonObj.status)) {
|
|
294
|
+
throw new Error(`Invalid ConceptMap: status must be one of ${validStatuses.join(', ')}, got "${this.jsonObj.status}"`);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// Validate identifier - should be array in R5 after conversion
|
|
298
|
+
if (this.jsonObj.identifier && !Array.isArray(this.jsonObj.identifier)) {
|
|
299
|
+
throw new Error('Invalid ConceptMap: identifier should be an array (converted from R3/R4 format)');
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Validate group structure if present
|
|
303
|
+
if (this.jsonObj.group && !Array.isArray(this.jsonObj.group)) {
|
|
304
|
+
throw new Error('Invalid ConceptMap: group must be an array if present');
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Validate group elements
|
|
308
|
+
if (this.jsonObj.group) {
|
|
309
|
+
this.jsonObj.group.forEach((group, groupIndex) => {
|
|
310
|
+
if (group.element && !Array.isArray(group.element)) {
|
|
311
|
+
throw new Error(`Invalid ConceptMap: group[${groupIndex}].element must be an array if present`);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
if (group.element) {
|
|
315
|
+
group.element.forEach((element, elementIndex) => {
|
|
316
|
+
|
|
317
|
+
if (element.target && !Array.isArray(element.target)) {
|
|
318
|
+
throw new Error(`Invalid ConceptMap: group[${groupIndex}].element[${elementIndex}].target must be an array if present`);
|
|
319
|
+
}
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
providesTranslation(sourceSystem, sourceScope, targetScope, targetSystem) {
|
|
327
|
+
let source = this.sourceScope;
|
|
328
|
+
let target = this.targetScope;
|
|
329
|
+
if (this.canonicalMatches(source, sourceScope) && this.canonicalMatches(target, targetScope)) {
|
|
330
|
+
return true;
|
|
331
|
+
}
|
|
332
|
+
for (let grp of this.jsonObj.group || []) {
|
|
333
|
+
let source = grp.source;
|
|
334
|
+
let target = grp.target;
|
|
335
|
+
if (this.canonicalMatches(source, sourceSystem) && this.canonicalMatches(target, targetSystem)) {
|
|
336
|
+
return true;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
return false;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
listTranslations(coding, targetScope, targetSystem) {
|
|
344
|
+
let result = [];
|
|
345
|
+
let vurl = VersionUtilities.vurl(coding.system, coding.version);
|
|
346
|
+
|
|
347
|
+
let all = this.canonicalMatches(targetScope, this.targetScope);
|
|
348
|
+
for (const g of this.jsonObj.group || []) {
|
|
349
|
+
if (all || (this.canonicalMatches(vurl, g.source) && this.canonicalMatches(targetSystem, g.target) )) {
|
|
350
|
+
for (const em of g.element || []) {
|
|
351
|
+
if (em.code === coding.code) {
|
|
352
|
+
let match = {
|
|
353
|
+
group: g,
|
|
354
|
+
match: em
|
|
355
|
+
};
|
|
356
|
+
result.push(match);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
return result;
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* Gets the source scope (R5) or source system (R3/R4)
|
|
365
|
+
* @returns {string|undefined} Source scope/system
|
|
366
|
+
*/
|
|
367
|
+
get sourceScope() {
|
|
368
|
+
return this.jsonObj.sourceScopeUri ? this.jsonObj.sourceScopeUri : this.jsonObj.sourceScopeCanonical;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Gets the target scope (R5) or target system (R3/R4)
|
|
373
|
+
* @returns {string|undefined} Target scope/system
|
|
374
|
+
*/
|
|
375
|
+
get targetScope() {
|
|
376
|
+
return this.jsonObj.targetScopeUri ? this.jsonObj.targetScopeUri : this.jsonObj.targetScopeCanonical;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* Gets all mapping groups
|
|
381
|
+
* @returns {Object[]} Array of group objects
|
|
382
|
+
*/
|
|
383
|
+
getGroups() {
|
|
384
|
+
return this.jsonObj.group || [];
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* Gets all source concepts across all groups
|
|
389
|
+
* @returns {Object[]} Array of {system, code, display} objects
|
|
390
|
+
*/
|
|
391
|
+
getSourceConcepts() {
|
|
392
|
+
const concepts = [];
|
|
393
|
+
this.getGroups().forEach(group => {
|
|
394
|
+
const system = group.source;
|
|
395
|
+
if (group.element) {
|
|
396
|
+
group.element.forEach(element => {
|
|
397
|
+
concepts.push({
|
|
398
|
+
system: system,
|
|
399
|
+
code: element.code,
|
|
400
|
+
display: element.display
|
|
401
|
+
});
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
});
|
|
405
|
+
return concepts;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* Gets all target concepts across all groups
|
|
410
|
+
* @returns {Object[]} Array of {system, code, display, equivalence/relationship} objects
|
|
411
|
+
*/
|
|
412
|
+
getTargetConcepts() {
|
|
413
|
+
const concepts = [];
|
|
414
|
+
this.getGroups().forEach(group => {
|
|
415
|
+
const system = group.target;
|
|
416
|
+
if (group.element) {
|
|
417
|
+
group.element.forEach(element => {
|
|
418
|
+
if (element.target) {
|
|
419
|
+
element.target.forEach(target => {
|
|
420
|
+
concepts.push({
|
|
421
|
+
system: system,
|
|
422
|
+
code: target.code,
|
|
423
|
+
display: target.display,
|
|
424
|
+
equivalence: target.equivalence,
|
|
425
|
+
relationship: target.relationship
|
|
426
|
+
});
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
});
|
|
432
|
+
return concepts;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* Finds mappings for a source concept
|
|
437
|
+
* @param {string} sourceSystem - Source system URL
|
|
438
|
+
* @param {string} sourceCode - Source concept code
|
|
439
|
+
* @returns {Object[]} Array of target mappings
|
|
440
|
+
*/
|
|
441
|
+
findMappings(sourceSystem, sourceCode) {
|
|
442
|
+
const mappings = [];
|
|
443
|
+
this.getGroups().forEach(group => {
|
|
444
|
+
if (group.source === sourceSystem && group.element) {
|
|
445
|
+
const element = group.element.find(el => el.code === sourceCode);
|
|
446
|
+
if (element && element.target) {
|
|
447
|
+
element.target.forEach(target => {
|
|
448
|
+
mappings.push({
|
|
449
|
+
targetSystem: group.target,
|
|
450
|
+
targetCode: target.code,
|
|
451
|
+
targetDisplay: target.display,
|
|
452
|
+
equivalence: target.equivalence,
|
|
453
|
+
relationship: target.relationship,
|
|
454
|
+
comment: target.comment
|
|
455
|
+
});
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
});
|
|
460
|
+
return mappings;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* Finds reverse mappings for a target concept
|
|
465
|
+
* @param {string} targetSystem - Target system URL
|
|
466
|
+
* @param {string} targetCode - Target concept code
|
|
467
|
+
* @returns {Object[]} Array of source mappings
|
|
468
|
+
*/
|
|
469
|
+
findReverseMappings(targetSystem, targetCode) {
|
|
470
|
+
const mappings = [];
|
|
471
|
+
this.getGroups().forEach(group => {
|
|
472
|
+
if (group.target === targetSystem && group.element) {
|
|
473
|
+
group.element.forEach(element => {
|
|
474
|
+
if (element.target) {
|
|
475
|
+
const targetMatch = element.target.find(t => t.code === targetCode);
|
|
476
|
+
if (targetMatch) {
|
|
477
|
+
mappings.push({
|
|
478
|
+
sourceSystem: group.source,
|
|
479
|
+
sourceCode: element.code,
|
|
480
|
+
sourceDisplay: element.display,
|
|
481
|
+
equivalence: targetMatch.equivalence,
|
|
482
|
+
relationship: targetMatch.relationship,
|
|
483
|
+
comment: targetMatch.comment
|
|
484
|
+
});
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
});
|
|
490
|
+
return mappings;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
/**
|
|
494
|
+
* Gets all unique source systems
|
|
495
|
+
* @returns {string[]} Array of source system URLs
|
|
496
|
+
*/
|
|
497
|
+
getSourceSystems() {
|
|
498
|
+
const systems = new Set();
|
|
499
|
+
this.getGroups().forEach(group => {
|
|
500
|
+
if (group.source) {
|
|
501
|
+
systems.add(group.source);
|
|
502
|
+
}
|
|
503
|
+
});
|
|
504
|
+
return Array.from(systems);
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
/**
|
|
508
|
+
* Gets all unique target systems
|
|
509
|
+
* @returns {string[]} Array of target system URLs
|
|
510
|
+
*/
|
|
511
|
+
getTargetSystems() {
|
|
512
|
+
const systems = new Set();
|
|
513
|
+
this.getGroups().forEach(group => {
|
|
514
|
+
if (group.target) {
|
|
515
|
+
systems.add(group.target);
|
|
516
|
+
}
|
|
517
|
+
});
|
|
518
|
+
return Array.from(systems);
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
/**
|
|
522
|
+
* Gets basic info about this concept map
|
|
523
|
+
* @returns {Object} Basic information object
|
|
524
|
+
*/
|
|
525
|
+
getInfo() {
|
|
526
|
+
const groups = this.getGroups();
|
|
527
|
+
const totalMappings = groups.reduce((sum, group) => {
|
|
528
|
+
return sum + (group.element ? group.element.reduce((elSum, el) => {
|
|
529
|
+
return elSum + (el.target ? el.target.length : 0);
|
|
530
|
+
}, 0) : 0);
|
|
531
|
+
}, 0);
|
|
532
|
+
|
|
533
|
+
return {
|
|
534
|
+
resourceType: this.jsonObj.resourceType,
|
|
535
|
+
url: this.jsonObj.url,
|
|
536
|
+
version: this.jsonObj.version,
|
|
537
|
+
name: this.jsonObj.name,
|
|
538
|
+
title: this.jsonObj.title,
|
|
539
|
+
status: this.jsonObj.status,
|
|
540
|
+
fhirVersion: this.version,
|
|
541
|
+
sourceScope: this.getSourceScope(),
|
|
542
|
+
targetScope: this.getTargetScope(),
|
|
543
|
+
groupCount: groups.length,
|
|
544
|
+
sourceSystems: this.getSourceSystems(),
|
|
545
|
+
targetSystems: this.getTargetSystems(),
|
|
546
|
+
totalMappings: totalMappings
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
canonicalMatches(value, pattern) {
|
|
551
|
+
if (!pattern || !value) {
|
|
552
|
+
return false;
|
|
553
|
+
}
|
|
554
|
+
const { url: vu, version: vv } = VersionUtilities.splitCanonical(value);
|
|
555
|
+
const { url: pu, version: pv } = VersionUtilities.splitCanonical(pattern);
|
|
556
|
+
|
|
557
|
+
if (!vu || !pu || vu != pu) {
|
|
558
|
+
return false;
|
|
559
|
+
}
|
|
560
|
+
if (!pv) {
|
|
561
|
+
return true;
|
|
562
|
+
}
|
|
563
|
+
return vv && VersionUtilities.versionMatchesByAlgorithm(pv, vv);
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
|
|
568
|
+
module.exports = { ConceptMap };
|