conceptql 0.4.1 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +44 -0
- data/config/vocabularies.csv +87 -87
- data/doc/spec.md +1951 -0
- data/lib/conceptql/behaviors/utilizable.rb +0 -2
- data/lib/conceptql/cli.rb +1 -1
- data/lib/conceptql/data_model/base.rb +1 -167
- data/lib/conceptql/data_model.rb +20 -4
- data/lib/conceptql/database.rb +2 -0
- data/lib/conceptql/lexicon/lexicon_strategy.rb +23 -3
- data/lib/conceptql/lexicon.rb +9 -1
- data/lib/conceptql/operators/co_reported.rb +4 -31
- data/lib/conceptql/operators/hospice.rb +1 -2
- data/lib/conceptql/operators/hospitalization.rb +1 -2
- data/lib/conceptql/operators/information_periods.rb +1 -3
- data/lib/conceptql/operators/one_in_two_out.rb +1 -2
- data/lib/conceptql/operators/operator.rb +3 -11
- data/lib/conceptql/operators/provenance.rb +1 -2
- data/lib/conceptql/operators/race.rb +2 -6
- data/lib/conceptql/operators/read.rb +1 -100
- data/lib/conceptql/operators/snf.rb +1 -2
- data/lib/conceptql/operators/vocabulary.rb +15 -2
- data/lib/conceptql/paths.rb +6 -0
- data/lib/conceptql/version.rb +1 -1
- data/lib/conceptql/vocabularies/entry.rb +0 -20
- data/lib/conceptql/vocabularies/vocabulary_class_factory.rb +2 -15
- metadata +2 -11
- data/lib/conceptql/data_model/omopv4_plus.rb +0 -12
- data/lib/conceptql/query_modifiers/omopv4_plus/admission_date_query_modifier.rb +0 -45
- data/lib/conceptql/query_modifiers/omopv4_plus/drug_query_modifier.rb +0 -80
- data/lib/conceptql/query_modifiers/omopv4_plus/pos_query_modifier.rb +0 -47
- data/lib/conceptql/query_modifiers/omopv4_plus/provenance_query_modifier.rb +0 -45
- data/lib/conceptql/query_modifiers/omopv4_plus/provider_query_modifier.rb +0 -26
- data/lib/conceptql/vocabularies/behaviors/costish.rb +0 -24
- data/lib/conceptql/vocabularies/behaviors/omopish.rb +0 -32
- data/schemas/omopv4_plus.yml +0 -729
- data/schemas/omopv4_plus_more_ignorables.tsv +0 -13
data/lib/conceptql/cli.rb
CHANGED
|
@@ -94,7 +94,7 @@ module ConceptQL
|
|
|
94
94
|
given by the connection options (or sequelizer's configuration).
|
|
95
95
|
DESC
|
|
96
96
|
option :counts, type: :boolean, default: false, desc: "annotate with per-domain row/person counts (needs a DB)"
|
|
97
|
-
option :data_model, desc: "data model: gdm, gdm_wide
|
|
97
|
+
option :data_model, desc: "data model: gdm, gdm_wide (default: $CONCEPTQL_DATA_MODEL or gdm)"
|
|
98
98
|
def render_json(statement_file)
|
|
99
99
|
cdb = if options[:counts]
|
|
100
100
|
ConceptQL::Database.new(db(options.except("counts", "data_model")),
|
|
@@ -10,6 +10,7 @@ module ConceptQL
|
|
|
10
10
|
def_delegators :nodifier, :cdb
|
|
11
11
|
def_delegators :cdb, :lexicon, :source_table_exists?
|
|
12
12
|
def_delegators :lexicon,
|
|
13
|
+
:code_meanings,
|
|
13
14
|
:concept_codes,
|
|
14
15
|
:concept_ids,
|
|
15
16
|
:concept_ids_by_name,
|
|
@@ -47,79 +48,14 @@ module ConceptQL
|
|
|
47
48
|
db[table]
|
|
48
49
|
end
|
|
49
50
|
|
|
50
|
-
# The data columns whose concept ids the display-name joins look up, by
|
|
51
|
-
# table. A data model without those joins has none.
|
|
52
|
-
def name_concept_columns
|
|
53
|
-
{}
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
def query_modifier_for(column)
|
|
57
|
-
{
|
|
58
|
-
visit_source_concept_id: ConceptQL::QueryModifiers::Omopv4Plus::PoSQueryModifier,
|
|
59
|
-
provider_id: ConceptQL::QueryModifiers::Omopv4Plus::ProviderQueryModifier,
|
|
60
|
-
context_specialty_concept_id: ConceptQL::QueryModifiers::Omopv4Plus::ProviderQueryModifier,
|
|
61
|
-
drug_name: ConceptQL::QueryModifiers::Omopv4Plus::DrugQueryModifier,
|
|
62
|
-
admission_date: ConceptQL::QueryModifiers::Omopv4Plus::AdmissionDateQueryModifier,
|
|
63
|
-
provenance_type: ConceptQL::QueryModifiers::Omopv4Plus::ProvenanceQueryModifier
|
|
64
|
-
}[column]
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
def criterion_id
|
|
68
|
-
:criterion_id
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
def table_id(table = nil)
|
|
72
|
-
return :person_id if table.to_sym == :death
|
|
73
|
-
|
|
74
|
-
:criterion_id if table.nil?
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
def person_table
|
|
78
|
-
:person
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
def make_table_id(table)
|
|
82
|
-
"#{table}_id".to_sym
|
|
83
|
-
end
|
|
84
|
-
|
|
85
51
|
def make_fk_id(table)
|
|
86
52
|
make_table_id(table)
|
|
87
53
|
end
|
|
88
54
|
|
|
89
|
-
def person_id(table)
|
|
90
|
-
case table.to_sym
|
|
91
|
-
when :procedure_cost, :drug_cost
|
|
92
|
-
nil
|
|
93
|
-
else
|
|
94
|
-
:person_id
|
|
95
|
-
end
|
|
96
|
-
end
|
|
97
|
-
|
|
98
55
|
def schema
|
|
99
56
|
SCHEMAS[data_model]
|
|
100
57
|
end
|
|
101
58
|
|
|
102
|
-
def data_model
|
|
103
|
-
:omopv4_plus
|
|
104
|
-
end
|
|
105
|
-
|
|
106
|
-
def pk_by_domain(domain)
|
|
107
|
-
case domain.to_sym
|
|
108
|
-
when :death
|
|
109
|
-
:person_id
|
|
110
|
-
when :procedure_cost
|
|
111
|
-
:procedure_occurrence_id
|
|
112
|
-
when :drug_cost
|
|
113
|
-
:drug_exposure_id
|
|
114
|
-
else
|
|
115
|
-
"#{domain}_id".to_sym
|
|
116
|
-
end
|
|
117
|
-
end
|
|
118
|
-
|
|
119
|
-
def fk_by_domain(domain)
|
|
120
|
-
"#{domain}_id".to_sym
|
|
121
|
-
end
|
|
122
|
-
|
|
123
59
|
def query_columns
|
|
124
60
|
@query_columns ||= Hash[operator.scope.query_columns.zip(operator.scope.query_columns)]
|
|
125
61
|
end
|
|
@@ -231,10 +167,6 @@ module ConceptQL
|
|
|
231
167
|
}
|
|
232
168
|
end
|
|
233
169
|
|
|
234
|
-
def table_to_domain(table)
|
|
235
|
-
table
|
|
236
|
-
end
|
|
237
|
-
|
|
238
170
|
def replace(replace_hash)
|
|
239
171
|
return {} unless replace_hash
|
|
240
172
|
|
|
@@ -276,14 +208,6 @@ module ConceptQL
|
|
|
276
208
|
end
|
|
277
209
|
end
|
|
278
210
|
|
|
279
|
-
def start_date_column(query, domain)
|
|
280
|
-
start_date_columns.merge(person: person_date_of_birth(query))[domain]
|
|
281
|
-
end
|
|
282
|
-
|
|
283
|
-
def end_date_column(query, domain)
|
|
284
|
-
end_date_columns.merge(person: person_date_of_birth(query))[domain]
|
|
285
|
-
end
|
|
286
|
-
|
|
287
211
|
def source_value_columns
|
|
288
212
|
@source_value_columns ||= assign_column_to_table do |table, columns|
|
|
289
213
|
reggy = /#{table.to_s.split('_').first}_source_value$/
|
|
@@ -292,81 +216,10 @@ module ConceptQL
|
|
|
292
216
|
end
|
|
293
217
|
end
|
|
294
218
|
|
|
295
|
-
def source_vocabulary_ids
|
|
296
|
-
@source_vocabulary_ids = assign_column_to_table do |table, columns|
|
|
297
|
-
next if %w[person death].any? { |tn| table.to_s =~ /#{tn}/ }
|
|
298
|
-
|
|
299
|
-
reggy = /#{table.to_s.split('_').first}_source_vocabulary_id/
|
|
300
|
-
column = columns.select { |k| k =~ reggy }.first
|
|
301
|
-
column || columns.select { |k| k =~ /_source_vocabulary_id/ }.first
|
|
302
|
-
end
|
|
303
|
-
end
|
|
304
|
-
|
|
305
|
-
def id_columns
|
|
306
|
-
@id_columns ||= assign_column_to_table do |table, columns|
|
|
307
|
-
reggy = /#{table}_id/
|
|
308
|
-
columns.select { |k| k =~ reggy }.first
|
|
309
|
-
end
|
|
310
|
-
end
|
|
311
|
-
|
|
312
|
-
def id_column(table)
|
|
313
|
-
id_columns[table]
|
|
314
|
-
end
|
|
315
|
-
|
|
316
|
-
def type_concept_id_columns
|
|
317
|
-
@type_concept_id_columns ||= assign_column_to_table do |_table, columns|
|
|
318
|
-
columns.select { |c| c =~ /_type_concept_id$/ }.first
|
|
319
|
-
end
|
|
320
|
-
end
|
|
321
|
-
|
|
322
|
-
def type_concept_id_column(domain)
|
|
323
|
-
type_concept_id_columns[table_by_domain(domain)]
|
|
324
|
-
end
|
|
325
|
-
|
|
326
|
-
def table_by_domain(domain)
|
|
327
|
-
domain
|
|
328
|
-
end
|
|
329
|
-
|
|
330
|
-
def period_table
|
|
331
|
-
:observation_period
|
|
332
|
-
end
|
|
333
|
-
|
|
334
219
|
def source_value_column(table)
|
|
335
220
|
source_value_columns[table]
|
|
336
221
|
end
|
|
337
222
|
|
|
338
|
-
def code_provenance_type_column(_query, domain)
|
|
339
|
-
{
|
|
340
|
-
condition_occurrence: :condition_type_concept_id,
|
|
341
|
-
death: :death_type_concept_id,
|
|
342
|
-
drug_exposure: :drug_type_concept_id,
|
|
343
|
-
observation: :observation_type_concept_id,
|
|
344
|
-
procedure_occurrence: :procedure_type_concept_id
|
|
345
|
-
}[domain]
|
|
346
|
-
end
|
|
347
|
-
|
|
348
|
-
def file_provenance_type_column(_query, domain)
|
|
349
|
-
{
|
|
350
|
-
condition_occurrence: :condition_type_concept_id,
|
|
351
|
-
death: :death_type_concept_id,
|
|
352
|
-
drug_exposure: :drug_type_concept_id,
|
|
353
|
-
observation: :observation_type_concept_id,
|
|
354
|
-
procedure_occurrence: :procedure_type_concept_id
|
|
355
|
-
}[domain]
|
|
356
|
-
end
|
|
357
|
-
|
|
358
|
-
def provider_id_column(_query, domain)
|
|
359
|
-
{
|
|
360
|
-
condition_occurrence: :associated_provider_id,
|
|
361
|
-
death: :death_type_concept_id,
|
|
362
|
-
drug_exposure: :prescribing_provider_id,
|
|
363
|
-
observation: :associated_provider_id,
|
|
364
|
-
person: :provider_id,
|
|
365
|
-
procedure_occurrence: :associated_provider_id,
|
|
366
|
-
provider: :provider_id
|
|
367
|
-
}[domain]
|
|
368
|
-
end
|
|
369
|
-
|
|
370
223
|
def place_of_service_concept_id_column(_query, domain)
|
|
371
224
|
return nil if domain.nil?
|
|
372
225
|
return Sequel.cast(:visit_source_concept_id, :Bigint) if table_cols(domain).include?(pos_table_fk)
|
|
@@ -374,15 +227,6 @@ module ConceptQL
|
|
|
374
227
|
nil
|
|
375
228
|
end
|
|
376
229
|
|
|
377
|
-
def pos_table_fk
|
|
378
|
-
:visit_occurrence_id
|
|
379
|
-
end
|
|
380
|
-
|
|
381
|
-
def person_date_of_birth(_query)
|
|
382
|
-
rdbms.cast_date(ConceptQL::Utils.assemble_date(:year_of_birth, :month_of_birth, :day_of_birth,
|
|
383
|
-
database_type: nodifier.database_type))
|
|
384
|
-
end
|
|
385
|
-
|
|
386
230
|
def date_columns(query, table = nil)
|
|
387
231
|
sd = start_date_column(query, table)
|
|
388
232
|
sd = rdbms.cast_date(Sequel.expr(sd)).as(:start_date) unless sd == :start_date
|
|
@@ -428,10 +272,6 @@ module ConceptQL
|
|
|
428
272
|
source_vocabulary_ids[table_to_sym(table)]
|
|
429
273
|
end
|
|
430
274
|
|
|
431
|
-
def table_is_missing?(db)
|
|
432
|
-
!(source_table_exists?(db, :observation_period) && source_table_exists?(db, :condition_occurrence))
|
|
433
|
-
end
|
|
434
|
-
|
|
435
275
|
def concept_id(table)
|
|
436
276
|
table_cols(table).find do |col|
|
|
437
277
|
col = col.to_s
|
|
@@ -451,12 +291,6 @@ module ConceptQL
|
|
|
451
291
|
end
|
|
452
292
|
end
|
|
453
293
|
end
|
|
454
|
-
|
|
455
|
-
def information_period_where_clause(arguments)
|
|
456
|
-
return if arguments.empty?
|
|
457
|
-
|
|
458
|
-
{ plan_source_value: arguments }
|
|
459
|
-
end
|
|
460
294
|
end
|
|
461
295
|
end
|
|
462
296
|
end
|
data/lib/conceptql/data_model.rb
CHANGED
|
@@ -2,11 +2,29 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative "data_model/gdm"
|
|
4
4
|
require_relative "data_model/gdm_wide"
|
|
5
|
-
require_relative "data_model/omopv4_plus"
|
|
6
5
|
require_relative "rdbms"
|
|
7
6
|
|
|
8
7
|
module ConceptQL
|
|
9
8
|
module DataModel
|
|
9
|
+
SUPPORTED = %i[gdm gdm_wide].freeze
|
|
10
|
+
|
|
11
|
+
# Raised when a caller asks for a data model that ConceptQL does not have.
|
|
12
|
+
class UnsupportedError < ArgumentError
|
|
13
|
+
REMOVED = { omopv4_plus: "0.5.0" }.freeze
|
|
14
|
+
|
|
15
|
+
def initialize(data_model)
|
|
16
|
+
message = "ConceptQL has no data model #{data_model.inspect}. Use one of: #{SUPPORTED.join(', ')}."
|
|
17
|
+
if (version = REMOVED[data_model])
|
|
18
|
+
message = "The #{data_model} data model was removed in ConceptQL #{version}. Use one of: #{SUPPORTED.join(', ')}."
|
|
19
|
+
end
|
|
20
|
+
super(message)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def self.assert_supported!(data_model)
|
|
25
|
+
raise UnsupportedError, data_model unless SUPPORTED.include?(data_model)
|
|
26
|
+
end
|
|
27
|
+
|
|
10
28
|
def self.get(data_model, opts = {})
|
|
11
29
|
operator = opts[:operator]
|
|
12
30
|
nodifier = opts[:nodifier]
|
|
@@ -16,10 +34,8 @@ module ConceptQL
|
|
|
16
34
|
Gdm.new(operator, nodifier)
|
|
17
35
|
when :gdm_wide
|
|
18
36
|
GdmWide.new(operator, nodifier)
|
|
19
|
-
when :omopv4_plus
|
|
20
|
-
Omopv4Plus.new(operator, nodifier)
|
|
21
37
|
else
|
|
22
|
-
raise
|
|
38
|
+
raise UnsupportedError, data_model
|
|
23
39
|
end
|
|
24
40
|
end
|
|
25
41
|
|
data/lib/conceptql/database.rb
CHANGED
|
@@ -28,6 +28,7 @@ module ConceptQL
|
|
|
28
28
|
@opts = ConceptQL::Utils.rekey(opts, rekey_values: true)
|
|
29
29
|
|
|
30
30
|
@opts[:data_model] ||= (ENV["CONCEPTQL_DATA_MODEL"] || ConceptQL::DEFAULT_DATA_MODEL).to_sym
|
|
31
|
+
ConceptQL::DataModel.assert_supported!(@opts[:data_model])
|
|
31
32
|
|
|
32
33
|
self.class.db_extensions(db, @opts[:data_model], @opts[:use_cold_col])
|
|
33
34
|
db_type = db ? db.database_type.to_sym : :postgres
|
|
@@ -139,6 +140,7 @@ module ConceptQL
|
|
|
139
140
|
# differs from this Database's.
|
|
140
141
|
def operators(data_model = opts[:data_model])
|
|
141
142
|
data_model = data_model.to_sym
|
|
143
|
+
ConceptQL::DataModel.assert_supported!(data_model)
|
|
142
144
|
@operators ||= {}
|
|
143
145
|
@operators[data_model] ||= begin
|
|
144
146
|
vocabulary_operators = Vocabularies::DynamicVocabularies.new(lexicon).operators(data_model)
|
|
@@ -8,9 +8,9 @@ module ConceptQL
|
|
|
8
8
|
@db = db
|
|
9
9
|
end
|
|
10
10
|
|
|
11
|
-
# A lookup is always scoped to a vocabulary. `vocabulary_id` is
|
|
12
|
-
#
|
|
13
|
-
#
|
|
11
|
+
# A lookup is always scoped to a vocabulary. `vocabulary_id` is a String or
|
|
12
|
+
# an Array of vocabulary names (the provenance-type pairs), which Sequel turns
|
|
13
|
+
# into an IN clause.
|
|
14
14
|
#
|
|
15
15
|
# There is deliberately no wildcard. `'*'` is meaningful elsewhere in the
|
|
16
16
|
# operator family, but only in `arguments` — Vocabulary#select_all? reads it
|
|
@@ -98,6 +98,26 @@ module ConceptQL
|
|
|
98
98
|
concepts(db, vocabulary_id, codes).select_map(%i[concept_code concept_text])
|
|
99
99
|
end
|
|
100
100
|
|
|
101
|
+
# The distinct names of each code that has more than one name, as
|
|
102
|
+
# { downcased code => [name, ...] } with the names sorted. A vocabulary
|
|
103
|
+
# can reuse a code for a new meaning: DRG 410 meant chemotherapy until
|
|
104
|
+
# 2007-09-30 and a biliary tract procedure after that. OHDSI keeps each
|
|
105
|
+
# meaning as its own concept row, and the GDM vocabulary keeps both names.
|
|
106
|
+
# Names that differ only in case or spacing count as one name.
|
|
107
|
+
# A caller can answer "is the concept table missing?" with a block.
|
|
108
|
+
def code_meanings(data_db, vocabulary_id, codes)
|
|
109
|
+
return {} if codes.blank? || db.nil? || db_is_mock?(data_db)
|
|
110
|
+
return {} if block_given? ? yield : table_is_missing?(data_db)
|
|
111
|
+
|
|
112
|
+
concepts(db, vocabulary_id, codes)
|
|
113
|
+
.select_map(%i[concept_code concept_text])
|
|
114
|
+
.group_by { |code, _| code.downcase }
|
|
115
|
+
.transform_values { |rows| rows.map(&:last).compact.uniq { |name| name.downcase.split.join(" ") }.sort }
|
|
116
|
+
.select { |_, names| names.length > 1 }
|
|
117
|
+
rescue Sequel::DatabaseError
|
|
118
|
+
{}
|
|
119
|
+
end
|
|
120
|
+
|
|
101
121
|
def vocabulary_is_empty?(data_db, vocabulary_id)
|
|
102
122
|
concepts_table(data_db).where(vocabulary_id: vocabulary_id).count.zero?
|
|
103
123
|
end
|
data/lib/conceptql/lexicon.rb
CHANGED
|
@@ -14,6 +14,7 @@ module ConceptQL
|
|
|
14
14
|
# cache never outlives one export (seed conceptql-a5f). Methods that return
|
|
15
15
|
# datasets are not cached: their query runs later, in the caller.
|
|
16
16
|
CACHED_LOOKUPS = %i[
|
|
17
|
+
code_meanings
|
|
17
18
|
concept_codes
|
|
18
19
|
concept_ids
|
|
19
20
|
concept_ids_by_name
|
|
@@ -43,7 +44,7 @@ module ConceptQL
|
|
|
43
44
|
@cache = {} if cache
|
|
44
45
|
end
|
|
45
46
|
|
|
46
|
-
(CACHED_LOOKUPS - %i[concepts_to_codes known_codes table_is_missing?]).each do |name|
|
|
47
|
+
(CACHED_LOOKUPS - %i[code_meanings concepts_to_codes known_codes table_is_missing?]).each do |name|
|
|
47
48
|
define_method(name) do |*args|
|
|
48
49
|
lookup(name, args) { @strategy.public_send(name, *args) }
|
|
49
50
|
end
|
|
@@ -65,6 +66,13 @@ module ConceptQL
|
|
|
65
66
|
end
|
|
66
67
|
end
|
|
67
68
|
|
|
69
|
+
# Like concepts_to_codes, it asks about the concept table through the block.
|
|
70
|
+
def code_meanings(data_db, vocabulary_id, codes)
|
|
71
|
+
lookup(:code_meanings, [ data_db, vocabulary_id, codes ]) do
|
|
72
|
+
@strategy.code_meanings(data_db, vocabulary_id, codes) { table_is_missing?(data_db) }
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
68
76
|
# Only "the table is there" is kept. A missing table (or a database error,
|
|
69
77
|
# which Sequel's table_exists? turns into "missing") is checked again on
|
|
70
78
|
# the next call, so one transient error does not last the whole export.
|
|
@@ -17,7 +17,10 @@ module ConceptQL
|
|
|
17
17
|
vocabularies.length > 1
|
|
18
18
|
end
|
|
19
19
|
|
|
20
|
-
desc "Passes along
|
|
20
|
+
desc "Passes along the events of its upstream streams that were reported in the same source record " \
|
|
21
|
+
"(the same context). A record is kept only if every upstream stream " \
|
|
22
|
+
"has at least one event in it. If any one upstream has no event in a record, the events of all " \
|
|
23
|
+
"streams in that record are dropped, without a warning."
|
|
21
24
|
|
|
22
25
|
allows_many_upstreams
|
|
23
26
|
category "Combine Streams"
|
|
@@ -27,14 +30,6 @@ module ConceptQL
|
|
|
27
30
|
require_column :context_id
|
|
28
31
|
|
|
29
32
|
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
33
|
contexteds = upstreams.map do |stream|
|
|
39
34
|
[ stream.cte_name(:contextified), contextify(db, stream) ]
|
|
40
35
|
end.to_h
|
|
@@ -84,28 +79,6 @@ module ConceptQL
|
|
|
84
79
|
# .select_append(Sequel[:c][:context_id].as(:context_id))
|
|
85
80
|
# .from_self
|
|
86
81
|
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
82
|
end
|
|
110
83
|
end
|
|
111
84
|
end
|
|
@@ -5,8 +5,7 @@ require_relative "operator"
|
|
|
5
5
|
module ConceptQL
|
|
6
6
|
module Operators
|
|
7
7
|
class Hospice < Operator
|
|
8
|
-
|
|
9
|
-
register __FILE__, :gdm, :gdm_wide
|
|
8
|
+
register __FILE__
|
|
10
9
|
include ConceptQL::Behaviors::Utilizable
|
|
11
10
|
|
|
12
11
|
desc "Selects admission records of type 'hospice'"
|
|
@@ -5,8 +5,7 @@ require_relative "operator"
|
|
|
5
5
|
module ConceptQL
|
|
6
6
|
module Operators
|
|
7
7
|
class Hospitalization < Operator
|
|
8
|
-
|
|
9
|
-
register __FILE__, :gdm, :gdm_wide
|
|
8
|
+
register __FILE__
|
|
10
9
|
include ConceptQL::Behaviors::Utilizable
|
|
11
10
|
|
|
12
11
|
desc "Selects admission records of type 'inpatient'"
|
|
@@ -10,8 +10,7 @@ module ConceptQL
|
|
|
10
10
|
class OneInTwoOut < Operator
|
|
11
11
|
include ConceptQL::Behaviors::SingleEvent
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
register __FILE__, :gdm, :gdm_wide
|
|
13
|
+
register __FILE__
|
|
15
14
|
|
|
16
15
|
desc "Identifies an event that appears either once in an inpatient setting or twice within a specified interval in an outpatient setting."
|
|
17
16
|
|
|
@@ -9,7 +9,7 @@ module ConceptQL
|
|
|
9
9
|
# Built-in operators only: the classes that call Operator.register when
|
|
10
10
|
# their file loads. Vocabulary operators are not in here; each
|
|
11
11
|
# ConceptQL::Database builds its own from its lexicon (Database#operators).
|
|
12
|
-
STATIC_OPERATORS = {
|
|
12
|
+
STATIC_OPERATORS = { gdm: {}, gdm_wide: {} }.freeze
|
|
13
13
|
|
|
14
14
|
SELECTED_COLUMNS = %i[person_id
|
|
15
15
|
criterion_id
|
|
@@ -348,14 +348,6 @@ module ConceptQL
|
|
|
348
348
|
end
|
|
349
349
|
end
|
|
350
350
|
|
|
351
|
-
def omopv4_plus?
|
|
352
|
-
data_model == :omopv4_plus
|
|
353
|
-
end
|
|
354
|
-
|
|
355
|
-
def gdm?
|
|
356
|
-
%i[gdm gdm_wide].include?(data_model)
|
|
357
|
-
end
|
|
358
|
-
|
|
359
351
|
def wide?
|
|
360
352
|
%i[gdm_wide].include?(data_model)
|
|
361
353
|
end
|
|
@@ -467,10 +459,10 @@ module ConceptQL
|
|
|
467
459
|
cast_column(:file_provenance_type, dm.provenance_type_column(query, table))
|
|
468
460
|
end
|
|
469
461
|
|
|
470
|
-
def provider_id(query,
|
|
462
|
+
def provider_id(query, _table)
|
|
471
463
|
return :provider_id if query_columns(query).include?(:provider_id)
|
|
472
464
|
|
|
473
|
-
cast_column(:provider_id
|
|
465
|
+
cast_column(:provider_id)
|
|
474
466
|
end
|
|
475
467
|
|
|
476
468
|
def determine_tables
|
|
@@ -16,8 +16,7 @@ module ConceptQL
|
|
|
16
16
|
#
|
|
17
17
|
# Multiple provenances can be specified at once
|
|
18
18
|
class Provenance < Operator
|
|
19
|
-
|
|
20
|
-
register __FILE__, :gdm, :gdm_wide
|
|
19
|
+
register __FILE__
|
|
21
20
|
|
|
22
21
|
desc "Passes along records with the indicated provenance (e.g. inpatient, outpatient, file type)."
|
|
23
22
|
|
|
@@ -38,11 +38,7 @@ module ConceptQL
|
|
|
38
38
|
end
|
|
39
39
|
|
|
40
40
|
def source_table
|
|
41
|
-
|
|
42
|
-
:patients
|
|
43
|
-
else
|
|
44
|
-
:person
|
|
45
|
-
end
|
|
41
|
+
:patients
|
|
46
42
|
end
|
|
47
43
|
|
|
48
44
|
def query(db)
|
|
@@ -52,7 +48,7 @@ module ConceptQL
|
|
|
52
48
|
|
|
53
49
|
c_ids = (concept_ids + actual_ids).map { |i| [ i, race_descendents[i] ] }.flatten.compact.uniq.sort
|
|
54
50
|
|
|
55
|
-
c_ids = dm.related_concept_ids(db, c_ids)
|
|
51
|
+
c_ids = dm.related_concept_ids(db, c_ids)
|
|
56
52
|
|
|
57
53
|
q = db.from(source_table)
|
|
58
54
|
if words.any? { |w| w.match(/unknown/i) }
|
|
@@ -18,25 +18,15 @@ module ConceptQL
|
|
|
18
18
|
conceptql_spec_id "vocabulary"
|
|
19
19
|
|
|
20
20
|
def query(db)
|
|
21
|
-
gdm? ? gdm(db) : omopv4(db)
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
def gdm(db)
|
|
25
21
|
ops(db).first.evaluate(db)
|
|
26
22
|
end
|
|
27
23
|
|
|
28
|
-
def omopv4(db)
|
|
29
|
-
streams = ops(db).map { |op| op.evaluate(db) }
|
|
30
|
-
|
|
31
|
-
streams.inject { |q, query| q.union(query, all: true) }.from_self
|
|
32
|
-
end
|
|
33
|
-
|
|
34
24
|
def domains(db)
|
|
35
25
|
ops.first.domains(db)
|
|
36
26
|
end
|
|
37
27
|
|
|
38
28
|
def query_cols
|
|
39
|
-
|
|
29
|
+
ops.first.query_cols
|
|
40
30
|
end
|
|
41
31
|
|
|
42
32
|
def required_columns
|
|
@@ -53,40 +43,11 @@ module ConceptQL
|
|
|
53
43
|
ops.flat_map { |op| op.describe_codes(db, codes) }
|
|
54
44
|
end
|
|
55
45
|
|
|
56
|
-
def klasses
|
|
57
|
-
@klasses ||= {
|
|
58
|
-
condition_occurrence: ReadCondition,
|
|
59
|
-
procedure_occurrence: ReadProcedure,
|
|
60
|
-
drug_exposure: ReadDrug,
|
|
61
|
-
observation: ReadObservation
|
|
62
|
-
}
|
|
63
|
-
end
|
|
64
|
-
|
|
65
46
|
class ReadBase < ConceptQL::Operators::Vocabulary
|
|
66
47
|
preferred_name "READ"
|
|
67
48
|
argument :read_codes, type: :codelist, vocab: "Read"
|
|
68
49
|
end
|
|
69
50
|
|
|
70
|
-
class ReadOmopBase < ReadBase
|
|
71
|
-
include ConceptQL::Vocabularies::Behaviors::Omopish
|
|
72
|
-
|
|
73
|
-
def vocabulary_id
|
|
74
|
-
17
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
def source_table
|
|
78
|
-
table
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
def domain
|
|
82
|
-
table
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
def domains(_db)
|
|
86
|
-
Array(domain)
|
|
87
|
-
end
|
|
88
|
-
end
|
|
89
|
-
|
|
90
51
|
class ReadGDM < ReadBase
|
|
91
52
|
include ConceptQL::Vocabularies::Behaviors::Gdmish
|
|
92
53
|
include ConceptQL::Behaviors::Labish
|
|
@@ -103,66 +64,6 @@ module ConceptQL
|
|
|
103
64
|
[ :condition_occurrence ]
|
|
104
65
|
end
|
|
105
66
|
end
|
|
106
|
-
|
|
107
|
-
class ReadCondition < ReadOmopBase
|
|
108
|
-
def table
|
|
109
|
-
:procedure_occurrence
|
|
110
|
-
end
|
|
111
|
-
|
|
112
|
-
def source_column
|
|
113
|
-
:procedure_source_value
|
|
114
|
-
end
|
|
115
|
-
|
|
116
|
-
def concept_column
|
|
117
|
-
:procedure_concept_id
|
|
118
|
-
end
|
|
119
|
-
end
|
|
120
|
-
|
|
121
|
-
class ReadProcedure < ReadOmopBase
|
|
122
|
-
def table
|
|
123
|
-
:procedure_occurrence
|
|
124
|
-
end
|
|
125
|
-
|
|
126
|
-
def source_column
|
|
127
|
-
:procedure_source_value
|
|
128
|
-
end
|
|
129
|
-
|
|
130
|
-
def concept_column
|
|
131
|
-
:procedure_concept_id
|
|
132
|
-
end
|
|
133
|
-
end
|
|
134
|
-
|
|
135
|
-
class ReadObservation < ReadOmopBase
|
|
136
|
-
include ConceptQL::Behaviors::Labish
|
|
137
|
-
|
|
138
|
-
def table
|
|
139
|
-
:observation
|
|
140
|
-
end
|
|
141
|
-
|
|
142
|
-
def source_column
|
|
143
|
-
:observation_source_value
|
|
144
|
-
end
|
|
145
|
-
|
|
146
|
-
def concept_column
|
|
147
|
-
:observation_concept_id
|
|
148
|
-
end
|
|
149
|
-
end
|
|
150
|
-
|
|
151
|
-
class ReadDrug < ReadOmopBase
|
|
152
|
-
include ConceptQL::Behaviors::Drugish
|
|
153
|
-
|
|
154
|
-
def table
|
|
155
|
-
:drug_exposure
|
|
156
|
-
end
|
|
157
|
-
|
|
158
|
-
def source_column
|
|
159
|
-
:drug_source_value
|
|
160
|
-
end
|
|
161
|
-
|
|
162
|
-
def concept_column
|
|
163
|
-
:drug_concept_id
|
|
164
|
-
end
|
|
165
|
-
end
|
|
166
67
|
end
|
|
167
68
|
end
|
|
168
69
|
end
|