conceptql 0.1.1 → 0.2.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 (196) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/CHANGELOG.md +53 -1
  4. data/Guardfile +52 -24
  5. data/conceptql.gemspec +5 -3
  6. data/doc/spec.md +2 -2
  7. data/lib/conceptql/behaviors/debuggable.rb +70 -0
  8. data/lib/conceptql/behaviors/dottable.rb +28 -14
  9. data/lib/conceptql/behaviors/metadatable.rb +97 -0
  10. data/lib/conceptql/behaviors/preppable.rb +20 -0
  11. data/lib/conceptql/cli.rb +31 -5
  12. data/lib/conceptql/converter.rb +65 -0
  13. data/lib/conceptql/debugger.rb +48 -0
  14. data/lib/conceptql/graph.rb +14 -13
  15. data/lib/conceptql/graph_nodifier.rb +49 -17
  16. data/lib/conceptql/nodifier.rb +31 -6
  17. data/lib/conceptql/operators/after.rb +25 -0
  18. data/lib/conceptql/operators/any_overlap.rb +15 -0
  19. data/lib/conceptql/operators/before.rb +21 -0
  20. data/lib/conceptql/{nodes/binary_operator_node.rb → operators/binary_operator_operator.rb} +12 -8
  21. data/lib/conceptql/{nodes/casting_node.rb → operators/casting_operator.rb} +8 -7
  22. data/lib/conceptql/{nodes → operators}/complement.rb +14 -11
  23. data/lib/conceptql/operators/concept.rb +70 -0
  24. data/lib/conceptql/{nodes → operators}/condition_type.rb +13 -5
  25. data/lib/conceptql/operators/contains.rb +17 -0
  26. data/lib/conceptql/operators/count.rb +26 -0
  27. data/lib/conceptql/operators/cpt.rb +25 -0
  28. data/lib/conceptql/{nodes → operators}/date_range.rb +11 -6
  29. data/lib/conceptql/operators/death.rb +23 -0
  30. data/lib/conceptql/operators/drug_type_concept.rb +21 -0
  31. data/lib/conceptql/{nodes → operators}/during.rb +9 -3
  32. data/lib/conceptql/operators/equal.rb +13 -0
  33. data/lib/conceptql/operators/except.rb +28 -0
  34. data/lib/conceptql/operators/filter.rb +21 -0
  35. data/lib/conceptql/{nodes → operators}/first.rb +7 -5
  36. data/lib/conceptql/{nodes → operators}/from.rb +2 -2
  37. data/lib/conceptql/operators/from_seer_visits.rb +23 -0
  38. data/lib/conceptql/{nodes → operators}/gender.rb +6 -6
  39. data/lib/conceptql/operators/hcpcs.rb +25 -0
  40. data/lib/conceptql/operators/icd10.rb +28 -0
  41. data/lib/conceptql/operators/icd9.rb +28 -0
  42. data/lib/conceptql/operators/icd9_procedure.rb +25 -0
  43. data/lib/conceptql/{nodes → operators}/intersect.rb +7 -3
  44. data/lib/conceptql/{nodes → operators}/last.rb +7 -5
  45. data/lib/conceptql/operators/loinc.rb +25 -0
  46. data/lib/conceptql/operators/medcode.rb +28 -0
  47. data/lib/conceptql/operators/medcode_procedure.rb +27 -0
  48. data/lib/conceptql/operators/ndc.rb +29 -0
  49. data/lib/conceptql/operators/numeric.rb +54 -0
  50. data/lib/conceptql/operators/observation_by_enttype.rb +29 -0
  51. data/lib/conceptql/operators/observation_period.rb +30 -0
  52. data/lib/conceptql/operators/occurrence.rb +70 -0
  53. data/lib/conceptql/operators/one_in_two_out.rb +62 -0
  54. data/lib/conceptql/{nodes/node.rb → operators/operator.rb} +84 -52
  55. data/lib/conceptql/operators/overlapped_by.rb +23 -0
  56. data/lib/conceptql/operators/overlaps.rb +19 -0
  57. data/lib/conceptql/operators/pass_thru.rb +11 -0
  58. data/lib/conceptql/{nodes → operators}/person.rb +8 -4
  59. data/lib/conceptql/operators/person_filter.rb +13 -0
  60. data/lib/conceptql/{nodes → operators}/place_of_service_code.rb +7 -7
  61. data/lib/conceptql/operators/procedure_occurrence.rb +25 -0
  62. data/lib/conceptql/operators/prodcode.rb +29 -0
  63. data/lib/conceptql/{nodes → operators}/race.rb +7 -7
  64. data/lib/conceptql/operators/recall.rb +38 -0
  65. data/lib/conceptql/operators/rxnorm.rb +24 -0
  66. data/lib/conceptql/operators/snomed.rb +24 -0
  67. data/lib/conceptql/operators/snomed_condition.rb +26 -0
  68. data/lib/conceptql/{nodes/source_vocabulary_node.rb → operators/source_vocabulary_operator.rb} +7 -4
  69. data/lib/conceptql/{nodes/standard_vocabulary_node.rb → operators/standard_vocabulary_operator.rb} +6 -4
  70. data/lib/conceptql/{nodes → operators}/started_by.rb +9 -3
  71. data/lib/conceptql/{nodes → operators}/sum.rb +9 -4
  72. data/lib/conceptql/{nodes/temporal_node.rb → operators/temporal_operator.rb} +7 -4
  73. data/lib/conceptql/{nodes → operators}/time_window.rb +19 -7
  74. data/lib/conceptql/operators/to_seer_visits.rb +24 -0
  75. data/lib/conceptql/operators/trim_date_end.rb +55 -0
  76. data/lib/conceptql/operators/trim_date_start.rb +56 -0
  77. data/lib/conceptql/{nodes → operators}/union.rb +6 -2
  78. data/lib/conceptql/operators/visit.rb +15 -0
  79. data/lib/conceptql/{nodes → operators}/visit_occurrence.rb +7 -3
  80. data/lib/conceptql/query.rb +19 -17
  81. data/lib/conceptql/scope.rb +69 -0
  82. data/lib/conceptql/tree.rb +33 -18
  83. data/lib/conceptql/utils/temp_table.rb +72 -0
  84. data/lib/conceptql/version.rb +1 -1
  85. data/spec/conceptql/behaviors/dottable_spec.rb +39 -51
  86. data/spec/conceptql/converter_spec.rb +51 -0
  87. data/spec/conceptql/date_adjuster_spec.rb +15 -15
  88. data/spec/conceptql/operators/after_spec.rb +16 -0
  89. data/spec/conceptql/operators/before_spec.rb +16 -0
  90. data/spec/conceptql/{nodes/casting_node_spec.rb → operators/casting_operator_spec.rb} +16 -20
  91. data/spec/conceptql/operators/complement_spec.rb +15 -0
  92. data/spec/conceptql/operators/concept_spec.rb +40 -0
  93. data/spec/conceptql/{nodes → operators}/condition_type_spec.rb +39 -24
  94. data/spec/conceptql/operators/contains_spec.rb +19 -0
  95. data/spec/conceptql/operators/cpt_spec.rb +29 -0
  96. data/spec/conceptql/operators/date_range_spec.rb +33 -0
  97. data/spec/conceptql/operators/death_spec.rb +10 -0
  98. data/spec/conceptql/operators/during_spec.rb +30 -0
  99. data/spec/conceptql/operators/except_spec.rb +15 -0
  100. data/spec/conceptql/operators/first_spec.rb +35 -0
  101. data/spec/conceptql/operators/from_spec.rb +13 -0
  102. data/spec/conceptql/operators/gender_spec.rb +27 -0
  103. data/spec/conceptql/operators/hcpcs_spec.rb +29 -0
  104. data/spec/conceptql/operators/icd10_spec.rb +34 -0
  105. data/spec/conceptql/operators/icd9_procedure_spec.rb +29 -0
  106. data/spec/conceptql/operators/icd9_spec.rb +34 -0
  107. data/spec/conceptql/operators/intersect_spec.rb +28 -0
  108. data/spec/conceptql/operators/last_spec.rb +36 -0
  109. data/spec/conceptql/operators/loinc_spec.rb +29 -0
  110. data/spec/conceptql/operators/medcode_procedure_spec.rb +34 -0
  111. data/spec/conceptql/operators/medcode_spec.rb +34 -0
  112. data/spec/conceptql/operators/observation_period_spec.rb +10 -0
  113. data/spec/conceptql/operators/occurrence_spec.rb +87 -0
  114. data/spec/conceptql/operators/overlapped_by_spec.rb +32 -0
  115. data/spec/conceptql/operators/overlaps_spec.rb +21 -0
  116. data/spec/conceptql/operators/person_filter_spec.rb +15 -0
  117. data/spec/conceptql/operators/person_spec.rb +10 -0
  118. data/spec/conceptql/{nodes → operators}/place_of_service_code_spec.rb +6 -8
  119. data/spec/conceptql/operators/procedure_occurrence_spec.rb +10 -0
  120. data/spec/conceptql/operators/prodcode_spec.rb +35 -0
  121. data/spec/conceptql/operators/query_double.rb +20 -0
  122. data/spec/conceptql/operators/query_double_spec.rb +7 -0
  123. data/spec/conceptql/operators/race_spec.rb +21 -0
  124. data/spec/conceptql/operators/rxnorm_spec.rb +29 -0
  125. data/spec/conceptql/operators/snomed_spec.rb +29 -0
  126. data/spec/conceptql/operators/source_vocabulary_operator_spec.rb +35 -0
  127. data/spec/conceptql/operators/standard_vocabulary_operator_spec.rb +35 -0
  128. data/spec/conceptql/operators/started_by_spec.rb +22 -0
  129. data/spec/conceptql/{nodes/temporal_node_spec.rb → operators/temporal_operator_spec.rb} +11 -17
  130. data/spec/conceptql/operators/time_window_spec.rb +77 -0
  131. data/spec/conceptql/operators/union_spec.rb +21 -0
  132. data/spec/conceptql/operators/visit_occurrence_spec.rb +10 -0
  133. data/spec/conceptql/query_spec.rb +10 -9
  134. data/spec/conceptql/tree_spec.rb +24 -28
  135. data/spec/doubles/stream_for_casting_double.rb +1 -1
  136. data/spec/doubles/stream_for_occurrence_double.rb +1 -1
  137. data/spec/doubles/stream_for_temporal_double.rb +1 -1
  138. data/spec/spec_helper.rb +74 -58
  139. metadata +202 -133
  140. data/lib/conceptql/nodes/after.rb +0 -12
  141. data/lib/conceptql/nodes/before.rb +0 -11
  142. data/lib/conceptql/nodes/concept.rb +0 -38
  143. data/lib/conceptql/nodes/count.rb +0 -23
  144. data/lib/conceptql/nodes/cpt.rb +0 -20
  145. data/lib/conceptql/nodes/death.rb +0 -19
  146. data/lib/conceptql/nodes/define.rb +0 -96
  147. data/lib/conceptql/nodes/drug_type_concept.rb +0 -18
  148. data/lib/conceptql/nodes/equal.rb +0 -11
  149. data/lib/conceptql/nodes/except.rb +0 -11
  150. data/lib/conceptql/nodes/hcpcs.rb +0 -20
  151. data/lib/conceptql/nodes/icd10.rb +0 -23
  152. data/lib/conceptql/nodes/icd9.rb +0 -23
  153. data/lib/conceptql/nodes/icd9_procedure.rb +0 -20
  154. data/lib/conceptql/nodes/loinc.rb +0 -20
  155. data/lib/conceptql/nodes/numeric.rb +0 -40
  156. data/lib/conceptql/nodes/occurrence.rb +0 -49
  157. data/lib/conceptql/nodes/pass_thru.rb +0 -11
  158. data/lib/conceptql/nodes/person_filter.rb +0 -12
  159. data/lib/conceptql/nodes/procedure_occurrence.rb +0 -21
  160. data/lib/conceptql/nodes/recall.rb +0 -50
  161. data/lib/conceptql/nodes/rxnorm.rb +0 -20
  162. data/lib/conceptql/nodes/snomed.rb +0 -19
  163. data/lib/conceptql/nodes/visit.rb +0 -11
  164. data/spec/conceptql/nodes/after_spec.rb +0 -18
  165. data/spec/conceptql/nodes/before_spec.rb +0 -18
  166. data/spec/conceptql/nodes/complement_spec.rb +0 -15
  167. data/spec/conceptql/nodes/concept_spec.rb +0 -34
  168. data/spec/conceptql/nodes/cpt_spec.rb +0 -31
  169. data/spec/conceptql/nodes/date_range_spec.rb +0 -35
  170. data/spec/conceptql/nodes/death_spec.rb +0 -12
  171. data/spec/conceptql/nodes/during_spec.rb +0 -32
  172. data/spec/conceptql/nodes/except_spec.rb +0 -18
  173. data/spec/conceptql/nodes/first_spec.rb +0 -37
  174. data/spec/conceptql/nodes/from_spec.rb +0 -15
  175. data/spec/conceptql/nodes/gender_spec.rb +0 -29
  176. data/spec/conceptql/nodes/hcpcs_spec.rb +0 -31
  177. data/spec/conceptql/nodes/icd10_spec.rb +0 -36
  178. data/spec/conceptql/nodes/icd9_procedure_spec.rb +0 -31
  179. data/spec/conceptql/nodes/icd9_spec.rb +0 -36
  180. data/spec/conceptql/nodes/intersect_spec.rb +0 -33
  181. data/spec/conceptql/nodes/last_spec.rb +0 -38
  182. data/spec/conceptql/nodes/loinc_spec.rb +0 -31
  183. data/spec/conceptql/nodes/occurrence_spec.rb +0 -89
  184. data/spec/conceptql/nodes/person_filter_spec.rb +0 -18
  185. data/spec/conceptql/nodes/person_spec.rb +0 -12
  186. data/spec/conceptql/nodes/procedure_occurrence_spec.rb +0 -12
  187. data/spec/conceptql/nodes/query_double.rb +0 -19
  188. data/spec/conceptql/nodes/race_spec.rb +0 -23
  189. data/spec/conceptql/nodes/rxnorm_spec.rb +0 -31
  190. data/spec/conceptql/nodes/snomed_spec.rb +0 -31
  191. data/spec/conceptql/nodes/source_vocabulary_node_spec.rb +0 -37
  192. data/spec/conceptql/nodes/standard_vocabulary_node_spec.rb +0 -40
  193. data/spec/conceptql/nodes/started_by_spec.rb +0 -25
  194. data/spec/conceptql/nodes/time_window_spec.rb +0 -85
  195. data/spec/conceptql/nodes/union_spec.rb +0 -25
  196. data/spec/conceptql/nodes/visit_occurrence_spec.rb +0 -12
@@ -0,0 +1,62 @@
1
+ require_relative 'operator'
2
+ require_relative 'visit_occurrence'
3
+
4
+ module ConceptQL
5
+ module Operators
6
+ class OneInTwoOut < Operator
7
+ desc <<-EOF
8
+ Represents a common pattern in research algorithms: searching for a condition
9
+ that appears either two times in an outpatient setting with a 30-day gap or once
10
+ in an inpatient setting
11
+ EOF
12
+ allows_one_upstream
13
+ category %w(Temporal Relative)
14
+
15
+ def types
16
+ [:visit_occurrence]
17
+ end
18
+
19
+ def query(db)
20
+ inpatient = select_it(visit_query(db).where(place_of_service_concept_id: 8717), :visit_occurrence).from_self
21
+ outpatient = select_it(visit_query(db).exclude(place_of_service_concept_id: 8717), :visit_occurrence).from_self
22
+
23
+ gap = options[:gap] || 30
24
+ valid_outpatient_people = outpatient
25
+ .group_by(:person_id)
26
+ .select(:person_id)
27
+ .select_append(Sequel.expr(Sequel.function(:max, :start_date) - Sequel.function(:min, :end_date)).as(:date_diff))
28
+ .from_self
29
+ .where{ date_diff >= gap}
30
+
31
+ relevant_outpatient = outpatient.where(person_id: valid_outpatient_people.select(:person_id))
32
+ earliest(db, inpatient.union(relevant_outpatient))
33
+ end
34
+
35
+ private
36
+ def visit_query(db)
37
+ VisitOccurrence.new(FakeOperator.new(stream.evaluate(db).from_self, stream.types)).query(db)
38
+ end
39
+
40
+ def earliest(db, query)
41
+ db[:earliest]
42
+ .with(:earliest,
43
+ query.select_append { |o| o.row_number(:over, partition: :person_id, order: [Sequel.asc(:start_date), :criterion_type, :criterion_id]){}.as(:rn) })
44
+ .where(rn: 1)
45
+ .from_self
46
+ end
47
+
48
+ class FakeOperator < Operator
49
+ attr :types
50
+ def initialize(query, types)
51
+ @query = query
52
+ @types = types
53
+ end
54
+
55
+ def query(db)
56
+ @query
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+
@@ -1,27 +1,48 @@
1
1
  require 'zlib'
2
- require 'active_support/core_ext/hash'
2
+ require_relative '../behaviors/metadatable'
3
+ require 'facets/array/extract_options'
4
+ require 'facets/hash/deep_rekey'
5
+ require 'forwardable'
6
+
3
7
  module ConceptQL
4
- module Nodes
5
- class Node
8
+ module Operators
9
+ class Operator
10
+ extend Forwardable
11
+ extend Metadatable
6
12
  COLUMNS = [
7
13
  :person_id,
8
14
  :criterion_id,
9
15
  :criterion_type,
10
16
  :start_date,
11
17
  :end_date,
12
- :value_as_numeric,
18
+ :value_as_number,
13
19
  :value_as_string,
14
- :value_as_concept_id
20
+ :value_as_concept_id,
21
+ :units_source_value,
22
+ :source_value
15
23
  ]
16
- attr :values, :options
17
- attr_accessor :tree
24
+
25
+ attr :values, :options, :arguments, :upstreams
26
+
27
+ option :label, type: :string
28
+
18
29
  def initialize(*args)
19
- args.flatten!
20
- if args.last.is_a?(Hash)
21
- @options = args.pop.symbolize_keys
22
- end
23
- @options ||= {}
24
- @values = args.flatten
30
+ set_values(*args)
31
+ end
32
+
33
+ def values=(*args)
34
+ set_values(*args)
35
+ end
36
+
37
+ def set_values(*args)
38
+ @options = args.extract_options!.deep_rekey
39
+ @upstreams, @arguments = args.partition { |arg| arg.is_a?(Operator) }
40
+ @values = args
41
+ end
42
+
43
+ def scope=(scope)
44
+ @scope = scope
45
+ scope.add_operator(self)
25
46
  end
26
47
 
27
48
  def evaluate(db)
@@ -34,23 +55,19 @@ module ConceptQL
34
55
 
35
56
  def select_it(query, specific_type = nil)
36
57
  specific_type = type if specific_type.nil? && respond_to?(:type)
37
- query.select(*columns(query, specific_type))
58
+ q = query.select(*columns(query, specific_type))
59
+ if scope && scope.person_ids && upstreams.empty?
60
+ q = q.where(person_id: scope.person_ids).from_self
61
+ end
62
+ q
38
63
  end
39
64
 
40
65
  def types
41
66
  @types ||= determine_types
42
67
  end
43
68
 
44
- def children
45
- @children ||= values.select { |v| v.is_a?(Node) }
46
- end
47
-
48
69
  def stream
49
- @stream ||= children.first
50
- end
51
-
52
- def arguments
53
- @arguments ||= values.reject { |v| v.is_a?(Node) }
70
+ @stream ||= upstreams.first
54
71
  end
55
72
 
56
73
  def columns(query, local_type = nil)
@@ -62,25 +79,15 @@ module ConceptQL
62
79
  type_id(local_type),
63
80
  criterion_type]
64
81
  columns += date_columns(query, local_type)
65
- columns += value_columns(query)
82
+ columns += value_columns(query, local_type)
83
+ end
84
+
85
+ def label
86
+ options[:label]
66
87
  end
67
88
 
68
89
  private
69
- # There have been a few times now that I've wanted a node to be able
70
- # to pass information to another node that is not directly a child
71
- #
72
- # Since tree is only object that touches each node in a statement,
73
- # I'm going to employ tree as a way to communicate between nodes
74
- #
75
- # This is an ugly hack, but the use case for this hack is I'm changing
76
- # the way `define` and `recall` nodes pass type information between
77
- # each other. They used to take the type information onto the
78
- # database connection, but there were issues where sometimes the
79
- # type information was needed before we passed around the database
80
- # connection.
81
- #
82
- # At least this way we don't have timing issues when reading types
83
- attr :tree
90
+ attr :scope
84
91
 
85
92
  def criterion_id
86
93
  :criterion_id
@@ -100,17 +107,19 @@ module ConceptQL
100
107
  "#{table}___tab".to_sym
101
108
  end
102
109
 
103
- def value_columns(query)
110
+ def value_columns(query, type)
104
111
  [
105
112
  numeric_value(query),
106
113
  string_value(query),
107
- concept_id_value(query)
114
+ concept_id_value(query),
115
+ units_source_value(query),
116
+ source_value(query, type)
108
117
  ]
109
118
  end
110
119
 
111
120
  def numeric_value(query)
112
- return :value_as_numeric if query.columns.include?(:value_as_numeric)
113
- Sequel.cast_numeric(nil, Float).as(:value_as_numeric)
121
+ return :value_as_number if query.columns.include?(:value_as_number)
122
+ Sequel.cast_numeric(nil, Float).as(:value_as_number)
114
123
  end
115
124
 
116
125
  def string_value(query)
@@ -123,13 +132,23 @@ module ConceptQL
123
132
  Sequel.cast_numeric(nil).as(:value_as_concept_id)
124
133
  end
125
134
 
135
+ def units_source_value(query)
136
+ return :units_source_value if query.columns.include?(:units_source_value)
137
+ Sequel.cast_string(nil).as(:units_source_value)
138
+ end
139
+
140
+ def source_value(query, type)
141
+ return :source_value if query.columns.include?(:source_value)
142
+ Sequel.cast_string(source_value_column(query, type)).as(:source_value)
143
+ end
144
+
126
145
  def date_columns(query, type = nil)
127
146
  return [:start_date, :end_date] if (query.columns.include?(:start_date) && query.columns.include?(:end_date))
128
147
  return [:start_date, :end_date] unless type
129
148
  sd = start_date_column(query, type)
130
149
  sd = Sequel.expr(sd).cast(:date).as(:start_date) unless sd == :start_date
131
150
  ed = end_date_column(query, type)
132
- ed = Sequel.expr(ed).cast(:date).as(:end_date) unless ed == :end_date
151
+ ed = Sequel.function(:coalesce, Sequel.expr(ed).cast(:date), Sequel.expr(start_date_column(query, type))).as(:end_date) unless ed == :end_date
133
152
  [sd, ed]
134
153
  end
135
154
 
@@ -144,6 +163,7 @@ module ConceptQL
144
163
  procedure_occurrence: :procedure_date,
145
164
  procedure_cost: nil,
146
165
  observation: :observation_date,
166
+ observation_period: :observation_period_start_date,
147
167
  visit_occurrence: :visit_start_date
148
168
  }[type]
149
169
  end
@@ -159,10 +179,27 @@ module ConceptQL
159
179
  procedure_occurrence: :procedure_date,
160
180
  procedure_cost: nil,
161
181
  observation: :observation_date,
182
+ observation_period: :observation_period_end_date,
162
183
  visit_occurrence: :visit_end_date
163
184
  }[type]
164
185
  end
165
186
 
187
+ def source_value_column(query, type)
188
+ {
189
+ condition_occurrence: :condition_source_value,
190
+ death: :cause_of_death_source_value,
191
+ drug_exposure: :drug_source_value,
192
+ drug_cost: nil,
193
+ payer_plan_period: :payer_plan_period_source_value,
194
+ person: :person_source_value,
195
+ procedure_occurrence: :procedure_source_value,
196
+ procedure_cost: nil,
197
+ observation: :observation_source_value,
198
+ observation_period: nil,
199
+ visit_occurrence: :place_of_service_source_value
200
+ }[type]
201
+ end
202
+
166
203
  def person_date_of_birth(query)
167
204
  assemble_date(query, :year_of_birth, :month_of_birth, :day_of_birth)
168
205
  end
@@ -184,21 +221,16 @@ module ConceptQL
184
221
  end
185
222
 
186
223
  def determine_types
187
- if children.empty?
224
+ if upstreams.empty?
188
225
  if respond_to?(:type)
189
226
  [type]
190
227
  else
191
- raise "Node doesn't seem to specify any type"
228
+ raise "Operator doesn't seem to specify any type"
192
229
  end
193
230
  else
194
- children.map(&:types).flatten.uniq
231
+ upstreams.map(&:types).flatten.uniq
195
232
  end
196
233
  end
197
-
198
- def namify(name)
199
- digest = Zlib.crc32 name
200
- ('_' + digest.to_s).to_sym
201
- end
202
234
  end
203
235
  end
204
236
  end
@@ -0,0 +1,23 @@
1
+ require_relative 'temporal_operator'
2
+
3
+ module ConceptQL
4
+ module Operators
5
+ class OverlappedBy < 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 occurs between the start_date and end_date of a result from the RHR, that result is passed through.
9
+ All other results are discarded, including all results in the RHR.
10
+ L-------N-------L
11
+ R-----R
12
+ L-----Y----L
13
+ EOF
14
+ def where_clause
15
+ if inclusive?
16
+ [Proc.new { r__start_date <= l__start_date}, Proc.new { l__start_date <= r__end_date }]
17
+ else
18
+ [Proc.new { r__start_date <= l__start_date}, Proc.new { l__start_date <= r__end_date }, Proc.new { r__end_date <= l__end_date }]
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,19 @@
1
+ require_relative 'temporal_operator'
2
+
3
+ module ConceptQL
4
+ module Operators
5
+ class Overlaps < 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 end_date occurs between the start_date and end_date of a result from the RHR, that result is passed through.
9
+ All other results are discarded, including all results in the RHR.
10
+ L-------Y-------L
11
+ R-----R
12
+ L-----N----L
13
+ EOF
14
+ def where_clause
15
+ [Proc.new { l__start_date <= r__start_date}, Proc.new { r__start_date <= l__end_date }, Proc.new { l__end_date <= r__end_date }]
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,11 @@
1
+ require_relative 'operator'
2
+
3
+ module ConceptQL
4
+ module Operators
5
+ class PassThru < Operator
6
+ def types
7
+ upstreams.map(&:types).flatten.uniq
8
+ end
9
+ end
10
+ end
11
+ end
@@ -1,8 +1,12 @@
1
- require_relative 'casting_node'
1
+ require_relative 'casting_operator'
2
2
 
3
3
  module ConceptQL
4
- module Nodes
5
- class Person < CastingNode
4
+ module Operators
5
+ class Person < CastingOperator
6
+ desc 'Returns all people in the database, or if given a upstream, converts all results to the set of patients contained in those results.'
7
+ allows_one_upstream
8
+ types :person
9
+
6
10
  def my_type
7
11
  :person
8
12
  end
@@ -16,7 +20,7 @@ module ConceptQL
16
20
  # when there is no explicit casting defined, is to convert everything to
17
21
  # person IDs
18
22
  #
19
- # So by defining no known castable relationships in this node, all
23
+ # So by defining no known castable relationships in this operator, all
20
24
  # types will be converted to person
21
25
  []
22
26
  end
@@ -0,0 +1,13 @@
1
+ require_relative 'binary_operator_operator'
2
+
3
+ module ConceptQL
4
+ module Operators
5
+ class PersonFilter < BinaryOperatorOperator
6
+ desc 'Only passes through a result from the LHR if the person appears in the RHR.'
7
+ def query(db)
8
+ db.from(left.evaluate(db))
9
+ .where(person_id: right.evaluate(db).select_group(:person_id))
10
+ end
11
+ end
12
+ end
13
+ end
@@ -1,17 +1,17 @@
1
- require_relative 'node'
1
+ require_relative 'operator'
2
2
 
3
3
  module ConceptQL
4
- module Nodes
5
- # Represents a node that will grab all person rows that match the given place_of_service_codes
4
+ module Operators
5
+ # Represents a operator that will grab all person rows that match the given place_of_service_codes
6
6
  #
7
7
  # PlaceOfServiceCode parameters are passed in as a set of strings. Each string represents
8
8
  # a single place_of_service_code. The place_of_service_code string must match one of the values in the
9
9
  # concept_name column of the concept table. If you misspell the place_of_service_code name
10
10
  # you won't get any matches
11
- class PlaceOfServiceCode < Node
12
- def type
13
- :visit_occurrence
14
- end
11
+ class PlaceOfServiceCode < Operator
12
+ desc 'Finds all visit_occurrences that match the Place of Service codes'
13
+ argument :places_of_service, type: :codelist, vocab: 'Place of Service'
14
+ types :visit_occurrence
15
15
 
16
16
  def query(db)
17
17
  db.from(:visit_occurrence___v)
@@ -0,0 +1,25 @@
1
+ require_relative 'casting_operator'
2
+
3
+ module ConceptQL
4
+ module Operators
5
+ class ProcedureOccurrence < CastingOperator
6
+ desc 'Generates all procedure_occurrence records, or, if fed a stream, fetches all procedure_occurrence records for the people represented in the incoming result set.'
7
+ types :procedure_occurrence
8
+ allows_one_upstream
9
+
10
+ def my_type
11
+ :procedure_occurrence
12
+ end
13
+
14
+ def i_point_at
15
+ [ :person ]
16
+ end
17
+
18
+ def these_point_at_me
19
+ %i[
20
+ procedure_cost
21
+ ]
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,29 @@
1
+ require_relative 'source_vocabulary_operator'
2
+
3
+ module ConceptQL
4
+ module Operators
5
+ class Prodcode < SourceVocabularyOperator
6
+ desc 'Searches the drug_exposure table for all conditions with matching Prodcodes'
7
+ argument :prodcodes, type: :codelist, vocab_id: '203'
8
+ predominant_types :drug_exposure
9
+
10
+ def table
11
+ :drug_exposure
12
+ end
13
+
14
+ def vocabulary_id
15
+ 200
16
+ end
17
+
18
+ def source_column
19
+ :drug_source_value
20
+ end
21
+
22
+ def concept_column
23
+ :drug_concept_id
24
+ end
25
+ end
26
+ end
27
+ end
28
+
29
+
@@ -1,17 +1,17 @@
1
- require_relative 'node'
1
+ require_relative 'operator'
2
2
 
3
3
  module ConceptQL
4
- module Nodes
5
- # Represents a node that will grab all person rows that match the given races
4
+ module Operators
5
+ # Represents a operator that will grab all person rows that match the given races
6
6
  #
7
7
  # Race parameters are passed in as a set of strings. Each string represents
8
8
  # a single race. The race string must match one of the values in the
9
9
  # concept_name column of the concept table. If you misspell the race name
10
10
  # you won't get any matches
11
- class Race < Node
12
- def type
13
- :person
14
- end
11
+ class Race < Operator
12
+ desc 'Finds all people that match the races'
13
+ argument :races, type: :codelist, vocab: 'Race'
14
+ types :person
15
15
 
16
16
  def query(db)
17
17
  db.from(:person___p)
@@ -0,0 +1,38 @@
1
+ require_relative 'pass_thru'
2
+
3
+ module ConceptQL
4
+ module Operators
5
+ # Mimics using a variable that has been set via "define" operator
6
+ #
7
+ # The idea is that a concept might be very complex and it helps to break
8
+ # that complex concept into a set of sub-concepts to better understand it.
9
+ #
10
+ # This operator will look for a sub-concept that has been created through the
11
+ # "define" operator and will fetch the results cached in the corresponding table
12
+ class Recall < Operator
13
+ desc <<-EOF
14
+ Recalls a set of named results that were previously stored using the Define operator.
15
+ Must be surrounded by the same Let operator as surrounds the corresponding Define operator.
16
+ EOF
17
+ argument :name, type: :string
18
+ category 'Variable Assignment'
19
+
20
+ def query(db)
21
+ scope.from(db, source)
22
+ end
23
+
24
+ def columns(query, local_type)
25
+ COLUMNS
26
+ end
27
+
28
+ def types
29
+ scope.types(source)
30
+ end
31
+
32
+ def source
33
+ arguments.first
34
+ end
35
+ end
36
+ end
37
+ end
38
+
@@ -0,0 +1,24 @@
1
+ require_relative 'standard_vocabulary_operator'
2
+
3
+ module ConceptQL
4
+ module Operators
5
+ class Rxnorm < StandardVocabularyOperator
6
+ preferred_name 'RxNorm'
7
+ desc 'Finds all drug_exposures by RxNorm codes'
8
+ argument :rxnorms, type: :codelist, vocab: 'RxNorm'
9
+
10
+ def table
11
+ :drug_exposure
12
+ end
13
+
14
+ def vocabulary_id
15
+ 8
16
+ end
17
+
18
+ def concept_column
19
+ :drug_concept_id
20
+ end
21
+ end
22
+ end
23
+ end
24
+
@@ -0,0 +1,24 @@
1
+ require_relative 'standard_vocabulary_operator'
2
+
3
+ module ConceptQL
4
+ module Operators
5
+ class Snomed < StandardVocabularyOperator
6
+ preferred_name 'SNOMED'
7
+ desc 'Find all condition_occurrences by SNOMED codes'
8
+ argument :snomeds, type: :codelist, vocab: 'SNOMED'
9
+ predominant_types :condition_occurrence
10
+
11
+ def table
12
+ :condition_occurrence
13
+ end
14
+
15
+ def vocabulary_id
16
+ 1
17
+ end
18
+
19
+ def concept_column
20
+ :condition_concept_id
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,26 @@
1
+ require_relative 'standard_vocabulary_operator'
2
+
3
+ module ConceptQL
4
+ module Operators
5
+ class SnomedCondition < StandardVocabularyOperator
6
+ preferred_name 'SNOMED'
7
+ desc 'Find all condition_occurrences by SNOMED codes'
8
+ argument :snomeds, type: :codelist, vocab: 'SNOMED'
9
+ predominant_types :condition_occurrence
10
+
11
+ def table
12
+ :condition_occurrence
13
+ end
14
+
15
+ def vocabulary_id
16
+ 1
17
+ end
18
+
19
+ def concept_column
20
+ :condition_concept_id
21
+ end
22
+ end
23
+ end
24
+ end
25
+
26
+
@@ -1,8 +1,8 @@
1
- require_relative 'node'
1
+ require_relative 'operator'
2
2
 
3
3
  module ConceptQL
4
- module Nodes
5
- # A SourceVocabularyNode is a superclass for a node that represents a criterion whose column stores information associated with a source vocabulary.
4
+ module Operators
5
+ # A SourceVocabularyOperator is a superclass for a operator that represents a criterion whose column stores information associated with a source vocabulary.
6
6
  #
7
7
  # If that seems confusing, then think of ICD-9 or NDC criteria. That type of criterion takes a set of values that are mapped via the source_to_concept_map table into a standard vocabulary.
8
8
  #
@@ -25,7 +25,10 @@ module ConceptQL
25
25
  # * vocabulary_id
26
26
  # * The vocabulary ID of the source vocabulary for the criterion
27
27
  # * e.g. for ICD-9, a value of 2 (for ICD-9-CM)
28
- class SourceVocabularyNode < Node
28
+ class SourceVocabularyOperator < Operator
29
+ category 'Source Vocabulary'
30
+ category 'Code Lists'
31
+
29
32
  def query(db)
30
33
  db.from(table_name)
31
34
  .join(:vocabulary__source_to_concept_map___scm, scm__target_concept_id: table_concept_column)
@@ -1,8 +1,8 @@
1
- require_relative 'node'
1
+ require_relative 'operator'
2
2
 
3
3
  module ConceptQL
4
- module Nodes
5
- # A StandardVocabularyNode is a superclass for a node that represents a criterion whose column stores information associated with a standard vocabulary.
4
+ module Operators
5
+ # A StandardVocabularyOperator is a superclass for a operator that represents a criterion whose column stores information associated with a standard vocabulary.
6
6
  #
7
7
  # If that seems confusing, then think of CPT or SNOMED criteria. That type of criterion takes a set of values that live in the OMOP concept table.
8
8
  #
@@ -18,7 +18,9 @@ module ConceptQL
18
18
  # * vocabulary_id
19
19
  # * The vocabulary ID of the source vocabulary for the criterion
20
20
  # * e.g. for CPT, a value of 4 (for CPT-4)
21
- class StandardVocabularyNode < Node
21
+ class StandardVocabularyOperator < Operator
22
+ category 'Standard Vocabulary'
23
+ category 'Code Lists'
22
24
  def query(db)
23
25
  db.from(table_name)
24
26
  .join(:vocabulary__concept___c, c__concept_id: table_concept_column)
@@ -1,8 +1,14 @@
1
- require_relative 'temporal_node'
1
+ require_relative 'temporal_operator'
2
2
 
3
3
  module ConceptQL
4
- module Nodes
5
- class StartedBy < TemporalNode
4
+ module Operators
5
+ class StartedBy < TemporalOperator
6
+ desc <<-EOF
7
+ If LHR has the same start date as RHR, but LHR's end_date falls on or after end_date of RHR, LHR is passed through.
8
+ L----Y----L
9
+ R-------R
10
+ L--N--L
11
+ EOF
6
12
  def where_clause
7
13
  [ { l__start_date: :r__start_date } ] + \
8
14
  if inclusive?