conceptql 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/CHANGELOG.md +226 -117
- data/LICENSE +21 -0
- data/README.md +35 -83
- data/bin/conceptql +2 -1
- data/bin/conceptql_utils +6 -0
- data/config/multiple_vocabularies.csv +4 -0
- data/config/provenance.yml +489 -0
- data/config/race.yml +50 -0
- data/config/vocabularies.csv +87 -0
- data/lib/conceptql/behaviors/code_lister.rb +23 -0
- data/lib/conceptql/behaviors/drugish.rb +17 -0
- data/lib/conceptql/behaviors/labish.rb +19 -0
- data/lib/conceptql/behaviors/metadatable.rb +226 -73
- data/lib/conceptql/behaviors/provenanceable.rb +26 -0
- data/lib/conceptql/behaviors/single_event.rb +11 -0
- data/lib/conceptql/behaviors/timeless.rb +11 -0
- data/lib/conceptql/behaviors/utilizable.rb +206 -0
- data/lib/conceptql/behaviors/windowable.rb +27 -0
- data/lib/conceptql/cli.rb +117 -108
- data/lib/conceptql/columnizer.rb +75 -0
- data/lib/conceptql/data_model/base.rb +462 -0
- data/lib/conceptql/data_model/gdm.rb +155 -0
- data/lib/conceptql/data_model/gdm_wide.rb +42 -0
- data/lib/conceptql/data_model/omopv4_plus.rb +12 -0
- data/lib/conceptql/data_model.rb +30 -0
- data/lib/conceptql/database.rb +214 -0
- data/lib/conceptql/date_adjuster.rb +66 -18
- data/lib/conceptql/diagram.rb +243 -0
- data/lib/conceptql/lexicon/lexicon_gdm.rb +47 -0
- data/lib/conceptql/lexicon/lexicon_no_db.rb +49 -0
- data/lib/conceptql/lexicon/lexicon_ohdsi.rb +63 -0
- data/lib/conceptql/lexicon/lexicon_strategy.rb +116 -0
- data/lib/conceptql/lexicon.rb +111 -0
- data/lib/conceptql/nodifier.rb +65 -24
- data/lib/conceptql/operators/after.rb +34 -14
- data/lib/conceptql/operators/any_overlap.rb +8 -6
- data/lib/conceptql/operators/before.rb +34 -10
- data/lib/conceptql/operators/binary_operator_operator.rb +66 -23
- data/lib/conceptql/operators/casting_operator.rb +51 -36
- data/lib/conceptql/operators/co_reported.rb +111 -0
- data/lib/conceptql/operators/concurrent_within.rb +69 -0
- data/lib/conceptql/operators/contains.rb +12 -8
- data/lib/conceptql/operators/count.rb +17 -9
- data/lib/conceptql/operators/date_range.rb +48 -14
- data/lib/conceptql/operators/death.rb +14 -4
- data/lib/conceptql/operators/dedup.rb +31 -0
- data/lib/conceptql/operators/during.rb +7 -12
- data/lib/conceptql/operators/episode.rb +146 -0
- data/lib/conceptql/operators/equal.rb +9 -3
- data/lib/conceptql/operators/ethnicity.rb +49 -0
- data/lib/conceptql/operators/except.rb +19 -12
- data/lib/conceptql/operators/filter.rb +23 -8
- data/lib/conceptql/operators/first.rb +16 -3
- data/lib/conceptql/operators/from.rb +52 -4
- data/lib/conceptql/operators/gender.rb +34 -11
- data/lib/conceptql/operators/hospice.rb +19 -0
- data/lib/conceptql/operators/hospitalization.rb +19 -0
- data/lib/conceptql/operators/information_periods.rb +36 -0
- data/lib/conceptql/operators/invalid.rb +35 -0
- data/lib/conceptql/operators/last.rb +16 -3
- data/lib/conceptql/operators/match.rb +91 -0
- data/lib/conceptql/operators/multiple_vocabularies.rb +172 -0
- data/lib/conceptql/operators/numeric.rb +65 -23
- data/lib/conceptql/operators/numeric_filter.rb +68 -0
- data/lib/conceptql/operators/occurrence.rb +153 -32
- data/lib/conceptql/operators/one_in_two_out.rb +237 -38
- data/lib/conceptql/operators/operator.rb +624 -178
- data/lib/conceptql/operators/pass_thru.rb +17 -3
- data/lib/conceptql/operators/person.rb +18 -6
- data/lib/conceptql/operators/person_filter.rb +9 -3
- data/lib/conceptql/operators/place_of_service_filter.rb +43 -0
- data/lib/conceptql/operators/provenance.rb +55 -0
- data/lib/conceptql/operators/provider_filter.rb +62 -0
- data/lib/conceptql/operators/race.rb +55 -7
- data/lib/conceptql/operators/read.rb +168 -0
- data/lib/conceptql/operators/recall.rb +49 -14
- data/lib/conceptql/operators/snf.rb +19 -0
- data/lib/conceptql/operators/sum.rb +29 -12
- data/lib/conceptql/operators/temporal_operator.rb +108 -16
- data/lib/conceptql/operators/time_window.rb +20 -32
- data/lib/conceptql/operators/trim_date.rb +71 -0
- data/lib/conceptql/operators/trim_date_end.rb +28 -40
- data/lib/conceptql/operators/trim_date_start.rb +28 -41
- data/lib/conceptql/operators/union.rb +67 -6
- data/lib/conceptql/operators/vocabulary.rb +151 -0
- data/lib/conceptql/paths.rb +41 -0
- data/lib/conceptql/query.rb +191 -26
- data/lib/conceptql/query_modifiers/gdm/admission_date_query_modifier.rb +46 -0
- data/lib/conceptql/query_modifiers/gdm/drug_query_modifier.rb +54 -0
- data/lib/conceptql/query_modifiers/gdm/lab_query_modifier.rb +57 -0
- data/lib/conceptql/query_modifiers/gdm/pos_query_modifier.rb +46 -0
- data/lib/conceptql/query_modifiers/gdm/provenance_query_modifier.rb +45 -0
- data/lib/conceptql/query_modifiers/gdm/provider_query_modifier.rb +53 -0
- data/lib/conceptql/query_modifiers/gdm_wide/admission_date_query_modifier.rb +42 -0
- data/lib/conceptql/query_modifiers/gdm_wide/drug_query_modifier.rb +52 -0
- data/lib/conceptql/query_modifiers/gdm_wide/lab_query_modifier.rb +55 -0
- data/lib/conceptql/query_modifiers/gdm_wide/pos_query_modifier.rb +37 -0
- data/lib/conceptql/query_modifiers/gdm_wide/provenance_query_modifier.rb +38 -0
- data/lib/conceptql/query_modifiers/omopv4_plus/admission_date_query_modifier.rb +45 -0
- data/lib/conceptql/query_modifiers/omopv4_plus/drug_query_modifier.rb +80 -0
- data/lib/conceptql/query_modifiers/omopv4_plus/pos_query_modifier.rb +47 -0
- data/lib/conceptql/query_modifiers/omopv4_plus/provenance_query_modifier.rb +45 -0
- data/lib/conceptql/query_modifiers/omopv4_plus/provider_query_modifier.rb +26 -0
- data/lib/conceptql/query_modifiers/query_modifier.rb +20 -0
- data/lib/conceptql/rdbms/duckdb.rb +25 -0
- data/lib/conceptql/rdbms/generic.rb +120 -0
- data/lib/conceptql/rdbms/postgres.rb +51 -0
- data/lib/conceptql/rdbms/spark.rb +22 -0
- data/lib/conceptql/rdbms.rb +22 -0
- data/lib/conceptql/scope.rb +481 -31
- data/lib/conceptql/sql_formatters/formatter.rb +25 -0
- data/lib/conceptql/sql_formatters.rb +22 -0
- data/lib/conceptql/utils.rb +113 -0
- data/lib/conceptql/utils_cli.rb +17 -0
- data/lib/conceptql/version.rb +3 -1
- data/lib/conceptql/visitors/metadata_extractor.rb +54 -0
- data/lib/conceptql/vocabularies/behaviors/costish.rb +24 -0
- data/lib/conceptql/vocabularies/behaviors/gdmish.rb +19 -0
- data/lib/conceptql/vocabularies/behaviors/omopish.rb +32 -0
- data/lib/conceptql/vocabularies/dynamic_vocabularies.rb +63 -0
- data/lib/conceptql/vocabularies/entry.rb +138 -0
- data/lib/conceptql/vocabularies/vocabulary_class_factory.rb +85 -0
- data/lib/conceptql/window/base.rb +26 -0
- data/lib/conceptql/window/date_range.rb +25 -0
- data/lib/conceptql/window/table.rb +116 -0
- data/lib/conceptql/window.rb +72 -0
- data/lib/conceptql.rb +65 -2
- data/lib/sequel/extensions/pg_ctas_explain.rb +22 -0
- data/lib/sequel/extensions/pg_vacuum_table.rb +23 -0
- data/schemas/gdm.yml +882 -0
- data/schemas/gdm_wide.yml +757 -0
- data/schemas/ohdsi_vocabs.yml +143 -0
- data/schemas/omopv4_plus.yml +729 -0
- data/schemas/omopv4_plus_more_ignorables.tsv +13 -0
- metadata +267 -207
- data/.gitignore +0 -23
- data/Gemfile +0 -4
- data/Guardfile +0 -56
- data/LICENSE.txt +0 -22
- data/Rakefile +0 -1
- data/conceptql.gemspec +0 -32
- data/doc/ConceptQL Specification (alpha).pdf +0 -0
- data/doc/diagram_0.png +0 -0
- data/doc/implementation_notes.md +0 -39
- data/doc/spec.md +0 -1344
- data/lib/conceptql/behaviors/debuggable.rb +0 -70
- data/lib/conceptql/behaviors/dottable.rb +0 -103
- data/lib/conceptql/behaviors/preppable.rb +0 -20
- data/lib/conceptql/converter.rb +0 -65
- data/lib/conceptql/debugger.rb +0 -48
- data/lib/conceptql/fake_grapher.rb +0 -20
- data/lib/conceptql/graph.rb +0 -52
- data/lib/conceptql/graph_nodifier.rb +0 -203
- data/lib/conceptql/logger.rb +0 -10
- data/lib/conceptql/operators/complement.rb +0 -38
- data/lib/conceptql/operators/concept.rb +0 -70
- data/lib/conceptql/operators/condition_type.rb +0 -71
- data/lib/conceptql/operators/cpt.rb +0 -25
- data/lib/conceptql/operators/drug_type_concept.rb +0 -21
- data/lib/conceptql/operators/from_seer_visits.rb +0 -23
- data/lib/conceptql/operators/hcpcs.rb +0 -25
- data/lib/conceptql/operators/icd10.rb +0 -28
- data/lib/conceptql/operators/icd9.rb +0 -28
- data/lib/conceptql/operators/icd9_procedure.rb +0 -25
- data/lib/conceptql/operators/intersect.rb +0 -34
- data/lib/conceptql/operators/loinc.rb +0 -25
- data/lib/conceptql/operators/medcode.rb +0 -28
- data/lib/conceptql/operators/medcode_procedure.rb +0 -27
- data/lib/conceptql/operators/ndc.rb +0 -29
- data/lib/conceptql/operators/observation_by_enttype.rb +0 -29
- data/lib/conceptql/operators/observation_period.rb +0 -30
- data/lib/conceptql/operators/overlapped_by.rb +0 -23
- data/lib/conceptql/operators/overlaps.rb +0 -19
- data/lib/conceptql/operators/place_of_service_code.rb +0 -24
- data/lib/conceptql/operators/procedure_occurrence.rb +0 -25
- data/lib/conceptql/operators/prodcode.rb +0 -29
- data/lib/conceptql/operators/rxnorm.rb +0 -24
- data/lib/conceptql/operators/snomed.rb +0 -24
- data/lib/conceptql/operators/snomed_condition.rb +0 -26
- data/lib/conceptql/operators/source_vocabulary_operator.rb +0 -58
- data/lib/conceptql/operators/standard_vocabulary_operator.rb +0 -45
- data/lib/conceptql/operators/started_by.rb +0 -22
- data/lib/conceptql/operators/to_seer_visits.rb +0 -24
- data/lib/conceptql/operators/visit.rb +0 -15
- data/lib/conceptql/operators/visit_occurrence.rb +0 -30
- data/lib/conceptql/tree.rb +0 -53
- data/lib/conceptql/utils/temp_table.rb +0 -72
- data/spec/conceptql/behaviors/dottable_spec.rb +0 -99
- data/spec/conceptql/converter_spec.rb +0 -51
- data/spec/conceptql/date_adjuster_spec.rb +0 -68
- data/spec/conceptql/operators/after_spec.rb +0 -16
- data/spec/conceptql/operators/before_spec.rb +0 -16
- data/spec/conceptql/operators/casting_operator_spec.rb +0 -68
- data/spec/conceptql/operators/complement_spec.rb +0 -15
- data/spec/conceptql/operators/concept_spec.rb +0 -40
- data/spec/conceptql/operators/condition_type_spec.rb +0 -128
- data/spec/conceptql/operators/contains_spec.rb +0 -19
- data/spec/conceptql/operators/cpt_spec.rb +0 -29
- data/spec/conceptql/operators/date_range_spec.rb +0 -33
- data/spec/conceptql/operators/death_spec.rb +0 -10
- data/spec/conceptql/operators/during_spec.rb +0 -30
- data/spec/conceptql/operators/except_spec.rb +0 -15
- data/spec/conceptql/operators/first_spec.rb +0 -35
- data/spec/conceptql/operators/from_spec.rb +0 -13
- data/spec/conceptql/operators/gender_spec.rb +0 -27
- data/spec/conceptql/operators/hcpcs_spec.rb +0 -29
- data/spec/conceptql/operators/icd10_spec.rb +0 -34
- data/spec/conceptql/operators/icd9_procedure_spec.rb +0 -29
- data/spec/conceptql/operators/icd9_spec.rb +0 -34
- data/spec/conceptql/operators/intersect_spec.rb +0 -28
- data/spec/conceptql/operators/last_spec.rb +0 -36
- data/spec/conceptql/operators/loinc_spec.rb +0 -29
- data/spec/conceptql/operators/medcode_procedure_spec.rb +0 -34
- data/spec/conceptql/operators/medcode_spec.rb +0 -34
- data/spec/conceptql/operators/observation_period_spec.rb +0 -10
- data/spec/conceptql/operators/occurrence_spec.rb +0 -87
- data/spec/conceptql/operators/overlapped_by_spec.rb +0 -32
- data/spec/conceptql/operators/overlaps_spec.rb +0 -21
- data/spec/conceptql/operators/person_filter_spec.rb +0 -15
- data/spec/conceptql/operators/person_spec.rb +0 -10
- data/spec/conceptql/operators/place_of_service_code_spec.rb +0 -24
- data/spec/conceptql/operators/procedure_occurrence_spec.rb +0 -10
- data/spec/conceptql/operators/prodcode_spec.rb +0 -35
- data/spec/conceptql/operators/query_double.rb +0 -20
- data/spec/conceptql/operators/query_double_spec.rb +0 -7
- data/spec/conceptql/operators/race_spec.rb +0 -21
- data/spec/conceptql/operators/rxnorm_spec.rb +0 -29
- data/spec/conceptql/operators/snomed_spec.rb +0 -29
- data/spec/conceptql/operators/source_vocabulary_operator_spec.rb +0 -35
- data/spec/conceptql/operators/standard_vocabulary_operator_spec.rb +0 -35
- data/spec/conceptql/operators/started_by_spec.rb +0 -22
- data/spec/conceptql/operators/temporal_operator_spec.rb +0 -51
- data/spec/conceptql/operators/time_window_spec.rb +0 -77
- data/spec/conceptql/operators/union_spec.rb +0 -21
- data/spec/conceptql/operators/visit_occurrence_spec.rb +0 -10
- data/spec/conceptql/query_spec.rb +0 -22
- data/spec/conceptql/tree_spec.rb +0 -50
- data/spec/doubles/stream_for_casting_double.rb +0 -9
- data/spec/doubles/stream_for_occurrence_double.rb +0 -25
- data/spec/doubles/stream_for_temporal_double.rb +0 -6
- data/spec/spec_helper.rb +0 -102
|
@@ -1,35 +1,41 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "operator"
|
|
2
4
|
|
|
3
5
|
module ConceptQL
|
|
4
6
|
module Operators
|
|
5
7
|
# Parent class of all casting operators
|
|
6
8
|
#
|
|
7
9
|
# Subclasses must implement the following methods:
|
|
8
|
-
# -
|
|
10
|
+
# - my_domain
|
|
9
11
|
# - i_point_at
|
|
10
12
|
# - these_point_at_me
|
|
11
13
|
#
|
|
12
|
-
# i_point_at returns a list of
|
|
14
|
+
# i_point_at returns a list of domains for which the operator's table of origin
|
|
13
15
|
# has foreign_keys pointing to another table, e.g.:
|
|
14
16
|
# procedure_occurrence has an FK to visit_occurrence, so we'd put
|
|
15
17
|
# :visit_occurrence in the i_point_at array
|
|
16
18
|
#
|
|
17
|
-
# these_point_at_me is a list of
|
|
19
|
+
# these_point_at_me is a list of domains for which that domain's table
|
|
18
20
|
# of origin has a FK pointing to the current operator's
|
|
19
21
|
# table of origin, e.g.:
|
|
20
22
|
# procedure_cost has an FK to procedure_occurrence so we'd
|
|
21
23
|
# put :procedure_cost in procedure_occurrence's these_point_at_me array
|
|
22
24
|
#
|
|
23
25
|
# Also, if a casting operator is passed no streams, it will return all the
|
|
24
|
-
# rows in its table as
|
|
26
|
+
# rows in its table as records.
|
|
25
27
|
class CastingOperator < Operator
|
|
26
|
-
category
|
|
27
|
-
|
|
28
|
-
|
|
28
|
+
category "Get Related Data"
|
|
29
|
+
basic_type :cast
|
|
30
|
+
validate_at_most_one_upstream
|
|
31
|
+
validate_no_arguments
|
|
32
|
+
|
|
33
|
+
def domains(_db)
|
|
34
|
+
[ domain ]
|
|
29
35
|
end
|
|
30
36
|
|
|
31
|
-
def
|
|
32
|
-
|
|
37
|
+
def domain
|
|
38
|
+
my_domain
|
|
33
39
|
end
|
|
34
40
|
|
|
35
41
|
def castables
|
|
@@ -37,57 +43,66 @@ module ConceptQL
|
|
|
37
43
|
end
|
|
38
44
|
|
|
39
45
|
def query(db)
|
|
40
|
-
return db.from(
|
|
46
|
+
return db.from(table) if stream.nil?
|
|
47
|
+
|
|
41
48
|
base_query(db, stream.evaluate(db))
|
|
42
49
|
end
|
|
43
50
|
|
|
51
|
+
def table
|
|
52
|
+
dm.table_by_domain(my_domain)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def make_table_name(domain)
|
|
56
|
+
super(dm.table_by_domain(domain))
|
|
57
|
+
end
|
|
58
|
+
|
|
44
59
|
private
|
|
45
60
|
|
|
46
61
|
def base_query(db, stream_query)
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
62
|
+
uncastable_domains = stream.domains(db) - castables
|
|
63
|
+
to_me_domains = stream.domains(db) & these_point_at_me
|
|
64
|
+
from_me_domains = stream.domains(db) & i_point_at
|
|
50
65
|
|
|
51
|
-
destination_table = make_table_name(
|
|
66
|
+
destination_table = make_table_name(my_domain)
|
|
52
67
|
casting_query = db.from(destination_table)
|
|
53
68
|
wheres = []
|
|
54
69
|
|
|
55
|
-
unless
|
|
70
|
+
unless uncastable_domains.empty?
|
|
56
71
|
# We have a situation where one or more of the incoming streams
|
|
57
72
|
# isn't castable so we'll just return all rows for
|
|
58
73
|
# all people in each uncastable stream
|
|
59
74
|
uncastable_person_ids = db.from(stream_query)
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
wheres << Sequel.expr(person_id
|
|
75
|
+
.where(criterion_domain: uncastable_domains.map(&:to_s))
|
|
76
|
+
.select_group(:person_id)
|
|
77
|
+
wheres << Sequel.expr(dm.person_id(table) => uncastable_person_ids)
|
|
63
78
|
end
|
|
64
79
|
|
|
65
|
-
|
|
80
|
+
destination_domain_id = dm.make_table_id(my_domain)
|
|
66
81
|
|
|
67
|
-
unless
|
|
68
|
-
# For each castable
|
|
69
|
-
# casts each
|
|
82
|
+
unless to_me_domains.empty?
|
|
83
|
+
# For each castable domain in the stream, setup a query that
|
|
84
|
+
# casts each domain to a set of IDs, union those IDs and fetch
|
|
70
85
|
# them from the source table
|
|
71
|
-
|
|
86
|
+
castable_domain_query = to_me_domains.map do |source_domain|
|
|
72
87
|
source_ids = db.from(stream_query)
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
source_table = make_table_name(
|
|
76
|
-
|
|
88
|
+
.where(criterion_domain: source_domain.to_s)
|
|
89
|
+
.select_group(:criterion_id)
|
|
90
|
+
source_table = make_table_name(source_domain)
|
|
91
|
+
source_domain_id = dm.make_fk_id(source_domain)
|
|
77
92
|
|
|
78
93
|
db.from(source_table)
|
|
79
|
-
.where(
|
|
80
|
-
.select(
|
|
94
|
+
.where(source_domain_id => source_ids)
|
|
95
|
+
.select(destination_domain_id)
|
|
81
96
|
end.inject do |union_query, q|
|
|
82
|
-
union_query.union(q)
|
|
97
|
+
union_query.union(q, all: true)
|
|
83
98
|
end
|
|
84
|
-
wheres << Sequel.expr(
|
|
99
|
+
wheres << Sequel.expr(destination_domain_id => castable_domain_query)
|
|
85
100
|
end
|
|
86
101
|
|
|
87
|
-
unless
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
wheres << Sequel.expr(
|
|
102
|
+
unless from_me_domains.empty?
|
|
103
|
+
from_me_domains.each do |from_me_domain|
|
|
104
|
+
fk_domain_id = dm.fk_by_domain(from_me_domain)
|
|
105
|
+
wheres << Sequel.expr(fk_domain_id => db.from(stream_query).where(criterion_domain: from_me_domain.to_s).select_group(:criterion_id))
|
|
91
106
|
end
|
|
92
107
|
end
|
|
93
108
|
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "pass_thru"
|
|
4
|
+
|
|
5
|
+
module ConceptQL
|
|
6
|
+
module Operators
|
|
7
|
+
class CoReported < PassThru
|
|
8
|
+
register __FILE__
|
|
9
|
+
|
|
10
|
+
def events_per_patient
|
|
11
|
+
upstreams.all? { |u| u.events_per_patient == :single } ? :single : :multiple
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def multiple_vocabularies
|
|
15
|
+
return true if upstreams.any?(&:multiple_vocabularies)
|
|
16
|
+
|
|
17
|
+
vocabularies.length > 1
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
desc "Passes along all events that were co-reported in the same record in the source data."
|
|
21
|
+
|
|
22
|
+
allows_many_upstreams
|
|
23
|
+
category "Combine Streams"
|
|
24
|
+
default_query_columns
|
|
25
|
+
validate_at_least_one_upstream
|
|
26
|
+
validate_no_arguments
|
|
27
|
+
require_column :context_id
|
|
28
|
+
|
|
29
|
+
def query(db)
|
|
30
|
+
if gdm?
|
|
31
|
+
gdm(db)
|
|
32
|
+
else
|
|
33
|
+
events_with_common_visits(db).inject { |q, query| q.union(query, all: true) }
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def gdm(db)
|
|
38
|
+
contexteds = upstreams.map do |stream|
|
|
39
|
+
[ stream.cte_name(:contextified), contextify(db, stream) ]
|
|
40
|
+
end.to_h
|
|
41
|
+
|
|
42
|
+
# Get all context_id's that are in all streams and do not share the same critierion_id
|
|
43
|
+
first, *rest = *contexteds.keys
|
|
44
|
+
shared_context_ids = rest.inject(db[first].select(:context_id)) do |q, next_cte|
|
|
45
|
+
q.intersect(db[next_cte].select(:context_id))
|
|
46
|
+
end
|
|
47
|
+
# shared_context_ids = rest.inject(first.from_self(alias: :first)) do |q, shared_event|
|
|
48
|
+
# q.join(shared_event.select(:context_id, :criterion_id), context_id: :context_id) do |a,b|
|
|
49
|
+
# ~(Sequel.qualify(a,:criterion_id) =~ Sequel.qualify(b,:criterion_id) )
|
|
50
|
+
# end.select(Sequel[:first][:context_id]).distinct
|
|
51
|
+
# end.from_self
|
|
52
|
+
|
|
53
|
+
if ConceptQL.avoid_ctes?
|
|
54
|
+
context_id_ds = shared_context_ids
|
|
55
|
+
else
|
|
56
|
+
name = cte_name(:shared_context_ids)
|
|
57
|
+
context_id_ds = db[name]
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
shared_events = contexteds.keys.map do |contexted|
|
|
61
|
+
db[contexted].where(context_id: context_id_ds).select(*query_cols)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
ds = shared_events.inject do |q, shared_event|
|
|
65
|
+
q.union(shared_event)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
contexteds.each do |name, query|
|
|
69
|
+
ds = ds.with(name, query)
|
|
70
|
+
end
|
|
71
|
+
if name
|
|
72
|
+
opts = {}
|
|
73
|
+
opts[:materialized] = true if rdbms.supports_materialized?
|
|
74
|
+
ds = ds.with(name, shared_context_ids, opts)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
ds
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def contextify(db, stream)
|
|
81
|
+
stream.evaluate(db).from_self(alias: :s)
|
|
82
|
+
# .join(Sequel[:clinical_codes].as(:c), id: :criterion_id)
|
|
83
|
+
# .select_all(:s)
|
|
84
|
+
# .select_append(Sequel[:c][:context_id].as(:context_id))
|
|
85
|
+
# .from_self
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
private
|
|
89
|
+
|
|
90
|
+
def visit_occurrence_ids_in_common(db)
|
|
91
|
+
@visit_occurrence_ids_in_common ||= upstream_queries(db).map do |q|
|
|
92
|
+
q.select(:visit_occurrence_id)
|
|
93
|
+
end.inject do |q, query|
|
|
94
|
+
q.from_self(alias: :tab1)
|
|
95
|
+
.join(query.as(:tab2), visit_occurrence_id: :visit_occurrence_id)
|
|
96
|
+
.select(Sequel[:tab1][:visit_occurrence_id].as(:visit_occurrence_id))
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def events_with_common_visits(db)
|
|
101
|
+
upstream_queries(db).map { |q| q.where(visit_occurrence_id: visit_occurrence_ids_in_common(db)).from_self }
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def upstream_queries(db)
|
|
105
|
+
@upstream_queries ||= upstreams.map do |expression|
|
|
106
|
+
expression.evaluate(db).select(*query_cols).from_self
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "operator"
|
|
4
|
+
require_relative "../date_adjuster"
|
|
5
|
+
|
|
6
|
+
module ConceptQL
|
|
7
|
+
module Operators
|
|
8
|
+
class ConcurrentWithin < Operator
|
|
9
|
+
register __FILE__
|
|
10
|
+
|
|
11
|
+
def events_per_patient
|
|
12
|
+
upstreams.all? { |u| u.events_per_patient == :single } ? :single : :multiple
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def multiple_vocabularies
|
|
16
|
+
return true if upstreams.any?(&:multiple_vocabularies)
|
|
17
|
+
|
|
18
|
+
vocabularies.length > 1
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
desc "Filters each upstream to only include records where other upstreams' date ranges overlap"
|
|
22
|
+
option :start, type: :string
|
|
23
|
+
option :end, type: :string
|
|
24
|
+
allows_many_upstreams
|
|
25
|
+
validate_at_least_one_upstream
|
|
26
|
+
validate_no_arguments
|
|
27
|
+
validate_option DateAdjuster::VALID_INPUT, :start, :end
|
|
28
|
+
category "Combine Streams"
|
|
29
|
+
basic_type :set
|
|
30
|
+
default_query_columns
|
|
31
|
+
|
|
32
|
+
def query(db)
|
|
33
|
+
datasets = upstreams.map do |stream|
|
|
34
|
+
stream.evaluate(db)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
return datasets.first.from_self if datasets.length == 1
|
|
38
|
+
|
|
39
|
+
adjusted_start_date = DateAdjuster.new(self, options[:start]).adjust(Sequel[:l][:start_date], true)
|
|
40
|
+
adjusted_end_date = DateAdjuster.new(self, options[:end]).adjust(Sequel[:l][:end_date])
|
|
41
|
+
|
|
42
|
+
datasets = datasets.map do |ds|
|
|
43
|
+
matching = ds.from_self(alias: :l)
|
|
44
|
+
|
|
45
|
+
(datasets - [ ds ]).each do |other|
|
|
46
|
+
other = other
|
|
47
|
+
.from_self(alias: :r)
|
|
48
|
+
.where(adjusted_start_date <= Sequel[:r][:start_date])
|
|
49
|
+
.where(adjusted_end_date >= Sequel[:r][:end_date])
|
|
50
|
+
.select(*matching_columns)
|
|
51
|
+
|
|
52
|
+
matching = matching.where(other.where(matching_columns.map do |x|
|
|
53
|
+
[ Sequel.qualify(:l, x), Sequel.qualify(:r, x) ]
|
|
54
|
+
end).exists)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
matching
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
ds, *rest = datasets
|
|
61
|
+
rest.each do |other|
|
|
62
|
+
ds = ds.union(other, from_self: nil)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
ds.from_self
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -1,16 +1,20 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "temporal_operator"
|
|
2
4
|
|
|
3
5
|
module ConceptQL
|
|
4
6
|
module Operators
|
|
5
7
|
class Contains < TemporalOperator
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
L------Y--------L
|
|
11
|
-
EOF
|
|
8
|
+
register __FILE__
|
|
9
|
+
|
|
10
|
+
desc "For each person, passes along left hand records with a start_date and end_date containing a right hand record's start_date and end_date."
|
|
11
|
+
|
|
12
12
|
def where_clause
|
|
13
|
-
|
|
13
|
+
(within_start <= r_start_date) & (r_end_date <= within_end)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def within_source_table
|
|
17
|
+
Sequel[:l]
|
|
14
18
|
end
|
|
15
19
|
end
|
|
16
20
|
end
|
|
@@ -1,26 +1,34 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "pass_thru"
|
|
2
4
|
|
|
3
5
|
module ConceptQL
|
|
4
6
|
module Operators
|
|
5
7
|
class Count < PassThru
|
|
6
|
-
|
|
8
|
+
register __FILE__
|
|
9
|
+
|
|
10
|
+
desc "Counts the number of records that exactly match across all columns."
|
|
7
11
|
allows_one_upstream
|
|
12
|
+
validate_one_upstream
|
|
13
|
+
validate_no_arguments
|
|
14
|
+
require_column :value_as_number
|
|
15
|
+
|
|
16
|
+
def query_cols
|
|
17
|
+
dynamic_columns - [ :value_as_number ] + [ :value_as_number ]
|
|
18
|
+
end
|
|
8
19
|
|
|
9
20
|
def query(db)
|
|
10
21
|
db.from(unioned(db))
|
|
11
|
-
.
|
|
12
|
-
.
|
|
13
|
-
.select_append{count(1).as(:value_as_number)}
|
|
22
|
+
.select_group(*(query_cols - [ :value_as_number ]))
|
|
23
|
+
.select_append { count(1).as(:value_as_number) }
|
|
14
24
|
.from_self
|
|
15
25
|
end
|
|
16
26
|
|
|
17
27
|
def unioned(db)
|
|
18
|
-
upstreams.map { |c| c.evaluate(db) }.inject do |uni, q|
|
|
19
|
-
uni.union(q)
|
|
28
|
+
upstreams.map { |c| c.evaluate(db).select(*query_cols) }.inject do |uni, q|
|
|
29
|
+
uni.union(q.from_self)
|
|
20
30
|
end
|
|
21
31
|
end
|
|
22
32
|
end
|
|
23
33
|
end
|
|
24
34
|
end
|
|
25
|
-
|
|
26
|
-
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "operator"
|
|
4
|
+
require "date"
|
|
2
5
|
|
|
3
6
|
module ConceptQL
|
|
4
7
|
module Operators
|
|
@@ -8,23 +11,43 @@ module ConceptQL
|
|
|
8
11
|
# 'START' represents the first date of data in the data source,
|
|
9
12
|
# 'END' represents the last date of data in the data source,
|
|
10
13
|
class DateRange < Operator
|
|
11
|
-
|
|
14
|
+
register __FILE__
|
|
15
|
+
|
|
16
|
+
include ConceptQL::Behaviors::Windowable
|
|
17
|
+
include ConceptQL::Behaviors::Timeless
|
|
18
|
+
|
|
19
|
+
DATE_FORMAT = /\A#{Regexp.union([ /START/i, /END/i, /\d{4}-\d{2}-\d{2}/ ])}\z/.freeze
|
|
20
|
+
|
|
21
|
+
desc "Creates a set of all person records with the given start_date and end_date."
|
|
12
22
|
option :start, type: :string
|
|
13
23
|
option :end, type: :string
|
|
14
|
-
category
|
|
24
|
+
category "Select by Property"
|
|
25
|
+
basic_type :selection
|
|
26
|
+
validate_no_upstreams
|
|
27
|
+
validate_no_arguments
|
|
28
|
+
validate_option DATE_FORMAT, :start, :end
|
|
29
|
+
validate_required_options :start, :end
|
|
15
30
|
|
|
16
31
|
def query(db)
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
32
|
+
replace = {
|
|
33
|
+
start_date: start_date(db),
|
|
34
|
+
end_date: end_date(db)
|
|
35
|
+
}
|
|
36
|
+
db.from(source_table)
|
|
37
|
+
.select(*dm.columns(table: source_table, replace: replace))
|
|
38
|
+
.from_self
|
|
21
39
|
end
|
|
22
40
|
|
|
23
|
-
def
|
|
24
|
-
[:person]
|
|
41
|
+
def domains(_db)
|
|
42
|
+
[ :person ]
|
|
25
43
|
end
|
|
26
44
|
|
|
27
45
|
private
|
|
46
|
+
|
|
47
|
+
def source_table
|
|
48
|
+
dm.table_by_domain(:person)
|
|
49
|
+
end
|
|
50
|
+
|
|
28
51
|
def start_date(db)
|
|
29
52
|
date_from(db, options[:start])
|
|
30
53
|
end
|
|
@@ -36,12 +59,23 @@ module ConceptQL
|
|
|
36
59
|
# TODO: Select the earliest and latest dates of observation from
|
|
37
60
|
# the proper CDM table to represent the start and end of data
|
|
38
61
|
def date_from(db, str)
|
|
39
|
-
return db.from(
|
|
40
|
-
return db.from(
|
|
41
|
-
|
|
42
|
-
|
|
62
|
+
return db.from(period_table).get { |o| o.min(period_start_date(db)) } if str.upcase == "START"
|
|
63
|
+
return db.from(period_table).get { |o| o.max(period_end_date(db)) } if str.upcase == "END"
|
|
64
|
+
|
|
65
|
+
str
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def period_table
|
|
69
|
+
dm.period_table
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def period_start_date(db)
|
|
73
|
+
dm.start_date_column(db, period_table)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def period_end_date(db)
|
|
77
|
+
dm.end_date_column(db, period_table)
|
|
43
78
|
end
|
|
44
79
|
end
|
|
45
80
|
end
|
|
46
81
|
end
|
|
47
|
-
|
|
@@ -1,16 +1,26 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "casting_operator"
|
|
2
4
|
|
|
3
5
|
module ConceptQL
|
|
4
6
|
module Operators
|
|
5
7
|
class Death < CastingOperator
|
|
6
|
-
|
|
7
|
-
|
|
8
|
+
include ConceptQL::Behaviors::Windowable
|
|
9
|
+
|
|
10
|
+
register __FILE__
|
|
11
|
+
|
|
12
|
+
desc "Selects all death records"
|
|
13
|
+
domains :death
|
|
8
14
|
allows_one_upstream
|
|
9
15
|
|
|
10
|
-
def
|
|
16
|
+
def my_domain
|
|
11
17
|
:death
|
|
12
18
|
end
|
|
13
19
|
|
|
20
|
+
def source_table
|
|
21
|
+
dm.table_by_domain(:death)
|
|
22
|
+
end
|
|
23
|
+
|
|
14
24
|
def i_point_at
|
|
15
25
|
[ :person ]
|
|
16
26
|
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "pass_thru"
|
|
4
|
+
|
|
5
|
+
module ConceptQL
|
|
6
|
+
module Operators
|
|
7
|
+
class Dedup < PassThru
|
|
8
|
+
register __FILE__
|
|
9
|
+
|
|
10
|
+
desc "Counts the number of records that exactly match across all columns."
|
|
11
|
+
allows_one_upstream
|
|
12
|
+
validate_one_upstream
|
|
13
|
+
|
|
14
|
+
def query(db)
|
|
15
|
+
db.from(unioned(db))
|
|
16
|
+
.select_group(*query_cols)
|
|
17
|
+
.from_self
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def query_cols
|
|
21
|
+
dynamic_columns
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def unioned(db)
|
|
25
|
+
upstreams.map { |c| c.evaluate(db) }.inject do |uni, q|
|
|
26
|
+
uni.union(q)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -1,21 +1,16 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "temporal_operator"
|
|
2
4
|
|
|
3
5
|
module ConceptQL
|
|
4
6
|
module Operators
|
|
5
7
|
class During < TemporalOperator
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
For
|
|
9
|
-
All other results are discarded, including all results in the RHR.
|
|
10
|
-
EOF
|
|
8
|
+
register __FILE__
|
|
9
|
+
|
|
10
|
+
desc "For each person, passes along left hand records with a start_date and end_date within a right hand record's start_date and end_date."
|
|
11
11
|
|
|
12
12
|
def where_clause
|
|
13
|
-
|
|
14
|
-
Sequel.expr(Sequel.expr(Proc.new { r__start_date <= l__start_date}).&(Sequel.expr( Proc.new { l__start_date <= r__end_date })))
|
|
15
|
-
.|(Sequel.expr(Proc.new { r__start_date <= l__end_date}).&(Sequel.expr( Proc.new { l__end_date <= r__end_date })))
|
|
16
|
-
else
|
|
17
|
-
[Proc.new { r__start_date <= l__start_date}, Proc.new { l__end_date <= r__end_date }]
|
|
18
|
-
end
|
|
13
|
+
(within_start <= l_start_date) & (l_end_date <= within_end)
|
|
19
14
|
end
|
|
20
15
|
end
|
|
21
16
|
end
|