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,146 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "pass_thru"
4
+
5
+ module ConceptQL
6
+ module Operators
7
+ class Episode < PassThru
8
+ register __FILE__
9
+
10
+ desc "Groups each person's records into episodes, allowing for a gap between the end_date of one record and the start_date of the next."
11
+
12
+ allows_one_upstream
13
+ validate_one_upstream
14
+ option :gap_of, type: :integer,
15
+ instructions: "Allowed gap in days between end date of one event and start date of the next to consider them the same episode"
16
+ category "Modify Data"
17
+ basic_type :temporal
18
+
19
+ def table
20
+ nil
21
+ end
22
+
23
+ # Episode strips source_vocabulary_id from its output rows (the SQL emits
24
+ # NULL for that column), so downstream consumers can't tell which vocab
25
+ # the data originated from. The default introspection methods on Operator
26
+ # walk upstream and report whatever vocabularies were upstream of Episode,
27
+ # which misrepresents what Episode actually emits. Override both methods
28
+ # to reflect that Episode erases vocabulary metadata.
29
+ def vocabularies
30
+ []
31
+ end
32
+
33
+ def multiple_vocabularies
34
+ false
35
+ end
36
+
37
+ def query(db)
38
+ ds = unioned(db)
39
+
40
+ partition_vars
41
+
42
+ dt1 = ds.select(*partition_vars).select_append(
43
+ :start_date,
44
+ :end_date,
45
+ Sequel[:start_date].as(:episode_start_date),
46
+ Sequel.function(:max,
47
+ Sequel.function(:coalesce, :end_date,
48
+ :start_date)).over(partition: partition_vars, order: %i[start_date
49
+ end_date]).as(:episode_end_date)
50
+ )
51
+
52
+ # Get allowed episode gap
53
+ gap_of = get_episode_gap
54
+
55
+ gap_check = date_adjust_add(db,
56
+ Sequel.function(:lag, :episode_end_date).over(partition: partition_vars, order: %i[start_date end_date]), gap_of, "days")
57
+
58
+ cond_gap_lt_start = (gap_check < Sequel[:episode_end_date]) | (nil)
59
+
60
+ dt2 = dt1.from_self.select_append(
61
+ gap_of.as(:gap_of),
62
+ cond_gap_lt_start.as(:step) # True if new episode else null
63
+ )
64
+
65
+ tmp_episode_summary = dt2.from_self.select_append(
66
+ (Sequel.function(:count, :step).over(partition: partition_vars,
67
+ order: %i[start_date
68
+ end_date]) + 1).as(:episode)
69
+ )
70
+
71
+ ids_plus_episode = (partition_vars + [ :episode ])
72
+
73
+ # Get last dispensing in an episodes gap and
74
+ last_dispensing = tmp_episode_summary.from_self.select(*ids_plus_episode)
75
+ .select_append(
76
+ :gap_of,
77
+ rdbms.datediff(Sequel[:episode_start_date],
78
+ Sequel.function(:lag, :episode_end_date).over(
79
+ partition: partition_vars + [ :episode ], order: :start_date
80
+ )).as(:LEpisodeGap),
81
+ Sequel.function(:row_number).over(partition: ids_plus_episode,
82
+ order: [ Sequel.desc(:start_date) ]).as(:event_number)
83
+ )
84
+
85
+ last_dispensing = last_dispensing.from_self.where(event_number: 1)
86
+
87
+ join_hash = ids_plus_episode.map { |c| [ c, c ] }.to_h
88
+
89
+ episode_summary = tmp_episode_summary.from_self(alias: :e).join(last_dispensing, join_hash,
90
+ table_alias: :o)
91
+ e = Sequel[:e]
92
+ Sequel[:o]
93
+
94
+ grp_cols = ids_plus_episode.map { |c| e[c] }
95
+
96
+ episode_summary = episode_summary.select_group(*grp_cols).select_append(
97
+ Sequel.function(:min, :episode_start_date).as(:start_date),
98
+ Sequel.function(:max, :episode_end_date).as(:end_date)
99
+ )
100
+
101
+ # Episodes streams return null for criterion_id,table, and domain which messes up uuid generation so we had to add constants to these values
102
+ episode_summary = episode_summary.from_self.select_append(
103
+ Sequel[0].cast_numeric.as(:criterion_id),
104
+ Sequel.cast_string("episode").as(:criterion_table),
105
+ Sequel.cast_string("episode").as(:criterion_domain)
106
+ )
107
+
108
+ episode_query_cols = partition_vars + %i[start_date end_date criterion_id criterion_table
109
+ criterion_domain]
110
+ dm.selectify(episode_summary.from_self, { query_columns: episode_query_cols.zip(episode_query_cols).to_h,
111
+ replace: { column_family: ConceptQL::Scope::DEFAULT_COLUMN_FAMILY } })
112
+ end
113
+
114
+ def get_episode_gap
115
+ # If Create treatment episodes get episode gap otherwise allow only 0 gap
116
+ return Sequel[options[:gap_of].to_i] unless options[:gap_of].nil?
117
+
118
+ Sequel[0]
119
+ end
120
+
121
+ def unioned(db)
122
+ upstreams.map { |c| c.evaluate(db) }.inject do |uni, q|
123
+ uni.union(q)
124
+ end
125
+ end
126
+
127
+ def partition_vars
128
+ matching_columns
129
+ end
130
+
131
+ ##
132
+ # Adds `by` units of `timeframe` (days, months, etc) to the date `from`.
133
+ # PostgreSQL and DuckDB multiply a one-unit INTERVAL; other databases
134
+ # (Spark) use the INTERVAL n unit form.
135
+ #
136
+
137
+ def date_adjust_add(db, from, by, timeframe)
138
+ if %i[postgres duckdb].include?(db.database_type)
139
+ Sequel.cast(from + Sequel.lit("(? * INTERVAL '1' ?)", by, Sequel.lit(timeframe.sub(/s\z/, ""))), Date)
140
+ else
141
+ from + Sequel.lit("INTERVAL ? ?", by, Sequel.lit(timeframe))
142
+ end
143
+ end
144
+ end
145
+ end
146
+ end
@@ -1,12 +1,18 @@
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 Equal < TemporalOperator
6
- desc 'If a LHR result has the same value_as_number as a RHR result, it is passed through'
8
+ register __FILE__
9
+
10
+ desc "For each person, passes along left hand records that have the same value_as_number as a right hand record."
11
+
12
+ require_column :value_as_number
7
13
 
8
14
  def where_clause
9
- { r__value_as_number: :l__value_as_number }
15
+ { Sequel[:r][:value_as_number] => Sequel[:l][:value_as_number] }
10
16
  end
11
17
  end
12
18
  end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "operator"
4
+
5
+ module ConceptQL
6
+ module Operators
7
+ class Ethnicity < Operator
8
+ register __FILE__
9
+
10
+ desc "Selects person records that match the given set of Ethnicity codes."
11
+ domains :person
12
+ category "Select by Property"
13
+ basic_type :selection
14
+ argument :ethnicity, type: :string, options: [ "Hispanic or Latino", "Not Hispanic or Latino" ]
15
+ validate_no_upstreams
16
+ validate_at_least_one_argument
17
+
18
+ def query_cols
19
+ table_columns(table)
20
+ end
21
+
22
+ def table
23
+ source_table
24
+ end
25
+
26
+ def source_table
27
+ dm.person_table
28
+ end
29
+
30
+ def query(db)
31
+ db.from(source_table)
32
+ .where(ethnicity_concept_id: ethnicity_concept_ids)
33
+ end
34
+
35
+ def ethnicity_concept_ids
36
+ arguments.map do |words|
37
+ case words
38
+ when /^Not Hispanic/i
39
+ 38_003_564
40
+ when /^Hispanic/i
41
+ 38_003_563
42
+ when /unknown/i
43
+ [ 0, nil ]
44
+ end
45
+ end.flatten
46
+ end
47
+ end
48
+ end
49
+ end
@@ -1,21 +1,28 @@
1
- require_relative 'binary_operator_operator'
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "binary_operator_operator"
2
4
 
3
5
  module ConceptQL
4
6
  module Operators
5
7
  class Except < BinaryOperatorOperator
6
- desc 'If a LHR result appears in the RHR result, it is removed from the output result set.'
7
- category 'Set Logic'
8
+ register __FILE__
9
+
10
+ desc "Removes all incoming records that appear in the right hand set of records."
11
+ default_query_columns
8
12
 
9
13
  def query(db)
10
- if ignore_dates?
11
- query = db.from(Sequel.as(left.evaluate(db), :l))
12
- .left_join(Sequel.as(right.evaluate(db), :r), l__criterion_id: :r__criterion_id, l__criterion_type: :r__criterion_type)
13
- .where(r__criterion_id: nil)
14
- .select_all(:l)
15
- db.from(query)
16
- else
17
- left.evaluate(db).except(right.evaluate(db))
18
- end
14
+ cols_to_compare = %i[
15
+ criterion_id
16
+ criterion_table
17
+ ]
18
+ cols_to_compare += %i[start_date end_date] unless ignore_dates?
19
+ join_cols = cols_to_compare.zip(cols_to_compare).to_h
20
+
21
+ query = db.from(Sequel.as(left.evaluate(db), :l))
22
+ .left_join(Sequel.as(right.evaluate(db), :r), join_cols)
23
+ .where(Sequel[:r][:criterion_id] => nil)
24
+ .select_all(:l)
25
+ db.from(query)
19
26
  end
20
27
 
21
28
  private
@@ -1,21 +1,36 @@
1
- require_relative 'binary_operator_operator'
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "binary_operator_operator"
2
4
 
3
5
  module ConceptQL
4
6
  module Operators
5
7
  class Filter < BinaryOperatorOperator
6
- desc 'Only pass through results from the LHR that have a corresponding RHR with the same person, criterion_id, and criterion_type'
8
+ register __FILE__
9
+
10
+ desc "Keeps left hand records that share the same person, criterion_id, and criterion_domain with a right hand side record."
11
+ default_query_columns
7
12
 
8
13
  def query(db)
9
14
  rhs = right.evaluate(db)
10
- rhs = rhs.select_group(:person_id, :criterion_id, :criterion_type)
15
+ rhs = rhs.from_self.select_group(*columns)
11
16
  query = db.from(Sequel.as(left.evaluate(db), :l))
12
- query = query
13
- .left_join(Sequel.as(rhs, :r), l__person_id: :r__person_id, l__criterion_id: :r__criterion_id, l__criterion_type: :r__criterion_type)
14
- .exclude(r__criterion_id: nil)
15
- .select_all(:l)
17
+ query = semi_or_inner_join(query, rhs, join_columns)
16
18
  db.from(query)
17
19
  end
20
+
21
+ def columns
22
+ @columns ||= determine_columns
23
+ end
24
+
25
+ def join_columns
26
+ columns.map { |c| [ Sequel[:l][c], Sequel[:r][c] ] }
27
+ end
28
+
29
+ def determine_columns
30
+ columns = %w[person_id criterion_id criterion_domain]
31
+ columns += %w[start_date end_date] unless options[:ignore_dates]
32
+ columns.map(&:to_sym)
33
+ end
18
34
  end
19
35
  end
20
36
  end
21
-
@@ -1,4 +1,6 @@
1
- require_relative 'occurrence'
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "occurrence"
2
4
 
3
5
  module ConceptQL
4
6
  module Operators
@@ -15,12 +17,23 @@ module ConceptQL
15
17
  # If we ask for the first occurrence of something and a person has no
16
18
  # occurrences, this operator returns nothing for that person
17
19
  class First < Occurrence
18
- desc 'Only passes through the row with the earliest start_date per person. If more than one row qualifies, result is arbitrarily chosen.'
20
+ register __FILE__
21
+
22
+ desc "For each person, passes along the record with the earliest start_date."
23
+
24
+ # With either option, Occurrence's timed query returns every upstream
25
+ # row instead of one row per person
26
+ unsupported_options :at_least, :within
19
27
 
20
28
  def occurrence
21
29
  1
22
30
  end
31
+
32
+ # A non-blank at_least or within fails validation, but the statement
33
+ # still builds SQL. That SQL stays the plain query, not the timed one.
34
+ def timed?
35
+ false
36
+ end
23
37
  end
24
38
  end
25
39
  end
26
-
@@ -1,14 +1,62 @@
1
- require_relative 'pass_thru'
1
+ # frozen_string_literal: true
2
2
 
3
3
  module ConceptQL
4
4
  module Operators
5
5
  class From < Operator
6
+ include ConceptQL::Behaviors::Windowable
7
+ include ConceptQL::Behaviors::Timeless
8
+
9
+ register __FILE__
10
+ basic_type :selection
11
+ no_desc
12
+ option :domains, type: Array
13
+ option :query_cols, type: Array
14
+ validate_no_upstreams
15
+ validate_one_argument
16
+
6
17
  def query(db)
7
- db.from(values.first)
18
+ db.from(table_name)
19
+ end
20
+
21
+ def domains(_db)
22
+ doms = options[:domains]
23
+ if doms.nil? || doms.empty?
24
+ if dm.schema.key?(table_name)
25
+ [ table_name ]
26
+ else
27
+ [ :invalid ]
28
+ end
29
+ else
30
+ doms.map(&:to_sym)
31
+ end
32
+ end
33
+
34
+ def table_name
35
+ return @table_name if @table_name
36
+
37
+ name = values.first
38
+ if name.is_a?(String)
39
+ table, column = name.split("__", 2)
40
+ name = if column
41
+ Sequel.qualify(table, column)
42
+ else
43
+ name.to_sym
44
+ end
45
+ end
46
+ @table_name = name
47
+ end
48
+
49
+ def required_columns
50
+ override_columns.keys
51
+ end
52
+
53
+ def query_cols
54
+ required_columns
8
55
  end
9
56
 
10
- def types
11
- values[1..99].compact
57
+ def override_columns
58
+ cols = (options[:query_cols] || dynamic_columns).map(&:to_sym)
59
+ Hash[cols.zip(cols)]
12
60
  end
13
61
  end
14
62
  end
@@ -1,26 +1,49 @@
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
  class Gender < Operator
6
- desc 'Returns all person records that match the selected gender.'
7
- argument :gender, type: :string, options: ['Male', 'Female']
8
- types :person
8
+ register __FILE__
9
+
10
+ include ConceptQL::Behaviors::Windowable
11
+ include ConceptQL::Behaviors::Timeless
12
+
13
+ desc "Selects all person records that match the selected gender."
14
+ argument :gender, type: :string, options: %w[Male Female Unknown]
15
+ domains :person
16
+ category "Select by Property"
17
+ basic_type :selection
18
+ validate_no_upstreams
19
+ validate_at_least_one_argument
9
20
 
10
21
  def query(db)
11
- gender_concept_ids = values.map do |value|
12
- case value.to_s
22
+ clauses = arguments.map do |arg|
23
+ case arg.to_s
13
24
  when /^m/i
14
- 8507
25
+ Sequel.expr(gender_concept_id: male_ids(db))
15
26
  when /^f/i
16
- 8532
27
+ Sequel.expr(gender_concept_id: female_ids(db))
17
28
  else
18
- value.to_i
29
+ Sequel.expr(gender_concept_id: nil) | Sequel.~(gender_concept_id: male_ids(db) + female_ids(db))
19
30
  end
20
31
  end
21
32
 
22
- db.from(:person)
23
- .where(gender_concept_id: gender_concept_ids)
33
+ db.from(table)
34
+ .where(clauses.inject(&:|))
35
+ end
36
+
37
+ def table
38
+ dm.table_by_domain(:person)
39
+ end
40
+
41
+ def male_ids(db)
42
+ dm.related_concept_ids(db, 8507)
43
+ end
44
+
45
+ def female_ids(db)
46
+ dm.related_concept_ids(db, 8532)
24
47
  end
25
48
  end
26
49
  end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "operator"
4
+
5
+ module ConceptQL
6
+ module Operators
7
+ class Hospice < Operator
8
+ # GDM only: omopv4_plus has no provenance vocabularies (conceptql-myp).
9
+ register __FILE__, :gdm, :gdm_wide
10
+ include ConceptQL::Behaviors::Utilizable
11
+
12
+ desc "Selects admission records of type 'hospice'"
13
+
14
+ def collection_type
15
+ "hospice"
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "operator"
4
+
5
+ module ConceptQL
6
+ module Operators
7
+ class Hospitalization < Operator
8
+ # GDM only: omopv4_plus has no provenance vocabularies (conceptql-myp).
9
+ register __FILE__, :gdm, :gdm_wide
10
+ include ConceptQL::Behaviors::Utilizable
11
+
12
+ desc "Selects admission records of type 'inpatient'"
13
+
14
+ def collection_type
15
+ "inpatient"
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "operator"
4
+
5
+ module ConceptQL
6
+ module Operators
7
+ class InformationPeriods < Operator
8
+ register __FILE__
9
+
10
+ include ConceptQL::Behaviors::Windowable
11
+ include ConceptQL::Behaviors::Timeless
12
+
13
+ desc "Generates all observation_period records, or, if fed a stream, fetches all observation_period records for the people represented in the incoming record set."
14
+ domains :observation_period
15
+ category "Get Related Data"
16
+ basic_type :selection
17
+ validate_no_upstreams
18
+
19
+ def query(db)
20
+ ds = db[source_table]
21
+ ds = ds.where(where_clause) if where_clause
22
+ ds
23
+ end
24
+
25
+ def source_table
26
+ tab = dm.table_by_domain(domain)
27
+ tab = :payer_plan_period if tab == :observation_period && arguments.present?
28
+ tab
29
+ end
30
+
31
+ def where_clause
32
+ dm.information_period_where_clause(arguments)
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ConceptQL
4
+ module Operators
5
+ class Invalid < Operator
6
+ register __FILE__
7
+
8
+ no_desc
9
+ default_query_columns
10
+
11
+ def initialize(*args)
12
+ super
13
+ @errors = @options.delete(:errors)
14
+ end
15
+
16
+ def operator_name
17
+ @operator_name ||= arguments.shift.to_s || "invalid"
18
+ end
19
+
20
+ def query(_db)
21
+ raise "Invalid#query called. #{errors}"
22
+ end
23
+
24
+ def annotate(db, opts = {})
25
+ if options[:left] || options[:right]
26
+ options[:left] = to_op(options[:left]).annotate(db, opts) if options[:left]
27
+ options[:right] = to_op(options[:right]).annotate(db, opts) if options[:right]
28
+ end
29
+ super
30
+ end
31
+
32
+ def validate(db, opts = {}); end
33
+ end
34
+ end
35
+ end
@@ -1,4 +1,6 @@
1
- require_relative 'occurrence'
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "occurrence"
2
4
 
3
5
  module ConceptQL
4
6
  module Operators
@@ -15,12 +17,23 @@ module ConceptQL
15
17
  # If we ask for the last occurrence of something and a person has no
16
18
  # occurrences, this operator returns nothing for that person
17
19
  class Last < Occurrence
18
- desc 'Only passes through the row with the most recent start_date per person. If more than one row qualifies, result is arbitrarily chosen.'
20
+ register __FILE__
21
+
22
+ desc "For each person, passes along the record with the most recent start_date."
23
+
24
+ # With either option, Occurrence's timed query returns every upstream
25
+ # row instead of one row per person
26
+ unsupported_options :at_least, :within
19
27
 
20
28
  def occurrence
21
29
  -1
22
30
  end
31
+
32
+ # A non-blank at_least or within fails validation, but the statement
33
+ # still builds SQL. That SQL stays the plain query, not the timed one.
34
+ def timed?
35
+ false
36
+ end
23
37
  end
24
38
  end
25
39
  end
26
-