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.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/CHANGELOG.md +53 -1
- data/Guardfile +52 -24
- data/conceptql.gemspec +5 -3
- data/doc/spec.md +2 -2
- data/lib/conceptql/behaviors/debuggable.rb +70 -0
- data/lib/conceptql/behaviors/dottable.rb +28 -14
- data/lib/conceptql/behaviors/metadatable.rb +97 -0
- data/lib/conceptql/behaviors/preppable.rb +20 -0
- data/lib/conceptql/cli.rb +31 -5
- data/lib/conceptql/converter.rb +65 -0
- data/lib/conceptql/debugger.rb +48 -0
- data/lib/conceptql/graph.rb +14 -13
- data/lib/conceptql/graph_nodifier.rb +49 -17
- data/lib/conceptql/nodifier.rb +31 -6
- data/lib/conceptql/operators/after.rb +25 -0
- data/lib/conceptql/operators/any_overlap.rb +15 -0
- data/lib/conceptql/operators/before.rb +21 -0
- data/lib/conceptql/{nodes/binary_operator_node.rb → operators/binary_operator_operator.rb} +12 -8
- data/lib/conceptql/{nodes/casting_node.rb → operators/casting_operator.rb} +8 -7
- data/lib/conceptql/{nodes → operators}/complement.rb +14 -11
- data/lib/conceptql/operators/concept.rb +70 -0
- data/lib/conceptql/{nodes → operators}/condition_type.rb +13 -5
- data/lib/conceptql/operators/contains.rb +17 -0
- data/lib/conceptql/operators/count.rb +26 -0
- data/lib/conceptql/operators/cpt.rb +25 -0
- data/lib/conceptql/{nodes → operators}/date_range.rb +11 -6
- data/lib/conceptql/operators/death.rb +23 -0
- data/lib/conceptql/operators/drug_type_concept.rb +21 -0
- data/lib/conceptql/{nodes → operators}/during.rb +9 -3
- data/lib/conceptql/operators/equal.rb +13 -0
- data/lib/conceptql/operators/except.rb +28 -0
- data/lib/conceptql/operators/filter.rb +21 -0
- data/lib/conceptql/{nodes → operators}/first.rb +7 -5
- data/lib/conceptql/{nodes → operators}/from.rb +2 -2
- data/lib/conceptql/operators/from_seer_visits.rb +23 -0
- data/lib/conceptql/{nodes → operators}/gender.rb +6 -6
- data/lib/conceptql/operators/hcpcs.rb +25 -0
- data/lib/conceptql/operators/icd10.rb +28 -0
- data/lib/conceptql/operators/icd9.rb +28 -0
- data/lib/conceptql/operators/icd9_procedure.rb +25 -0
- data/lib/conceptql/{nodes → operators}/intersect.rb +7 -3
- data/lib/conceptql/{nodes → operators}/last.rb +7 -5
- data/lib/conceptql/operators/loinc.rb +25 -0
- data/lib/conceptql/operators/medcode.rb +28 -0
- data/lib/conceptql/operators/medcode_procedure.rb +27 -0
- data/lib/conceptql/operators/ndc.rb +29 -0
- data/lib/conceptql/operators/numeric.rb +54 -0
- data/lib/conceptql/operators/observation_by_enttype.rb +29 -0
- data/lib/conceptql/operators/observation_period.rb +30 -0
- data/lib/conceptql/operators/occurrence.rb +70 -0
- data/lib/conceptql/operators/one_in_two_out.rb +62 -0
- data/lib/conceptql/{nodes/node.rb → operators/operator.rb} +84 -52
- data/lib/conceptql/operators/overlapped_by.rb +23 -0
- data/lib/conceptql/operators/overlaps.rb +19 -0
- data/lib/conceptql/operators/pass_thru.rb +11 -0
- data/lib/conceptql/{nodes → operators}/person.rb +8 -4
- data/lib/conceptql/operators/person_filter.rb +13 -0
- data/lib/conceptql/{nodes → operators}/place_of_service_code.rb +7 -7
- data/lib/conceptql/operators/procedure_occurrence.rb +25 -0
- data/lib/conceptql/operators/prodcode.rb +29 -0
- data/lib/conceptql/{nodes → operators}/race.rb +7 -7
- data/lib/conceptql/operators/recall.rb +38 -0
- data/lib/conceptql/operators/rxnorm.rb +24 -0
- data/lib/conceptql/operators/snomed.rb +24 -0
- data/lib/conceptql/operators/snomed_condition.rb +26 -0
- data/lib/conceptql/{nodes/source_vocabulary_node.rb → operators/source_vocabulary_operator.rb} +7 -4
- data/lib/conceptql/{nodes/standard_vocabulary_node.rb → operators/standard_vocabulary_operator.rb} +6 -4
- data/lib/conceptql/{nodes → operators}/started_by.rb +9 -3
- data/lib/conceptql/{nodes → operators}/sum.rb +9 -4
- data/lib/conceptql/{nodes/temporal_node.rb → operators/temporal_operator.rb} +7 -4
- data/lib/conceptql/{nodes → operators}/time_window.rb +19 -7
- data/lib/conceptql/operators/to_seer_visits.rb +24 -0
- data/lib/conceptql/operators/trim_date_end.rb +55 -0
- data/lib/conceptql/operators/trim_date_start.rb +56 -0
- data/lib/conceptql/{nodes → operators}/union.rb +6 -2
- data/lib/conceptql/operators/visit.rb +15 -0
- data/lib/conceptql/{nodes → operators}/visit_occurrence.rb +7 -3
- data/lib/conceptql/query.rb +19 -17
- data/lib/conceptql/scope.rb +69 -0
- data/lib/conceptql/tree.rb +33 -18
- data/lib/conceptql/utils/temp_table.rb +72 -0
- data/lib/conceptql/version.rb +1 -1
- data/spec/conceptql/behaviors/dottable_spec.rb +39 -51
- data/spec/conceptql/converter_spec.rb +51 -0
- data/spec/conceptql/date_adjuster_spec.rb +15 -15
- data/spec/conceptql/operators/after_spec.rb +16 -0
- data/spec/conceptql/operators/before_spec.rb +16 -0
- data/spec/conceptql/{nodes/casting_node_spec.rb → operators/casting_operator_spec.rb} +16 -20
- data/spec/conceptql/operators/complement_spec.rb +15 -0
- data/spec/conceptql/operators/concept_spec.rb +40 -0
- data/spec/conceptql/{nodes → operators}/condition_type_spec.rb +39 -24
- data/spec/conceptql/operators/contains_spec.rb +19 -0
- data/spec/conceptql/operators/cpt_spec.rb +29 -0
- data/spec/conceptql/operators/date_range_spec.rb +33 -0
- data/spec/conceptql/operators/death_spec.rb +10 -0
- data/spec/conceptql/operators/during_spec.rb +30 -0
- data/spec/conceptql/operators/except_spec.rb +15 -0
- data/spec/conceptql/operators/first_spec.rb +35 -0
- data/spec/conceptql/operators/from_spec.rb +13 -0
- data/spec/conceptql/operators/gender_spec.rb +27 -0
- data/spec/conceptql/operators/hcpcs_spec.rb +29 -0
- data/spec/conceptql/operators/icd10_spec.rb +34 -0
- data/spec/conceptql/operators/icd9_procedure_spec.rb +29 -0
- data/spec/conceptql/operators/icd9_spec.rb +34 -0
- data/spec/conceptql/operators/intersect_spec.rb +28 -0
- data/spec/conceptql/operators/last_spec.rb +36 -0
- data/spec/conceptql/operators/loinc_spec.rb +29 -0
- data/spec/conceptql/operators/medcode_procedure_spec.rb +34 -0
- data/spec/conceptql/operators/medcode_spec.rb +34 -0
- data/spec/conceptql/operators/observation_period_spec.rb +10 -0
- data/spec/conceptql/operators/occurrence_spec.rb +87 -0
- data/spec/conceptql/operators/overlapped_by_spec.rb +32 -0
- data/spec/conceptql/operators/overlaps_spec.rb +21 -0
- data/spec/conceptql/operators/person_filter_spec.rb +15 -0
- data/spec/conceptql/operators/person_spec.rb +10 -0
- data/spec/conceptql/{nodes → operators}/place_of_service_code_spec.rb +6 -8
- data/spec/conceptql/operators/procedure_occurrence_spec.rb +10 -0
- data/spec/conceptql/operators/prodcode_spec.rb +35 -0
- data/spec/conceptql/operators/query_double.rb +20 -0
- data/spec/conceptql/operators/query_double_spec.rb +7 -0
- data/spec/conceptql/operators/race_spec.rb +21 -0
- data/spec/conceptql/operators/rxnorm_spec.rb +29 -0
- data/spec/conceptql/operators/snomed_spec.rb +29 -0
- data/spec/conceptql/operators/source_vocabulary_operator_spec.rb +35 -0
- data/spec/conceptql/operators/standard_vocabulary_operator_spec.rb +35 -0
- data/spec/conceptql/operators/started_by_spec.rb +22 -0
- data/spec/conceptql/{nodes/temporal_node_spec.rb → operators/temporal_operator_spec.rb} +11 -17
- data/spec/conceptql/operators/time_window_spec.rb +77 -0
- data/spec/conceptql/operators/union_spec.rb +21 -0
- data/spec/conceptql/operators/visit_occurrence_spec.rb +10 -0
- data/spec/conceptql/query_spec.rb +10 -9
- data/spec/conceptql/tree_spec.rb +24 -28
- data/spec/doubles/stream_for_casting_double.rb +1 -1
- data/spec/doubles/stream_for_occurrence_double.rb +1 -1
- data/spec/doubles/stream_for_temporal_double.rb +1 -1
- data/spec/spec_helper.rb +74 -58
- metadata +202 -133
- data/lib/conceptql/nodes/after.rb +0 -12
- data/lib/conceptql/nodes/before.rb +0 -11
- data/lib/conceptql/nodes/concept.rb +0 -38
- data/lib/conceptql/nodes/count.rb +0 -23
- data/lib/conceptql/nodes/cpt.rb +0 -20
- data/lib/conceptql/nodes/death.rb +0 -19
- data/lib/conceptql/nodes/define.rb +0 -96
- data/lib/conceptql/nodes/drug_type_concept.rb +0 -18
- data/lib/conceptql/nodes/equal.rb +0 -11
- data/lib/conceptql/nodes/except.rb +0 -11
- data/lib/conceptql/nodes/hcpcs.rb +0 -20
- data/lib/conceptql/nodes/icd10.rb +0 -23
- data/lib/conceptql/nodes/icd9.rb +0 -23
- data/lib/conceptql/nodes/icd9_procedure.rb +0 -20
- data/lib/conceptql/nodes/loinc.rb +0 -20
- data/lib/conceptql/nodes/numeric.rb +0 -40
- data/lib/conceptql/nodes/occurrence.rb +0 -49
- data/lib/conceptql/nodes/pass_thru.rb +0 -11
- data/lib/conceptql/nodes/person_filter.rb +0 -12
- data/lib/conceptql/nodes/procedure_occurrence.rb +0 -21
- data/lib/conceptql/nodes/recall.rb +0 -50
- data/lib/conceptql/nodes/rxnorm.rb +0 -20
- data/lib/conceptql/nodes/snomed.rb +0 -19
- data/lib/conceptql/nodes/visit.rb +0 -11
- data/spec/conceptql/nodes/after_spec.rb +0 -18
- data/spec/conceptql/nodes/before_spec.rb +0 -18
- data/spec/conceptql/nodes/complement_spec.rb +0 -15
- data/spec/conceptql/nodes/concept_spec.rb +0 -34
- data/spec/conceptql/nodes/cpt_spec.rb +0 -31
- data/spec/conceptql/nodes/date_range_spec.rb +0 -35
- data/spec/conceptql/nodes/death_spec.rb +0 -12
- data/spec/conceptql/nodes/during_spec.rb +0 -32
- data/spec/conceptql/nodes/except_spec.rb +0 -18
- data/spec/conceptql/nodes/first_spec.rb +0 -37
- data/spec/conceptql/nodes/from_spec.rb +0 -15
- data/spec/conceptql/nodes/gender_spec.rb +0 -29
- data/spec/conceptql/nodes/hcpcs_spec.rb +0 -31
- data/spec/conceptql/nodes/icd10_spec.rb +0 -36
- data/spec/conceptql/nodes/icd9_procedure_spec.rb +0 -31
- data/spec/conceptql/nodes/icd9_spec.rb +0 -36
- data/spec/conceptql/nodes/intersect_spec.rb +0 -33
- data/spec/conceptql/nodes/last_spec.rb +0 -38
- data/spec/conceptql/nodes/loinc_spec.rb +0 -31
- data/spec/conceptql/nodes/occurrence_spec.rb +0 -89
- data/spec/conceptql/nodes/person_filter_spec.rb +0 -18
- data/spec/conceptql/nodes/person_spec.rb +0 -12
- data/spec/conceptql/nodes/procedure_occurrence_spec.rb +0 -12
- data/spec/conceptql/nodes/query_double.rb +0 -19
- data/spec/conceptql/nodes/race_spec.rb +0 -23
- data/spec/conceptql/nodes/rxnorm_spec.rb +0 -31
- data/spec/conceptql/nodes/snomed_spec.rb +0 -31
- data/spec/conceptql/nodes/source_vocabulary_node_spec.rb +0 -37
- data/spec/conceptql/nodes/standard_vocabulary_node_spec.rb +0 -40
- data/spec/conceptql/nodes/started_by_spec.rb +0 -25
- data/spec/conceptql/nodes/time_window_spec.rb +0 -85
- data/spec/conceptql/nodes/union_spec.rb +0 -25
- 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
|
-
|
|
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
|
|
5
|
-
class
|
|
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
|
-
:
|
|
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
|
-
|
|
17
|
-
|
|
24
|
+
|
|
25
|
+
attr :values, :options, :arguments, :upstreams
|
|
26
|
+
|
|
27
|
+
option :label, type: :string
|
|
28
|
+
|
|
18
29
|
def initialize(*args)
|
|
19
|
-
args
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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 ||=
|
|
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
|
-
|
|
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 :
|
|
113
|
-
Sequel.cast_numeric(nil, Float).as(:
|
|
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
|
|
224
|
+
if upstreams.empty?
|
|
188
225
|
if respond_to?(:type)
|
|
189
226
|
[type]
|
|
190
227
|
else
|
|
191
|
-
raise "
|
|
228
|
+
raise "Operator doesn't seem to specify any type"
|
|
192
229
|
end
|
|
193
230
|
else
|
|
194
|
-
|
|
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
|
|
@@ -1,8 +1,12 @@
|
|
|
1
|
-
require_relative '
|
|
1
|
+
require_relative 'casting_operator'
|
|
2
2
|
|
|
3
3
|
module ConceptQL
|
|
4
|
-
module
|
|
5
|
-
class Person <
|
|
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
|
|
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 '
|
|
1
|
+
require_relative 'operator'
|
|
2
2
|
|
|
3
3
|
module ConceptQL
|
|
4
|
-
module
|
|
5
|
-
# Represents a
|
|
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 <
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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 '
|
|
1
|
+
require_relative 'operator'
|
|
2
2
|
|
|
3
3
|
module ConceptQL
|
|
4
|
-
module
|
|
5
|
-
# Represents a
|
|
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 <
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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
|
+
|
data/lib/conceptql/{nodes/source_vocabulary_node.rb → operators/source_vocabulary_operator.rb}
RENAMED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
require_relative '
|
|
1
|
+
require_relative 'operator'
|
|
2
2
|
|
|
3
3
|
module ConceptQL
|
|
4
|
-
module
|
|
5
|
-
# A
|
|
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
|
|
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)
|
data/lib/conceptql/{nodes/standard_vocabulary_node.rb → operators/standard_vocabulary_operator.rb}
RENAMED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
require_relative '
|
|
1
|
+
require_relative 'operator'
|
|
2
2
|
|
|
3
3
|
module ConceptQL
|
|
4
|
-
module
|
|
5
|
-
# A
|
|
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
|
|
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 '
|
|
1
|
+
require_relative 'temporal_operator'
|
|
2
2
|
|
|
3
3
|
module ConceptQL
|
|
4
|
-
module
|
|
5
|
-
class StartedBy <
|
|
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?
|