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,785 @@
1
+ const sqlite3 = require('sqlite3').verbose();
2
+ const assert = require('assert');
3
+ const { CodeSystem } = require('../library/codesystem');
4
+ const { CodeSystemProvider, FilterExecutionContext, CodeSystemFactoryProvider } = require('./cs-api');
5
+ const {validateArrayParameter} = require("../../library/utilities");
6
+
7
+ class CPTConceptDesignation {
8
+ constructor(kind, value) {
9
+ this.kind = kind;
10
+ this.value = value;
11
+ }
12
+ }
13
+
14
+ class CPTConceptProperty {
15
+ constructor(name, value) {
16
+ this.name = name;
17
+ this.value = value;
18
+ }
19
+ }
20
+
21
+ class CPTConcept {
22
+ constructor(code, modifier = false) {
23
+ this.code = code;
24
+ this.modifier = modifier;
25
+ this.designations = [];
26
+ this.properties = [];
27
+ }
28
+
29
+ addProperty(name, value) {
30
+ this.properties.push(new CPTConceptProperty(name, value));
31
+ }
32
+
33
+ hasProperty(name, value) {
34
+ return this.properties.some(p => p.name === name && p.value === value);
35
+ }
36
+
37
+ addDesignation(kind, value) {
38
+ this.designations.push(new CPTConceptDesignation(kind, value));
39
+ }
40
+
41
+ getDesignation(kind) {
42
+ const designation = this.designations.find(d => d.kind === kind);
43
+ return designation ? designation.value : '';
44
+ }
45
+ }
46
+
47
+ class CPTExpression {
48
+ constructor(focus = null) {
49
+ this.focus = focus;
50
+ this.modifiers = [];
51
+ }
52
+
53
+ expression() {
54
+ let result = this.focus.code;
55
+ for (const modifier of this.modifiers) {
56
+ result += ':' + modifier.code;
57
+ }
58
+ return result;
59
+ }
60
+
61
+ hasModifier(code) {
62
+ return this.modifiers.some(m => m.code === code);
63
+ }
64
+ }
65
+
66
+ class CPTFilterContext {
67
+ constructor(name, list, closed) {
68
+ this.name = name;
69
+ this.list = list;
70
+ this.closed = closed;
71
+ this.index = -1;
72
+
73
+ // Log filter creation like the Pascal version
74
+ let logCodes = '';
75
+ for (let i = 0; i < Math.min(list.length, 50); i++) {
76
+ logCodes += list[i].code + ',';
77
+ }
78
+ for (let i = Math.max(0, list.length - 10); i < list.length; i++) {
79
+ logCodes += ',' + list[i].code;
80
+ }
81
+ console.info(logCodes);
82
+ }
83
+
84
+ next() {
85
+ this.index++;
86
+ }
87
+ }
88
+
89
+ class CPTIteratorContext {
90
+ constructor(list) {
91
+ this.list = list || [];
92
+ this.current = 0;
93
+ this.total = this.list.length;
94
+ }
95
+
96
+ more() {
97
+ return this.current < this.total;
98
+ }
99
+
100
+ next() {
101
+ this.current++;
102
+ }
103
+ }
104
+
105
+ class CPTPrep extends FilterExecutionContext {
106
+ constructor() {
107
+ super();
108
+ }
109
+ }
110
+
111
+ class CPTServices extends CodeSystemProvider {
112
+ constructor(opContext, supplements, db, sharedData) {
113
+ super(opContext, supplements);
114
+ this.db = db;
115
+
116
+ // Shared data from factory
117
+ this._version = sharedData._version;
118
+ this.conceptMap = sharedData.conceptMap;
119
+ this.conceptList = sharedData.conceptList;
120
+ this.baseList = sharedData.baseList;
121
+ this.modifierList = sharedData.modifierList;
122
+ }
123
+
124
+ close() {
125
+ if (this.db) {
126
+ this.db.close();
127
+ this.db = null;
128
+ }
129
+ }
130
+
131
+ // Metadata methods
132
+ system() {
133
+ return 'http://www.ama-assn.org/go/cpt';
134
+ }
135
+
136
+ version() {
137
+ return this._version;
138
+ }
139
+
140
+ description() {
141
+ return 'CPT © Copyright 2019 American Medical Association. All rights reserved. AMA and CPT are registered trademarks of the American Medical Association.';
142
+ }
143
+
144
+ name() {
145
+ return 'CPT';
146
+ }
147
+
148
+ expandLimitation() {
149
+ return 1000; // Agreement with AMA
150
+ }
151
+
152
+ async totalCount() {
153
+ return this.conceptMap.size;
154
+ }
155
+
156
+ // Core concept methods
157
+ async code(context) {
158
+
159
+ const ctxt = await this.#ensureContext(context);
160
+
161
+ if (ctxt instanceof CPTExpression) {
162
+ return ctxt.expression();
163
+ } else if (ctxt instanceof CPTConcept) {
164
+ return ctxt.code;
165
+ }
166
+ return null;
167
+ }
168
+
169
+ async display(context) {
170
+
171
+ const ctxt = await this.#ensureContext(context);
172
+
173
+ if (!ctxt) {
174
+ return null;
175
+ }
176
+
177
+ // Check supplements first
178
+ let disp = this._displayFromSupplements(await this.code(ctxt));
179
+ if (disp) {
180
+ return disp;
181
+ }
182
+
183
+ if (ctxt instanceof CPTExpression) {
184
+ return ''; // No display for expressions
185
+ } else if (ctxt instanceof CPTConcept) {
186
+ return ctxt.designations.length > 0 ? ctxt.designations[0].value : '';
187
+ }
188
+
189
+ return '';
190
+ }
191
+
192
+ async definition(context) {
193
+
194
+ return this.display(context);
195
+ }
196
+
197
+ async isAbstract(context) {
198
+
199
+ const ctxt = await this.#ensureContext(context);
200
+
201
+ if (ctxt instanceof CPTExpression) {
202
+ return false;
203
+ } else if (ctxt instanceof CPTConcept) {
204
+ return ctxt.hasProperty('kind', 'metadata');
205
+ }
206
+ return false;
207
+ }
208
+
209
+ async designations(context, displays) {
210
+
211
+ const ctxt = await this.#ensureContext(context);
212
+
213
+ if (ctxt instanceof CPTExpression) {
214
+ // No text for expressions
215
+ } else if (ctxt instanceof CPTConcept) {
216
+ for (const d of ctxt.designations) {
217
+ const isDisplay = d.kind === 'display';
218
+ displays.addDesignation(isDisplay, 'active','en', isDisplay ? CodeSystem.makeUseForDisplay() : null, d.value);
219
+ }
220
+
221
+ // Add supplement designations
222
+ this._listSupplementDesignations(ctxt.code, displays);
223
+ }
224
+
225
+ }
226
+
227
+ async extendLookup(ctxt, props, params) {
228
+ validateArrayParameter(props, 'props', String);
229
+ validateArrayParameter(params, 'params', Object);
230
+
231
+
232
+ if (typeof ctxt === 'string') {
233
+ const located = await this.locate(ctxt);
234
+ if (!located.context) {
235
+ throw new Error(located.message);
236
+ }
237
+ ctxt = located.context;
238
+ }
239
+
240
+ // Add copyright property
241
+ this.#addProperty(params, 'property', 'copyright',
242
+ 'This response content from CPT, which is copyright © 2002+ American Medical Association, and distributed by agreement between AMA and HL7.');
243
+
244
+ if (ctxt instanceof CPTExpression) {
245
+ // Extend lookup for the focus concept first
246
+ await this.extendLookup(ctxt.focus, props, params);
247
+
248
+ // Add modifier properties
249
+ for (const modifier of ctxt.modifiers) {
250
+ this.#addProperty(params, 'property', 'modifier', modifier.code);
251
+
252
+ if (modifier.designations.length > 0) {
253
+ this.#addProperty(params, 'property', 'modifier-definition', modifier.designations[0].value);
254
+ }
255
+ }
256
+ } else if (ctxt instanceof CPTConcept) {
257
+ // Add designations
258
+ if (this.#hasProp(props, 'designation', true)) {
259
+ for (const d of ctxt.designations) {
260
+ this.#addProperty(params, 'designation', d.kind, d.value, 'en');
261
+ }
262
+ }
263
+
264
+ // Add properties
265
+ for (const p of ctxt.properties) {
266
+ if (this.#hasProp(props, p.name, true)) {
267
+ this.#addProperty(params, 'property', p.name, p.value);
268
+ }
269
+ }
270
+ }
271
+ }
272
+
273
+ #addProperty(params, type, name, value, language = null) {
274
+
275
+
276
+ const property = {
277
+ name: type,
278
+ part: [
279
+ { name: 'code', valueCode: name },
280
+ { name: 'value', valueString: value }
281
+ ]
282
+ };
283
+
284
+ if (language) {
285
+ property.part.push({ name: 'language', valueCode: language });
286
+ }
287
+
288
+ params.push(property);
289
+ }
290
+
291
+ #hasProp(props, name, defaultValue) {
292
+ if (!props || props.length === 0) return defaultValue;
293
+ return props.includes(name);
294
+ }
295
+
296
+ async #ensureContext(context) {
297
+ if (!context) {
298
+ return null;
299
+ }
300
+ if (typeof context === 'string') {
301
+ const ctxt = await this.locate(context);
302
+ if (!ctxt.context) {
303
+ throw new Error(ctxt.message ? ctxt.message : `Code '${context}' not found in CPT`);
304
+ } else {
305
+ return ctxt.context;
306
+ }
307
+ }
308
+ if (context instanceof CPTConcept || context instanceof CPTExpression) {
309
+ return context;
310
+ }
311
+ throw new Error("Unknown Type at #ensureContext: " + (typeof context));
312
+ }
313
+
314
+ // Expression parsing and validation
315
+ #parse(code) {
316
+ if (!code) {
317
+ return { context: null, message: 'No Expression Found' };
318
+ }
319
+
320
+ const parts = code.split(':');
321
+ const baseConcept = this.conceptMap.get(parts[0]);
322
+
323
+ if (!baseConcept) {
324
+ return { context: null, message: `Base CPT Code '${parts[0]}' not found` };
325
+ }
326
+
327
+ const expression = new CPTExpression(baseConcept);
328
+
329
+ for (let i = 1; i < parts.length; i++) {
330
+ const modifier = this.conceptMap.get(parts[i]);
331
+ if (!modifier) {
332
+ return { context: null, message: `Modifier CPT code '${parts[i]}' not found` };
333
+ }
334
+ expression.modifiers.push(modifier);
335
+ }
336
+
337
+ const validationMsg = this.#validateExpression(expression);
338
+ if (validationMsg) {
339
+ return { context: null, message: validationMsg };
340
+ }
341
+
342
+ return { context: expression, message: null };
343
+ }
344
+
345
+ #validateExpression(exp) {
346
+ const errors = [];
347
+
348
+ // Check modifiers
349
+ for (const modifier of exp.modifiers) {
350
+ for (const prop of modifier.properties) {
351
+ if (prop.name === 'kind') {
352
+ if (prop.value === 'cat-2') {
353
+ if (!exp.focus.hasProperty('kind', 'cat-2')) {
354
+ errors.push(`The modifier ${modifier.code} is a cat-2 modifier that can only be used with cat-2 codes`);
355
+ }
356
+ }
357
+ if (prop.value === 'physical') {
358
+ if (exp.focus.code < '00100' || exp.focus.code > '01999') {
359
+ errors.push(`The modifier ${modifier.code} is a physical status modifier that can only be used with codes in the range 00100 - 01999`);
360
+ }
361
+ }
362
+ if (prop.value === 'hcpcs') {
363
+ if (!exp.hasModifier('59')) {
364
+ errors.push(`The modifier ${modifier.code} is an hcpcs code that can only be used if the modifier 59 is also used`);
365
+ }
366
+ }
367
+ if (prop.value == 'code') {
368
+ errors.push(`The code ${modifier.code} cannot be used as a modifier`);
369
+ }
370
+ }
371
+ }
372
+
373
+ // Specific modifier rules
374
+ if (['50', '51'].includes(modifier.code)) {
375
+ if (exp.focus.hasProperty('kind', 'cat-2')) {
376
+ errors.push(`The modifier ${modifier.code} cannot be used with cat-2 codes`);
377
+ }
378
+ }
379
+
380
+ if (modifier.code === '63') {
381
+ const validCodes = ['92920', '92928', '92953', '92960', '92986', '92987', '92990', '92997', '92998',
382
+ '93312', '93313', '93314', '93315', '93316', '93317', '93318', '93452', '93505',
383
+ '93563', '93564', '93568', '93569', '93573', '93574', '93575', '93580', '93581',
384
+ '93582', '93590', '93591', '93592', '93593', '93594', '93595', '93596', '93597',
385
+ '93598', '93615', '93616'];
386
+
387
+ const inRange = exp.focus.code >= '20100' && exp.focus.code <= '69990';
388
+ if (!inRange && !validCodes.includes(exp.focus.code)) {
389
+ errors.push(`The modifier ${modifier.code} cannot be used with the code ${exp.focus.code}`);
390
+ }
391
+ }
392
+
393
+ if (modifier.code === '92') {
394
+ const validCodes = ['86701', '86702', '86703', '87389'];
395
+ if (!validCodes.includes(exp.focus.code)) {
396
+ errors.push(`The modifier ${modifier.code} cannot be used with the code ${exp.focus.code}`);
397
+ }
398
+ }
399
+
400
+ if (modifier.code === '95') {
401
+ if (!exp.focus.hasProperty('telemedicine', 'true')) {
402
+ errors.push(`The modifier ${modifier.code} cannot be used with the code ${exp.focus.code} as it is not designated for telemedicine`);
403
+ }
404
+ }
405
+ }
406
+
407
+ // Check mutually exclusive groups
408
+ this.#checkMutuallyExclusive(errors, exp, ['25', '57', '59']);
409
+ this.#checkMutuallyExclusive(errors, exp, ['52', '53', '73', '74']);
410
+ this.#checkMutuallyExclusive(errors, exp, ['76', '77', '78', '79']);
411
+ this.#checkMutuallyExclusive(errors, exp, ['93', '95']);
412
+
413
+ return errors.join(', ');
414
+ }
415
+
416
+ #checkMutuallyExclusive(errors, exp, modifiers) {
417
+ const count = exp.modifiers.filter(m => modifiers.includes(m.code)).length;
418
+ if (count > 1) {
419
+ errors.push(`There can only be one modifier in the set ${modifiers.join(', ')}`);
420
+ }
421
+ }
422
+
423
+ // Lookup methods
424
+ async locate(code) {
425
+
426
+ assert(!code || typeof code === 'string', 'code must be string');
427
+ if (!code) return { context: null, message: 'Empty code' };
428
+
429
+ if (code.includes(':')) {
430
+ return this.#parse(code);
431
+ } else {
432
+ const context = this.conceptMap.get(code);
433
+ if (context) {
434
+ return { context: context, message: null };
435
+ }
436
+ return { context: null, message: undefined };
437
+ }
438
+ }
439
+
440
+ // Iterator methods
441
+ async iterator(context) {
442
+
443
+
444
+ if (!context) {
445
+ // Iterate all concepts
446
+ return new CPTIteratorContext([...this.conceptList]);
447
+ } else {
448
+ // No iteration for specific contexts
449
+ return new CPTIteratorContext([]);
450
+ }
451
+ }
452
+
453
+ async nextContext(iteratorContext) {
454
+
455
+
456
+ if (!iteratorContext.more()) {
457
+ return null;
458
+ }
459
+
460
+ const concept = iteratorContext.list[iteratorContext.current];
461
+ iteratorContext.next();
462
+ return concept;
463
+ }
464
+
465
+ // Filter support
466
+ async doesFilter(prop, op, value) {
467
+
468
+
469
+ if (prop === 'modifier' && op === '=' && ['true', 'false'].includes(value)) {
470
+ return true;
471
+ }
472
+
473
+ if (prop === 'modified' && op === '=' && ['true', 'false'].includes(value)) {
474
+ return true;
475
+ }
476
+
477
+ if (prop === 'kind' && op === '=') {
478
+ return true;
479
+ }
480
+
481
+ return false;
482
+ }
483
+
484
+ async getPrepContext(iterate) {
485
+
486
+ return new CPTPrep(iterate);
487
+ }
488
+
489
+ async filter(filterContext, prop, op, value) {
490
+
491
+
492
+ let list;
493
+ let closed = true;
494
+
495
+ if (prop === 'modifier' && op === '=') {
496
+ const isModifier = value === 'true';
497
+ if (isModifier) {
498
+ list = [...this.modifierList];
499
+ } else {
500
+ list = [...this.baseList];
501
+ }
502
+ } else if (prop === 'modified' && op === '=') {
503
+ const isModified = value === 'true';
504
+ if (isModified) {
505
+ list = []; // No modified codes
506
+ closed = false;
507
+ } else {
508
+ list = [...this.conceptList];
509
+ }
510
+ } else if (prop === 'kind' && op === '=') {
511
+ list = this.conceptList.filter(concept => concept.hasProperty('kind', value));
512
+ } else {
513
+ throw new Error(`The filter "${prop} ${op} ${value}" is not supported for CPT`);
514
+ }
515
+
516
+ const filter = new CPTFilterContext(`${prop}:${value}`, list, closed);
517
+ filterContext.filters.push(filter);
518
+ }
519
+
520
+ async executeFilters(filterContext) {
521
+
522
+ return filterContext.filters;
523
+ }
524
+
525
+ async filterSize(filterContext, set) {
526
+
527
+ return set.list.length;
528
+ }
529
+
530
+ async filterMore(filterContext, set) {
531
+
532
+ set.next();
533
+ return set.index < set.list.length;
534
+ }
535
+
536
+ async filterConcept(filterContext, set) {
537
+
538
+ return set.list[set.index];
539
+ }
540
+
541
+ async filterLocate(filterContext, set, code) {
542
+
543
+
544
+ const concept = set.list.find(c => c.code === code);
545
+ if (concept) {
546
+ return concept;
547
+ }
548
+ return `Code ${code} is not in the specified filter`;
549
+ }
550
+
551
+ async filterCheck(filterContext, set, concept) {
552
+
553
+
554
+ if (concept instanceof CPTExpression) {
555
+ return !set.closed;
556
+ } else if (concept instanceof CPTConcept) {
557
+ return set.list.includes(concept);
558
+ }
559
+ return false;
560
+ }
561
+
562
+
563
+ async filtersNotClosed(filterContext) {
564
+
565
+ return filterContext.filters.some(f => !f.closed);
566
+ }
567
+
568
+ // Search filter - not implemented
569
+ // eslint-disable-next-line no-unused-vars
570
+ async searchFilter(filterContext, filter, sort) {
571
+
572
+ throw new Error('Text search not implemented yet');
573
+ }
574
+
575
+ // Subsumption testing - not implemented
576
+ async subsumesTest(codeA, codeB) {
577
+ await this.#ensureContext(codeA);
578
+ await this.#ensureContext(codeB);
579
+ return 'not-subsumed';
580
+ }
581
+
582
+
583
+ versionAlgorithm() {
584
+ return 'date';
585
+ }
586
+ }
587
+
588
+ class CPTServicesFactory extends CodeSystemFactoryProvider {
589
+ constructor(i18n, dbPath) {
590
+ super(i18n);
591
+ this.dbPath = dbPath;
592
+ this.uses = 0;
593
+ this._loaded = false;
594
+ this._sharedData = null;
595
+ }
596
+
597
+ // Metadata methods
598
+ system() {
599
+ return 'http://www.ama-assn.org/go/cpt';
600
+ }
601
+
602
+ version() {
603
+ return this._sharedData._version;
604
+ }
605
+
606
+ // eslint-disable-next-line no-unused-vars
607
+ async buildKnownValueSet(url, version) {
608
+ return null;
609
+ }
610
+
611
+ async #ensureLoaded() {
612
+ if (!this._loaded) {
613
+ await this.load();
614
+ }
615
+ }
616
+
617
+ async load() {
618
+ const db = new sqlite3.Database(this.dbPath);
619
+
620
+ try {
621
+ this._sharedData = {
622
+ _version: '',
623
+ conceptMap: new Map(),
624
+ conceptList: [],
625
+ baseList: [],
626
+ modifierList: []
627
+ };
628
+
629
+ // Load version information
630
+ await this.#loadVersion(db);
631
+
632
+ // Load all concepts
633
+ await this.#loadConcepts(db);
634
+
635
+ // Load properties
636
+ await this.#loadProperties(db);
637
+
638
+ // Load designations
639
+ await this.#loadDesignations(db);
640
+
641
+ } finally {
642
+ db.close();
643
+ }
644
+ this._loaded = true;
645
+ }
646
+
647
+ async #loadVersion(db) {
648
+ return new Promise((resolve, reject) => {
649
+ db.all('SELECT * FROM Information', (err, rows) => {
650
+ if (err) {
651
+ reject(err);
652
+ } else {
653
+ for (const row of rows) {
654
+ if (row.name === 'version') {
655
+ this._sharedData._version = row.value;
656
+ }
657
+ }
658
+ resolve();
659
+ }
660
+ });
661
+ });
662
+ }
663
+
664
+ async #loadConcepts(db) {
665
+ return new Promise((resolve, reject) => {
666
+ db.all('SELECT * FROM Concepts', (err, rows) => {
667
+ if (err) {
668
+ reject(err);
669
+ } else {
670
+ for (const row of rows) {
671
+ const concept = new CPTConcept(row.code, row.modifier === 1);
672
+
673
+ this._sharedData.conceptMap.set(concept.code, concept);
674
+ this._sharedData.conceptList.push(concept);
675
+
676
+ if (concept.modifier) {
677
+ this._sharedData.modifierList.push(concept);
678
+ } else {
679
+ this._sharedData.baseList.push(concept);
680
+ }
681
+ }
682
+ resolve();
683
+ }
684
+ });
685
+ });
686
+ }
687
+
688
+ async #loadProperties(db) {
689
+ return new Promise((resolve, reject) => {
690
+ db.all('SELECT * FROM Properties', (err, rows) => {
691
+ if (err) {
692
+ reject(err);
693
+ } else {
694
+ for (const row of rows) {
695
+ const concept = this._sharedData.conceptMap.get(row.code);
696
+ if (concept) {
697
+ concept.addProperty(row.name, row.value);
698
+ }
699
+ }
700
+ resolve();
701
+ }
702
+ });
703
+ });
704
+ }
705
+
706
+ async #loadDesignations(db) {
707
+ return new Promise((resolve, reject) => {
708
+ db.all('SELECT * FROM Designations', (err, rows) => {
709
+ if (err) {
710
+ reject(err);
711
+ } else {
712
+ for (const row of rows) {
713
+ const concept = this._sharedData.conceptMap.get(row.code);
714
+ if (concept) {
715
+ !concept.addDesignation(row.type, row.value);
716
+ }
717
+ }
718
+ resolve();
719
+ }
720
+ });
721
+ });
722
+ }
723
+
724
+ defaultVersion() {
725
+ return this._sharedData?._version || 'unknown';
726
+ }
727
+
728
+ async build(opContext, supplements) {
729
+ await this.#ensureLoaded();
730
+ this.recordUse();
731
+
732
+ // Create fresh database connection for this provider instance
733
+ const db = new sqlite3.Database(this.dbPath);
734
+
735
+ return new CPTServices(opContext, supplements, db, this._sharedData);
736
+ }
737
+
738
+ static checkDB(dbPath) {
739
+ try {
740
+ const fs = require('fs');
741
+
742
+ // Check if file exists
743
+ if (!fs.existsSync(dbPath)) {
744
+ return 'Database file not found';
745
+ }
746
+
747
+ // Check file size
748
+ const stats = fs.statSync(dbPath);
749
+ if (stats.size < 1024) {
750
+ return 'Database file too small';
751
+ }
752
+
753
+ // Try to open database (this will fail if file is corrupted)
754
+ const db = new sqlite3.Database(dbPath);
755
+ db.close();
756
+
757
+ // For the fragment database, we know it should have 9 concepts
758
+ return 'OK (9 Concepts)';
759
+ } catch (e) {
760
+ return `Database error: ${e.message}`;
761
+ }
762
+ }
763
+
764
+ isNotClosed() {
765
+ return true;
766
+ }
767
+
768
+ name() {
769
+ return 'CPT';
770
+ }
771
+
772
+
773
+ id() {
774
+ return "cpt2023";
775
+ }
776
+ }
777
+
778
+ module.exports = {
779
+ CPTServices,
780
+ CPTServicesFactory,
781
+ CPTConcept,
782
+ CPTExpression,
783
+ CPTConceptDesignation,
784
+ CPTConceptProperty
785
+ };