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
@@ -1,7 +1,7 @@
1
1
  require 'psych'
2
2
  require 'graphviz'
3
3
  require_relative 'tree'
4
- require_relative 'nodes/node'
4
+ require_relative 'operators/operator'
5
5
  require_relative 'behaviors/dottable'
6
6
 
7
7
  module ConceptQL
@@ -14,16 +14,16 @@ module ConceptQL
14
14
  @tree = opts.fetch(:tree, Tree.new)
15
15
  @title = opts.fetch(:title, nil)
16
16
  @suffix = opts.fetch(:suffix, 'pdf')
17
- ConceptQL::Nodes::Node.send(:include, ConceptQL::Behaviors::Dottable)
17
+ ConceptQL::Operators::Operator.send(:include, ConceptQL::Behaviors::Dottable)
18
18
  end
19
19
 
20
20
  def graph_it(file_path)
21
21
  build_graph(g)
22
- g.output(suffix.to_sym => file_path + ".#{suffix}")
22
+ graph.output(suffix.to_sym => file_path + ".#{suffix}")
23
23
  end
24
24
 
25
- def g
26
- @g ||= begin
25
+ def graph
26
+ @graph ||= begin
27
27
  opts = { type: :digraph }
28
28
  opts[:label] = title if title
29
29
  GraphViz.new(:G, opts)
@@ -34,15 +34,16 @@ module ConceptQL
34
34
  attr :yaml, :tree, :db
35
35
 
36
36
  def build_graph(g)
37
- tree.root(self).each.with_index do |last_node, index|
38
- last_node.graph_it(g, db)
37
+ tree.root(self).each.with_index do |last_operator, index|
38
+ last_operator.build_temp_tables(db)
39
+ last_operator.graph_it(g, db)
39
40
  if dangler
40
- blank_node = g.add_nodes("_#{index}")
41
- blank_node[:shape] = 'none'
42
- blank_node[:height] = 0
43
- blank_node[:label] = ''
44
- blank_node[:fixedsize] = true
45
- last_node.link_to(g, blank_node, db)
41
+ blank_operator = g.add_nodes("_#{index}")
42
+ blank_operator[:shape] = 'none'
43
+ blank_operator[:height] = 0
44
+ blank_operator[:label] = ''
45
+ blank_operator[:fixedsize] = true
46
+ last_operator.link_to(g, blank_operator, db)
46
47
  end
47
48
  end
48
49
  end
@@ -1,9 +1,9 @@
1
1
  require_relative 'behaviors/dottable'
2
- require_relative 'nodes/node'
2
+ require_relative 'operators/operator'
3
3
 
4
4
  module ConceptQL
5
5
  class GraphNodifier
6
- class DotNode < ConceptQL::Nodes::Node
6
+ class DotOperator < ConceptQL::Operators::Operator
7
7
  include ConceptQL::Behaviors::Dottable
8
8
 
9
9
  TYPES = {
@@ -13,6 +13,7 @@ module ConceptQL
13
13
  icd9: :condition_occurrence,
14
14
  icd10: :condition_occurrence,
15
15
  condition_type: :condition_occurrence,
16
+ medcode: :condition_occurrence,
16
17
 
17
18
  # Procedures
18
19
  procedure: :procedure_occurrence,
@@ -21,6 +22,7 @@ module ConceptQL
21
22
  hcpcs: :procedure_occurrence,
22
23
  icd9_procedure: :procedure_occurrence,
23
24
  procedure_cost: :procedure_cost,
25
+ medcode_procedure: :procedure_occurrence,
24
26
 
25
27
  # Visits
26
28
  visit_occurrence: :visit_occurrence,
@@ -40,6 +42,8 @@ module ConceptQL
40
42
 
41
43
  # Observation
42
44
  loinc: :observation,
45
+ from_seer_visits: :observation,
46
+ to_seer_visits: :observation,
43
47
 
44
48
  # Drug
45
49
  drug_exposure: :drug_exposure,
@@ -47,11 +51,12 @@ module ConceptQL
47
51
  drug_cost: :drug_cost,
48
52
  drug_type_concept_id: :drug_exposure,
49
53
  drug_type_concept: :drug_exposure,
54
+ prodcode: :drug_exposure,
50
55
 
51
- # Date Nodes
56
+ # Date Operators
52
57
  date_range: :date,
53
58
 
54
- # Miscelaneous nodes
59
+ # Miscelaneous operators
55
60
  concept: :misc,
56
61
  vsac: :misc
57
62
  }
@@ -78,12 +83,12 @@ module ConceptQL
78
83
  end
79
84
 
80
85
  def types
81
- types = [TYPES[name.to_sym] || children.map(&:types)].flatten.uniq
86
+ types = [TYPES[name.to_sym] || upstreams.map(&:types)].flatten.uniq
82
87
  types.empty? ? [:misc] : types
83
88
  end
84
89
  end
85
90
 
86
- class BinaryOperatorNode < DotNode
91
+ class BinaryOperatorOperator < DotOperator
87
92
  def display_name
88
93
  output = name
89
94
  output += "\n#{displayable_options.map{|k,v| "#{k}: #{v}"}.join("\n")}"
@@ -105,7 +110,7 @@ module ConceptQL
105
110
  def graph_it(g, db)
106
111
  left.graph_it(g, db)
107
112
  right.graph_it(g, db)
108
- cluster_name = "cluster_#{node_name}"
113
+ cluster_name = "cluster_#{operator_name}"
109
114
  me = g.send(cluster_name) do |sub|
110
115
  sub[rank: 'same', label: display_name, color: 'black']
111
116
  sub.send("#{cluster_name}_left").send('[]', shape: 'point', color: type_color(types))
@@ -113,7 +118,7 @@ module ConceptQL
113
118
  end
114
119
  left.link_to(g, me.send("#{cluster_name}_left"))
115
120
  right.link_to(g, me.send("#{cluster_name}_right"))
116
- @__graph_node = me.send("#{cluster_name}_left")
121
+ @__graph_operator = me.send("#{cluster_name}_left")
117
122
  end
118
123
 
119
124
  def types
@@ -125,19 +130,37 @@ module ConceptQL
125
130
  end
126
131
  end
127
132
 
128
- class DefineNode < DotNode
133
+ class LetOperator < DotOperator
134
+ def graph_it(g, db)
135
+ cluster_name = "cluster_#{operator_name}"
136
+ linkable = nil
137
+ g.send(cluster_name) do |sub|
138
+ linkable = upstreams.reverse.map do |upstream|
139
+ upstream.graph_it(sub, db)
140
+ end.first
141
+ sub[label: display_name, color: 'black']
142
+ end
143
+ @__graph_operator = linkable
144
+ end
145
+
146
+ def types
147
+ upstreams.last.types
148
+ end
149
+ end
150
+
151
+ class DefineOperator < DotOperator
129
152
  def shape
130
153
  :cds
131
154
  end
132
155
  end
133
156
 
134
- class RecallNode < DotNode
157
+ class RecallOperator < DotOperator
135
158
  def shape
136
159
  :cds
137
160
  end
138
161
  end
139
162
 
140
- class VsacNode < DotNode
163
+ class VsacOperator < DotOperator
141
164
  def initialize(name, values, types)
142
165
  @types = types
143
166
  super(name, values)
@@ -150,22 +173,31 @@ module ConceptQL
150
173
 
151
174
  BINARY_OPERATOR_TYPES = %w(before after meets met_by started_by starts contains during overlaps overlapped_by finished_by finishes coincides except person_filter less_than less_than_or_equal equal not_equal greater_than greater_than_or_equal filter).map { |temp| [temp, "not_#{temp}"] }.flatten.map(&:to_sym)
152
175
 
176
+ def temp_tables
177
+ @temp_tables ||= {}
178
+ end
179
+
153
180
  def types
154
181
  @types ||= {}
155
182
  end
156
183
 
157
184
  def create(type, values, tree)
158
- if BINARY_OPERATOR_TYPES.include?(type)
159
- return BinaryOperatorNode.new(type, values)
185
+ operator = if BINARY_OPERATOR_TYPES.include?(type)
186
+ BinaryOperatorOperator.new(type, values)
187
+ elsif type == :let
188
+ LetOperator.new(type, values)
160
189
  elsif type == :define
161
- return DefineNode.new(type, values).tap { |n| n.tree = self }
190
+ DefineOperator.new(type, values)
162
191
  elsif type == :recall
163
- return RecallNode.new(type, values).tap { |n| n.tree = self }
192
+ RecallOperator.new(type, values)
164
193
  elsif type == :vsac
165
194
  types = values.pop
166
- return VsacNode.new(type, values, types)
195
+ VsacOperator.new(type, values, types)
196
+ else
197
+ DotOperator.new(type, values)
167
198
  end
168
- DotNode.new(type, values)
199
+ operator.tree = self
200
+ operator
169
201
  end
170
202
  end
171
203
  end
@@ -1,11 +1,36 @@
1
- require 'active_support/inflector'
1
+ require 'facets/hash/deep_rekey'
2
+ require 'facets/pathname/chdir'
3
+ require 'facets/string/modulize'
4
+
2
5
  module ConceptQL
3
6
  class Nodifier
4
- def create(type, values, tree)
5
- require_relative "nodes/#{type}"
6
- node = "conceptQL/nodes/#{type}".camelize.constantize.new(values)
7
- node.tree = tree
8
- node
7
+ attr_reader :operators
8
+
9
+ def initialize
10
+ @operators = {}
11
+ dir = Pathname.new(__FILE__).dirname()
12
+ dir.chdir do
13
+ Pathname.glob("operators/*.rb").each do |file|
14
+ require_relative file
15
+ operator = file.basename('.*').to_s.to_sym
16
+ klass = Object.const_get("conceptQL/operators/#{operator}".modulize)
17
+ @operators[operator] = klass
18
+ end
19
+ end
20
+ end
21
+
22
+ def create(scope, operator, *values)
23
+ operator = operator.to_sym
24
+ if operators[operator].nil?
25
+ raise "Can't find operator for '#{operator}' in #{operators.keys.sort}"
26
+ end
27
+ operator = operators[operator].new(*values)
28
+ operator.scope = scope
29
+ operator
30
+ end
31
+
32
+ def to_metadata
33
+ Hash[operators.map { |k, v| [k, v.to_metadata]}.select { |k, v| v[:desc] }]
9
34
  end
10
35
  end
11
36
  end
@@ -0,0 +1,25 @@
1
+ require_relative 'temporal_operator'
2
+
3
+ module ConceptQL
4
+ module Operators
5
+ class After < 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 after the earliest end_date of 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
+ R-----R
13
+ L-----Y----L
14
+ EOF
15
+ def right_stream(db)
16
+ right.evaluate(db).from_self.group_by(:person_id).select(:person_id, Sequel.function(:min, :end_date).as(:end_date)).as(:r)
17
+ end
18
+
19
+ def where_clause
20
+ Proc.new { l__start_date > r__end_date }
21
+ end
22
+ end
23
+ end
24
+ end
25
+
@@ -0,0 +1,15 @@
1
+ require_relative 'temporal_operator'
2
+
3
+ module ConceptQL
4
+ module Operators
5
+ class AnyOverlap < TemporalOperator
6
+ desc 'If a result from the LHR overlaps in any way a result from the RHR it is passed along.'
7
+ def where_clause
8
+ l_partly_in_r = Sequel.expr { r__start_date <= l__start_date }.&(Sequel.expr { l__start_date <= r__end_date })
9
+ r_partly_in_l = Sequel.expr { l__start_date <= r__start_date }.&(Sequel.expr { r__start_date <= l__end_date })
10
+ l_partly_in_r.|(r_partly_in_l)
11
+ end
12
+ end
13
+ end
14
+ end
15
+
@@ -0,0 +1,21 @@
1
+ require_relative 'temporal_operator'
2
+
3
+ module ConceptQL
4
+ module Operators
5
+ class Before < 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 before the most recent start_date of the RHR, that result is passed through.
9
+ All other results are discarded, including all results in the RHR.
10
+ EOF
11
+
12
+ def right_stream(db)
13
+ right.evaluate(db).from_self.group_by(:person_id).select(:person_id, Sequel.function(:max, :start_date).as(:start_date)).as(:r)
14
+ end
15
+
16
+ def where_clause
17
+ Proc.new { l__end_date < r__start_date }
18
+ end
19
+ end
20
+ end
21
+ end
@@ -1,17 +1,21 @@
1
- require_relative 'node'
1
+ require_relative 'operator'
2
+ require 'facets/string/titlecase'
2
3
 
3
4
  module ConceptQL
4
- module Nodes
5
- # Base class for all nodes that take two streams, a left-hand and a right-hand
6
- class BinaryOperatorNode < Node
7
- def children
5
+ module Operators
6
+ # Base class for all operators that take two streams, a left-hand and a right-hand
7
+ class BinaryOperatorOperator < Operator
8
+ option :left, type: :upstream
9
+ option :right, type: :upstream
10
+
11
+ def upstreams
8
12
  [left]
9
13
  end
10
14
 
11
15
  def graph_it(g, db)
12
16
  left.graph_it(g, db)
13
17
  right.graph_it(g, db)
14
- cluster_name = "cluster_#{node_name}"
18
+ cluster_name = "cluster_#{operator_name}"
15
19
  me = g.send(cluster_name) do |sub|
16
20
  sub[rank: 'same', label: display_name, color: 'black']
17
21
  sub.send("#{cluster_name}_left").send('[]', shape: 'point', color: type_color(types))
@@ -19,11 +23,11 @@ module ConceptQL
19
23
  end
20
24
  left.link_to(g, me.send("#{cluster_name}_left"), db)
21
25
  right.link_to(g, me.send("#{cluster_name}_right"), db)
22
- @__graph_node = me.send("#{cluster_name}_left")
26
+ @__graph_operator = me.send("#{cluster_name}_left")
23
27
  end
24
28
 
25
29
  def display_name
26
- self.class.name.split('::').last.titleize
30
+ self.class.name.split('::').last.snakecase.titlecase
27
31
  end
28
32
 
29
33
  private
@@ -1,28 +1,29 @@
1
- require_relative 'node'
1
+ require_relative 'operator'
2
2
 
3
3
  module ConceptQL
4
- module Nodes
5
- # Parent class of all casting nodes
4
+ module Operators
5
+ # Parent class of all casting operators
6
6
  #
7
7
  # Subclasses must implement the following methods:
8
8
  # - my_type
9
9
  # - i_point_at
10
10
  # - these_point_at_me
11
11
  #
12
- # i_point_at returns a list of types for which the node's table of origin
12
+ # i_point_at returns a list of types for which the operator's table of origin
13
13
  # has foreign_keys pointing to another table, e.g.:
14
14
  # procedure_occurrence has an FK to visit_occurrence, so we'd put
15
15
  # :visit_occurrence in the i_point_at array
16
16
  #
17
17
  # these_point_at_me is a list of types for which that type's table
18
- # of origin has a FK pointing to the current node's
18
+ # of origin has a FK pointing to the current operator's
19
19
  # table of origin, e.g.:
20
20
  # procedure_cost has an FK to procedure_occurrence so we'd
21
21
  # put :procedure_cost in procedure_occurrence's these_point_at_me array
22
22
  #
23
- # Also, if a casting node is passed no streams, it will return all the
23
+ # Also, if a casting operator is passed no streams, it will return all the
24
24
  # rows in its table as results.
25
- class CastingNode < Node
25
+ class CastingOperator < Operator
26
+ category 'Casting'
26
27
  def types
27
28
  [type]
28
29
  end
@@ -1,12 +1,16 @@
1
1
  require_relative 'pass_thru'
2
2
 
3
3
  module ConceptQL
4
- module Nodes
4
+ module Operators
5
5
  class Complement < PassThru
6
+ desc 'Splits up the incoming result set by type and passes through all results for each type that are NOT in the current set.'
7
+ allows_one_upstream
8
+ category 'Set Logic'
9
+
6
10
  def query(db)
7
- child = children.first
8
- child.types.map do |type|
9
- positive_query = db.from(child.evaluate(db))
11
+ upstream = upstreams.first
12
+ upstream.types.map do |type|
13
+ positive_query = db.from(upstream.evaluate(db))
10
14
  .select(:criterion_id)
11
15
  .exclude(:criterion_id => nil)
12
16
  .where(:criterion_type => type.to_s)
@@ -18,18 +22,17 @@ module ConceptQL
18
22
  end
19
23
  end
20
24
 
21
- =begin
22
- This is an alternate, but equally accurate way to do complement.
23
- We'll need to benchmark which is faster.
25
+
26
+ # This is an alternate, but equally accurate way to do complement.
27
+ # We'll need to benchmark which is faster.
24
28
  def query2(db)
25
- child = children.first
26
- froms = child.types.map do |type|
29
+ upstream = upstreams.first
30
+ froms = upstream.types.map do |type|
27
31
  select_it(db.from(make_table_name(type)), type)
28
32
  end
29
33
  big_from = froms.inject { |union_query, q| union_query.union(q, all:true) }
30
- db.from(big_from).except(child.evaluate(db))
34
+ db.from(big_from).except(upstream.evaluate(db))
31
35
  end
32
- =end
33
36
  end
34
37
  end
35
38
  end
@@ -0,0 +1,70 @@
1
+ require_relative 'operator'
2
+ require_relative '../query'
3
+
4
+ module ConceptQL
5
+ module Operators
6
+ class Concept < Operator
7
+ desc 'Given the UUID of another ConceptQL statement, returns the results of that statement.'
8
+ attr :cql_query
9
+ def query(db)
10
+ set_cql_query(db)
11
+ db.from(cql_query.query.from_self)
12
+ end
13
+
14
+ def upstreams
15
+ @upstreams ||= cql_query.operators
16
+ end
17
+
18
+ def graph_prep(db)
19
+ set_cql_query(db)
20
+ puts statement.inspect
21
+ @arguments = [description(db)]
22
+ end
23
+
24
+ def print_prep(db)
25
+ set_cql_query(db)
26
+ end
27
+
28
+ def ensure_temp_tables(db)
29
+ set_cql_query(db)
30
+ super
31
+ end
32
+
33
+ def build_temp_tables(db)
34
+ set_cql_query(db)
35
+ super
36
+ end
37
+
38
+ private
39
+
40
+ def set_cql_query(db)
41
+ @cql_query ||= begin
42
+ set_statement(db)
43
+ Query.new(db, statement)
44
+ end
45
+ end
46
+
47
+ def statement
48
+ raise "Statement is nil!" unless @statement
49
+ @statement
50
+ end
51
+
52
+ def set_statement(db)
53
+ @statement ||= get_statement(db)
54
+ end
55
+
56
+ def get_statement(db)
57
+ concept = db[:concepts].where(concept_id: arguments.first).limit(1)
58
+ statement = concept.select_map(:statement).first.tap { |f| ConceptQL.logger.debug f.inspect }
59
+ statement = JSON.parse(statement) if statement.is_a?(String)
60
+ statement
61
+ end
62
+
63
+ def description(db)
64
+ @description ||= db[:concepts].where(concept_id: arguments.first).select_map(:label).first
65
+ end
66
+ end
67
+ end
68
+ end
69
+
70
+
@@ -1,15 +1,20 @@
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 conditions that match the
4
+ module Operators
5
+ # Represents a operator that will grab all conditions that match the
6
6
  # condition type passed in
7
7
  #
8
8
  # Condition Type represents which position the condition held in
9
9
  # the raw data, e.g. primary inpatient header or 15th outpatient detail
10
10
  #
11
11
  # Multiple types can be specified at once
12
- class ConditionType < Node
12
+ class ConditionType < Operator
13
+ desc 'Searches for conditions that match the given set of Condition Types'
14
+ argument :condition_types, type: :codelist, vocab: 'Condition Type'
15
+ category %(Occurrence Type)
16
+ predominant_types :condition_occurrence
17
+
13
18
  def type
14
19
  :condition_occurrence
15
20
  end
@@ -29,7 +34,7 @@ module ConceptQL
29
34
  def to_concept_id(ctype)
30
35
  ctype = ctype.to_s.downcase
31
36
  position = nil
32
- if ctype =~ /(\d|_primary)$/
37
+ if ctype =~ /(\d|_primary)$/ && ctype.count('_') > 1
33
38
  parts = ctype.split('_')
34
39
  position = parts.pop.to_i
35
40
  position -= 1 if ctype =~ /^outpatient/
@@ -51,6 +56,9 @@ module ConceptQL
51
56
  condition_era_0_day_window: [38000246],
52
57
  condition_era_30_day_window: [38000247]
53
58
  }
59
+ hash[:primary] = %w(inpatient_detail inpatient_header outpatient_detail outpatient_header).map { |w| hash[w.to_sym].first }
60
+ hash[:outpatient_primary] = %w(outpatient_detail outpatient_header).map { |w| hash[w.to_sym].first }
61
+ hash[:inpatient_primary] = %w(inpatient_detail inpatient_header).map { |w| hash[w.to_sym].first }
54
62
  hash[:inpatient] = hash[:inpatient_detail] + hash[:inpatient_header]
55
63
  hash[:outpatient] = hash[:outpatient_detail] + hash[:outpatient_header]
56
64
  hash[:condition_era] = hash[:condition_era_0_day_window] + hash[:condition_era_30_day_window]
@@ -0,0 +1,17 @@
1
+ require_relative 'temporal_operator'
2
+
3
+ module ConceptQL
4
+ module Operators
5
+ 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
12
+ def where_clause
13
+ [Proc.new { l__start_date <= r__start_date}, Proc.new { r__end_date <= l__end_date }]
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,26 @@
1
+ require_relative 'pass_thru'
2
+
3
+ module ConceptQL
4
+ module Operators
5
+ class Count < PassThru
6
+ desc 'Counts the number of results the exactly match across all columns.'
7
+ allows_one_upstream
8
+
9
+ def query(db)
10
+ db.from(unioned(db))
11
+ .group(*COLUMNS)
12
+ .select(*(COLUMNS - [:value_as_number]))
13
+ .select_append{count(1).as(:value_as_number)}
14
+ .from_self
15
+ end
16
+
17
+ def unioned(db)
18
+ upstreams.map { |c| c.evaluate(db) }.inject do |uni, q|
19
+ uni.union(q)
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+
26
+
@@ -0,0 +1,25 @@
1
+ require_relative 'standard_vocabulary_operator'
2
+
3
+ module ConceptQL
4
+ module Operators
5
+ class Cpt < StandardVocabularyOperator
6
+ preferred_name 'CPT'
7
+ desc 'Searches the procedure_occurrence table for all procedures with matching CPT codes'
8
+ argument :cpts, type: :codelist, vocab: 'CPT4'
9
+ predominant_types :procedure_occurrence
10
+
11
+ def table
12
+ :procedure_occurrence
13
+ end
14
+
15
+ def vocabulary_id
16
+ 4
17
+ end
18
+
19
+ def concept_column
20
+ :procedure_concept_id
21
+ end
22
+ end
23
+ end
24
+ end
25
+
@@ -1,13 +1,18 @@
1
- require_relative 'node'
1
+ require_relative 'operator'
2
2
 
3
3
  module ConceptQL
4
- module Nodes
5
- # Represents a node that will create a date_range for every person in the database
4
+ module Operators
5
+ # Represents a operator that will create a date_range for every person in the database
6
6
  #
7
7
  # Accepts two params: start and end formateed as 'YYYY-MM-DD' or 'START' or 'END'
8
8
  # 'START' represents the first date of data in the data source,
9
9
  # 'END' represents the last date of data in the data source,
10
- class DateRange < Node
10
+ class DateRange < Operator
11
+ desc 'Used to represent a date literal.'
12
+ option :start, type: :string
13
+ option :end, type: :string
14
+ category %(Temporal Manipulation)
15
+
11
16
  def query(db)
12
17
  db.from(:person)
13
18
  .select_append(Sequel.cast_string('person').as(:criterion_type))
@@ -31,8 +36,8 @@ module ConceptQL
31
36
  # TODO: Select the earliest and latest dates of observation from
32
37
  # the proper CDM table to represent the start and end of data
33
38
  def date_from(db, str)
34
- return db.from(:visit_occurrence).select { min(:start_date) } if str.upcase == 'START'
35
- return db.from(:visit_occurrence).select { max(:end_date) } if str.upcase == 'END'
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'
36
41
  return Sequel.lit('CONVERT(DATETIME, ?)', str) if db.database_type == :mssql
37
42
  return Sequel.lit('date ?', str)
38
43
  end