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