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,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../query_modifier"
4
+
5
+ module ConceptQL
6
+ module QueryModifiers
7
+ module Gdm
8
+ class ProviderQueryModifier < QueryModifier
9
+ def self.provided_columns
10
+ %i[provider_id context_specialty_concept_id practitioner_specialty_concept_id role_type_concept_id]
11
+ end
12
+
13
+ def self.has_required_columns?(cols)
14
+ needed = %i[practitioner_id context_id].sort
15
+ found = needed | cols
16
+ !found.empty?
17
+ end
18
+
19
+ def modified_query
20
+ if dm.table_cols(source_table).include?(:context_id)
21
+ query.from_self(alias: :c)
22
+ .left_join(Sequel[:contexts_practitioners].as(:cp), Sequel[:c][:context_id] => Sequel[:cp][:context_id])
23
+ .left_join(Sequel[:practitioners].as(:p), Sequel[:p][:id] => Sequel[:cp][:practitioner_id])
24
+ .select_all(:c)
25
+ .select_append(
26
+ Sequel[:cp][:practitioner_id].as(:provider_id),
27
+ Sequel[:cp][:role_type_concept_id].as(:role_type_concept_id),
28
+ Sequel[:cp][:specialty_type_concept_id].as(:context_specialty_concept_id),
29
+ Sequel[:p][:specialty_concept_id].as(:practitioner_specialty_concept_id)
30
+ )
31
+ else
32
+ ds = query.select_all
33
+ ds = if ds.columns.include?(:provider_id)
34
+ ds
35
+ elsif dm.table_cols(source_table).include?(:practitioner_id)
36
+ ds.select_append(Sequel[:practitioner_id].as(:provider_id))
37
+ else
38
+ ds.select_append(Sequel.cast(nil, :Bigint).as(:provider_id))
39
+ end
40
+ %i[role_type_concept_id context_specialty_concept_id practitioner_specialty_concept_id].each do |col|
41
+ ds = if ds.columns.include?(col)
42
+ ds
43
+ else
44
+ ds.select_append(Sequel.cast(nil, :Bigint).as(col))
45
+ end
46
+ end
47
+ ds
48
+ end.from_self
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../query_modifier"
4
+
5
+ module ConceptQL
6
+ module QueryModifiers
7
+ module GdmWide
8
+ class AdmissionDateQueryModifier < QueryModifier
9
+ def self.provided_columns
10
+ %i[
11
+ admission_date
12
+ discharge_date
13
+ ]
14
+ end
15
+
16
+ def self.has_required_columns?(cols)
17
+ needed = %i[admit_admission_date admit_discharge_date].sort
18
+ found = needed & cols
19
+ needed == found
20
+ end
21
+
22
+ def modified_query
23
+ return query unless dm.table_cols(source_table).include?(:admit_admission_date)
24
+
25
+ query.from_self
26
+ .select_append(Sequel.function(:coalesce, :admit_admission_date, :start_date).as(:admission_date))
27
+ .select_append(Sequel.function(:coalesce, :admit_discharge_date, :end_date,
28
+ :start_date).as(:discharge_date))
29
+ .from_self
30
+ end
31
+
32
+ private
33
+
34
+ def domain
35
+ op.domain
36
+ rescue StandardError
37
+ nil
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../query_modifier"
4
+
5
+ module ConceptQL
6
+ module QueryModifiers
7
+ module GdmWide
8
+ class DrugQueryModifier < QueryModifier
9
+ def self.provided_columns
10
+ %i[
11
+ drug_name
12
+ drug_amount
13
+ drug_amount_units
14
+ drug_quantity
15
+ drug_strength_source_value
16
+ drug_days_supply
17
+ ]
18
+ end
19
+
20
+ def self.has_required_columns?(cols)
21
+ needed = [ :dose_unit_concept_id ]
22
+ found = needed & cols
23
+ needed == found
24
+ end
25
+
26
+ def modified_query
27
+ return query unless dm.table_cols(source_table).include?(:dose_unit_concept_id)
28
+
29
+ query.from_self(alias: :cc)
30
+ .left_join(dm.name_concepts_table(query.db).as(:dose_con), Sequel[:cc][:dose_unit_concept_id] => Sequel[:dose_con][:id])
31
+ .left_join(dm.name_concepts_table(query.db).as(:ing_con), Sequel[:cc][:clinical_code_concept_id] => Sequel[:ing_con][:id])
32
+ .select_all(:cc)
33
+ .select_append(Sequel[:cc][:dose_value].as(:drug_amount))
34
+ .select_append(Sequel[:dose_con][:concept_text].as(:drug_amount_units))
35
+ .select_append(Sequel[:ing_con][:concept_text].as(:drug_name))
36
+ .select_append(Sequel[:cc][:days_supply].as(:drug_days_supply))
37
+ .select_append(Sequel[:cc][:strength_source_value].as(:drug_strength_source_value))
38
+ .select_append(Sequel[:cc][:quantity].as(:drug_quantity))
39
+ .from_self
40
+ end
41
+
42
+ private
43
+
44
+ def domain
45
+ op.domain
46
+ rescue StandardError
47
+ nil
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../query_modifier"
4
+
5
+ module ConceptQL
6
+ module QueryModifiers
7
+ module GdmWide
8
+ class LabQueryModifier < QueryModifier
9
+ def self.provided_columns
10
+ %i[
11
+ value_as_number
12
+ value_as_string
13
+ value_as_concept_id
14
+ value_as_concept
15
+ unit_concept_id
16
+ unit_concept
17
+ range_low
18
+ range_high
19
+ ]
20
+ end
21
+
22
+ def self.has_required_columns?(cols)
23
+ needed = [ :unit_concept_id ]
24
+ found = needed & cols
25
+ needed == found
26
+ end
27
+
28
+ def modified_query
29
+ return query unless dm.table_cols(source_table).include?(:unit_concept_id)
30
+
31
+ query.from_self(alias: :cc)
32
+ .left_join(dm.name_concepts_table(query.db).as(:unit_con), Sequel[:cc][:unit_concept_id] => Sequel[:unit_con][:id])
33
+ .left_join(dm.name_concepts_table(query.db).as(:concept_con), Sequel[:cc][:result_as_concept_id] => Sequel[:concept_con][:id])
34
+ .select_all(:cc)
35
+ .select_append(Sequel[:cc][:result_as_number].as(:value_as_number))
36
+ .select_append(Sequel[:cc][:result_as_string].as(:value_as_string))
37
+ .select_append(Sequel[:cc][:result_as_concept_id].as(:value_as_concept_id))
38
+ .select_append(Sequel[:concept_con][:concept_code].as(:value_as_concept))
39
+ .select_append(Sequel[:unit_con][:concept_code].as(:unit_concept))
40
+ .select_append(Sequel[:cc][:normal_range_low].as(:range_low))
41
+ .select_append(Sequel[:cc][:normal_range_high].as(:range_high))
42
+ .from_self
43
+ end
44
+
45
+ private
46
+
47
+ def domain
48
+ op.domain
49
+ rescue StandardError
50
+ nil
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../query_modifier"
4
+
5
+ module ConceptQL
6
+ module QueryModifiers
7
+ module GdmWide
8
+ class PoSQueryModifier < QueryModifier
9
+ attr_reader :source_column, :column
10
+
11
+ def self.provided_columns
12
+ [ :visit_source_concept_id ]
13
+ end
14
+
15
+ def self.has_required_columns?(cols)
16
+ needed = [ :context_id ]
17
+ found = needed & cols
18
+ needed == found
19
+ end
20
+
21
+ def initialize(*args)
22
+ super
23
+ @column = :visit_source_concept_id
24
+ @source_column = :pos_concept_id
25
+ end
26
+
27
+ def modified_query
28
+ return query unless dm.table_cols(source_table).include?(:context_id)
29
+
30
+ query.from_self
31
+ .select_append(Sequel[source_column].as(column))
32
+ .from_self
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../query_modifier"
4
+
5
+ module ConceptQL
6
+ module QueryModifiers
7
+ module GdmWide
8
+ class ProvenanceQueryModifier < QueryModifier
9
+ RELATED_COLUMNS = %w[
10
+ provenance_concept_id
11
+ source_type_concept_id
12
+ ].sort.map(&:to_sym)
13
+
14
+ attr_reader :db
15
+
16
+ def self.provided_columns
17
+ %i[
18
+ file_provenance_type
19
+ code_provenance_type
20
+ ]
21
+ end
22
+
23
+ def self.has_required_columns?(cols)
24
+ !(RELATED_COLUMNS & cols).empty?
25
+ end
26
+
27
+ def modified_query
28
+ return query unless self.class.has_required_columns?(dm.table_cols(source_table))
29
+
30
+ query.from_self
31
+ .select_append(Sequel[:source_type_concept_id].as(:file_provenance_type),
32
+ Sequel[:provenance_concept_id].as(:code_provenance_type))
33
+ .from_self
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../query_modifier"
4
+
5
+ module ConceptQL
6
+ module QueryModifiers
7
+ module Omopv4Plus
8
+ class AdmissionDateQueryModifier < QueryModifier
9
+ def self.provided_columns
10
+ %i[
11
+ admission_date
12
+ discharge_date
13
+ ]
14
+ end
15
+
16
+ def self.has_required_columns?(cols)
17
+ cols.map(&:to_s).any? { |col| col =~ /start_date/i }
18
+ end
19
+
20
+ def modified_query
21
+ cols = dm.table_cols(source_table).map(&:to_s)
22
+ return query unless cols.any? { |col| col =~ /start_date/i }
23
+
24
+ start_col = cols.find { |col| col =~ /start_date/i }.to_sym
25
+ end_col = cols.find { |col| col =~ /end_date/i }.to_sym
26
+
27
+ query.from_self(alias: :t)
28
+ .select_all(:t)
29
+ .select_append(Sequel[:t][start_col].as(:admission_date))
30
+ .select_append(Sequel.function(:coalesce, Sequel[:t][end_col],
31
+ Sequel[:t][start_col]).as(:discharge_date))
32
+ .from_self
33
+ end
34
+
35
+ private
36
+
37
+ def domain
38
+ op.domain
39
+ rescue StandardError
40
+ nil
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../query_modifier"
4
+
5
+ module ConceptQL
6
+ module QueryModifiers
7
+ module Omopv4Plus
8
+ class DrugQueryModifier < QueryModifier
9
+ attr_reader :db
10
+
11
+ def self.provided_columns
12
+ %i[
13
+ drug_name
14
+ drug_amount
15
+ drug_amount_units
16
+ drug_quantity
17
+ drug_days_supply
18
+ ]
19
+ end
20
+
21
+ def self.has_required_columns?(cols)
22
+ needed = [ :drug_concept_id ]
23
+ found = needed & cols
24
+ needed == found
25
+ end
26
+
27
+ def initialize(*args)
28
+ super
29
+ @db = query.db
30
+ end
31
+
32
+ def modified_query
33
+ return query unless dm.table_cols(source_table).include?(:drug_concept_id)
34
+
35
+ query.db
36
+ drug_concept_ids = query.select(:drug_concept_id).distinct
37
+ query.from_self(alias: :de)
38
+ .left_join(micro_table(drug_concept_ids).as(:mt), drug_concept_id: :drug_concept_id)
39
+ .select_all(:de)
40
+ .select_append(Sequel[:de][:quantity].as(:drug_quantity))
41
+ .select_append(Sequel[:de][:days_supply].as(:drug_days_supply))
42
+ .select_append(Sequel[:mt][:amount_value].as(:drug_amount))
43
+ .select_append(Sequel[:mt][:amount_unit].as(:drug_amount_units))
44
+ .select_append(Sequel[:mt][:drug_name].as(:drug_name))
45
+ .from_self
46
+ end
47
+
48
+ private
49
+
50
+ def micro_table(drug_concept_ids)
51
+ # TODO: Does drug_strength only have RXNORM concept_ids?
52
+ # TODO: What is vocabulary for units? Can we shrink concept table to just that vocab before joining?
53
+ collapsed_strengths = db[:drug_strength]
54
+ .where(drug_concept_id: drug_concept_ids)
55
+ .select_group(:drug_concept_id)
56
+ .select_append(
57
+ Sequel.function(:min, :amount_value).as(:amount_value),
58
+ Sequel.function(:min, :amount_unit).as(:amount_unit),
59
+ Sequel.function(:count, 1).as(:dcount)
60
+ )
61
+
62
+ value_case = Sequel.case({ 1 => :amount_value }, nil, :dcount).as(:amount_value)
63
+ unit_case = Sequel.case({ 1 => :amount_unit }, nil, :dcount).as(:amount_unit)
64
+
65
+ collapsed_strengths = collapsed_strengths.from_self.select(:drug_concept_id, value_case, unit_case)
66
+
67
+ db.from(Sequel[:concept].as(:dc))
68
+ .left_join(collapsed_strengths, { drug_concept_id: :concept_id }, { table_alias: :ds })
69
+ .where(Sequel[:dc][:concept_id] => drug_concept_ids)
70
+ .select(
71
+ Sequel[:dc][:concept_id].as(:drug_concept_id),
72
+ Sequel[:dc][:concept_name].as(:drug_name),
73
+ Sequel[:ds][:amount_value].as(:amount_value),
74
+ Sequel[:ds][:amount_unit].as(:amount_unit)
75
+ )
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../query_modifier"
4
+
5
+ module ConceptQL
6
+ module QueryModifiers
7
+ module Omopv4Plus
8
+ class PoSQueryModifier < QueryModifier
9
+ attr_reader :join_id, :table, :source_column, :column
10
+
11
+ def self.provided_columns
12
+ [ :visit_source_concept_id ]
13
+ end
14
+
15
+ def self.has_required_columns?(cols)
16
+ needed = [ :visit_occurrence_id ]
17
+ found = needed & cols
18
+ needed == found
19
+ end
20
+
21
+ def initialize(*args)
22
+ super
23
+ @column = :visit_source_concept_id
24
+ @join_id = :visit_occurrence_id
25
+ @table = :visit_occurrence
26
+ @source_column = :visit_source_concept_id
27
+ end
28
+
29
+ def modified_query
30
+ return query unless dm.table_cols(source_table).include?(join_id)
31
+ return query if dm.table_cols(source_table).include?(column)
32
+
33
+ left_alias = "tab1".to_sym
34
+ right_alias = "tab2".to_sym
35
+
36
+ extra_table = query.db.from(table).select(Sequel.as(source_column, column), join_id)
37
+ ds = query.from_self(alias: left_alias)
38
+ ds.left_join(extra_table.as(right_alias),
39
+ Sequel.qualify(left_alias, join_id) => Sequel.qualify(right_alias, join_id))
40
+ .select_all(left_alias)
41
+ .select_append(Sequel.qualify(right_alias, column).as(column))
42
+ .from_self
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../query_modifier"
4
+
5
+ module ConceptQL
6
+ module QueryModifiers
7
+ module Omopv4Plus
8
+ class ProvenanceQueryModifier < QueryModifier
9
+ RELATED_COLUMNS = %w[
10
+ condition_type_concept_id
11
+ procedure_type_concept_id
12
+ death_type_concept_id
13
+ observation_type_concept_id
14
+ drug_type_concept_id
15
+ ].sort.map(&:to_sym)
16
+
17
+ attr_reader :db
18
+
19
+ def self.provided_columns
20
+ %i[
21
+ file_provenance_type
22
+ code_provenance_type
23
+ ]
24
+ end
25
+
26
+ def self.has_required_columns?(cols)
27
+ !(RELATED_COLUMNS & cols).empty?
28
+ end
29
+
30
+ def initialize(*args)
31
+ super
32
+ @db = query.db
33
+ end
34
+
35
+ def modified_query
36
+ return query unless self.class.has_required_columns?(dm.table_cols(source_table))
37
+
38
+ query.select_append(Sequel[dm.file_provenance_type_column(query, source_table)].as(:file_provenance_type),
39
+ Sequel[dm.code_provenance_type_column(query,
40
+ source_table)].as(:code_provenance_type)).from_self
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../query_modifier"
4
+
5
+ module ConceptQL
6
+ module QueryModifiers
7
+ module Omopv4Plus
8
+ class ProviderQueryModifier < QueryModifier
9
+ def self.provided_columns
10
+ [ :provider_id ]
11
+ end
12
+
13
+ def self.has_required_columns?(cols)
14
+ cols.find { |col| col.to_s =~ /provider_id/ }
15
+ end
16
+
17
+ def modified_query
18
+ col = self.class.has_required_columns?(dm.table_cols(source_table))
19
+ return query if col.nil? || col.to_sym == :provider_id
20
+
21
+ query.select_append(Sequel[col].as(:provider_id)).from_self
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ConceptQL
4
+ module QueryModifiers
5
+ class QueryModifier
6
+ attr_reader :query, :op, :source_table, :dm
7
+
8
+ def initialize(query, op, table, dm)
9
+ @query = query
10
+ @op = op
11
+ @source_table = table
12
+ @dm = dm
13
+ end
14
+
15
+ def modified_query
16
+ raise NotImplementedError
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "generic"
4
+
5
+ module ConceptQL
6
+ module Rdbms
7
+ class DuckDB < Generic
8
+ # Returns from - to (see Generic#datediff). DuckDB's
9
+ # datediff(part, startdate, enddate) returns enddate - startdate, so the
10
+ # arguments go in reversed.
11
+ def datediff(from, to)
12
+ Sequel.function(:datediff, "day", to, from)
13
+ end
14
+
15
+ def preferred_formatter
16
+ SqlFormatters::PG_FORMAT
17
+ end
18
+
19
+ # DuckDB accepts WITH ... AS MATERIALIZED since v0.8.0.
20
+ def supports_materialized?
21
+ true
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,120 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ConceptQL
4
+ module Rdbms
5
+ class Generic
6
+ def process(column, value = nil)
7
+ type = Scope::COLUMN_TYPES.fetch(column)
8
+ new_column = case type
9
+ when String, :String
10
+ Sequel.cast_string(value)
11
+ when Date, :Date
12
+ cast_date(value)
13
+ when Float, :Bigint, :Float
14
+ Sequel.cast_numeric(value, type)
15
+ else
16
+ raise "Unexpected type: '#{type.inspect}' for column: '#{column}'"
17
+ end
18
+ new_column.as(column)
19
+ end
20
+
21
+ def semi_join(ds, table, *exprs)
22
+ ds = Sequel[ds] if ds.is_a?(Symbol)
23
+ table = Sequel[table] if table.is_a?(Symbol)
24
+ expr = exprs.inject(&:&)
25
+ ds.from_self(alias: :l).where(ds.db[table.as(:r)]
26
+ .select(1)
27
+ .where(expr)
28
+ .exists).from_self
29
+ end
30
+
31
+ # True when lhs and rhs are equal or both are NULL. IS NOT DISTINCT
32
+ # FROM is standard SQL, and Postgres and DuckDB support it. Spark
33
+ # overrides this with its <=> operator.
34
+ def null_safe_equal(lhs, rhs)
35
+ Sequel.lit("(? IS NOT DISTINCT FROM ?)", lhs, rhs)
36
+ end
37
+
38
+ def cast_date(date)
39
+ Sequel.cast(date, Date)
40
+ end
41
+
42
+ # The number of days from from_column to to_column: to_column - from_column.
43
+ # Note that datediff takes its arguments in the opposite order.
44
+ def days_between(from_column, to_column)
45
+ cast_date(to_column) - cast_date(from_column)
46
+ end
47
+
48
+ def uuid
49
+ uuid_items
50
+ .zip([ Sequel.cast_string("/") ] * (uuid_items.length - 1))
51
+ .flatten
52
+ .compact
53
+ .inject(:+)
54
+ end
55
+
56
+ def uuid_items
57
+ %w[person_id criterion_id criterion_table start_date end_date].map do |column|
58
+ Sequel.cast_string(column.to_sym)
59
+ end
60
+ end
61
+
62
+ def preferred_formatter
63
+ SqlFormatters::SQL_FORMATTER
64
+ end
65
+
66
+ # Returns from - to in days, NOT to - from, so datediff(later, earlier)
67
+ # is positive. Every adapter keeps this order. This default emits
68
+ # Spark's datediff(endDate, startDate), which returns endDate - startDate,
69
+ # and Spark uses it as is. days_between(from, to) returns to - from.
70
+ def datediff(from, to)
71
+ Sequel.function(:datediff, from, to)
72
+ end
73
+
74
+ def analyze_temp_tables?
75
+ false
76
+ end
77
+
78
+ def explain_temp_tables?
79
+ false
80
+ end
81
+
82
+ def supports_materialized?
83
+ false
84
+ end
85
+
86
+ def create_options(_scope, _ds)
87
+ opts = {}
88
+ opts[:analyze] = opts[:explain] = explain_temp_tables?
89
+ opts
90
+ end
91
+
92
+ def drop_options
93
+ {}
94
+ end
95
+
96
+ def post_create(db, table_name); end
97
+
98
+ # One primary clinical code per collection: the row with the lowest
99
+ # concept id. ROW_NUMBER() works on every supported RDBMS. Postgres
100
+ # overrides this with DISTINCT ON.
101
+ def primary_concepts(db, all_primary_ids)
102
+ db[Sequel[:clinical_codes].as(:pcc)]
103
+ .where(provenance_concept_id: all_primary_ids)
104
+ .select(
105
+ Sequel[:pcc][:collection_id].as(:collection_id),
106
+ Sequel[:pcc][:clinical_code_concept_id].as(:concept_id),
107
+ Sequel[:pcc][:clinical_code_source_value].as(:concept_code),
108
+ Sequel[:pcc][:clinical_code_vocabulary_id].as(:vocabulary_id),
109
+ Sequel[true].as(:is_primary)
110
+ )
111
+ .select_append(Sequel[:ROW_NUMBER].function.over(
112
+ partition: :collection_id,
113
+ order: [ Sequel[:pcc][:collection_id], Sequel[:pcc][:clinical_code_concept_id] ]
114
+ ).as(:nummy))
115
+ .from_self
116
+ .where(nummy: 1)
117
+ end
118
+ end
119
+ end
120
+ end