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