conceptql 0.2.0 → 0.4.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 (242) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +226 -117
  3. data/LICENSE +21 -0
  4. data/README.md +35 -83
  5. data/bin/conceptql +2 -1
  6. data/bin/conceptql_utils +6 -0
  7. data/config/multiple_vocabularies.csv +4 -0
  8. data/config/provenance.yml +489 -0
  9. data/config/race.yml +50 -0
  10. data/config/vocabularies.csv +87 -0
  11. data/lib/conceptql/behaviors/code_lister.rb +23 -0
  12. data/lib/conceptql/behaviors/drugish.rb +17 -0
  13. data/lib/conceptql/behaviors/labish.rb +19 -0
  14. data/lib/conceptql/behaviors/metadatable.rb +226 -73
  15. data/lib/conceptql/behaviors/provenanceable.rb +26 -0
  16. data/lib/conceptql/behaviors/single_event.rb +11 -0
  17. data/lib/conceptql/behaviors/timeless.rb +11 -0
  18. data/lib/conceptql/behaviors/utilizable.rb +206 -0
  19. data/lib/conceptql/behaviors/windowable.rb +27 -0
  20. data/lib/conceptql/cli.rb +117 -108
  21. data/lib/conceptql/columnizer.rb +75 -0
  22. data/lib/conceptql/data_model/base.rb +462 -0
  23. data/lib/conceptql/data_model/gdm.rb +155 -0
  24. data/lib/conceptql/data_model/gdm_wide.rb +42 -0
  25. data/lib/conceptql/data_model/omopv4_plus.rb +12 -0
  26. data/lib/conceptql/data_model.rb +30 -0
  27. data/lib/conceptql/database.rb +214 -0
  28. data/lib/conceptql/date_adjuster.rb +66 -18
  29. data/lib/conceptql/diagram.rb +243 -0
  30. data/lib/conceptql/lexicon/lexicon_gdm.rb +47 -0
  31. data/lib/conceptql/lexicon/lexicon_no_db.rb +49 -0
  32. data/lib/conceptql/lexicon/lexicon_ohdsi.rb +63 -0
  33. data/lib/conceptql/lexicon/lexicon_strategy.rb +116 -0
  34. data/lib/conceptql/lexicon.rb +111 -0
  35. data/lib/conceptql/nodifier.rb +65 -24
  36. data/lib/conceptql/operators/after.rb +34 -14
  37. data/lib/conceptql/operators/any_overlap.rb +8 -6
  38. data/lib/conceptql/operators/before.rb +34 -10
  39. data/lib/conceptql/operators/binary_operator_operator.rb +66 -23
  40. data/lib/conceptql/operators/casting_operator.rb +51 -36
  41. data/lib/conceptql/operators/co_reported.rb +111 -0
  42. data/lib/conceptql/operators/concurrent_within.rb +69 -0
  43. data/lib/conceptql/operators/contains.rb +12 -8
  44. data/lib/conceptql/operators/count.rb +17 -9
  45. data/lib/conceptql/operators/date_range.rb +48 -14
  46. data/lib/conceptql/operators/death.rb +14 -4
  47. data/lib/conceptql/operators/dedup.rb +31 -0
  48. data/lib/conceptql/operators/during.rb +7 -12
  49. data/lib/conceptql/operators/episode.rb +146 -0
  50. data/lib/conceptql/operators/equal.rb +9 -3
  51. data/lib/conceptql/operators/ethnicity.rb +49 -0
  52. data/lib/conceptql/operators/except.rb +19 -12
  53. data/lib/conceptql/operators/filter.rb +23 -8
  54. data/lib/conceptql/operators/first.rb +16 -3
  55. data/lib/conceptql/operators/from.rb +52 -4
  56. data/lib/conceptql/operators/gender.rb +34 -11
  57. data/lib/conceptql/operators/hospice.rb +19 -0
  58. data/lib/conceptql/operators/hospitalization.rb +19 -0
  59. data/lib/conceptql/operators/information_periods.rb +36 -0
  60. data/lib/conceptql/operators/invalid.rb +35 -0
  61. data/lib/conceptql/operators/last.rb +16 -3
  62. data/lib/conceptql/operators/match.rb +91 -0
  63. data/lib/conceptql/operators/multiple_vocabularies.rb +172 -0
  64. data/lib/conceptql/operators/numeric.rb +65 -23
  65. data/lib/conceptql/operators/numeric_filter.rb +68 -0
  66. data/lib/conceptql/operators/occurrence.rb +153 -32
  67. data/lib/conceptql/operators/one_in_two_out.rb +237 -38
  68. data/lib/conceptql/operators/operator.rb +624 -178
  69. data/lib/conceptql/operators/pass_thru.rb +17 -3
  70. data/lib/conceptql/operators/person.rb +18 -6
  71. data/lib/conceptql/operators/person_filter.rb +9 -3
  72. data/lib/conceptql/operators/place_of_service_filter.rb +43 -0
  73. data/lib/conceptql/operators/provenance.rb +55 -0
  74. data/lib/conceptql/operators/provider_filter.rb +62 -0
  75. data/lib/conceptql/operators/race.rb +55 -7
  76. data/lib/conceptql/operators/read.rb +168 -0
  77. data/lib/conceptql/operators/recall.rb +49 -14
  78. data/lib/conceptql/operators/snf.rb +19 -0
  79. data/lib/conceptql/operators/sum.rb +29 -12
  80. data/lib/conceptql/operators/temporal_operator.rb +108 -16
  81. data/lib/conceptql/operators/time_window.rb +20 -32
  82. data/lib/conceptql/operators/trim_date.rb +71 -0
  83. data/lib/conceptql/operators/trim_date_end.rb +28 -40
  84. data/lib/conceptql/operators/trim_date_start.rb +28 -41
  85. data/lib/conceptql/operators/union.rb +67 -6
  86. data/lib/conceptql/operators/vocabulary.rb +151 -0
  87. data/lib/conceptql/paths.rb +41 -0
  88. data/lib/conceptql/query.rb +191 -26
  89. data/lib/conceptql/query_modifiers/gdm/admission_date_query_modifier.rb +46 -0
  90. data/lib/conceptql/query_modifiers/gdm/drug_query_modifier.rb +54 -0
  91. data/lib/conceptql/query_modifiers/gdm/lab_query_modifier.rb +57 -0
  92. data/lib/conceptql/query_modifiers/gdm/pos_query_modifier.rb +46 -0
  93. data/lib/conceptql/query_modifiers/gdm/provenance_query_modifier.rb +45 -0
  94. data/lib/conceptql/query_modifiers/gdm/provider_query_modifier.rb +53 -0
  95. data/lib/conceptql/query_modifiers/gdm_wide/admission_date_query_modifier.rb +42 -0
  96. data/lib/conceptql/query_modifiers/gdm_wide/drug_query_modifier.rb +52 -0
  97. data/lib/conceptql/query_modifiers/gdm_wide/lab_query_modifier.rb +55 -0
  98. data/lib/conceptql/query_modifiers/gdm_wide/pos_query_modifier.rb +37 -0
  99. data/lib/conceptql/query_modifiers/gdm_wide/provenance_query_modifier.rb +38 -0
  100. data/lib/conceptql/query_modifiers/omopv4_plus/admission_date_query_modifier.rb +45 -0
  101. data/lib/conceptql/query_modifiers/omopv4_plus/drug_query_modifier.rb +80 -0
  102. data/lib/conceptql/query_modifiers/omopv4_plus/pos_query_modifier.rb +47 -0
  103. data/lib/conceptql/query_modifiers/omopv4_plus/provenance_query_modifier.rb +45 -0
  104. data/lib/conceptql/query_modifiers/omopv4_plus/provider_query_modifier.rb +26 -0
  105. data/lib/conceptql/query_modifiers/query_modifier.rb +20 -0
  106. data/lib/conceptql/rdbms/duckdb.rb +25 -0
  107. data/lib/conceptql/rdbms/generic.rb +120 -0
  108. data/lib/conceptql/rdbms/postgres.rb +51 -0
  109. data/lib/conceptql/rdbms/spark.rb +22 -0
  110. data/lib/conceptql/rdbms.rb +22 -0
  111. data/lib/conceptql/scope.rb +481 -31
  112. data/lib/conceptql/sql_formatters/formatter.rb +25 -0
  113. data/lib/conceptql/sql_formatters.rb +22 -0
  114. data/lib/conceptql/utils.rb +113 -0
  115. data/lib/conceptql/utils_cli.rb +17 -0
  116. data/lib/conceptql/version.rb +3 -1
  117. data/lib/conceptql/visitors/metadata_extractor.rb +54 -0
  118. data/lib/conceptql/vocabularies/behaviors/costish.rb +24 -0
  119. data/lib/conceptql/vocabularies/behaviors/gdmish.rb +19 -0
  120. data/lib/conceptql/vocabularies/behaviors/omopish.rb +32 -0
  121. data/lib/conceptql/vocabularies/dynamic_vocabularies.rb +63 -0
  122. data/lib/conceptql/vocabularies/entry.rb +138 -0
  123. data/lib/conceptql/vocabularies/vocabulary_class_factory.rb +85 -0
  124. data/lib/conceptql/window/base.rb +26 -0
  125. data/lib/conceptql/window/date_range.rb +25 -0
  126. data/lib/conceptql/window/table.rb +116 -0
  127. data/lib/conceptql/window.rb +72 -0
  128. data/lib/conceptql.rb +65 -2
  129. data/lib/sequel/extensions/pg_ctas_explain.rb +22 -0
  130. data/lib/sequel/extensions/pg_vacuum_table.rb +23 -0
  131. data/schemas/gdm.yml +882 -0
  132. data/schemas/gdm_wide.yml +757 -0
  133. data/schemas/ohdsi_vocabs.yml +143 -0
  134. data/schemas/omopv4_plus.yml +729 -0
  135. data/schemas/omopv4_plus_more_ignorables.tsv +13 -0
  136. metadata +267 -207
  137. data/.gitignore +0 -23
  138. data/Gemfile +0 -4
  139. data/Guardfile +0 -56
  140. data/LICENSE.txt +0 -22
  141. data/Rakefile +0 -1
  142. data/conceptql.gemspec +0 -32
  143. data/doc/ConceptQL Specification (alpha).pdf +0 -0
  144. data/doc/diagram_0.png +0 -0
  145. data/doc/implementation_notes.md +0 -39
  146. data/doc/spec.md +0 -1344
  147. data/lib/conceptql/behaviors/debuggable.rb +0 -70
  148. data/lib/conceptql/behaviors/dottable.rb +0 -103
  149. data/lib/conceptql/behaviors/preppable.rb +0 -20
  150. data/lib/conceptql/converter.rb +0 -65
  151. data/lib/conceptql/debugger.rb +0 -48
  152. data/lib/conceptql/fake_grapher.rb +0 -20
  153. data/lib/conceptql/graph.rb +0 -52
  154. data/lib/conceptql/graph_nodifier.rb +0 -203
  155. data/lib/conceptql/logger.rb +0 -10
  156. data/lib/conceptql/operators/complement.rb +0 -38
  157. data/lib/conceptql/operators/concept.rb +0 -70
  158. data/lib/conceptql/operators/condition_type.rb +0 -71
  159. data/lib/conceptql/operators/cpt.rb +0 -25
  160. data/lib/conceptql/operators/drug_type_concept.rb +0 -21
  161. data/lib/conceptql/operators/from_seer_visits.rb +0 -23
  162. data/lib/conceptql/operators/hcpcs.rb +0 -25
  163. data/lib/conceptql/operators/icd10.rb +0 -28
  164. data/lib/conceptql/operators/icd9.rb +0 -28
  165. data/lib/conceptql/operators/icd9_procedure.rb +0 -25
  166. data/lib/conceptql/operators/intersect.rb +0 -34
  167. data/lib/conceptql/operators/loinc.rb +0 -25
  168. data/lib/conceptql/operators/medcode.rb +0 -28
  169. data/lib/conceptql/operators/medcode_procedure.rb +0 -27
  170. data/lib/conceptql/operators/ndc.rb +0 -29
  171. data/lib/conceptql/operators/observation_by_enttype.rb +0 -29
  172. data/lib/conceptql/operators/observation_period.rb +0 -30
  173. data/lib/conceptql/operators/overlapped_by.rb +0 -23
  174. data/lib/conceptql/operators/overlaps.rb +0 -19
  175. data/lib/conceptql/operators/place_of_service_code.rb +0 -24
  176. data/lib/conceptql/operators/procedure_occurrence.rb +0 -25
  177. data/lib/conceptql/operators/prodcode.rb +0 -29
  178. data/lib/conceptql/operators/rxnorm.rb +0 -24
  179. data/lib/conceptql/operators/snomed.rb +0 -24
  180. data/lib/conceptql/operators/snomed_condition.rb +0 -26
  181. data/lib/conceptql/operators/source_vocabulary_operator.rb +0 -58
  182. data/lib/conceptql/operators/standard_vocabulary_operator.rb +0 -45
  183. data/lib/conceptql/operators/started_by.rb +0 -22
  184. data/lib/conceptql/operators/to_seer_visits.rb +0 -24
  185. data/lib/conceptql/operators/visit.rb +0 -15
  186. data/lib/conceptql/operators/visit_occurrence.rb +0 -30
  187. data/lib/conceptql/tree.rb +0 -53
  188. data/lib/conceptql/utils/temp_table.rb +0 -72
  189. data/spec/conceptql/behaviors/dottable_spec.rb +0 -99
  190. data/spec/conceptql/converter_spec.rb +0 -51
  191. data/spec/conceptql/date_adjuster_spec.rb +0 -68
  192. data/spec/conceptql/operators/after_spec.rb +0 -16
  193. data/spec/conceptql/operators/before_spec.rb +0 -16
  194. data/spec/conceptql/operators/casting_operator_spec.rb +0 -68
  195. data/spec/conceptql/operators/complement_spec.rb +0 -15
  196. data/spec/conceptql/operators/concept_spec.rb +0 -40
  197. data/spec/conceptql/operators/condition_type_spec.rb +0 -128
  198. data/spec/conceptql/operators/contains_spec.rb +0 -19
  199. data/spec/conceptql/operators/cpt_spec.rb +0 -29
  200. data/spec/conceptql/operators/date_range_spec.rb +0 -33
  201. data/spec/conceptql/operators/death_spec.rb +0 -10
  202. data/spec/conceptql/operators/during_spec.rb +0 -30
  203. data/spec/conceptql/operators/except_spec.rb +0 -15
  204. data/spec/conceptql/operators/first_spec.rb +0 -35
  205. data/spec/conceptql/operators/from_spec.rb +0 -13
  206. data/spec/conceptql/operators/gender_spec.rb +0 -27
  207. data/spec/conceptql/operators/hcpcs_spec.rb +0 -29
  208. data/spec/conceptql/operators/icd10_spec.rb +0 -34
  209. data/spec/conceptql/operators/icd9_procedure_spec.rb +0 -29
  210. data/spec/conceptql/operators/icd9_spec.rb +0 -34
  211. data/spec/conceptql/operators/intersect_spec.rb +0 -28
  212. data/spec/conceptql/operators/last_spec.rb +0 -36
  213. data/spec/conceptql/operators/loinc_spec.rb +0 -29
  214. data/spec/conceptql/operators/medcode_procedure_spec.rb +0 -34
  215. data/spec/conceptql/operators/medcode_spec.rb +0 -34
  216. data/spec/conceptql/operators/observation_period_spec.rb +0 -10
  217. data/spec/conceptql/operators/occurrence_spec.rb +0 -87
  218. data/spec/conceptql/operators/overlapped_by_spec.rb +0 -32
  219. data/spec/conceptql/operators/overlaps_spec.rb +0 -21
  220. data/spec/conceptql/operators/person_filter_spec.rb +0 -15
  221. data/spec/conceptql/operators/person_spec.rb +0 -10
  222. data/spec/conceptql/operators/place_of_service_code_spec.rb +0 -24
  223. data/spec/conceptql/operators/procedure_occurrence_spec.rb +0 -10
  224. data/spec/conceptql/operators/prodcode_spec.rb +0 -35
  225. data/spec/conceptql/operators/query_double.rb +0 -20
  226. data/spec/conceptql/operators/query_double_spec.rb +0 -7
  227. data/spec/conceptql/operators/race_spec.rb +0 -21
  228. data/spec/conceptql/operators/rxnorm_spec.rb +0 -29
  229. data/spec/conceptql/operators/snomed_spec.rb +0 -29
  230. data/spec/conceptql/operators/source_vocabulary_operator_spec.rb +0 -35
  231. data/spec/conceptql/operators/standard_vocabulary_operator_spec.rb +0 -35
  232. data/spec/conceptql/operators/started_by_spec.rb +0 -22
  233. data/spec/conceptql/operators/temporal_operator_spec.rb +0 -51
  234. data/spec/conceptql/operators/time_window_spec.rb +0 -77
  235. data/spec/conceptql/operators/union_spec.rb +0 -21
  236. data/spec/conceptql/operators/visit_occurrence_spec.rb +0 -10
  237. data/spec/conceptql/query_spec.rb +0 -22
  238. data/spec/conceptql/tree_spec.rb +0 -50
  239. data/spec/doubles/stream_for_casting_double.rb +0 -9
  240. data/spec/doubles/stream_for_occurrence_double.rb +0 -25
  241. data/spec/doubles/stream_for_temporal_double.rb +0 -6
  242. data/spec/spec_helper.rb +0 -102
@@ -0,0 +1,462 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "forwardable"
4
+ require "psych"
5
+
6
+ module ConceptQL
7
+ module DataModel
8
+ class Base
9
+ extend Forwardable
10
+ def_delegators :nodifier, :cdb
11
+ def_delegators :cdb, :lexicon, :source_table_exists?
12
+ def_delegators :lexicon,
13
+ :concept_codes,
14
+ :concept_ids,
15
+ :concept_ids_by_name,
16
+ :concepts,
17
+ :concepts_table,
18
+ :concepts_by_name,
19
+ :concepts_ds,
20
+ :concepts_to_codes,
21
+ :descendants_of,
22
+ :known_codes,
23
+ :related_concept_ids
24
+
25
+ SCHEMAS = ConceptQL.schemas_dir.glob("*.yml").to_h do |schema_file|
26
+ [ schema_file.basename(".*").to_s.to_sym, Psych.load_file(schema_file) ]
27
+ end
28
+
29
+ attr_reader :operator, :nodifier
30
+
31
+ def initialize(operator, nodifier)
32
+ @operator = operator
33
+ @nodifier = nodifier
34
+ end
35
+
36
+ def rdbms
37
+ @rdbms ||= ConceptQL::Rdbms.generate(nodifier.database_type)
38
+ end
39
+
40
+ # The concept table that the display-name joins read. It is the
41
+ # Database's name_concepts_table when it has one, and the full concept
42
+ # table otherwise (bead conceptql-auu, seed conceptql-8u6).
43
+ def name_concepts_table(db)
44
+ table = cdb.opts[:name_concepts_table]
45
+ return concepts_table(db) unless table
46
+
47
+ db[table]
48
+ end
49
+
50
+ # The data columns whose concept ids the display-name joins look up, by
51
+ # table. A data model without those joins has none.
52
+ def name_concept_columns
53
+ {}
54
+ end
55
+
56
+ def query_modifier_for(column)
57
+ {
58
+ visit_source_concept_id: ConceptQL::QueryModifiers::Omopv4Plus::PoSQueryModifier,
59
+ provider_id: ConceptQL::QueryModifiers::Omopv4Plus::ProviderQueryModifier,
60
+ context_specialty_concept_id: ConceptQL::QueryModifiers::Omopv4Plus::ProviderQueryModifier,
61
+ drug_name: ConceptQL::QueryModifiers::Omopv4Plus::DrugQueryModifier,
62
+ admission_date: ConceptQL::QueryModifiers::Omopv4Plus::AdmissionDateQueryModifier,
63
+ provenance_type: ConceptQL::QueryModifiers::Omopv4Plus::ProvenanceQueryModifier
64
+ }[column]
65
+ end
66
+
67
+ def criterion_id
68
+ :criterion_id
69
+ end
70
+
71
+ def table_id(table = nil)
72
+ return :person_id if table.to_sym == :death
73
+
74
+ :criterion_id if table.nil?
75
+ end
76
+
77
+ def person_table
78
+ :person
79
+ end
80
+
81
+ def make_table_id(table)
82
+ "#{table}_id".to_sym
83
+ end
84
+
85
+ def make_fk_id(table)
86
+ make_table_id(table)
87
+ end
88
+
89
+ def person_id(table)
90
+ case table.to_sym
91
+ when :procedure_cost, :drug_cost
92
+ nil
93
+ else
94
+ :person_id
95
+ end
96
+ end
97
+
98
+ def schema
99
+ SCHEMAS[data_model]
100
+ end
101
+
102
+ def data_model
103
+ :omopv4_plus
104
+ end
105
+
106
+ def pk_by_domain(domain)
107
+ case domain.to_sym
108
+ when :death
109
+ :person_id
110
+ when :procedure_cost
111
+ :procedure_occurrence_id
112
+ when :drug_cost
113
+ :drug_exposure_id
114
+ else
115
+ "#{domain}_id".to_sym
116
+ end
117
+ end
118
+
119
+ def fk_by_domain(domain)
120
+ "#{domain}_id".to_sym
121
+ end
122
+
123
+ def query_columns
124
+ @query_columns ||= Hash[operator.scope.query_columns.zip(operator.scope.query_columns)]
125
+ end
126
+
127
+ def columns(opts = {})
128
+ table = get_table(opts)
129
+
130
+ cols_hash = if table.nil? && opts[:query_columns].nil?
131
+ query_columns
132
+ else
133
+ columns_in_table(table,
134
+ opts).merge(modifier_columns(table,
135
+ opts)).merge(nullified_columns(table, opts))
136
+ end.dup
137
+
138
+ except_keys = opts[:except] || []
139
+ if cols_hash.key?(:uuid)
140
+ if opts[:uuid]
141
+ except_keys << :uuid
142
+ elsif table
143
+ cols_hash.merge!(uuid: Sequel.cast(nil, String).as(:uuid))
144
+ end
145
+ end
146
+ col_keys = cols_hash.keys
147
+ col_keys -= except_keys
148
+ col_keys &= opts[:only] if opts[:only]
149
+
150
+ if (qualifier = opts[:qualifier])
151
+ cols_hash = cols_hash.transform_values { |c| Sequel.qualify(qualifier, c) }
152
+ end
153
+
154
+ cols_hash.merge!(replace(opts[:replace]))
155
+ cols_hash.values_at(*col_keys)
156
+ end
157
+
158
+ def modifier_columns(table, opts = {})
159
+ return {} if table.nil? && opts[:query_columns].nil?
160
+
161
+ remainder = query_columns.keys - (opts[:query_columns] ? opts[:query_columns].keys : columns_in_table(table).keys)
162
+ Hash[remainder.zip(remainder)]
163
+ end
164
+
165
+ def nullified_columns(table, opts = {})
166
+ return {} if table.nil? && opts[:query_columns].nil?
167
+
168
+ remainder = query_columns.keys
169
+ if opts[:query_columns]
170
+ remainder -= opts[:query_columns].keys
171
+ else
172
+ remainder -= table_cols(table)
173
+ remainder -= columns_in_table(table).keys
174
+ remainder -= applicable_query_modifiers(table).flat_map(&:provided_columns)
175
+ end
176
+
177
+ Hash[remainder.map { |r| [ r, rdbms.process(r, nil) ] }]
178
+ end
179
+
180
+ def selectify(query, opts = {})
181
+ ds = modify_query(query, get_table(opts)).select(*columns(opts))
182
+ ds = ds.from_self.select_append(rdbms.uuid.as(:uuid)) if opts[:uuid]
183
+ ds.from_self
184
+ end
185
+
186
+ def get_table(opts)
187
+ opts[:table] || table_by_domain(opts[:domain])
188
+ end
189
+
190
+ def modify_query(query, table)
191
+ return query if table.nil?
192
+
193
+ applicable_query_modifiers(table).each do |klass|
194
+ query = klass.new(query, operator, table, self).modified_query
195
+ end
196
+
197
+ query
198
+ end
199
+
200
+ def applicable_query_modifiers(table)
201
+ query_modifiers.values_at(*query_columns.keys).compact.select do |klass|
202
+ klass.has_required_columns?(table_cols(table))
203
+ end.uniq
204
+ end
205
+
206
+ def query_modifiers
207
+ {
208
+ visit_source_concept_id: query_modifier_for(:visit_source_concept_id),
209
+ provider_id: query_modifier_for(:provider_id),
210
+ context_specialty_concept_id: query_modifier_for(:context_specialty_concept_id),
211
+ drug_name: query_modifier_for(:drug_name),
212
+ file_provenance_type: query_modifier_for(:provenance_type),
213
+ admission_date: query_modifier_for(:admission_date),
214
+ value_as_number: query_modifier_for(:value_as_number)
215
+ }
216
+ end
217
+
218
+ def columns_in_table(table, opts = {})
219
+ return opts[:query_columns] unless opts[:query_columns].nil?
220
+
221
+ start_date, end_date = *date_columns(nil, table)
222
+ {
223
+ person_id: Sequel.expr(person_id(table)).as(:person_id),
224
+ criterion_id: Sequel.identifier(pk_by_domain(table)).as(:criterion_id),
225
+ criterion_table: Sequel.cast_string(table.to_s).as(:criterion_table),
226
+ criterion_domain: Sequel.cast_string((opts[:criterion_domain] || table_to_domain(table) || table).to_s).as(:criterion_domain),
227
+ start_date: start_date,
228
+ end_date: end_date,
229
+ source_value: Sequel.cast_string(source_value_column(table)).as(:source_value),
230
+ source_vocabulary_id: Sequel.cast_string(source_vocabulary_id(table)).as(:source_vocabulary_id)
231
+ }
232
+ end
233
+
234
+ def table_to_domain(table)
235
+ table
236
+ end
237
+
238
+ def replace(replace_hash)
239
+ return {} unless replace_hash
240
+
241
+ replace_hash.each_with_object({}) do |(column, value), h|
242
+ h[column] = rdbms.process(column, value)
243
+ end
244
+ end
245
+
246
+ def place_of_service_concept_id(query, domain)
247
+ place_of_service_concept_id_column(query, domain)
248
+ end
249
+
250
+ def determine_table(table_method)
251
+ return nil unless operator.respond_to?(table_method)
252
+
253
+ table = table_by_domain(operator.send(table_method))
254
+ table if schema.keys.include?(table)
255
+ end
256
+
257
+ def assign_column_to_table
258
+ schema.each_with_object({}) do |(table, table_info), cols|
259
+ columns_info = table_info[:columns]
260
+ column = yield table, columns_info.keys.map(&:to_s)
261
+ cols[table] = column ? column.to_sym : nil
262
+ end
263
+ end
264
+
265
+ def start_date_columns
266
+ @start_date_columns ||= assign_column_to_table do |_table, columns|
267
+ column = columns.select { |k| k =~ /start_date$/ }.first
268
+ column || columns.select { |k| k =~ /date$/ }.first
269
+ end
270
+ end
271
+
272
+ def end_date_columns
273
+ @end_date_columns ||= assign_column_to_table do |_table, columns|
274
+ column = columns.select { |k| k =~ /end_date$/ }.first
275
+ column || columns.select { |k| k =~ /date$/ }.first
276
+ end
277
+ end
278
+
279
+ def start_date_column(query, domain)
280
+ start_date_columns.merge(person: person_date_of_birth(query))[domain]
281
+ end
282
+
283
+ def end_date_column(query, domain)
284
+ end_date_columns.merge(person: person_date_of_birth(query))[domain]
285
+ end
286
+
287
+ def source_value_columns
288
+ @source_value_columns ||= assign_column_to_table do |table, columns|
289
+ reggy = /#{table.to_s.split('_').first}_source_value$/
290
+ column = columns.select { |k| k =~ reggy }.first
291
+ column || columns.select { |k| k =~ /source_value$/ }.first
292
+ end
293
+ end
294
+
295
+ def source_vocabulary_ids
296
+ @source_vocabulary_ids = assign_column_to_table do |table, columns|
297
+ next if %w[person death].any? { |tn| table.to_s =~ /#{tn}/ }
298
+
299
+ reggy = /#{table.to_s.split('_').first}_source_vocabulary_id/
300
+ column = columns.select { |k| k =~ reggy }.first
301
+ column || columns.select { |k| k =~ /_source_vocabulary_id/ }.first
302
+ end
303
+ end
304
+
305
+ def id_columns
306
+ @id_columns ||= assign_column_to_table do |table, columns|
307
+ reggy = /#{table}_id/
308
+ columns.select { |k| k =~ reggy }.first
309
+ end
310
+ end
311
+
312
+ def id_column(table)
313
+ id_columns[table]
314
+ end
315
+
316
+ def type_concept_id_columns
317
+ @type_concept_id_columns ||= assign_column_to_table do |_table, columns|
318
+ columns.select { |c| c =~ /_type_concept_id$/ }.first
319
+ end
320
+ end
321
+
322
+ def type_concept_id_column(domain)
323
+ type_concept_id_columns[table_by_domain(domain)]
324
+ end
325
+
326
+ def table_by_domain(domain)
327
+ domain
328
+ end
329
+
330
+ def period_table
331
+ :observation_period
332
+ end
333
+
334
+ def source_value_column(table)
335
+ source_value_columns[table]
336
+ end
337
+
338
+ def code_provenance_type_column(_query, domain)
339
+ {
340
+ condition_occurrence: :condition_type_concept_id,
341
+ death: :death_type_concept_id,
342
+ drug_exposure: :drug_type_concept_id,
343
+ observation: :observation_type_concept_id,
344
+ procedure_occurrence: :procedure_type_concept_id
345
+ }[domain]
346
+ end
347
+
348
+ def file_provenance_type_column(_query, domain)
349
+ {
350
+ condition_occurrence: :condition_type_concept_id,
351
+ death: :death_type_concept_id,
352
+ drug_exposure: :drug_type_concept_id,
353
+ observation: :observation_type_concept_id,
354
+ procedure_occurrence: :procedure_type_concept_id
355
+ }[domain]
356
+ end
357
+
358
+ def provider_id_column(_query, domain)
359
+ {
360
+ condition_occurrence: :associated_provider_id,
361
+ death: :death_type_concept_id,
362
+ drug_exposure: :prescribing_provider_id,
363
+ observation: :associated_provider_id,
364
+ person: :provider_id,
365
+ procedure_occurrence: :associated_provider_id,
366
+ provider: :provider_id
367
+ }[domain]
368
+ end
369
+
370
+ def place_of_service_concept_id_column(_query, domain)
371
+ return nil if domain.nil?
372
+ return Sequel.cast(:visit_source_concept_id, :Bigint) if table_cols(domain).include?(pos_table_fk)
373
+
374
+ nil
375
+ end
376
+
377
+ def pos_table_fk
378
+ :visit_occurrence_id
379
+ end
380
+
381
+ def person_date_of_birth(_query)
382
+ rdbms.cast_date(ConceptQL::Utils.assemble_date(:year_of_birth, :month_of_birth, :day_of_birth,
383
+ database_type: nodifier.database_type))
384
+ end
385
+
386
+ def date_columns(query, table = nil)
387
+ sd = start_date_column(query, table)
388
+ sd = rdbms.cast_date(Sequel.expr(sd)).as(:start_date) unless sd == :start_date
389
+ ed = end_date_column(query, table)
390
+ unless ed == :end_date
391
+ ed = rdbms.cast_date(Sequel.function(:coalesce, Sequel.expr(ed),
392
+ start_date_column(query, table))).as(:end_date)
393
+ end
394
+ [ sd, ed ]
395
+ end
396
+
397
+ def table_to_sym(table)
398
+ case table
399
+ when Symbol
400
+ table = Sequel.split_symbol(table)[1].to_sym if Sequel.split_symbols?
401
+ when Sequel::SQL::AliasedExpression
402
+ table = table.expression
403
+ when Sequel::SQL::QualifiedIdentifier
404
+ table = table.column
405
+ when Sequel::SQL::Identifier
406
+ table = table.value
407
+ end
408
+ table.to_sym
409
+ end
410
+
411
+ def table_cols(table)
412
+ table = table_to_sym(table)
413
+ schema.fetch(table)[:columns].keys
414
+ end
415
+
416
+ def table_columns(*tables)
417
+ tables.map { |t| table_cols(t) }.flatten.tap do |o|
418
+ p o
419
+ p tables
420
+ end
421
+ end
422
+
423
+ def table_source_value(table)
424
+ source_value_columns.fetch(table_to_sym(table))
425
+ end
426
+
427
+ def source_vocabulary_id(table)
428
+ source_vocabulary_ids[table_to_sym(table)]
429
+ end
430
+
431
+ def table_is_missing?(db)
432
+ !(source_table_exists?(db, :observation_period) && source_table_exists?(db, :condition_occurrence))
433
+ end
434
+
435
+ def concept_id(table)
436
+ table_cols(table).find do |col|
437
+ col = col.to_s
438
+ next unless col =~ /concept_id/
439
+
440
+ table_parts = table.to_s.chomp("s").to_s.split("_")
441
+
442
+ possible_names = [ "concept_id" ]
443
+
444
+ until table_parts.empty?
445
+ possible_names.unshift([ table_parts.join("_"), possible_names.last ].join("_"))
446
+ table_parts.pop
447
+ end
448
+
449
+ possible_names.any? do |name|
450
+ col == name
451
+ end
452
+ end
453
+ end
454
+
455
+ def information_period_where_clause(arguments)
456
+ return if arguments.empty?
457
+
458
+ { plan_source_value: arguments }
459
+ end
460
+ end
461
+ end
462
+ end
@@ -0,0 +1,155 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+
5
+ module ConceptQL
6
+ module DataModel
7
+ class Gdm < Base
8
+ def query_modifier_for(column)
9
+ {
10
+ visit_source_concept_id: ConceptQL::QueryModifiers::Gdm::PoSQueryModifier,
11
+ provider_id: ConceptQL::QueryModifiers::Gdm::ProviderQueryModifier,
12
+ context_specialty_concept_id: ConceptQL::QueryModifiers::Gdm::ProviderQueryModifier,
13
+ drug_name: ConceptQL::QueryModifiers::Gdm::DrugQueryModifier,
14
+ admission_date: ConceptQL::QueryModifiers::Gdm::AdmissionDateQueryModifier,
15
+ provenance_type: ConceptQL::QueryModifiers::Gdm::ProvenanceQueryModifier,
16
+ value_as_number: ConceptQL::QueryModifiers::Gdm::LabQueryModifier
17
+ }[column]
18
+ end
19
+
20
+ def person_id(table = nil)
21
+ return :id if table == :patients
22
+
23
+ :patient_id
24
+ end
25
+
26
+ def person_table
27
+ :patients
28
+ end
29
+
30
+ def period_table
31
+ :information_periods
32
+ end
33
+
34
+ def criterion_id
35
+ Sequel.expr(:id).as(:criterion_id)
36
+ end
37
+
38
+ def table_id(table = nil)
39
+ return :criterion_id if table.nil?
40
+
41
+ Sequel.expr(make_table_id(table)).as(:criterion_id)
42
+ end
43
+
44
+ def make_table_id(_table)
45
+ :id
46
+ end
47
+
48
+ def fk_by_domain(domain)
49
+ table = table_by_domain(domain)
50
+ "#{table.to_s.gsub(/_id/, '').chomp('s')}_id".to_sym
51
+ end
52
+
53
+ def pk_by_domain(_domain)
54
+ :id
55
+ end
56
+
57
+ def base
58
+ :gdm
59
+ end
60
+
61
+ def name_concept_columns
62
+ {
63
+ clinical_codes: %i[clinical_code_concept_id],
64
+ drug_exposure_details: %i[dose_unit_concept_id],
65
+ measurement_details: %i[unit_concept_id result_as_concept_id],
66
+ admission_details: %i[admit_source_concept_id discharge_location_concept_id]
67
+ }
68
+ end
69
+
70
+ def table_by_domain(table)
71
+ return nil unless table
72
+
73
+ case table
74
+ when :person, :patients
75
+ :patients
76
+ when :death, :deaths
77
+ :deaths
78
+ when :observation_period, :information_periods
79
+ :information_periods
80
+ when :provider, :practitioners
81
+ :practitioners
82
+ when :collections
83
+ :collections
84
+ else
85
+ :clinical_codes
86
+ end
87
+ end
88
+
89
+ def table_to_domain(table)
90
+ {
91
+ patients: :person,
92
+ deaths: :death,
93
+ information_periods: :observation_period,
94
+ practitioners: :provider
95
+ }[table]
96
+ end
97
+
98
+ def data_model
99
+ :gdm
100
+ end
101
+
102
+ def start_date_column(_query, domain)
103
+ start_date_columns[domain]
104
+ end
105
+
106
+ def end_date_column(_query, domain)
107
+ end_date_columns[domain]
108
+ end
109
+
110
+ def id_column(_table)
111
+ :id
112
+ end
113
+
114
+ def pos_table_fk
115
+ :context_id
116
+ end
117
+
118
+ def source_vocabulary_ids
119
+ @source_vocabulary_ids = assign_column_to_table do |table, columns|
120
+ next if %w[patients death].any? { |tn| table.to_s =~ /#{tn}/ }
121
+
122
+ reggy = /#{table.to_s.split('_').first}_vocabulary_id/
123
+ column = columns.select { |k| k =~ reggy }.first
124
+ column || columns.select { |k| k =~ /_vocabulary_id/ }.first
125
+ end
126
+ end
127
+
128
+ def information_period_where_clause(arguments)
129
+ return if arguments.empty?
130
+
131
+ { information_type_concept_id: arguments.map(&:to_i) }
132
+ end
133
+
134
+ def code_provenance_type(_query, _domain)
135
+ :provenance_concept_id
136
+ end
137
+
138
+ def file_provenance_type(_query, _domain)
139
+ :source_type_concept_id
140
+ end
141
+
142
+ def file_provenance_types_vocab
143
+ %w[JIGSAW_FILE_PROVENANCE_TYPE JS_FILE_PROV_TYPE]
144
+ end
145
+
146
+ def code_provenance_types_vocab
147
+ %w[JIGSAW_CODE_PROVENANCE_TYPE JS_CODE_PROV_TYPE]
148
+ end
149
+
150
+ def table_is_missing?(db)
151
+ !(source_table_exists?(db, :patients) && source_table_exists?(db, :clinical_codes))
152
+ end
153
+ end
154
+ end
155
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "gdm"
4
+
5
+ module ConceptQL
6
+ module DataModel
7
+ class GdmWide < Gdm
8
+ def query_modifier_for(column)
9
+ {
10
+ visit_source_concept_id: ConceptQL::QueryModifiers::GdmWide::PoSQueryModifier,
11
+ provider_id: ConceptQL::QueryModifiers::Gdm::ProviderQueryModifier,
12
+ context_specialty_concept_id: ConceptQL::QueryModifiers::Gdm::ProviderQueryModifier,
13
+ drug_name: ConceptQL::QueryModifiers::GdmWide::DrugQueryModifier,
14
+ admission_date: ConceptQL::QueryModifiers::GdmWide::AdmissionDateQueryModifier,
15
+ provenance_type: ConceptQL::QueryModifiers::GdmWide::ProvenanceQueryModifier,
16
+ value_as_number: ConceptQL::QueryModifiers::GdmWide::LabQueryModifier
17
+ }[column]
18
+ end
19
+
20
+ def table_by_domain(table)
21
+ tab = super(table)
22
+ tab = :observations if %i[clinical_codes collections].include?(tab)
23
+ tab
24
+ end
25
+
26
+ def data_model
27
+ :gdm_wide
28
+ end
29
+
30
+ def name_concept_columns
31
+ {
32
+ observations: %i[clinical_code_concept_id dose_unit_concept_id unit_concept_id result_as_concept_id
33
+ admit_source_concept_id discharge_location_concept_id]
34
+ }
35
+ end
36
+
37
+ def table_is_missing?(db)
38
+ !source_table_exists?(db, :observations)
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+ module ConceptQL
5
+ module DataModel
6
+ class Omopv4Plus < Base
7
+ def base
8
+ :omop
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "data_model/gdm"
4
+ require_relative "data_model/gdm_wide"
5
+ require_relative "data_model/omopv4_plus"
6
+ require_relative "rdbms"
7
+
8
+ module ConceptQL
9
+ module DataModel
10
+ def self.get(data_model, opts = {})
11
+ operator = opts[:operator]
12
+ nodifier = opts[:nodifier]
13
+
14
+ case data_model
15
+ when :gdm
16
+ Gdm.new(operator, nodifier)
17
+ when :gdm_wide
18
+ GdmWide.new(operator, nodifier)
19
+ when :omopv4_plus
20
+ Omopv4Plus.new(operator, nodifier)
21
+ else
22
+ raise "No DataModel defined for #{data_model.inspect}"
23
+ end
24
+ end
25
+
26
+ def self.for(operator, nodifier)
27
+ get(nodifier.data_model, nodifier: nodifier, operator: operator)
28
+ end
29
+ end
30
+ end