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,1025 @@
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 {validateOptionalParameter, validateArrayParameter} = require("../../library/utilities");
6
+ const {ConceptMap} = require("../library/conceptmap");
7
+
8
+ class OMOPConcept {
9
+ constructor(code, display, domain, conceptClass, standard, vocabulary) {
10
+ this.code = code;
11
+ this.display = display;
12
+ this.domain = domain;
13
+ this.conceptClass = conceptClass;
14
+ this.standard = standard || 'NS';
15
+ this.vocabulary = vocabulary;
16
+ }
17
+ }
18
+
19
+ class OMOPFilter extends FilterExecutionContext {
20
+ constructor(db, sql, value = null) {
21
+ super();
22
+ this.db = db;
23
+ this.sql = sql;
24
+ this.value = value;
25
+ this.rows = [];
26
+ this.cursor = 0;
27
+ this.executed = false;
28
+ }
29
+
30
+ async execute(params = []) {
31
+ if (this.executed) return;
32
+
33
+ return new Promise((resolve, reject) => {
34
+ const callback = (err, rows) => {
35
+ if (err) {
36
+ reject(err);
37
+ } else {
38
+ this.rows = rows || [];
39
+ this.executed = true;
40
+ resolve();
41
+ }
42
+ };
43
+
44
+ if (params.length > 0) {
45
+ this.db.all(this.sql, params, callback);
46
+ } else {
47
+ this.db.all(this.sql, callback);
48
+ }
49
+ });
50
+ }
51
+
52
+ async executeForLocate(params) {
53
+ return new Promise((resolve, reject) => {
54
+ this.db.get(this.sql, params, (err, row) => {
55
+ if (err) {
56
+ reject(err);
57
+ } else {
58
+ resolve(row);
59
+ }
60
+ });
61
+ });
62
+ }
63
+
64
+ close() {
65
+ // Database connection is managed by the provider
66
+ }
67
+ }
68
+
69
+ class OMOPPrep extends FilterExecutionContext {
70
+ iterate;
71
+
72
+ constructor(iterate) {
73
+ super();
74
+ this.iterate = iterate;
75
+ }
76
+ }
77
+
78
+ // Vocabulary mapping functions
79
+ function getVocabId(url) {
80
+ const mapping = {
81
+ 'http://hl7.org/fhir/sid/icd-9-cm': 5046,
82
+ 'http://snomed.info/sct': 44819097,
83
+ 'http://hl7.org/fhir/sid/icd-10-cm': 44819098,
84
+ 'http://hl7.org/fhir/sid/icd-9-proc': 44819099,
85
+ 'http://www.ama-assn.org/go/cpt': 44819100,
86
+ 'http://terminology.hl7.org/CodeSystem/HCPCS-all-codes': 44819101,
87
+ 'http://loinc.org': 44819102,
88
+ 'http://www.nlm.nih.gov/research/umls/rxnorm': 44819104,
89
+ 'http://hl7.org/fhir/sid/ndc': 44819105,
90
+ 'http://unitsofmeasure.org': 44819107,
91
+ 'http://nucc.org/provider-taxonomy': 44819137,
92
+ 'http://www.whocc.no/atc': 44819117
93
+ };
94
+ return mapping[url] || -1;
95
+ }
96
+
97
+ function getUri(key) {
98
+ const mapping = {
99
+ 5046: 'http://hl7.org/fhir/sid/icd-9-cm',
100
+ 44819097: 'http://snomed.info/sct',
101
+ 44819098: 'http://hl7.org/fhir/sid/icd-10-cm',
102
+ 44819099: 'http://hl7.org/fhir/sid/icd-9-proc',
103
+ 44819100: 'http://www.ama-assn.org/go/cpt',
104
+ 44819101: 'http://terminology.hl7.org/CodeSystem/HCPCS-all-codes',
105
+ 44819102: 'http://loinc.org',
106
+ 44819104: 'http://www.nlm.nih.gov/research/umls/rxnorm',
107
+ 44819105: 'http://hl7.org/fhir/sid/ndc',
108
+ 44819107: 'http://unitsofmeasure.org',
109
+ 44819117: 'http://www.whocc.no/atc',
110
+ 44819137: 'http://nucc.org/provider-taxonomy'
111
+ };
112
+ return mapping[key] || '';
113
+ }
114
+
115
+ function getUriOrError(key) {
116
+ const uri = getUri(key);
117
+ if (!uri) {
118
+ throw new Error(`Unmapped OMOP Vocabulary id: ${key}`);
119
+ }
120
+ return uri;
121
+ }
122
+
123
+ function getLang(langConcept) {
124
+ if (langConcept === 'English language') return 'en';
125
+ if (langConcept === 'Spanish language') return 'es';
126
+ return 'en'; // default
127
+ }
128
+
129
+ class OMOPServices extends CodeSystemProvider {
130
+ constructor(opContext, supplements, db, sharedData) {
131
+ super(opContext, supplements);
132
+ this.db = db;
133
+ this._version = sharedData._version;
134
+ }
135
+
136
+ close() {
137
+ if (this.db) {
138
+ this.db.close();
139
+ this.db = null;
140
+ }
141
+ }
142
+
143
+ // Metadata methods
144
+ system() {
145
+ return 'https://fhir-terminology.ohdsi.org';
146
+ }
147
+
148
+ version() {
149
+ return this._version;
150
+ }
151
+
152
+ name() {
153
+ return `OMOP Concepts`;
154
+ }
155
+
156
+ description() {
157
+ return `OMOP Concepts, release ${this._version}`;
158
+ }
159
+
160
+ async totalCount() {
161
+ return new Promise((resolve, reject) => {
162
+ this.db.get('SELECT COUNT(*) as count FROM Concepts', (err, row) => {
163
+ if (err) reject(err);
164
+ else resolve(row.count);
165
+ });
166
+ });
167
+ }
168
+
169
+ // Core concept methods
170
+ async code(context) {
171
+
172
+ const ctxt = await this.#ensureContext(context);
173
+ return ctxt ? ctxt.code : null;
174
+ }
175
+
176
+ async display(context) {
177
+
178
+ const ctxt = await this.#ensureContext(context);
179
+
180
+ if (!ctxt) {
181
+ return null;
182
+ }
183
+
184
+ // Check supplements first
185
+ let disp = this._displayFromSupplements(ctxt.code);
186
+ if (disp) {
187
+ return disp;
188
+ }
189
+
190
+ return ctxt.display || '';
191
+ }
192
+
193
+ async definition(context) {
194
+ await this.#ensureContext(context);
195
+ return ''; // OMOP doesn't provide definitions
196
+ }
197
+
198
+ async isAbstract(context) {
199
+ await this.#ensureContext(context);
200
+ return false; // OMOP concepts are not abstract
201
+ }
202
+
203
+ async isInactive(context) {
204
+ await this.#ensureContext(context);
205
+ return false; // Handle via standard_concept if needed
206
+ }
207
+
208
+ async isDeprecated(context) {
209
+ await this.#ensureContext(context);
210
+ return false; // Handle via invalid_reason if needed
211
+ }
212
+
213
+ async designations(context, displays) {
214
+
215
+ const ctxt = await this.#ensureContext(context);
216
+
217
+ if (ctxt) {
218
+ // Add main display
219
+ displays.addDesignation(true, 'active', 'en', CodeSystem.makeUseForDisplay(), ctxt.display);
220
+
221
+ // Add synonyms
222
+ const synonyms = await this.#getSynonyms(ctxt.code);
223
+ for (const synonym of synonyms) {
224
+ displays.addDesignation(false, 'active', synonym.language, null, synonym.value);
225
+ }
226
+
227
+ // Add supplement designations
228
+ this._listSupplementDesignations(String(ctxt.code), displays);
229
+ }
230
+ }
231
+
232
+ async #getSynonyms(code) {
233
+ return new Promise((resolve, reject) => {
234
+ const sql = `
235
+ SELECT concept_synonym_name, concept_name
236
+ FROM ConceptSynonyms, Concepts
237
+ WHERE ConceptSynonyms.language_concept_id = Concepts.concept_id
238
+ AND ConceptSynonyms.concept_id = ?
239
+ `;
240
+
241
+ this.db.all(sql, [code], (err, rows) => {
242
+ if (err) {
243
+ reject(err);
244
+ } else {
245
+ const synonyms = rows.map(row => ({
246
+ language: getLang(row.concept_name),
247
+ value: row.concept_synonym_name
248
+ }));
249
+ resolve(synonyms);
250
+ }
251
+ });
252
+ });
253
+ }
254
+
255
+ async extendLookup(ctxt, props, params) {
256
+ validateArrayParameter(props, 'props', String);
257
+ validateArrayParameter(params, 'params', Object);
258
+
259
+
260
+ if (typeof ctxt === 'string') {
261
+ const located = await this.locate(ctxt);
262
+ if (!located.context) {
263
+ throw new Error(located.message);
264
+ }
265
+ ctxt = located.context;
266
+ }
267
+
268
+ if (!(ctxt instanceof OMOPConcept)) {
269
+ throw new Error('Invalid context for OMOP lookup');
270
+ }
271
+
272
+ // Add basic properties
273
+ if (this.#hasProp(props, 'domain-id', true)) {
274
+ this.#addCodeProperty(params, 'property', 'domain-id', ctxt.domain);
275
+ }
276
+ if (this.#hasProp(props, 'concept-class-id', true)) {
277
+ this.#addCodeProperty(params, 'property', 'concept-class-id', ctxt.conceptClass);
278
+ }
279
+ if (this.#hasProp(props, 'standard-concept', true)) {
280
+ this.#addCodeProperty(params, 'property', 'standard-concept', ctxt.standard);
281
+ }
282
+ if (this.#hasProp(props, 'vocabulary-id', true)) {
283
+ this.#addStringProperty(params, 'property', 'vocabulary-id', ctxt.vocabulary);
284
+ }
285
+
286
+ // Add synonyms as designations
287
+ const synonyms = await this.#getSynonyms(ctxt.code);
288
+ for (const synonym of synonyms) {
289
+ this.#addStringProperty(params, 'designation', 'synonym', synonym.value, synonym.language);
290
+ }
291
+
292
+ // Add extended properties from database
293
+ await this.#addExtendedProperties(ctxt, props, params);
294
+
295
+ // Add relationships
296
+ await this.#addRelationships(ctxt, props, params);
297
+ }
298
+
299
+ async #addExtendedProperties(ctxt, props, params) {
300
+ return new Promise((resolve, reject) => {
301
+ const sql = 'SELECT * FROM Concepts WHERE concept_id = ?';
302
+
303
+ this.db.get(sql, [ctxt.code], (err, row) => {
304
+ if (err) {
305
+ reject(err);
306
+ } else if (row) {
307
+ if (this.#hasProp(props, 'concept-class-concept-id', true)) {
308
+ this.#addCodeProperty(params, 'property', 'concept-class-concept-id', row.concept_class_id);
309
+ }
310
+ if (this.#hasProp(props, 'domain-concept-id', true)) {
311
+ this.#addCodeProperty(params, 'property', 'domain-concept-id', row.domain_id);
312
+ }
313
+ if (this.#hasProp(props, 'valid-start-date', true) && row.valid_start_date) {
314
+ this.#addDateProperty(params, 'property', 'valid-start-date', row.valid_start_date);
315
+ }
316
+ if (this.#hasProp(props, 'valid-end-date', true) && row.valid_end_date) {
317
+ this.#addDateProperty(params, 'property', 'valid-end-date', row.valid_end_date);
318
+ }
319
+ if (this.#hasProp(props, 'source-concept-code', true) && row.concept_code && getUri(row.vocabulary_id)) {
320
+ this.#addCodingProperty(params, 'property', 'source-concept-code',
321
+ getUriOrError(row.vocabulary_id), row.concept_code);
322
+ }
323
+ if (this.#hasProp(props, 'vocabulary-concept-id', true)) {
324
+ this.#addCodeProperty(params, 'property', 'vocabulary-concept-id', row.vocabulary_id);
325
+ }
326
+ if (this.#hasProp(props, 'invalid-reason', true) && row.invalid_reason) {
327
+ this.#addStringProperty(params, 'property', 'invalid-reason', row.invalid_reason);
328
+ }
329
+ resolve();
330
+ } else {
331
+ resolve();
332
+ }
333
+ });
334
+ });
335
+ }
336
+
337
+ async #addRelationships(ctxt, props, params) {
338
+ const seenConcepts = new Set();
339
+
340
+ // Forward relationships
341
+ await new Promise((resolve, reject) => {
342
+ const sql = `
343
+ SELECT Concepts.concept_id, Concepts.concept_name, Relationships.relationship_id
344
+ FROM Concepts, ConceptRelationships, Relationships
345
+ WHERE ConceptRelationships.relationship_id = Relationships.relationship_concept_id
346
+ AND ConceptRelationships.concept_id_2 = Concepts.concept_id
347
+ AND ConceptRelationships.concept_id_1 = ?
348
+ `;
349
+
350
+ this.db.all(sql, [ctxt.code], (err, rows) => {
351
+ if (err) {
352
+ reject(err);
353
+ } else {
354
+ for (const row of rows) {
355
+ seenConcepts.add(row.concept_id);
356
+ if (this.#hasProp(props, row.relationship_id, true)) {
357
+ this.#addCodingProperty(params, 'property', row.relationship_id,
358
+ this.system(), row.concept_id, row.concept_name);
359
+ }
360
+ }
361
+ resolve();
362
+ }
363
+ });
364
+ });
365
+
366
+ // Reverse relationships
367
+ await new Promise((resolve, reject) => {
368
+ const sql = `
369
+ SELECT Concepts.concept_id, Concepts.concept_name, Relationships.reverse_relationship_id
370
+ FROM Concepts, ConceptRelationships, Relationships
371
+ WHERE ConceptRelationships.relationship_id = Relationships.relationship_concept_id
372
+ AND ConceptRelationships.concept_id_1 = Concepts.concept_id
373
+ AND ConceptRelationships.concept_id_2 = ?
374
+ `;
375
+
376
+ this.db.all(sql, [ctxt.code], (err, rows) => {
377
+ if (err) {
378
+ reject(err);
379
+ } else {
380
+ for (const row of rows) {
381
+ if (!seenConcepts.has(row.concept_id)) {
382
+ if (this.#hasProp(props, row.reverse_relationship_id, true)) {
383
+ this.#addCodingProperty(params, 'property', row.reverse_relationship_id,
384
+ this.system(), row.concept_id, row.concept_name);
385
+ }
386
+ }
387
+ }
388
+ resolve();
389
+ }
390
+ });
391
+ });
392
+ }
393
+
394
+ #addStringProperty(params, type, name, value, language = null) {
395
+ const property = {
396
+ name: type,
397
+ part: [
398
+ { name: 'code', valueCode: name },
399
+ { name: 'value', valueString: String(value) } // Ensure value is always a string
400
+ ]
401
+ };
402
+
403
+ if (language) {
404
+ property.part.push({ name: 'language', valueCode: language });
405
+ }
406
+
407
+ params.push(property);
408
+ }
409
+
410
+ #addDateProperty(params, type, name, value, language = null) {
411
+ value = String(value);
412
+ if (value && value.length === 8 && !value.includes('-')) {
413
+ value = value.substring(0, 4) + '-' + value.substring(4, 6) + '-' + value.substring(6, 8);
414
+ }
415
+
416
+ const property = {
417
+ name: type,
418
+ part: [
419
+ { name: 'code', valueCode: name },
420
+ { name: 'value', valueDate: value }
421
+ ]
422
+ };
423
+
424
+ if (language) {
425
+ property.part.push({ name: 'language', valueCode: language });
426
+ }
427
+
428
+ params.push(property);
429
+ }
430
+
431
+ #addCodingProperty(params, type, name, system, code, display = undefined) {
432
+ const valueCoding = {
433
+ system: system,
434
+ code: String(code)
435
+ };
436
+
437
+ if (display !== undefined) {
438
+ valueCoding.display = display;
439
+ }
440
+ const property = {
441
+ name: type,
442
+ part: [
443
+ { name: 'code', valueCode: name },
444
+ { name: 'value', valueCoding: valueCoding }
445
+ ]
446
+ };
447
+
448
+ params.push(property);
449
+ }
450
+
451
+ #addCodeProperty(params, type, name, value, language = null) {
452
+ const property = {
453
+ name: type,
454
+ part: [
455
+ { name: 'code', valueCode: name },
456
+ { name: 'value', valueCode: String(value) } // Ensure value is always a string
457
+ ]
458
+ };
459
+
460
+ if (language) {
461
+ property.part.push({ name: 'language', valueCode: language });
462
+ }
463
+
464
+ params.push(property);
465
+ }
466
+
467
+ #hasProp(props, name, defaultValue) {
468
+ if (!props || props.length === 0) return defaultValue;
469
+ return props.includes(name);
470
+ }
471
+
472
+ async #ensureContext(context) {
473
+ if (!context) {
474
+ return null;
475
+ }
476
+ if (typeof context === 'string') {
477
+ const ctxt = await this.locate(context);
478
+ if (!ctxt.context) {
479
+ throw new Error(ctxt.message ? ctxt.message : `OMOP Concept '${context}' not found`);
480
+ } else {
481
+ return ctxt.context;
482
+ }
483
+ }
484
+ if (context instanceof OMOPConcept) {
485
+ return context;
486
+ }
487
+ throw new Error("Unknown Type at #ensureContext: " + (typeof context));
488
+ }
489
+
490
+ // Lookup methods
491
+ async locate(code) {
492
+
493
+ assert(!code || typeof code === 'string', 'code must be string');
494
+ if (!code) return { context: null, message: 'Empty code' };
495
+
496
+ return new Promise((resolve, reject) => {
497
+ const sql = `
498
+ SELECT concept_id, concept_name, standard_concept,
499
+ Domains.domain_id, ConceptClasses.concept_class_id,
500
+ Vocabularies.vocabulary_id
501
+ FROM Concepts, Domains, ConceptClasses, Vocabularies
502
+ WHERE Concepts.domain_id = Domains.domain_concept_id
503
+ AND ConceptClasses.concept_class_concept_id = Concepts.concept_class_id
504
+ AND Concepts.vocabulary_id = Vocabularies.vocabulary_concept_id
505
+ AND concept_id = ?
506
+ `;
507
+
508
+ this.db.get(sql, [code], (err, row) => {
509
+ if (err) {
510
+ reject(err);
511
+ } else if (row && row.concept_id.toString() === code) {
512
+ const concept = new OMOPConcept(
513
+ code,
514
+ row.concept_name,
515
+ row.domain_id,
516
+ row.concept_class_id,
517
+ row.standard_concept || 'NS',
518
+ row.vocabulary_id
519
+ );
520
+ resolve({ context: concept, message: null });
521
+ } else {
522
+ resolve({ context: null, message: undefined });
523
+ }
524
+ });
525
+ });
526
+ }
527
+
528
+ // Iterator methods - not supported for OMOP due to size
529
+ async iterator(context) {
530
+ await this.#ensureContext(context);
531
+ throw new Error('getNextContext not supported by OMOP - too large to iterate');
532
+ }
533
+
534
+ // eslint-disable-next-line no-unused-vars
535
+ async nextContext(iteratorContext) {
536
+ throw new Error('getNextContext not supported by OMOP - too large to iterate');
537
+ }
538
+
539
+ // Filter support
540
+ async doesFilter(prop, op, value) {
541
+ if (prop === 'domain' && op === '=') {
542
+ return value != null;
543
+ }
544
+ return false;
545
+ }
546
+
547
+ async getPrepContext(iterate) {
548
+ return new OMOPPrep(iterate);
549
+ }
550
+
551
+ async filter(filterContext, prop, op, value) {
552
+
553
+
554
+ if (prop === 'domain' && op === '=') {
555
+ let sql = `
556
+ SELECT concept_id, concept_name, domain_id
557
+ FROM Concepts
558
+ WHERE standard_concept = 'S'
559
+ AND domain_id IN (
560
+ SELECT domain_concept_id
561
+ FROM Domains
562
+ WHERE domain_id = ?
563
+ )
564
+ `;
565
+
566
+ let filter;
567
+ if (filterContext.iterate) {
568
+ filter = new OMOPFilter(this.db, sql, value);
569
+ await filter.execute([value]);
570
+ } else {
571
+ sql = sql + ' and concept_id = ?';
572
+ filter = new OMOPFilter(this.db, sql, value);
573
+ }
574
+ filterContext.filters.push(filter);
575
+ } else {
576
+ throw new Error(`Filter "${prop} ${op} ${value}" not understood for OMOP`);
577
+ }
578
+ }
579
+
580
+ async executeFilters(filterContext) {
581
+ return filterContext.filters;
582
+ }
583
+
584
+ async filterSize(filterContext, set) {
585
+ return set.rows.length;
586
+ }
587
+
588
+ async filterMore(filterContext, set) {
589
+ return set.cursor < set.rows.length;
590
+ }
591
+
592
+ async filterConcept(filterContext, set) {
593
+ if (set.cursor >= set.rows.length) {
594
+ return null;
595
+ }
596
+
597
+ const row = set.rows[set.cursor];
598
+ set.cursor++;
599
+
600
+ return new OMOPConcept(
601
+ row.concept_id,
602
+ row.concept_name,
603
+ row.domain_id,
604
+ '', // concept_class not in basic filter query
605
+ 'S', // standard_concept is 'S' by filter
606
+ '' // vocabulary not in basic filter query
607
+ );
608
+ }
609
+
610
+ async filterLocate(filterContext, set, code) {
611
+ if (filterContext.iterate) {
612
+ return `Filter not configured for locate operations`;
613
+ }
614
+
615
+ const row = await set.executeForLocate([set.value, code]);
616
+ if (row && row.concept_id.toString() === code) {
617
+ return new OMOPConcept(
618
+ String(row.concept_id),
619
+ row.concept_name,
620
+ row.domain_id,
621
+ '',
622
+ 'S',
623
+ ''
624
+ );
625
+ } else {
626
+ return `Code '${code}' is not in the value set`;
627
+ }
628
+ }
629
+
630
+ async filterCheck(filterContext, set, concept) {
631
+
632
+
633
+ if (!(concept instanceof OMOPConcept)) {
634
+ return false;
635
+ }
636
+
637
+ return set.rows.some(row => row.concept_id.toString() === concept.code);
638
+ }
639
+
640
+ async filterFinish(filterContext) {
641
+
642
+ for (const filter of filterContext.filters) {
643
+ filter.close();
644
+ }
645
+ }
646
+
647
+ async filtersNotClosed(filterContext) {
648
+ validateOptionalParameter(filterContext, "filterContext", FilterExecutionContext);
649
+ return false; // OMOP filters are closed
650
+ }
651
+
652
+ // Search filter - not implemented
653
+ // eslint-disable-next-line no-unused-vars
654
+ async searchFilter(filterContext, filter, sort) {
655
+
656
+ throw new Error('Search filter not implemented yet');
657
+ }
658
+
659
+ // Subsumption testing - not implemented
660
+ async subsumesTest(codeA, codeB) {
661
+ await this.#ensureContext(codeA);
662
+ await this.#ensureContext(codeB);
663
+
664
+ return 'not-subsumed';
665
+ }
666
+
667
+ // Translation support
668
+ async getTranslations(coding, target) {
669
+
670
+
671
+ const vocabId = getVocabId(target);
672
+ if (vocabId === -1) {
673
+ return [];
674
+ }
675
+
676
+ return new Promise((resolve, reject) => {
677
+ const sql = `
678
+ SELECT concept_code, concept_name
679
+ FROM Concepts
680
+ WHERE concept_id = ? AND vocabulary_id = ?
681
+ `;
682
+
683
+ this.db.all(sql, [coding.code, vocabId], (err, rows) => {
684
+ if (err) {
685
+ reject(err);
686
+ } else {
687
+ const translations = rows.map(row => ({
688
+ uri: target,
689
+ code: row.concept_code,
690
+ display: row.concept_name,
691
+ relationship: 'equivalent',
692
+ map: `${this.system()}/ConceptMap/to-${vocabId}|${this._version}`
693
+ }));
694
+ resolve(translations);
695
+ }
696
+ });
697
+ });
698
+ }
699
+
700
+ // Build value sets for domains
701
+ async buildValueSet(factory, id) {
702
+ const domain = id.substring(44); // Remove prefix
703
+
704
+ return new Promise((resolve, reject) => {
705
+ const sql = `
706
+ SELECT concept_id, concept_name, Domains.domain_id
707
+ FROM Concepts, Domains
708
+ WHERE Domains.domain_id = ?
709
+ AND Domains.domain_concept_id = Concepts.concept_id
710
+ `;
711
+
712
+ this.db.get(sql, [domain], (err, row) => {
713
+ if (err) {
714
+ reject(err);
715
+ } else if (row && row.domain_id === domain) {
716
+ // Create value set structure
717
+ const valueSet = {
718
+ url: id,
719
+ status: 'active',
720
+ version: this._version,
721
+ name: `OMOPDomain${domain}`,
722
+ description: `OMOP value set for domain ${row.concept_name}`,
723
+ date: new Date().toISOString(),
724
+ experimental: false,
725
+ compose: {
726
+ include: [{
727
+ system: this.system(),
728
+ filter: [{
729
+ property: 'domain',
730
+ op: '=',
731
+ value: domain
732
+ }]
733
+ }]
734
+ }
735
+ };
736
+ resolve(valueSet);
737
+ } else {
738
+ reject(new Error(`Unknown Value Domain ${id}`));
739
+ }
740
+ });
741
+ });
742
+ }
743
+
744
+ // Register concept maps for vocabularies
745
+ async registerConceptMaps(list) {
746
+ return new Promise((resolve, reject) => {
747
+ const sql = 'SELECT DISTINCT vocabulary_id FROM Concepts';
748
+
749
+ this.db.all(sql, (err, rows) => {
750
+ if (err) {
751
+ reject(err);
752
+ } else {
753
+ for (const row of rows) {
754
+ const key = row.vocabulary_id;
755
+ const uri = getUri(key);
756
+ if (uri) {
757
+ // Create concept maps (simplified structure)
758
+ list.push({
759
+ id: `to-${key}`,
760
+ url: `${this.system()}/ConceptMap/to-${key}`,
761
+ sourceUri: this.system(),
762
+ targetUri: uri
763
+ });
764
+ list.push({
765
+ id: `from-${key}`,
766
+ url: `${this.system()}/ConceptMap/from-${key}`,
767
+ sourceUri: uri,
768
+ targetUri: this.system()
769
+ });
770
+ }
771
+ }
772
+ resolve();
773
+ }
774
+ });
775
+ });
776
+ }
777
+
778
+ versionAlgorithm() {
779
+ return 'date';
780
+ }
781
+ }
782
+
783
+ class OMOPServicesFactory extends CodeSystemFactoryProvider {
784
+ constructor(i18n, dbPath) {
785
+ super(i18n);
786
+ this.dbPath = dbPath;
787
+ this.uses = 0;
788
+ this._loaded = false;
789
+ this._sharedData = null;
790
+ }
791
+
792
+ system() {
793
+ return 'https://fhir-terminology.ohdsi.org';
794
+ }
795
+
796
+ version() {
797
+ return this._sharedData._version;
798
+ }
799
+
800
+ async buildKnownValueSet(url, version) {
801
+ if (!url.startsWith('https://fhir-terminology.ohdsi.org/ValueSet')) {
802
+ return null;
803
+ }
804
+ if (version && version != this.version()) {
805
+ return null;
806
+ }
807
+ if (url == 'https://fhir-terminology.ohdsi.org') {
808
+ return {
809
+ resourceType: 'ValueSet',
810
+ url: url,
811
+ status: 'active',
812
+ version: this.version(),
813
+ name: 'OMOP',
814
+ description: 'OMOP value set',
815
+ date: new Date().toISOString(),
816
+ experimental: false,
817
+ compose: {
818
+ include: [{
819
+ system: this.system()
820
+ }]
821
+ }
822
+ }
823
+ }
824
+ const domain = url.substring(44);
825
+ return {
826
+ resourceType: 'ValueSet',
827
+ url: url,
828
+ status: 'active',
829
+ version: this.version(),
830
+ name: 'OMOPDomain' + domain,
831
+ description: 'OMOP value set for domain ' + domain,
832
+ date: new Date().toISOString(),
833
+ experimental: false,
834
+ compose: {
835
+ include: [{
836
+ system: this.system(),
837
+ filter: [{
838
+ property: 'domain',
839
+ op: '=',
840
+ value: domain
841
+ }]
842
+ }]
843
+ }
844
+ };
845
+
846
+
847
+ }
848
+
849
+ async #ensureLoaded() {
850
+ if (!this._loaded) {
851
+ await this.load();
852
+ }
853
+ }
854
+
855
+ async load() {
856
+ const db = new sqlite3.Database(this.dbPath);
857
+
858
+ try {
859
+ this._sharedData = {
860
+ _version: 'unknown'
861
+ };
862
+
863
+ // Load version from OMOP Extension vocabulary
864
+ await this.#loadVersion(db);
865
+
866
+ } finally {
867
+ db.close();
868
+ }
869
+ this._loaded = true;
870
+ }
871
+
872
+ async #loadVersion(db) {
873
+ return new Promise((resolve, reject) => {
874
+ const sql = `
875
+ SELECT vocabulary_version
876
+ FROM Vocabularies
877
+ WHERE vocabulary_id = 'OMOP Extension'
878
+ `;
879
+
880
+ db.get(sql, (err, row) => {
881
+ if (err) {
882
+ reject(err);
883
+ } else if (row) {
884
+ // Extract version number from the end of the version string
885
+ const version = row.vocabulary_version;
886
+ const lastSpaceIndex = version.lastIndexOf(' ');
887
+ this._sharedData._version = lastSpaceIndex !== -1 ?
888
+ version.substring(lastSpaceIndex + 1) : version;
889
+ resolve();
890
+ } else {
891
+ this._sharedData._version = 'unknown';
892
+ resolve();
893
+ }
894
+ });
895
+ });
896
+ }
897
+
898
+ defaultVersion() {
899
+ return this._sharedData?._version || 'unknown';
900
+ }
901
+
902
+ async build(opContext, supplements) {
903
+ await this.#ensureLoaded();
904
+ this.recordUse();
905
+
906
+ // Create fresh database connection for this provider instance
907
+ const db = new sqlite3.Database(this.dbPath);
908
+
909
+ return new OMOPServices(opContext, supplements, db, this._sharedData);
910
+ }
911
+
912
+ static checkDB(dbPath) {
913
+ try {
914
+ const fs = require('fs');
915
+
916
+ // Check if file exists
917
+ if (!fs.existsSync(dbPath)) {
918
+ return 'Database file not found';
919
+ }
920
+
921
+ // Check file size
922
+ const stats = fs.statSync(dbPath);
923
+ if (stats.size < 1024) {
924
+ return 'Database file too small';
925
+ }
926
+
927
+ // Try to open database and check for required tables
928
+ const db = new sqlite3.Database(dbPath);
929
+
930
+ try {
931
+ // Simple count query to verify database integrity
932
+ db.get('SELECT COUNT(*) as count FROM Concepts', (err) => {
933
+ if (err) {
934
+ db.close();
935
+ return 'Missing Tables - needs re-importing (by java)';
936
+ }
937
+ });
938
+
939
+ db.close();
940
+ return 'OK (check via provider for count)';
941
+ } catch (e) {
942
+ return 'Missing Tables - needs re-importing (by java)';
943
+ }
944
+ } catch (e) {
945
+ return `Database error: ${e.message}`;
946
+ }
947
+ }
948
+
949
+
950
+ /**
951
+ * build and return a known concept map from the URL, if there is one.
952
+ *
953
+ * @param url
954
+ * @param version
955
+ * @returns {ConceptMap}
956
+ */
957
+ async findImplicitConceptMaps(conceptMaps, source, dest) {
958
+ if (source == 'https://fhir-terminology.ohdsi.org') {
959
+ let key = this.#getVocabId(dest);
960
+ if (key) {
961
+ conceptMaps.push(new ConceptMap(this.makeCM(source, dest, key)));
962
+ }
963
+ } else if (dest == 'https://fhir-terminology.ohdsi.org') {
964
+ let key = this.#getVocabId(source);
965
+ if (key) {
966
+ conceptMaps.push(new ConceptMap(this.makeCM(source, dest, key)));
967
+ }
968
+ } else {
969
+ // nothing
970
+ }
971
+ }
972
+
973
+ // eslint-disable-next-line no-unused-vars
974
+ async findImplicitConceptMap(url, version) {
975
+ return null;
976
+ }
977
+
978
+ #getVocabId(url) {
979
+ const vocabMap = {
980
+ 'http://hl7.org/fhir/sid/icd-9-cm': 5046,
981
+ 'http://snomed.info/sct': 44819097,
982
+ 'http://hl7.org/fhir/sid/icd-10-cm': 44819098,
983
+ // 'http://hl7.org/fhir/sid/icd-9-cm': 44819099, // duplicate - using first value
984
+ 'http://www.ama-assn.org/go/cpt': 44819100,
985
+ 'http://terminology.hl7.org/CodeSystem/HCPCS-all-codes': 44819101,
986
+ 'http://loinc.org': 44819102,
987
+ 'http://www.nlm.nih.gov/research/umls/rxnorm': 44819104,
988
+ 'http://hl7.org/fhir/sid/ndc': 44819105,
989
+ 'http://unitsofmeasure.org': 44819107,
990
+ 'http://nucc.org/provider-taxonomy': 44819137,
991
+ 'http://www.whocc.no/atc': 44819117
992
+ };
993
+
994
+ return vocabMap[url];
995
+ }
996
+
997
+ makeCM(source, dest, key) {
998
+ return {
999
+ resourceType: 'ConceptMap',
1000
+ internalSource: this,
1001
+ url: this.system() + '/ConceptMap/' + key,
1002
+ status: 'active',
1003
+ group: [{
1004
+ source: source,
1005
+ target: dest
1006
+ }]
1007
+ };
1008
+ }
1009
+
1010
+
1011
+ name() {
1012
+ return `OMOP Concepts`;
1013
+ }
1014
+
1015
+ id() {
1016
+ return "omop";
1017
+ }
1018
+ }
1019
+
1020
+ module.exports = {
1021
+ OMOPServices,
1022
+ OMOPServicesFactory,
1023
+ OMOPConcept,
1024
+ OMOPFilter
1025
+ };