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.
Files changed (277) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/FHIRsmith.png +0 -0
  3. package/README.md +277 -0
  4. package/config-template.json +144 -0
  5. package/library/folder-setup.js +58 -0
  6. package/library/html-server.js +166 -0
  7. package/library/html.js +835 -0
  8. package/library/i18nsupport.js +259 -0
  9. package/library/languages.js +779 -0
  10. package/library/logger-telnet.js +205 -0
  11. package/library/logger.js +279 -0
  12. package/library/package-manager.js +876 -0
  13. package/library/utilities.js +196 -0
  14. package/library/version-utilities.js +1056 -0
  15. package/npmprojector/config-example.json +13 -0
  16. package/npmprojector/indexer.js +394 -0
  17. package/npmprojector/npmprojector.js +395 -0
  18. package/npmprojector/readme.md +174 -0
  19. package/npmprojector/watcher.js +335 -0
  20. package/package.json +119 -0
  21. package/packages/package-crawler.js +846 -0
  22. package/packages/packages-template.html +126 -0
  23. package/packages/packages.js +2838 -0
  24. package/passwords.ini +2 -0
  25. package/publisher/publisher-template.html +208 -0
  26. package/publisher/publisher.js +2167 -0
  27. package/publisher/task-draft.js +458 -0
  28. package/registry/api.js +735 -0
  29. package/registry/crawler.js +637 -0
  30. package/registry/model.js +513 -0
  31. package/registry/readme.md +243 -0
  32. package/registry/registry-data.json +121015 -0
  33. package/registry/registry-template.html +126 -0
  34. package/registry/registry.js +1395 -0
  35. package/registry/test-runner.js +237 -0
  36. package/root-template.html +124 -0
  37. package/server.js +524 -0
  38. package/shl/private-key.pem +5 -0
  39. package/shl/public-key.pem +18 -0
  40. package/shl/shl.js +1125 -0
  41. package/shl/vhl.js +69 -0
  42. package/static/FHIRsmith128.png +0 -0
  43. package/static/FHIRsmith16.png +0 -0
  44. package/static/FHIRsmith32.png +0 -0
  45. package/static/FHIRsmith64.png +0 -0
  46. package/static/assets/css/bootstrap-fhir.css +5302 -0
  47. package/static/assets/css/bootstrap-glyphicons.css +2 -0
  48. package/static/assets/css/bootstrap.css +4097 -0
  49. package/static/assets/css/jquery-ui.css +523 -0
  50. package/static/assets/css/jquery-ui.structure.css +863 -0
  51. package/static/assets/css/jquery-ui.structure.min.css +5 -0
  52. package/static/assets/css/jquery-ui.theme.css +439 -0
  53. package/static/assets/css/jquery-ui.theme.min.css +5 -0
  54. package/static/assets/css/jquery.ui.all.css +7 -0
  55. package/static/assets/css/modules.css +18 -0
  56. package/static/assets/css/project.css +367 -0
  57. package/static/assets/css/pygments-manni.css +66 -0
  58. package/static/assets/css/tags.css +74 -0
  59. package/static/assets/css/xml.css +2 -0
  60. package/static/assets/fonts/glyphiconshalflings-regular.eot +0 -0
  61. package/static/assets/fonts/glyphiconshalflings-regular.otf +0 -0
  62. package/static/assets/fonts/glyphiconshalflings-regular.svg +175 -0
  63. package/static/assets/fonts/glyphiconshalflings-regular.ttf +0 -0
  64. package/static/assets/fonts/glyphiconshalflings-regular.woff +0 -0
  65. package/static/assets/ico/apple-touch-icon-114-precomposed.png +0 -0
  66. package/static/assets/ico/apple-touch-icon-144-precomposed.png +0 -0
  67. package/static/assets/ico/apple-touch-icon-57-precomposed.png +0 -0
  68. package/static/assets/ico/apple-touch-icon-72-precomposed.png +0 -0
  69. package/static/assets/ico/favicon.ico +0 -0
  70. package/static/assets/ico/favicon.png +0 -0
  71. package/static/assets/images/fhir-logo-www.png +0 -0
  72. package/static/assets/images/fhir-logo.png +0 -0
  73. package/static/assets/images/hl7-logo.png +0 -0
  74. package/static/assets/images/logo_ansinew.jpg +0 -0
  75. package/static/assets/images/search.png +0 -0
  76. package/static/assets/images/stripe.png +0 -0
  77. package/static/assets/images/target.png +0 -0
  78. package/static/assets/images/tx-registry-root.gif +0 -0
  79. package/static/assets/images/tx-registry.png +0 -0
  80. package/static/assets/images/tx-server.png +0 -0
  81. package/static/assets/images/tx-version.png +0 -0
  82. package/static/assets/js/bootstrap.min.js +6 -0
  83. package/static/assets/js/fhir-gw.js +259 -0
  84. package/static/assets/js/fhir.js +2 -0
  85. package/static/assets/js/html5shiv.js +8 -0
  86. package/static/assets/js/jcookie.js +96 -0
  87. package/static/assets/js/jquery-ui.min.js +6 -0
  88. package/static/assets/js/jquery.js +10716 -0
  89. package/static/assets/js/jquery.min.js +2 -0
  90. package/static/assets/js/jquery.ui.core.js +314 -0
  91. package/static/assets/js/jquery.ui.draggable.js +825 -0
  92. package/static/assets/js/jquery.ui.mouse.js +162 -0
  93. package/static/assets/js/jquery.ui.resizable.js +842 -0
  94. package/static/assets/js/jquery.ui.widget.js +268 -0
  95. package/static/assets/js/json2.js +487 -0
  96. package/static/assets/js/jtip.js +97 -0
  97. package/static/assets/js/respond.min.js +6 -0
  98. package/static/assets/js/statuspage.js +70 -0
  99. package/static/assets/js/xml.js +2 -0
  100. package/static/dist/js/bootstrap.js +1964 -0
  101. package/static/favicon.png +0 -0
  102. package/static/fhir.css +626 -0
  103. package/static/icon-fhir-16.png +0 -0
  104. package/static/images/ui-bg_diagonals-thick_18_b81900_40x40.png +0 -0
  105. package/static/images/ui-bg_diagonals-thick_20_666666_40x40.png +0 -0
  106. package/static/images/ui-bg_flat_10_000000_40x100.png +0 -0
  107. package/static/images/ui-bg_glass_100_f6f6f6_1x400.png +0 -0
  108. package/static/images/ui-bg_glass_100_fdf5ce_1x400.png +0 -0
  109. package/static/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
  110. package/static/images/ui-bg_gloss-wave_35_f6a828_500x100.png +0 -0
  111. package/static/images/ui-bg_highlight-soft_100_eeeeee_1x100.png +0 -0
  112. package/static/images/ui-bg_highlight-soft_75_ffe45c_1x100.png +0 -0
  113. package/static/images/ui-icons_222222_256x240.png +0 -0
  114. package/static/images/ui-icons_228ef1_256x240.png +0 -0
  115. package/static/images/ui-icons_ef8c08_256x240.png +0 -0
  116. package/static/images/ui-icons_ffd27a_256x240.png +0 -0
  117. package/static/images/ui-icons_ffffff_256x240.png +0 -0
  118. package/static/js/jquery.effects.blind.js +49 -0
  119. package/static/js/jquery.effects.bounce.js +78 -0
  120. package/static/js/jquery.effects.clip.js +54 -0
  121. package/static/js/jquery.effects.core.js +763 -0
  122. package/static/js/jquery.effects.drop.js +50 -0
  123. package/static/js/jquery.effects.explode.js +79 -0
  124. package/static/js/jquery.effects.fade.js +32 -0
  125. package/static/js/jquery.effects.fold.js +56 -0
  126. package/static/js/jquery.effects.highlight.js +50 -0
  127. package/static/js/jquery.effects.pulsate.js +51 -0
  128. package/static/js/jquery.effects.scale.js +178 -0
  129. package/static/js/jquery.effects.shake.js +57 -0
  130. package/static/js/jquery.effects.slide.js +50 -0
  131. package/static/js/jquery.effects.transfer.js +45 -0
  132. package/static/js/jquery.ui.accordion.js +611 -0
  133. package/static/js/jquery.ui.autocomplete.js +612 -0
  134. package/static/js/jquery.ui.button.js +416 -0
  135. package/static/js/jquery.ui.datepicker.js +1823 -0
  136. package/static/js/jquery.ui.dialog.js +878 -0
  137. package/static/js/jquery.ui.droppable.js +296 -0
  138. package/static/js/jquery.ui.position.js +252 -0
  139. package/static/js/jquery.ui.progressbar.js +109 -0
  140. package/static/js/jquery.ui.selectable.js +266 -0
  141. package/static/js/jquery.ui.slider.js +666 -0
  142. package/static/js/jquery.ui.sortable.js +1077 -0
  143. package/static/js/jquery.ui.tabs.js +758 -0
  144. package/stats.js +80 -0
  145. package/test-cache/vsac/vsac-valuesets.db +0 -0
  146. package/token/nginx_passport_setup.md +383 -0
  147. package/token/security_guide.md +294 -0
  148. package/token/token-template.html +330 -0
  149. package/token/token.js +1300 -0
  150. package/translations/Messages.properties +1510 -0
  151. package/translations/Messages_ar.properties +1399 -0
  152. package/translations/Messages_de.properties +836 -0
  153. package/translations/Messages_es.properties +737 -0
  154. package/translations/Messages_fr.properties +1 -0
  155. package/translations/Messages_ja.properties +893 -0
  156. package/translations/Messages_nl.properties +1357 -0
  157. package/translations/Messages_pt.properties +1302 -0
  158. package/translations/Messages_ru.properties +1 -0
  159. package/translations/Messages_uz.properties +1 -0
  160. package/translations/Messages_zh.properties +1 -0
  161. package/translations/rendering-phrases.properties +1128 -0
  162. package/translations/rendering-phrases_ar.properties +1091 -0
  163. package/translations/rendering-phrases_de.properties +6 -0
  164. package/translations/rendering-phrases_es.properties +6 -0
  165. package/translations/rendering-phrases_fr.properties +624 -0
  166. package/translations/rendering-phrases_ja.properties +21 -0
  167. package/translations/rendering-phrases_nl.properties +970 -0
  168. package/translations/rendering-phrases_pt.properties +1020 -0
  169. package/translations/rendering-phrases_ru.properties +1094 -0
  170. package/translations/rendering-phrases_uz.properties +1 -0
  171. package/translations/rendering-phrases_zh.properties +1 -0
  172. package/tx/README.md +418 -0
  173. package/tx/cm/cm-api.js +110 -0
  174. package/tx/cm/cm-database.js +735 -0
  175. package/tx/cm/cm-package.js +325 -0
  176. package/tx/cs/cs-api.js +789 -0
  177. package/tx/cs/cs-areacode.js +615 -0
  178. package/tx/cs/cs-country.js +1110 -0
  179. package/tx/cs/cs-cpt.js +785 -0
  180. package/tx/cs/cs-cs.js +1579 -0
  181. package/tx/cs/cs-currency.js +539 -0
  182. package/tx/cs/cs-db.js +1321 -0
  183. package/tx/cs/cs-hgvs.js +329 -0
  184. package/tx/cs/cs-lang.js +465 -0
  185. package/tx/cs/cs-loinc.js +1485 -0
  186. package/tx/cs/cs-mimetypes.js +238 -0
  187. package/tx/cs/cs-ndc.js +704 -0
  188. package/tx/cs/cs-omop.js +1025 -0
  189. package/tx/cs/cs-provider-api.js +43 -0
  190. package/tx/cs/cs-provider-list.js +37 -0
  191. package/tx/cs/cs-rxnorm.js +808 -0
  192. package/tx/cs/cs-snomed.js +1102 -0
  193. package/tx/cs/cs-ucum.js +514 -0
  194. package/tx/cs/cs-unii.js +271 -0
  195. package/tx/cs/cs-uri.js +218 -0
  196. package/tx/cs/cs-usstates.js +305 -0
  197. package/tx/dev.fhir.org.yml +14 -0
  198. package/tx/fixtures/test-cases-setup.json +18 -0
  199. package/tx/fixtures/test-cases.yml +16 -0
  200. package/tx/html/codesystem-operations.liquid +25 -0
  201. package/tx/html/home-metrics.liquid +247 -0
  202. package/tx/html/operations-form.liquid +148 -0
  203. package/tx/html/search-form.liquid +62 -0
  204. package/tx/html/tx-template.html +133 -0
  205. package/tx/html/valueset-operations.liquid +54 -0
  206. package/tx/importers/atc-to-fhir.js +316 -0
  207. package/tx/importers/import-loinc.module.js +1536 -0
  208. package/tx/importers/import-ndc.module.js +1088 -0
  209. package/tx/importers/import-rxnorm.module.js +898 -0
  210. package/tx/importers/import-sct.module.js +2457 -0
  211. package/tx/importers/import-unii.module.js +601 -0
  212. package/tx/importers/readme.md +453 -0
  213. package/tx/importers/subset-loinc.module.js +1081 -0
  214. package/tx/importers/subset-rxnorm.module.js +938 -0
  215. package/tx/importers/tx-import-base.js +351 -0
  216. package/tx/importers/tx-import-settings.js +310 -0
  217. package/tx/importers/tx-import.js +357 -0
  218. package/tx/library/canonical-resource.js +88 -0
  219. package/tx/library/capabilitystatement.js +292 -0
  220. package/tx/library/codesystem.js +774 -0
  221. package/tx/library/conceptmap.js +568 -0
  222. package/tx/library/designations.js +932 -0
  223. package/tx/library/errors.js +77 -0
  224. package/tx/library/extensions.js +117 -0
  225. package/tx/library/namingsystem.js +322 -0
  226. package/tx/library/operation-outcome.js +127 -0
  227. package/tx/library/parameters.js +105 -0
  228. package/tx/library/renderer.js +1559 -0
  229. package/tx/library/terminologycapabilities.js +418 -0
  230. package/tx/library/ucum-parsers.js +1029 -0
  231. package/tx/library/ucum-service.js +370 -0
  232. package/tx/library/ucum-types.js +1099 -0
  233. package/tx/library/valueset.js +543 -0
  234. package/tx/library.js +676 -0
  235. package/tx/ocl/cm-ocl.js +106 -0
  236. package/tx/ocl/cs-ocl.js +39 -0
  237. package/tx/ocl/vs-ocl.js +105 -0
  238. package/tx/operation-context.js +568 -0
  239. package/tx/params.js +613 -0
  240. package/tx/provider.js +403 -0
  241. package/tx/sct/ecl.js +1560 -0
  242. package/tx/sct/expressions.js +2077 -0
  243. package/tx/sct/structures.js +1396 -0
  244. package/tx/tx-html.js +1063 -0
  245. package/tx/tx.fhir.org.yml +39 -0
  246. package/tx/tx.js +927 -0
  247. package/tx/vs/vs-api.js +112 -0
  248. package/tx/vs/vs-database.js +786 -0
  249. package/tx/vs/vs-package.js +358 -0
  250. package/tx/vs/vs-vsac.js +366 -0
  251. package/tx/workers/batch-validate.js +129 -0
  252. package/tx/workers/batch.js +361 -0
  253. package/tx/workers/closure.js +32 -0
  254. package/tx/workers/expand.js +1845 -0
  255. package/tx/workers/lookup.js +407 -0
  256. package/tx/workers/metadata.js +467 -0
  257. package/tx/workers/operations.js +34 -0
  258. package/tx/workers/read.js +164 -0
  259. package/tx/workers/search.js +384 -0
  260. package/tx/workers/subsumes.js +334 -0
  261. package/tx/workers/translate.js +492 -0
  262. package/tx/workers/validate.js +2504 -0
  263. package/tx/workers/worker.js +904 -0
  264. package/tx/xml/capabilitystatement-xml.js +63 -0
  265. package/tx/xml/codesystem-xml.js +62 -0
  266. package/tx/xml/conceptmap-xml.js +65 -0
  267. package/tx/xml/namingsystem-xml.js +65 -0
  268. package/tx/xml/operationoutcome-xml.js +127 -0
  269. package/tx/xml/parameters-xml.js +312 -0
  270. package/tx/xml/terminologycapabilities-xml.js +64 -0
  271. package/tx/xml/valueset-xml.js +64 -0
  272. package/tx/xml/xml-base.js +603 -0
  273. package/vcl/vcl-parser.js +1098 -0
  274. package/vcl/vcl.js +253 -0
  275. package/windows-install.js +19 -0
  276. package/xig/xig-template.html +124 -0
  277. 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 };