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
@@ -1,35 +1,41 @@
1
- require_relative 'operator'
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "operator"
2
4
 
3
5
  module ConceptQL
4
6
  module Operators
5
7
  # Parent class of all casting operators
6
8
  #
7
9
  # Subclasses must implement the following methods:
8
- # - my_type
10
+ # - my_domain
9
11
  # - i_point_at
10
12
  # - these_point_at_me
11
13
  #
12
- # i_point_at returns a list of types for which the operator's table of origin
14
+ # i_point_at returns a list of domains for which the operator's table of origin
13
15
  # has foreign_keys pointing to another table, e.g.:
14
16
  # procedure_occurrence has an FK to visit_occurrence, so we'd put
15
17
  # :visit_occurrence in the i_point_at array
16
18
  #
17
- # these_point_at_me is a list of types for which that type's table
19
+ # these_point_at_me is a list of domains for which that domain's table
18
20
  # of origin has a FK pointing to the current operator's
19
21
  # table of origin, e.g.:
20
22
  # procedure_cost has an FK to procedure_occurrence so we'd
21
23
  # put :procedure_cost in procedure_occurrence's these_point_at_me array
22
24
  #
23
25
  # Also, if a casting operator is passed no streams, it will return all the
24
- # rows in its table as results.
26
+ # rows in its table as records.
25
27
  class CastingOperator < Operator
26
- category 'Casting'
27
- def types
28
- [type]
28
+ category "Get Related Data"
29
+ basic_type :cast
30
+ validate_at_most_one_upstream
31
+ validate_no_arguments
32
+
33
+ def domains(_db)
34
+ [ domain ]
29
35
  end
30
36
 
31
- def type
32
- my_type
37
+ def domain
38
+ my_domain
33
39
  end
34
40
 
35
41
  def castables
@@ -37,57 +43,66 @@ module ConceptQL
37
43
  end
38
44
 
39
45
  def query(db)
40
- return db.from(make_table_name(my_type)) if stream.nil?
46
+ return db.from(table) if stream.nil?
47
+
41
48
  base_query(db, stream.evaluate(db))
42
49
  end
43
50
 
51
+ def table
52
+ dm.table_by_domain(my_domain)
53
+ end
54
+
55
+ def make_table_name(domain)
56
+ super(dm.table_by_domain(domain))
57
+ end
58
+
44
59
  private
45
60
 
46
61
  def base_query(db, stream_query)
47
- uncastable_types = stream.types - castables
48
- to_me_types = stream.types & these_point_at_me
49
- from_me_types = stream.types & i_point_at
62
+ uncastable_domains = stream.domains(db) - castables
63
+ to_me_domains = stream.domains(db) & these_point_at_me
64
+ from_me_domains = stream.domains(db) & i_point_at
50
65
 
51
- destination_table = make_table_name(my_type)
66
+ destination_table = make_table_name(my_domain)
52
67
  casting_query = db.from(destination_table)
53
68
  wheres = []
54
69
 
55
- unless uncastable_types.empty?
70
+ unless uncastable_domains.empty?
56
71
  # We have a situation where one or more of the incoming streams
57
72
  # isn't castable so we'll just return all rows for
58
73
  # all people in each uncastable stream
59
74
  uncastable_person_ids = db.from(stream_query)
60
- .where(criterion_type: uncastable_types.map(&:to_s))
61
- .select_group(:person_id)
62
- wheres << Sequel.expr(person_id: uncastable_person_ids)
75
+ .where(criterion_domain: uncastable_domains.map(&:to_s))
76
+ .select_group(:person_id)
77
+ wheres << Sequel.expr(dm.person_id(table) => uncastable_person_ids)
63
78
  end
64
79
 
65
- destination_type_id = make_type_id(my_type)
80
+ destination_domain_id = dm.make_table_id(my_domain)
66
81
 
67
- unless to_me_types.empty?
68
- # For each castable type in the stream, setup a query that
69
- # casts each type to a set of IDs, union those IDs and fetch
82
+ unless to_me_domains.empty?
83
+ # For each castable domain in the stream, setup a query that
84
+ # casts each domain to a set of IDs, union those IDs and fetch
70
85
  # them from the source table
71
- castable_type_query = to_me_types.map do |source_type|
86
+ castable_domain_query = to_me_domains.map do |source_domain|
72
87
  source_ids = db.from(stream_query)
73
- .where(criterion_type: source_type.to_s)
74
- .select_group(:criterion_id)
75
- source_table = make_table_name(source_type)
76
- source_type_id = make_type_id(source_type)
88
+ .where(criterion_domain: source_domain.to_s)
89
+ .select_group(:criterion_id)
90
+ source_table = make_table_name(source_domain)
91
+ source_domain_id = dm.make_fk_id(source_domain)
77
92
 
78
93
  db.from(source_table)
79
- .where(source_type_id => source_ids)
80
- .select(destination_type_id)
94
+ .where(source_domain_id => source_ids)
95
+ .select(destination_domain_id)
81
96
  end.inject do |union_query, q|
82
- union_query.union(q)
97
+ union_query.union(q, all: true)
83
98
  end
84
- wheres << Sequel.expr(destination_type_id => castable_type_query)
99
+ wheres << Sequel.expr(destination_domain_id => castable_domain_query)
85
100
  end
86
101
 
87
- unless from_me_types.empty?
88
- from_me_types.each do |from_me_type|
89
- fk_type_id = make_type_id(from_me_type)
90
- wheres << Sequel.expr(fk_type_id => db.from(stream_query).where(criterion_type: from_me_type.to_s).select_group(:criterion_id))
102
+ unless from_me_domains.empty?
103
+ from_me_domains.each do |from_me_domain|
104
+ fk_domain_id = dm.fk_by_domain(from_me_domain)
105
+ wheres << Sequel.expr(fk_domain_id => db.from(stream_query).where(criterion_domain: from_me_domain.to_s).select_group(:criterion_id))
91
106
  end
92
107
  end
93
108
 
@@ -0,0 +1,111 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "pass_thru"
4
+
5
+ module ConceptQL
6
+ module Operators
7
+ class CoReported < PassThru
8
+ register __FILE__
9
+
10
+ def events_per_patient
11
+ upstreams.all? { |u| u.events_per_patient == :single } ? :single : :multiple
12
+ end
13
+
14
+ def multiple_vocabularies
15
+ return true if upstreams.any?(&:multiple_vocabularies)
16
+
17
+ vocabularies.length > 1
18
+ end
19
+
20
+ desc "Passes along all events that were co-reported in the same record in the source data."
21
+
22
+ allows_many_upstreams
23
+ category "Combine Streams"
24
+ default_query_columns
25
+ validate_at_least_one_upstream
26
+ validate_no_arguments
27
+ require_column :context_id
28
+
29
+ def query(db)
30
+ if gdm?
31
+ gdm(db)
32
+ else
33
+ events_with_common_visits(db).inject { |q, query| q.union(query, all: true) }
34
+ end
35
+ end
36
+
37
+ def gdm(db)
38
+ contexteds = upstreams.map do |stream|
39
+ [ stream.cte_name(:contextified), contextify(db, stream) ]
40
+ end.to_h
41
+
42
+ # Get all context_id's that are in all streams and do not share the same critierion_id
43
+ first, *rest = *contexteds.keys
44
+ shared_context_ids = rest.inject(db[first].select(:context_id)) do |q, next_cte|
45
+ q.intersect(db[next_cte].select(:context_id))
46
+ end
47
+ # shared_context_ids = rest.inject(first.from_self(alias: :first)) do |q, shared_event|
48
+ # q.join(shared_event.select(:context_id, :criterion_id), context_id: :context_id) do |a,b|
49
+ # ~(Sequel.qualify(a,:criterion_id) =~ Sequel.qualify(b,:criterion_id) )
50
+ # end.select(Sequel[:first][:context_id]).distinct
51
+ # end.from_self
52
+
53
+ if ConceptQL.avoid_ctes?
54
+ context_id_ds = shared_context_ids
55
+ else
56
+ name = cte_name(:shared_context_ids)
57
+ context_id_ds = db[name]
58
+ end
59
+
60
+ shared_events = contexteds.keys.map do |contexted|
61
+ db[contexted].where(context_id: context_id_ds).select(*query_cols)
62
+ end
63
+
64
+ ds = shared_events.inject do |q, shared_event|
65
+ q.union(shared_event)
66
+ end
67
+
68
+ contexteds.each do |name, query|
69
+ ds = ds.with(name, query)
70
+ end
71
+ if name
72
+ opts = {}
73
+ opts[:materialized] = true if rdbms.supports_materialized?
74
+ ds = ds.with(name, shared_context_ids, opts)
75
+ end
76
+
77
+ ds
78
+ end
79
+
80
+ def contextify(db, stream)
81
+ stream.evaluate(db).from_self(alias: :s)
82
+ # .join(Sequel[:clinical_codes].as(:c), id: :criterion_id)
83
+ # .select_all(:s)
84
+ # .select_append(Sequel[:c][:context_id].as(:context_id))
85
+ # .from_self
86
+ end
87
+
88
+ private
89
+
90
+ def visit_occurrence_ids_in_common(db)
91
+ @visit_occurrence_ids_in_common ||= upstream_queries(db).map do |q|
92
+ q.select(:visit_occurrence_id)
93
+ end.inject do |q, query|
94
+ q.from_self(alias: :tab1)
95
+ .join(query.as(:tab2), visit_occurrence_id: :visit_occurrence_id)
96
+ .select(Sequel[:tab1][:visit_occurrence_id].as(:visit_occurrence_id))
97
+ end
98
+ end
99
+
100
+ def events_with_common_visits(db)
101
+ upstream_queries(db).map { |q| q.where(visit_occurrence_id: visit_occurrence_ids_in_common(db)).from_self }
102
+ end
103
+
104
+ def upstream_queries(db)
105
+ @upstream_queries ||= upstreams.map do |expression|
106
+ expression.evaluate(db).select(*query_cols).from_self
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "operator"
4
+ require_relative "../date_adjuster"
5
+
6
+ module ConceptQL
7
+ module Operators
8
+ class ConcurrentWithin < Operator
9
+ register __FILE__
10
+
11
+ def events_per_patient
12
+ upstreams.all? { |u| u.events_per_patient == :single } ? :single : :multiple
13
+ end
14
+
15
+ def multiple_vocabularies
16
+ return true if upstreams.any?(&:multiple_vocabularies)
17
+
18
+ vocabularies.length > 1
19
+ end
20
+
21
+ desc "Filters each upstream to only include records where other upstreams' date ranges overlap"
22
+ option :start, type: :string
23
+ option :end, type: :string
24
+ allows_many_upstreams
25
+ validate_at_least_one_upstream
26
+ validate_no_arguments
27
+ validate_option DateAdjuster::VALID_INPUT, :start, :end
28
+ category "Combine Streams"
29
+ basic_type :set
30
+ default_query_columns
31
+
32
+ def query(db)
33
+ datasets = upstreams.map do |stream|
34
+ stream.evaluate(db)
35
+ end
36
+
37
+ return datasets.first.from_self if datasets.length == 1
38
+
39
+ adjusted_start_date = DateAdjuster.new(self, options[:start]).adjust(Sequel[:l][:start_date], true)
40
+ adjusted_end_date = DateAdjuster.new(self, options[:end]).adjust(Sequel[:l][:end_date])
41
+
42
+ datasets = datasets.map do |ds|
43
+ matching = ds.from_self(alias: :l)
44
+
45
+ (datasets - [ ds ]).each do |other|
46
+ other = other
47
+ .from_self(alias: :r)
48
+ .where(adjusted_start_date <= Sequel[:r][:start_date])
49
+ .where(adjusted_end_date >= Sequel[:r][:end_date])
50
+ .select(*matching_columns)
51
+
52
+ matching = matching.where(other.where(matching_columns.map do |x|
53
+ [ Sequel.qualify(:l, x), Sequel.qualify(:r, x) ]
54
+ end).exists)
55
+ end
56
+
57
+ matching
58
+ end
59
+
60
+ ds, *rest = datasets
61
+ rest.each do |other|
62
+ ds = ds.union(other, from_self: nil)
63
+ end
64
+
65
+ ds.from_self
66
+ end
67
+ end
68
+ end
69
+ end
@@ -1,16 +1,20 @@
1
- require_relative 'temporal_operator'
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "temporal_operator"
2
4
 
3
5
  module ConceptQL
4
6
  module Operators
5
7
  class Contains < TemporalOperator
6
- desc <<-EOF
7
- Any result in the LHR whose start_date is on or before and whose end_date is on or after a result from the RHR.
8
- L--X-L
9
- R-----R
10
- L------Y--------L
11
- EOF
8
+ register __FILE__
9
+
10
+ desc "For each person, passes along left hand records with a start_date and end_date containing a right hand record's start_date and end_date."
11
+
12
12
  def where_clause
13
- [Proc.new { l__start_date <= r__start_date}, Proc.new { r__end_date <= l__end_date }]
13
+ (within_start <= r_start_date) & (r_end_date <= within_end)
14
+ end
15
+
16
+ def within_source_table
17
+ Sequel[:l]
14
18
  end
15
19
  end
16
20
  end
@@ -1,26 +1,34 @@
1
- require_relative 'pass_thru'
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "pass_thru"
2
4
 
3
5
  module ConceptQL
4
6
  module Operators
5
7
  class Count < PassThru
6
- desc 'Counts the number of results the exactly match across all columns.'
8
+ register __FILE__
9
+
10
+ desc "Counts the number of records that exactly match across all columns."
7
11
  allows_one_upstream
12
+ validate_one_upstream
13
+ validate_no_arguments
14
+ require_column :value_as_number
15
+
16
+ def query_cols
17
+ dynamic_columns - [ :value_as_number ] + [ :value_as_number ]
18
+ end
8
19
 
9
20
  def query(db)
10
21
  db.from(unioned(db))
11
- .group(*COLUMNS)
12
- .select(*(COLUMNS - [:value_as_number]))
13
- .select_append{count(1).as(:value_as_number)}
22
+ .select_group(*(query_cols - [ :value_as_number ]))
23
+ .select_append { count(1).as(:value_as_number) }
14
24
  .from_self
15
25
  end
16
26
 
17
27
  def unioned(db)
18
- upstreams.map { |c| c.evaluate(db) }.inject do |uni, q|
19
- uni.union(q)
28
+ upstreams.map { |c| c.evaluate(db).select(*query_cols) }.inject do |uni, q|
29
+ uni.union(q.from_self)
20
30
  end
21
31
  end
22
32
  end
23
33
  end
24
34
  end
25
-
26
-
@@ -1,4 +1,7 @@
1
- require_relative 'operator'
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "operator"
4
+ require "date"
2
5
 
3
6
  module ConceptQL
4
7
  module Operators
@@ -8,23 +11,43 @@ module ConceptQL
8
11
  # 'START' represents the first date of data in the data source,
9
12
  # 'END' represents the last date of data in the data source,
10
13
  class DateRange < Operator
11
- desc 'Used to represent a date literal.'
14
+ register __FILE__
15
+
16
+ include ConceptQL::Behaviors::Windowable
17
+ include ConceptQL::Behaviors::Timeless
18
+
19
+ DATE_FORMAT = /\A#{Regexp.union([ /START/i, /END/i, /\d{4}-\d{2}-\d{2}/ ])}\z/.freeze
20
+
21
+ desc "Creates a set of all person records with the given start_date and end_date."
12
22
  option :start, type: :string
13
23
  option :end, type: :string
14
- category %(Temporal Manipulation)
24
+ category "Select by Property"
25
+ basic_type :selection
26
+ validate_no_upstreams
27
+ validate_no_arguments
28
+ validate_option DATE_FORMAT, :start, :end
29
+ validate_required_options :start, :end
15
30
 
16
31
  def query(db)
17
- db.from(:person)
18
- .select_append(Sequel.cast_string('person').as(:criterion_type))
19
- .select_append(Sequel.expr(:person_id).as(:criterion_id))
20
- .select_append(start_date(db).as(:start_date), end_date(db).as(:end_date)).from_self
32
+ replace = {
33
+ start_date: start_date(db),
34
+ end_date: end_date(db)
35
+ }
36
+ db.from(source_table)
37
+ .select(*dm.columns(table: source_table, replace: replace))
38
+ .from_self
21
39
  end
22
40
 
23
- def types
24
- [:person]
41
+ def domains(_db)
42
+ [ :person ]
25
43
  end
26
44
 
27
45
  private
46
+
47
+ def source_table
48
+ dm.table_by_domain(:person)
49
+ end
50
+
28
51
  def start_date(db)
29
52
  date_from(db, options[:start])
30
53
  end
@@ -36,12 +59,23 @@ module ConceptQL
36
59
  # TODO: Select the earliest and latest dates of observation from
37
60
  # the proper CDM table to represent the start and end of data
38
61
  def date_from(db, str)
39
- return db.from(:observation_period).select { min(:observation_period_start_date) } if str.upcase == 'START'
40
- return db.from(:observation_period).select { max(:observation_period_end_date) } if str.upcase == 'END'
41
- return Sequel.lit('CONVERT(DATETIME, ?)', str) if db.database_type == :mssql
42
- return Sequel.lit('date ?', str)
62
+ return db.from(period_table).get { |o| o.min(period_start_date(db)) } if str.upcase == "START"
63
+ return db.from(period_table).get { |o| o.max(period_end_date(db)) } if str.upcase == "END"
64
+
65
+ str
66
+ end
67
+
68
+ def period_table
69
+ dm.period_table
70
+ end
71
+
72
+ def period_start_date(db)
73
+ dm.start_date_column(db, period_table)
74
+ end
75
+
76
+ def period_end_date(db)
77
+ dm.end_date_column(db, period_table)
43
78
  end
44
79
  end
45
80
  end
46
81
  end
47
-
@@ -1,16 +1,26 @@
1
- require_relative 'casting_operator'
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "casting_operator"
2
4
 
3
5
  module ConceptQL
4
6
  module Operators
5
7
  class Death < CastingOperator
6
- desc 'Generates all death records, or, if fed a stream, fetches all death records for the people represented in the incoming result set.'
7
- types :death
8
+ include ConceptQL::Behaviors::Windowable
9
+
10
+ register __FILE__
11
+
12
+ desc "Selects all death records"
13
+ domains :death
8
14
  allows_one_upstream
9
15
 
10
- def my_type
16
+ def my_domain
11
17
  :death
12
18
  end
13
19
 
20
+ def source_table
21
+ dm.table_by_domain(:death)
22
+ end
23
+
14
24
  def i_point_at
15
25
  [ :person ]
16
26
  end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "pass_thru"
4
+
5
+ module ConceptQL
6
+ module Operators
7
+ class Dedup < PassThru
8
+ register __FILE__
9
+
10
+ desc "Counts the number of records that exactly match across all columns."
11
+ allows_one_upstream
12
+ validate_one_upstream
13
+
14
+ def query(db)
15
+ db.from(unioned(db))
16
+ .select_group(*query_cols)
17
+ .from_self
18
+ end
19
+
20
+ def query_cols
21
+ dynamic_columns
22
+ end
23
+
24
+ def unioned(db)
25
+ upstreams.map { |c| c.evaluate(db) }.inject do |uni, q|
26
+ uni.union(q)
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -1,21 +1,16 @@
1
- require_relative 'temporal_operator'
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "temporal_operator"
2
4
 
3
5
  module ConceptQL
4
6
  module Operators
5
7
  class During < TemporalOperator
6
- desc <<-EOF
7
- Compares all results on a person-by-person basis between the left hand results (LHR) and the right hand resuls (RHR).
8
- For any result in the LHR whose start_date and end_date occur within the start_date and end_date of a RHR row, that result is passed through.
9
- All other results are discarded, including all results in the RHR.
10
- EOF
8
+ register __FILE__
9
+
10
+ desc "For each person, passes along left hand records with a start_date and end_date within a right hand record's start_date and end_date."
11
11
 
12
12
  def where_clause
13
- if inclusive?
14
- Sequel.expr(Sequel.expr(Proc.new { r__start_date <= l__start_date}).&(Sequel.expr( Proc.new { l__start_date <= r__end_date })))
15
- .|(Sequel.expr(Proc.new { r__start_date <= l__end_date}).&(Sequel.expr( Proc.new { l__end_date <= r__end_date })))
16
- else
17
- [Proc.new { r__start_date <= l__start_date}, Proc.new { l__end_date <= r__end_date }]
18
- end
13
+ (within_start <= l_start_date) & (l_end_date <= within_end)
19
14
  end
20
15
  end
21
16
  end