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,97 +1,250 @@
|
|
|
1
|
-
|
|
2
|
-
require 'facets/string/snakecase'
|
|
1
|
+
# frozen_string_literal: true
|
|
3
2
|
|
|
4
|
-
module
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
end
|
|
3
|
+
module ConceptQL
|
|
4
|
+
module Metadatable
|
|
5
|
+
def preferred_name(value = nil)
|
|
6
|
+
return @preferred_name unless value
|
|
9
7
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
@desc = value
|
|
13
|
-
end
|
|
8
|
+
@preferred_name = value
|
|
9
|
+
end
|
|
14
10
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
@predominant_types = values
|
|
18
|
-
end
|
|
11
|
+
def aliases(value = nil)
|
|
12
|
+
return @aliases unless value
|
|
19
13
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
@arguments << [name, options]
|
|
23
|
-
end
|
|
14
|
+
@aliases = value
|
|
15
|
+
end
|
|
24
16
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
17
|
+
def desc(value = nil)
|
|
18
|
+
return @desc unless value
|
|
19
|
+
|
|
20
|
+
@desc = value
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def short_name(value = nil)
|
|
24
|
+
return @short_name unless value
|
|
25
|
+
|
|
26
|
+
@short_name = value
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def long_name(value = nil)
|
|
30
|
+
return @long_name unless value
|
|
31
|
+
|
|
32
|
+
@long_name = value
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def conceptql_spec_id(value = nil)
|
|
36
|
+
return @conceptql_spec_id unless value
|
|
37
|
+
|
|
38
|
+
@conceptql_spec_id = value
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def predominant_domains(*values)
|
|
42
|
+
return @predominant_domains if values.empty?
|
|
43
|
+
|
|
44
|
+
@predominant_domains = values
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def argument(name, options = {})
|
|
48
|
+
(@arguments ||= [])
|
|
49
|
+
@arguments << [ name, auto_label(name, options) ]
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def option(name, options = {})
|
|
53
|
+
@options ||= {}
|
|
54
|
+
@options[name] = auto_label(name, options)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Takes an option that a subclass inherited out of its metadata
|
|
58
|
+
def remove_option(name)
|
|
59
|
+
@options.delete(name)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def auto_label(name, opts = {})
|
|
63
|
+
return opts if opts[:label]
|
|
64
|
+
return opts.merge(label: name.to_s.split("_").map(&:capitalize).join(" ")) unless opts[:type] == :codelist
|
|
29
65
|
|
|
30
|
-
|
|
31
|
-
@types = type_list
|
|
32
|
-
define_method(:types) do
|
|
33
|
-
type_list
|
|
66
|
+
opts.merge(label: "#{pref_name} Codes")
|
|
34
67
|
end
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
68
|
+
|
|
69
|
+
def domains(*domain_list)
|
|
70
|
+
@domains = domain_list
|
|
71
|
+
define_method(:domains) do |_db|
|
|
72
|
+
domain_list
|
|
73
|
+
end
|
|
74
|
+
return unless domain_list.length == 1
|
|
75
|
+
|
|
76
|
+
define_method(:domain) do
|
|
77
|
+
domain_list.first
|
|
38
78
|
end
|
|
39
79
|
end
|
|
40
|
-
end
|
|
41
80
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
end
|
|
81
|
+
def basic_type(value = nil)
|
|
82
|
+
return @basic_type unless value
|
|
45
83
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
end
|
|
84
|
+
@basic_type = value
|
|
85
|
+
end
|
|
49
86
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
87
|
+
def allows_many_upstreams
|
|
88
|
+
@max_upstreams = 99
|
|
89
|
+
end
|
|
53
90
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
91
|
+
def allows_one_upstream
|
|
92
|
+
@max_upstreams = 1
|
|
93
|
+
end
|
|
57
94
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
end
|
|
95
|
+
def just_class_name
|
|
96
|
+
to_s.split("::").last
|
|
97
|
+
end
|
|
62
98
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
99
|
+
def humanized_class_name
|
|
100
|
+
just_class_name.gsub(/([A-Z])/, ' \1').lstrip
|
|
101
|
+
end
|
|
66
102
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
103
|
+
def category(category)
|
|
104
|
+
(@categories ||= [])
|
|
105
|
+
@categories << Array(category)
|
|
70
106
|
end
|
|
71
107
|
|
|
72
|
-
|
|
73
|
-
|
|
108
|
+
def reset_categories
|
|
109
|
+
@categories = []
|
|
74
110
|
end
|
|
75
111
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
112
|
+
def inherited(upstream)
|
|
113
|
+
super
|
|
114
|
+
(@options || {}).each do |name, opt|
|
|
115
|
+
upstream.option name, opt
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
(@categories || []).each do |cat|
|
|
119
|
+
upstream.category cat
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
upstream.basic_type @basic_type
|
|
123
|
+
|
|
124
|
+
case @max_upstreams
|
|
125
|
+
when 1
|
|
126
|
+
upstream.allows_one_upstream
|
|
127
|
+
when 99
|
|
128
|
+
upstream.allows_many_upstreams
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def pref_name
|
|
133
|
+
@preferred_name || humanized_class_name
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def spec_id
|
|
137
|
+
"#{@conceptql_spec_id || pref_name} operator".gsub(/\s+/, "-").downcase
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def to_metadata(name, opts = {})
|
|
141
|
+
derive_metadata_from_validations
|
|
142
|
+
warn_about_missing_metadata if opts[:warn]
|
|
143
|
+
{
|
|
144
|
+
name: name,
|
|
145
|
+
preferred_name: pref_name,
|
|
146
|
+
short_name: short_name,
|
|
147
|
+
long_name: long_name,
|
|
148
|
+
operation: just_class_name.underscore,
|
|
149
|
+
min_upstreams: @max_upstreams || 0,
|
|
150
|
+
max_upstreams: @max_upstreams || 0,
|
|
151
|
+
arguments: @arguments || [],
|
|
152
|
+
options: process_options(@options),
|
|
153
|
+
predominant_domains: @domains || @predominant_domains || [],
|
|
154
|
+
desc: get_desc,
|
|
155
|
+
categories: @categories || [],
|
|
156
|
+
basic_type: @basic_type,
|
|
157
|
+
deprecated: nil, # kept for metadata consumers; no operator is deprecated
|
|
158
|
+
arity: arity,
|
|
159
|
+
aliases: @aliases,
|
|
160
|
+
spec_id: spec_id
|
|
161
|
+
}
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def arity
|
|
165
|
+
case basic_type
|
|
166
|
+
when :set
|
|
167
|
+
-1
|
|
168
|
+
when :temporal, :cast
|
|
169
|
+
1
|
|
170
|
+
when :selection, nil
|
|
171
|
+
0
|
|
172
|
+
when :filter
|
|
173
|
+
2
|
|
174
|
+
else
|
|
175
|
+
raise "#{name} has unknown basic_type: #{basic_type.pretty_inspect}"
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def get_desc
|
|
180
|
+
@get_desc ||= standard_description
|
|
81
181
|
end
|
|
82
|
-
end
|
|
83
182
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
183
|
+
def process_options(options)
|
|
184
|
+
return {} if options.nil? || options.empty?
|
|
185
|
+
|
|
186
|
+
# Build new hashes: the option hashes can be shared with a parent class
|
|
187
|
+
options.transform_values do |v|
|
|
188
|
+
v[:options].respond_to?(:call) ? v.merge(options: v[:options].call) : v
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def warn_about_missing_metadata
|
|
193
|
+
missing = []
|
|
194
|
+
missing << :categories if (@categories || []).empty?
|
|
195
|
+
missing << :desc if get_desc.empty?
|
|
196
|
+
missing << :basic_type unless @basic_type
|
|
197
|
+
puts "#{just_class_name} is missing #{missing.join(', ')}" unless missing.empty?
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# Each validation is [meth, *args]. Every argument reaches the hook, so
|
|
201
|
+
# validate_required_options :left, :right marks both options.
|
|
202
|
+
def derive_metadata_from_validations
|
|
203
|
+
@validations.each do |meth, *args|
|
|
204
|
+
hook = "#{meth}_to_metadata"
|
|
205
|
+
send(hook, *args) if respond_to?(hook)
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
def validate_no_upstreams_to_metadata(*_args)
|
|
210
|
+
@min_upstreams = 0
|
|
211
|
+
@max_upstreams = 0
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
def validate_one_upstream_to_metadata(*_args)
|
|
215
|
+
@min_upstreams = 1
|
|
216
|
+
@max_upstreams = 1
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def validate_at_least_one_upstream_to_metadata(*_args)
|
|
220
|
+
@min_upstreams = 1
|
|
221
|
+
@max_upstreams = 99
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
def validate_at_most_one_upstream_to_metadata(*_args)
|
|
225
|
+
@min_upstreams = 0
|
|
226
|
+
@max_upstreams = 1
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def validate_no_arguments_to_metadata(*_args)
|
|
230
|
+
@arguments = []
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
# A subclass shares the option hashes that it inherits (see #inherited),
|
|
234
|
+
# so mark a copy. Writing into the shared hash would mark the option as
|
|
235
|
+
# required for the parent class too.
|
|
236
|
+
def validate_required_options_to_metadata(*names)
|
|
237
|
+
names.each do |name|
|
|
238
|
+
@options[name] = @options.fetch(name).merge(required: true)
|
|
239
|
+
end
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
def standard_description
|
|
243
|
+
"Selects records based on the #{pref_name} vocabulary."
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
def no_desc
|
|
247
|
+
desc ""
|
|
248
|
+
end
|
|
95
249
|
end
|
|
96
250
|
end
|
|
97
|
-
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ConceptQL
|
|
4
|
+
module Provenanceable
|
|
5
|
+
def self.included(base)
|
|
6
|
+
base.require_column :file_provenance_type
|
|
7
|
+
base.require_column :code_provenance_type
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def prov_of(db, ancestors)
|
|
11
|
+
dm.descendants_of(db,
|
|
12
|
+
dm.concept_ids(db, [ dm.file_provenance_types_vocab, dm.code_provenance_types_vocab ].flatten,
|
|
13
|
+
ancestors))
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def build_where_from_codes(db, codes)
|
|
17
|
+
Sequel.|(
|
|
18
|
+
{ file_provenance_type: prov_of(db, codes) }, { code_provenance_type: prov_of(db, codes) }
|
|
19
|
+
)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def find_bad_keywords(db, codes)
|
|
23
|
+
codes - dm.concept_codes(db, [ dm.file_provenance_types_vocab, dm.code_provenance_types_vocab ].flatten)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_support/concern"
|
|
4
|
+
|
|
5
|
+
module ConceptQL
|
|
6
|
+
module Behaviors
|
|
7
|
+
module Utilizable
|
|
8
|
+
extend ActiveSupport::Concern
|
|
9
|
+
include ConceptQL::Behaviors::Windowable
|
|
10
|
+
|
|
11
|
+
included do
|
|
12
|
+
domains :condition_occurrence
|
|
13
|
+
category "Select by Property"
|
|
14
|
+
basic_type :selection
|
|
15
|
+
validate_no_upstreams
|
|
16
|
+
validate_no_arguments
|
|
17
|
+
|
|
18
|
+
require_column :admission_date
|
|
19
|
+
require_column :discharge_date
|
|
20
|
+
require_column :length_of_stay
|
|
21
|
+
require_column :admission_source_value
|
|
22
|
+
require_column :admission_source_description
|
|
23
|
+
require_column :discharge_location_source_value
|
|
24
|
+
require_column :discharge_location_source_description
|
|
25
|
+
require_column :primary_concept_id
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def column_family
|
|
29
|
+
"inpatient"
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# The operators that include this register for gdm and gdm_wide only.
|
|
33
|
+
def query(db)
|
|
34
|
+
gdm_it(db)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def collection_type
|
|
38
|
+
raise NotImplementedError
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# With the Database's share_utilization option, the list is read from a
|
|
42
|
+
# table that every statement on the Database shares. The WITH entry only
|
|
43
|
+
# carries the list to Scope#with_ctes, which takes it out of the SQL and
|
|
44
|
+
# creates the table when the query first reads rows.
|
|
45
|
+
def evaluate(db)
|
|
46
|
+
cdb = nodifier.cdb
|
|
47
|
+
return super unless cdb.opts[:share_utilization]
|
|
48
|
+
|
|
49
|
+
list = select_it(query(db))
|
|
50
|
+
name = cdb.shared_utilization_table(list, scope)
|
|
51
|
+
db[name].with(name, list, Scope::SHARED_TABLE => true)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def gdm_it(db)
|
|
55
|
+
source_type_id = dm.concept_ids(db, dm.file_provenance_types_vocab, collection_type)
|
|
56
|
+
all_source_type_ids = dm.descendants_of(db, source_type_id)
|
|
57
|
+
primary_id = dm.concept_ids(db, dm.code_provenance_types_vocab, "primary")
|
|
58
|
+
all_primary_ids = dm.descendants_of(db, primary_id)
|
|
59
|
+
# condition_vocabularies = dm.vocabularies_query.where(domain: 'condition_occurrence').select_map(:id)
|
|
60
|
+
|
|
61
|
+
return gdm_wide_it(db, all_primary_ids, all_source_type_ids) if wide?
|
|
62
|
+
|
|
63
|
+
# Get primary diagnosis codes
|
|
64
|
+
primary_concepts = rdbms.primary_concepts(db, all_primary_ids)
|
|
65
|
+
|
|
66
|
+
relevant_contexts = db[Sequel[:contexts].as(:cn)]
|
|
67
|
+
.where(Sequel[:cn][:source_type_concept_id] => all_source_type_ids)
|
|
68
|
+
.select(:collection_id)
|
|
69
|
+
|
|
70
|
+
db[:collections].from_self(alias: :cl)
|
|
71
|
+
.then { |cl| restrict_to_window_persons(cl, Sequel[:cl][:patient_id]) }
|
|
72
|
+
.join(:admission_details, { Sequel[:ad][:id] => Sequel[:cl][:admission_detail_id] }, table_alias: :ad)
|
|
73
|
+
.left_join(dm.name_concepts_table(db), { Sequel[:ad][:admit_source_concept_id] => Sequel[:asc][:id] }, table_alias: :asc)
|
|
74
|
+
.left_join(dm.name_concepts_table(db), { Sequel[:ad][:discharge_location_concept_id] => Sequel[:dlc][:id] }, table_alias: :dlc)
|
|
75
|
+
.left_join(primary_concepts, { Sequel[:pcon][:collection_id] => Sequel[:cl][:id] }, table_alias: :pcon)
|
|
76
|
+
.where(Sequel[:cl][:id] => relevant_contexts)
|
|
77
|
+
.select(
|
|
78
|
+
Sequel[:cl][:start_date].as(:collection_start_date),
|
|
79
|
+
Sequel[:cl][:end_date].as(:collection_end_date),
|
|
80
|
+
Sequel[:ad][:admission_date].as(:admission_date),
|
|
81
|
+
Sequel[:ad][:discharge_date].as(:discharge_date),
|
|
82
|
+
Sequel[:asc][:concept_code].as(:admission_source_value),
|
|
83
|
+
Sequel[:asc][:concept_text].as(:admission_source_description),
|
|
84
|
+
Sequel[:dlc][:concept_code].as(:discharge_location_source_value),
|
|
85
|
+
Sequel[:dlc][:concept_text].as(:discharge_location_source_description),
|
|
86
|
+
Sequel[:pcon][:concept_id].as(:primary_concept_id),
|
|
87
|
+
Sequel[:pcon][:concept_code].as(:source_value),
|
|
88
|
+
Sequel[:pcon][:vocabulary_id].as(:source_vocabulary_id),
|
|
89
|
+
Sequel[:cl][:patient_id].as(:patient_id),
|
|
90
|
+
Sequel[:cl][:id].as(:collection_id)
|
|
91
|
+
)
|
|
92
|
+
.select_append(Sequel[:ROW_NUMBER].function.over(
|
|
93
|
+
partition: [ :admission_detail_id ],
|
|
94
|
+
order: [
|
|
95
|
+
Sequel[:pcon][:is_primary].desc,
|
|
96
|
+
Sequel[:cl][:id],
|
|
97
|
+
Sequel[:pcon][:concept_code]
|
|
98
|
+
]
|
|
99
|
+
).as(:nummy))
|
|
100
|
+
.from_self
|
|
101
|
+
.where(nummy: 1)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def gdm_wide_it(db, all_primary_ids, all_source_type_ids)
|
|
105
|
+
# Emit `CASE WHEN provenance_concept_id IN (...) THEN 1 ELSE 0 END`.
|
|
106
|
+
# The previous multi-WHEN form (`Sequel.case({{prov=>id1}=>1, {prov=>id2}=>1, ...}, 0)`)
|
|
107
|
+
# is mis-evaluated by Spark 3.3.2 when its result feeds the outer
|
|
108
|
+
# `CASE is_primary WHEN 1 THEN col ELSE NULL END` below. See conceptql-pk7.
|
|
109
|
+
is_primary =
|
|
110
|
+
if all_primary_ids.empty?
|
|
111
|
+
Sequel[0]
|
|
112
|
+
else
|
|
113
|
+
Sequel.case(
|
|
114
|
+
[ [ { Sequel[:provenance_concept_id] => all_primary_ids }, 1 ] ],
|
|
115
|
+
0
|
|
116
|
+
)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
db[:observations]
|
|
120
|
+
.then { |observations| restrict_to_window_persons(observations, :patient_id) }
|
|
121
|
+
.where(source_type_concept_id: all_source_type_ids)
|
|
122
|
+
.exclude(admit_admission_date: nil)
|
|
123
|
+
.select_append(
|
|
124
|
+
is_primary.as(:is_primary)
|
|
125
|
+
)
|
|
126
|
+
.from_self
|
|
127
|
+
# One admission has many rows with the same concept on different
|
|
128
|
+
# line dates. The unique id ends the ORDER BY so the kept row is the
|
|
129
|
+
# same on every run, and admission_end_date takes the latest end_date
|
|
130
|
+
# over all of the admission's rows, not the kept row's. See conceptql-evk.
|
|
131
|
+
.select_append(
|
|
132
|
+
Sequel[:ROW_NUMBER].function.over(
|
|
133
|
+
partition: [ :admission_detail_id ],
|
|
134
|
+
order: [ Sequel[:is_primary].desc, Sequel[:collection_id], Sequel[:clinical_code_concept_id], Sequel[:id] ]
|
|
135
|
+
).as(:nummy),
|
|
136
|
+
Sequel.function(:max, :end_date).over(partition: [ :admission_detail_id ]).as(:admission_end_date)
|
|
137
|
+
)
|
|
138
|
+
.from_self
|
|
139
|
+
.where(nummy: 1)
|
|
140
|
+
.from_self
|
|
141
|
+
.left_join(dm.name_concepts_table(db), { Sequel[:admit_source_concept_id] => Sequel[:asc][:id] }, table_alias: :asc)
|
|
142
|
+
.left_join(dm.name_concepts_table(db), { Sequel[:discharge_location_concept_id] => Sequel[:dlc][:id] }, table_alias: :dlc)
|
|
143
|
+
.select_append(
|
|
144
|
+
Sequel[:asc][:concept_code].as(:admission_source_value),
|
|
145
|
+
Sequel[:asc][:concept_text].as(:admission_source_description),
|
|
146
|
+
Sequel[:dlc][:concept_code].as(:discharge_location_source_value),
|
|
147
|
+
Sequel[:dlc][:concept_text].as(:discharge_location_source_description),
|
|
148
|
+
Sequel.case({ 1 => :clinical_code_concept_id }, nil, :is_primary).as(:primary_concept_id),
|
|
149
|
+
Sequel.case({ 1 => :clinical_code_source_value }, nil, :is_primary).as(:source_value),
|
|
150
|
+
Sequel.case({ 1 => :clinical_code_vocabulary_id }, nil, :is_primary).as(:source_vocabulary_id)
|
|
151
|
+
)
|
|
152
|
+
.from_self
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# Keep only the persons that the scope's table windows cover, before the
|
|
156
|
+
# ROW_NUMBER over admission_detail_id. Without this, Spark numbers every
|
|
157
|
+
# inpatient row in the database and applies the window join afterwards.
|
|
158
|
+
# Output is unchanged because one admission never belongs to two persons
|
|
159
|
+
# (Ryan, 2026-10-14), so each ROW_NUMBER partition is kept or dropped whole.
|
|
160
|
+
def restrict_to_window_persons(ds, person_column)
|
|
161
|
+
return ds if skip_windows?
|
|
162
|
+
|
|
163
|
+
scope.windows.grep(Window::Table).inject(ds) do |restricted, window|
|
|
164
|
+
restricted.where(person_column => window.person_ids(restricted.db))
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def table
|
|
169
|
+
:collections
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def override_columns
|
|
173
|
+
return unless gdm?
|
|
174
|
+
|
|
175
|
+
{
|
|
176
|
+
length_of_stay: (rdbms.days_between(Sequel[:admission_date],
|
|
177
|
+
discharge_date_column) + 1).as(:length_of_stay),
|
|
178
|
+
criterion_table: Sequel.cast_string("collections").as(:criterion_table),
|
|
179
|
+
criterion_domain: Sequel.cast_string("condition_occurrence").as(:criterion_domain),
|
|
180
|
+
admission_source_value: Sequel[:admission_source_value],
|
|
181
|
+
admission_source_description: Sequel[:admission_source_description],
|
|
182
|
+
discharge_location_source_value: Sequel[:discharge_location_source_value],
|
|
183
|
+
discharge_location_source_description: Sequel[:discharge_location_source_description],
|
|
184
|
+
admission_date: Sequel[:admission_date],
|
|
185
|
+
discharge_date: discharge_date_column.as(:discharge_date),
|
|
186
|
+
person_id: Sequel[:patient_id].as(:person_id),
|
|
187
|
+
criterion_id: Sequel[:collection_id].as(:criterion_id),
|
|
188
|
+
start_date: Sequel[:collection_start_date].as(:start_date),
|
|
189
|
+
end_date: Sequel[:collection_end_date].as(:end_date),
|
|
190
|
+
primary_concept_id: Sequel[:primary_concept_id],
|
|
191
|
+
source_value: Sequel[:source_value].as(:source_value),
|
|
192
|
+
source_vocabulary_id: Sequel[:source_vocabulary_id].as(:source_vocabulary_id)
|
|
193
|
+
}
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# gdm_wide's AdmissionDateQueryModifier falls back to the kept row's own
|
|
197
|
+
# end_date when the admission has no discharge date. An admission falls
|
|
198
|
+
# back to its latest end_date instead (Ryan, 2026-10-16). See conceptql-evk.
|
|
199
|
+
def discharge_date_column
|
|
200
|
+
return Sequel[:discharge_date] unless wide?
|
|
201
|
+
|
|
202
|
+
Sequel.function(:coalesce, :admit_discharge_date, :admission_end_date, :discharge_date)
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ConceptQL
|
|
4
|
+
module Behaviors
|
|
5
|
+
# Applies scope windows to Operators
|
|
6
|
+
module Windowable
|
|
7
|
+
def select_it(query, opts = {})
|
|
8
|
+
ds = super
|
|
9
|
+
return ds if skip_windows?
|
|
10
|
+
|
|
11
|
+
scope.windows.each do |window|
|
|
12
|
+
ds = window.call(self, ds, timeless: timeless?)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
ds
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def skip_windows?
|
|
19
|
+
false
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def timeless?
|
|
23
|
+
false
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|