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,543 @@
1
+ const {CanonicalResource} = require("./canonical-resource");
2
+ const {getValueName} = require("../../library/utilities");
3
+ const {VersionUtilities} = require("../../library/version-utilities");
4
+
5
+ /**
6
+ * Represents a FHIR ValueSet resource with version conversion support
7
+ * @class
8
+ */
9
+ class ValueSet extends CanonicalResource {
10
+
11
+ /**
12
+ * Creates a new ValueSet instance
13
+ * @param {Object} jsonObj - The JSON object containing ValueSet data
14
+ * @param {string} [fhirVersion='R5'] - FHIR version ('R3', 'R4', or 'R5')
15
+ * @param {string} jsonObj.resourceType - Must be "ValueSet"
16
+ * @param {string} jsonObj.url - Canonical URL for the value set
17
+ * @param {string} [jsonObj.version] - Version of the value set
18
+ * @param {string} jsonObj.name - Name for this value set
19
+ * @param {string} jsonObj.status - Publication status (draft|active|retired|unknown)
20
+ * @param {Object} [jsonObj.compose] - Content logical definition of the value set
21
+ * @param {Object} [jsonObj.expansion] - Used when the value set is "expanded"
22
+ */
23
+ constructor(jsonObj, fhirVersion = 'R5') {
24
+ super(jsonObj, fhirVersion);
25
+ // Convert to R5 format internally (modifies input for performance)
26
+ this.jsonObj = this._convertToR5(jsonObj, fhirVersion);
27
+ this.validate();
28
+ this.buildMaps();
29
+ }
30
+
31
+ /**
32
+ * Map of system(#version)|code to expansion contains item for fast lookup
33
+ * @type {Map<string, Object>}
34
+ */
35
+ codeMap = new Map();
36
+
37
+ /**
38
+ * Static factory method for convenience
39
+ * @param {string} jsonString - JSON string representation of ValueSet
40
+ * @param {string} [version='R5'] - FHIR version ('R3', 'R4', or 'R5')
41
+ * @returns {ValueSet} New ValueSet instance
42
+ */
43
+ static fromJSON(jsonString, version = 'R5') {
44
+ return new ValueSet(JSON.parse(jsonString), version);
45
+ }
46
+
47
+
48
+ /**
49
+ * Returns JSON string representation
50
+ * @param {string} [version='R5'] - Target FHIR version ('R3', 'R4', or 'R5')
51
+ * @returns {string} JSON string
52
+ */
53
+ toJSONString(version = 'R5') {
54
+ const outputObj = this.convertFromR5(this.jsonObj, version);
55
+ return JSON.stringify(outputObj);
56
+ }
57
+
58
+ /**
59
+ * Converts input ValueSet to R5 format (modifies input object for performance)
60
+ * @param {Object} jsonObj - The input ValueSet object
61
+ * @param {string} version - Source FHIR version
62
+ * @returns {Object} The same object, potentially modified to R5 format
63
+ * @private
64
+ */
65
+ _convertToR5(jsonObj, version) {
66
+ if (version === 'R5') {
67
+ return jsonObj; // Already R5, no conversion needed
68
+ }
69
+
70
+ if (version === 'R3') {
71
+ // R3 to R5: Remove extensible field (we ignore it completely)
72
+ if (jsonObj.extensible !== undefined) {
73
+ delete jsonObj.extensible;
74
+ }
75
+ return jsonObj;
76
+ }
77
+
78
+ if (version === 'R4') {
79
+ // R4 to R5: No structural conversion needed
80
+ // R5 is backward compatible for the structural elements we care about
81
+ return jsonObj;
82
+ }
83
+
84
+ throw new Error(`Unsupported FHIR version: ${version}`);
85
+ }
86
+
87
+ /**
88
+ * Converts R5 ValueSet to target version format (clones object first)
89
+ * @param {Object} r5Obj - The R5 format ValueSet object
90
+ * @param {string} targetVersion - Target FHIR version
91
+ * @returns {Object} New object in target version format
92
+ * @private
93
+ */
94
+ convertFromR5(r5Obj, targetVersion) {
95
+ if (VersionUtilities.isR5Ver(targetVersion)) {
96
+ return r5Obj; // No conversion needed
97
+ }
98
+
99
+ // Clone the object to avoid modifying the original
100
+ const cloned = JSON.parse(JSON.stringify(r5Obj));
101
+
102
+ if (VersionUtilities.isR4Ver(targetVersion)) {
103
+ return this._convertR5ToR4(cloned);
104
+ } else if (VersionUtilities.isR3Ver(targetVersion)) {
105
+ return this._convertR5ToR3(cloned);
106
+ }
107
+
108
+ throw new Error(`Unsupported target FHIR version: ${targetVersion}`);
109
+ }
110
+
111
+ /**
112
+ * Converts R5 ValueSet to R4 format
113
+ * @param {Object} r5Obj - Cloned R5 ValueSet object
114
+ * @returns {Object} R4 format ValueSet
115
+ * @private
116
+ */
117
+ _convertR5ToR4(r5Obj) {
118
+ // Remove R5-specific elements that don't exist in R4
119
+ if (r5Obj.versionAlgorithmString) {
120
+ delete r5Obj.versionAlgorithmString;
121
+ }
122
+ if (r5Obj.versionAlgorithmCoding) {
123
+ delete r5Obj.versionAlgorithmCoding;
124
+ }
125
+
126
+ // Filter out R5-only filter operators in compose
127
+ if (r5Obj.compose && r5Obj.compose.include) {
128
+ r5Obj.compose.include = r5Obj.compose.include.map(include => {
129
+ if (include.filter && Array.isArray(include.filter)) {
130
+ include.filter = include.filter.map(filter => {
131
+ if (filter.op && this._isR5OnlyFilterOperator(filter.op)) {
132
+ // Remove R5-only operators
133
+ return null;
134
+ }
135
+ return filter;
136
+ }).filter(filter => filter !== null);
137
+ }
138
+ return include;
139
+ });
140
+ }
141
+
142
+ if (r5Obj.compose && r5Obj.compose.exclude) {
143
+ r5Obj.compose.exclude = r5Obj.compose.exclude.map(exclude => {
144
+ if (exclude.filter && Array.isArray(exclude.filter)) {
145
+ exclude.filter = exclude.filter.map(filter => {
146
+ if (filter.op && this._isR5OnlyFilterOperator(filter.op)) {
147
+ // Remove R5-only operators
148
+ return null;
149
+ }
150
+ return filter;
151
+ }).filter(filter => filter !== null);
152
+ }
153
+ return exclude;
154
+ });
155
+ }
156
+
157
+ if (r5Obj.expansion) {
158
+ let exp = r5Obj.expansion;
159
+
160
+ // Convert ValueSet.expansion.property to extensions
161
+ if (exp.property && exp.property.length > 0) {
162
+ exp.extension = exp.extension || [];
163
+ for (let prop of exp.property) {
164
+ exp.extension.push({
165
+ url: "http://hl7.org/fhir/5.0/StructureDefinition/extension-ValueSet.expansion.property",
166
+ extension: [
167
+ { url: "code", valueCode: prop.code },
168
+ { url: "uri", valueUri: prop.uri }
169
+ ]
170
+ });
171
+ }
172
+ delete exp.property;
173
+ this.convertContainsPropertyR5ToR4(exp.contains);
174
+
175
+ }
176
+ }
177
+
178
+ return r5Obj;
179
+ }
180
+
181
+ // Recursive function to convert contains.property
182
+ convertContainsPropertyR5ToR4(containsList) {
183
+ if (!containsList) return;
184
+
185
+ for (let item of containsList) {
186
+ if (item.property && item.property.length > 0) {
187
+ item.extension = item.extension || [];
188
+ for (let prop of item.property) {
189
+ let ext = {
190
+ url: "http://hl7.org/fhir/5.0/StructureDefinition/extension-ValueSet.expansion.contains.property",
191
+ extension: [
192
+ { url: "code", valueCode: prop.code }
193
+ ]
194
+ };
195
+ let pn = getValueName(prop);
196
+ let subExt = { url: "value" };
197
+ subExt[pn] = prop[pn];
198
+ ext.extension.push(subExt);
199
+ item.extension.push(ext);
200
+ }
201
+ delete item.property;
202
+ }
203
+
204
+ // Recurse into nested contains
205
+ if (item.contains) {
206
+ this.convertContainsPropertyR5ToR4(item.contains);
207
+ }
208
+ }
209
+ }
210
+
211
+ /**
212
+ * Converts R5 ValueSet to R3 format
213
+ * @param {Object} r5Obj - Cloned R5 ValueSet object
214
+ * @returns {Object} R3 format ValueSet
215
+ * @private
216
+ */
217
+ _convertR5ToR3(r5Obj) {
218
+ // First apply R4 conversions
219
+ const r4Obj = this._convertR5ToR4(r5Obj);
220
+
221
+ // R3 has more limited filter operator support
222
+ if (r4Obj.compose && r4Obj.compose.include) {
223
+ r4Obj.compose.include = r4Obj.compose.include.map(include => {
224
+ if (include.filter && Array.isArray(include.filter)) {
225
+ include.filter = include.filter.map(filter => {
226
+ if (filter.op && !this._isR3CompatibleFilterOperator(filter.op)) {
227
+ // Remove non-R3-compatible operators
228
+ return null;
229
+ }
230
+ return filter;
231
+ }).filter(filter => filter !== null);
232
+ }
233
+ return include;
234
+ });
235
+ }
236
+
237
+ if (r4Obj.compose && r4Obj.compose.exclude) {
238
+ r4Obj.compose.exclude = r4Obj.compose.exclude.map(exclude => {
239
+ if (exclude.filter && Array.isArray(exclude.filter)) {
240
+ exclude.filter = exclude.filter.map(filter => {
241
+ if (filter.op && !this._isR3CompatibleFilterOperator(filter.op)) {
242
+ // Remove non-R3-compatible operators
243
+ return null;
244
+ }
245
+ return filter;
246
+ }).filter(filter => filter !== null);
247
+ }
248
+ return exclude;
249
+ });
250
+ }
251
+
252
+ return r4Obj;
253
+ }
254
+
255
+ /**
256
+ * Checks if a filter operator is R5-only
257
+ * @param {string} operator - Filter operator code
258
+ * @returns {boolean} True if operator is R5-only
259
+ * @private
260
+ */
261
+ _isR5OnlyFilterOperator(operator) {
262
+ const r5OnlyOperators = [
263
+ 'generalizes', // Added in R5
264
+ // Add other R5-only operators as they're identified
265
+ ];
266
+ return r5OnlyOperators.includes(operator);
267
+ }
268
+
269
+ /**
270
+ * Checks if a filter operator is compatible with R3
271
+ * @param {string} operator - Filter operator code
272
+ * @returns {boolean} True if operator is R3-compatible
273
+ * @private
274
+ */
275
+ _isR3CompatibleFilterOperator(operator) {
276
+ const r3CompatibleOperators = [
277
+ '=', // Equal
278
+ 'is-a', // Is-A relationship
279
+ 'descendent-of', // Descendant of (note: R3 spelling)
280
+ 'is-not-a', // Is-Not-A relationship
281
+ 'regex', // Regular expression
282
+ 'in', // In set
283
+ 'not-in', // Not in set
284
+ 'exists', // Property exists
285
+ ];
286
+ return r3CompatibleOperators.includes(operator);
287
+ }
288
+
289
+ /**
290
+ * Gets the FHIR version this ValueSet was loaded from
291
+ * @returns {string} FHIR version ('R3', 'R4', or 'R5')
292
+ */
293
+ getFHIRVersion() {
294
+ return this.version;
295
+ }
296
+
297
+ /**
298
+ * Validates that this is a proper ValueSet resource
299
+ * @throws {Error} If validation fails
300
+ */
301
+ validate() {
302
+ if (!this.jsonObj || typeof this.jsonObj !== 'object') {
303
+ throw new Error('Invalid ValueSet: expected object');
304
+ }
305
+
306
+ if (this.jsonObj.resourceType !== 'ValueSet') {
307
+ throw new Error(`Invalid ValueSet: resourceType must be "ValueSet", got "${this.jsonObj.resourceType}"`);
308
+ }
309
+
310
+ if (this.jsonObj.url && typeof this.jsonObj.url !== 'string') {
311
+ throw new Error('Invalid ValueSet: url must be a string if present');
312
+ }
313
+
314
+ if (this.jsonObj.name && typeof this.jsonObj.name !== 'string') {
315
+ throw new Error('Invalid ValueSet: name must be a string if present');
316
+ }
317
+
318
+ if (this.jsonObj.status && typeof this.jsonObj.status !== 'string') {
319
+ throw new Error('Invalid ValueSet: status must be a string if present');
320
+ }
321
+
322
+ const validStatuses = ['draft', 'active', 'retired', 'unknown'];
323
+ if (this.jsonObj.status && !validStatuses.includes(this.jsonObj.status)) {
324
+ throw new Error(`Invalid ValueSet: status must be one of ${validStatuses.join(', ')}, got "${this.jsonObj.status}"`);
325
+ }
326
+
327
+ // Validate identifier - should always be array (no conversion needed for ValueSet)
328
+ if (this.jsonObj.identifier && !Array.isArray(this.jsonObj.identifier)) {
329
+ throw new Error('Invalid ValueSet: identifier should be an array');
330
+ }
331
+
332
+ // Validate compose structure if present
333
+ if (this.jsonObj.compose) {
334
+ if (this.jsonObj.compose.include && !Array.isArray(this.jsonObj.compose.include)) {
335
+ throw new Error('Invalid ValueSet: compose.include must be an array if present');
336
+ }
337
+ if (this.jsonObj.compose.exclude && !Array.isArray(this.jsonObj.compose.exclude)) {
338
+ throw new Error('Invalid ValueSet: compose.exclude must be an array if present');
339
+ }
340
+ }
341
+
342
+ // Validate expansion structure if present
343
+ if (this.jsonObj.expansion) {
344
+ if (this.jsonObj.expansion.contains && !Array.isArray(this.jsonObj.expansion.contains)) {
345
+ throw new Error('Invalid ValueSet: expansion.contains must be an array if present');
346
+ }
347
+ }
348
+ }
349
+
350
+ /**
351
+ * Builds internal maps for fast expansion lookup
352
+ * @private
353
+ */
354
+ buildMaps() {
355
+ this.id = this.jsonObj.id;
356
+ this.codeMap.clear();
357
+
358
+ if (!this.jsonObj.expansion || !this.jsonObj.expansion.contains) {
359
+ return;
360
+ }
361
+
362
+ // Build map of system(#version)|code -> expansion contains item
363
+ this._buildExpansionMap(this.jsonObj.expansion.contains);
364
+ }
365
+
366
+ /**
367
+ * Recursively builds expansion map from contains items
368
+ * @param {Object[]} contains - Array of expansion contains items
369
+ * @private
370
+ */
371
+ _buildExpansionMap(contains) {
372
+ contains.forEach(item => {
373
+ if (item.system && item.code) {
374
+ const key = this._buildCodeKey(item.system, item.version, item.code);
375
+ this.codeMap.set(key, item);
376
+ }
377
+
378
+ // Handle nested contains (hierarchical expansions)
379
+ if (item.contains && Array.isArray(item.contains)) {
380
+ this._buildExpansionMap(item.contains);
381
+ }
382
+ });
383
+ }
384
+
385
+ /**
386
+ * Builds a lookup key for system(#version)|code
387
+ * @param {string} system - Code system URL
388
+ * @param {string} [version] - Code system version
389
+ * @param {string} code - Code value
390
+ * @returns {string} Lookup key
391
+ * @private
392
+ */
393
+ _buildCodeKey(system, version, code) {
394
+ if (version) {
395
+ return `${system}#${version}|${code}`;
396
+ }
397
+ return `${system}|${code}`;
398
+ }
399
+
400
+ /**
401
+ * Gets an expansion item by system and code
402
+ * @param {string} system - Code system URL
403
+ * @param {string} code - Code value
404
+ * @param {string} [version] - Code system version
405
+ * @returns {Object|undefined} The expansion contains item or undefined if not found
406
+ */
407
+ getCode(system, code, version = null) {
408
+ const key = this._buildCodeKey(system, version, code);
409
+ return this.codeMap.get(key);
410
+ }
411
+
412
+ /**
413
+ * Checks if a code exists in this value set expansion
414
+ * @param {string} system - Code system URL
415
+ * @param {string} code - Code value
416
+ * @param {string} [version] - Code system version
417
+ * @returns {boolean} True if the code exists in expansion
418
+ */
419
+ hasCode(system, code, version = null) {
420
+ const key = this._buildCodeKey(system, version, code);
421
+ return this.codeMap.has(key);
422
+ }
423
+
424
+ /**
425
+ * Finds a contains entry in the expansion by system, version, and code
426
+ * Searches recursively through nested contains
427
+ * @param {string} systemUri - Code system URL
428
+ * @param {string} version - Code system version (can be empty string)
429
+ * @param {string} code - Code value
430
+ * @returns {Object|null} The contains entry or null if not found
431
+ */
432
+ findContains(systemUri, version, code) {
433
+ if (!this.jsonObj.expansion || !this.jsonObj.expansion.contains) {
434
+ return null;
435
+ }
436
+ return this._findContainsInList(this.jsonObj.expansion.contains, systemUri, version, code);
437
+ }
438
+
439
+ /**
440
+ * Recursively searches for a contains entry in a list
441
+ * @param {Object[]} list - Array of contains entries
442
+ * @param {string} systemUri - Code system URL
443
+ * @param {string} version - Code system version
444
+ * @param {string} code - Code value
445
+ * @returns {Object|null} The contains entry or null
446
+ * @private
447
+ */
448
+ _findContainsInList(list, systemUri, version, code) {
449
+ for (const cc of list) {
450
+ if (systemUri === cc.system && code === cc.code &&
451
+ (!version || version === cc.version)) {
452
+ return cc;
453
+ }
454
+ if (cc.contains && cc.contains.length > 0) {
455
+ const found = this._findContainsInList(cc.contains, systemUri, version, code);
456
+ if (found) {
457
+ return found;
458
+ }
459
+ }
460
+ }
461
+ return null;
462
+ }
463
+
464
+ /**
465
+ * Gets all codes in this value set expansion
466
+ * @returns {Object[]} Array of {system, version, code, display} objects
467
+ */
468
+ getAllCodes() {
469
+ return Array.from(this.codeMap.values()).map(item => ({
470
+ system: item.system,
471
+ version: item.version,
472
+ code: item.code,
473
+ display: item.display
474
+ }));
475
+ }
476
+
477
+ /**
478
+ * Gets all codes from a specific system
479
+ * @param {string} system - Code system URL
480
+ * @param {string} [version] - Code system version
481
+ * @returns {Object[]} Array of expansion contains items from the system
482
+ */
483
+ getCodesFromSystem(system, version = null) {
484
+ return Array.from(this.codeMap.values()).filter(item => {
485
+ if (version) {
486
+ return item.system === system && item.version === version;
487
+ }
488
+ return item.system === system;
489
+ });
490
+ }
491
+
492
+ /**
493
+ * Gets all unique systems in this value set expansion
494
+ * @returns {string[]} Array of system URLs
495
+ */
496
+ getSystems() {
497
+ const systems = new Set();
498
+ this.codeMap.forEach(item => {
499
+ if (item.system) {
500
+ systems.add(item.system);
501
+ }
502
+ });
503
+ return Array.from(systems);
504
+ }
505
+
506
+ /**
507
+ * Checks if the value set is expanded (has expansion.contains)
508
+ * @returns {boolean} True if the value set has an expansion
509
+ */
510
+ isExpanded() {
511
+ return !!(this.jsonObj.expansion && this.jsonObj.expansion.contains && this.jsonObj.expansion.contains.length > 0);
512
+ }
513
+
514
+ /**
515
+ * Gets the total count from expansion, if available
516
+ * @returns {number|undefined} Total count or undefined if not available
517
+ */
518
+ getExpansionTotal() {
519
+ return this.jsonObj.expansion?.total;
520
+ }
521
+
522
+ /**
523
+ * Gets basic info about this value set
524
+ * @returns {Object} Basic information object
525
+ */
526
+ getInfo() {
527
+ return {
528
+ resourceType: this.jsonObj.resourceType,
529
+ url: this.jsonObj.url,
530
+ version: this.jsonObj.version,
531
+ name: this.jsonObj.name,
532
+ title: this.jsonObj.title,
533
+ status: this.jsonObj.status,
534
+ fhirVersion: this.version,
535
+ isExpanded: this.isExpanded(),
536
+ expansionTotal: this.getExpansionTotal(),
537
+ codeCount: this.codeMap.size,
538
+ systemCount: this.getSystems().length
539
+ };
540
+ }
541
+ }
542
+
543
+ module.exports = ValueSet;