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,113 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_support/core_ext/array/conversions"
|
|
4
|
+
require "active_support/core_ext/object/blank"
|
|
5
|
+
require "active_support/core_ext/string/inflections"
|
|
6
|
+
require "json"
|
|
7
|
+
require "open3"
|
|
8
|
+
require "timeout"
|
|
9
|
+
|
|
10
|
+
module ConceptQL
|
|
11
|
+
module Utils
|
|
12
|
+
class << self
|
|
13
|
+
def rekey(h, opts = {})
|
|
14
|
+
# Thanks to: https://stackoverflow.com/a/10721936
|
|
15
|
+
case h
|
|
16
|
+
when Hash
|
|
17
|
+
Hash[
|
|
18
|
+
h.map do |k, v|
|
|
19
|
+
[ k.respond_to?(:to_sym) ? k.to_sym : k, rekey(v, opts) ]
|
|
20
|
+
end
|
|
21
|
+
]
|
|
22
|
+
when Sequel::Dataset
|
|
23
|
+
h
|
|
24
|
+
when Enumerable
|
|
25
|
+
h.map { |v| rekey(v, opts) }
|
|
26
|
+
else
|
|
27
|
+
return h unless opts[:rekey_values]
|
|
28
|
+
return h.to_sym if h.respond_to?(:to_sym)
|
|
29
|
+
|
|
30
|
+
h
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def timed_capture(*commands)
|
|
35
|
+
# Heavily adapted from:
|
|
36
|
+
# https://gist.github.com/lpar/1032297#gistcomment-1738285
|
|
37
|
+
opts = extract_opts!(commands)
|
|
38
|
+
timeout = opts.fetch(:timeout)
|
|
39
|
+
|
|
40
|
+
stdin, stdout, stderr, wait_thread = Open3.popen3(*commands)
|
|
41
|
+
wait_thread[:timed_out] = false
|
|
42
|
+
stdin.puts opts[:stdin_data] if opts[:stdin_data]
|
|
43
|
+
stdin.close
|
|
44
|
+
|
|
45
|
+
# Purposefully NOT using Timeout.rb because in general it is a dangerous API!
|
|
46
|
+
# http://blog.headius.com/2008/02/rubys-threadraise-threadkill-timeoutrb.html
|
|
47
|
+
Thread.new do
|
|
48
|
+
sleep timeout
|
|
49
|
+
if wait_thread.alive?
|
|
50
|
+
begin
|
|
51
|
+
wait_thread[:timed_out] = true
|
|
52
|
+
# please note: we are assuming the command will create ONE process (not handling subprocs / proc groups)
|
|
53
|
+
command = "kill -9 #{wait_thread.pid}"
|
|
54
|
+
system(command)
|
|
55
|
+
rescue(Errno::ESRCH)
|
|
56
|
+
# Do nothing!
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
if (es = wait_thread.value.exitstatus) && !es.zero?
|
|
62
|
+
raise stderr.read
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
out = stdout.read
|
|
66
|
+
stdout.close
|
|
67
|
+
|
|
68
|
+
if wait_thread[:timed_out]
|
|
69
|
+
raise Timeout::Error, "Command #{commands} failed to complete after #{timeout} seconds"
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
out
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def extract_opts!(arr)
|
|
76
|
+
return {} unless arr.last.is_a?(Hash)
|
|
77
|
+
|
|
78
|
+
arr.pop
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def assemble_date(*symbols)
|
|
82
|
+
opts = extract_opts!(symbols)
|
|
83
|
+
tab = opts[:table] ? Sequel[opts[:table].to_sym] : Sequel
|
|
84
|
+
|
|
85
|
+
strings = symbols.map do |symbol|
|
|
86
|
+
sub = "2000"
|
|
87
|
+
col = Sequel.cast_string(tab[symbol])
|
|
88
|
+
if symbol != :year_of_birth
|
|
89
|
+
sub = "01"
|
|
90
|
+
col = Sequel.function(:lpad, col, 2, "0")
|
|
91
|
+
end
|
|
92
|
+
Sequel.function(:coalesce, col, Sequel.expr(sub))
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
strings_with_dashes = strings.zip([ "-" ] * (symbols.length - 1)).flatten.compact
|
|
96
|
+
Sequel.join(strings_with_dashes)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def schema_dump(db)
|
|
100
|
+
schema = db.tables.sort.map do |t|
|
|
101
|
+
columns = db.schema(t).map do |column_name, column_info|
|
|
102
|
+
info = column_info.slice(:type, :allow_null, :primary_key, :default, :comment)
|
|
103
|
+
info[:null] = info.delete(:allow_null) ? nil : false
|
|
104
|
+
info[:primary_key] = info.delete(:primary_key) ? true : nil
|
|
105
|
+
[ column_name, info.compact ]
|
|
106
|
+
end.to_h
|
|
107
|
+
[ t, { columns: columns } ]
|
|
108
|
+
end.to_h
|
|
109
|
+
schema.to_yaml
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "thor"
|
|
4
|
+
require "sequelizer"
|
|
5
|
+
require "psych"
|
|
6
|
+
require_relative "utils"
|
|
7
|
+
|
|
8
|
+
module ConceptQL
|
|
9
|
+
class UtilsCLI < Thor
|
|
10
|
+
include Sequelizer
|
|
11
|
+
|
|
12
|
+
desc "dump_schema", "Dumps out the schema for all tables in YAML format"
|
|
13
|
+
def dump_schema
|
|
14
|
+
puts ConceptQL::Utils.schema_dump(db)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
data/lib/conceptql/version.rb
CHANGED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ConceptQL
|
|
4
|
+
module Visitors
|
|
5
|
+
class MetadataExtractor
|
|
6
|
+
attr_reader :operators
|
|
7
|
+
|
|
8
|
+
def initialize
|
|
9
|
+
@operators = []
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def visit(operator)
|
|
13
|
+
operators << operator
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def results
|
|
17
|
+
{
|
|
18
|
+
unique_operators: operator_names.uniq.sort,
|
|
19
|
+
operators_in_preorder: operator_names,
|
|
20
|
+
codeset_only: codeset_only?,
|
|
21
|
+
num_selection_nodes: num_selection_nodes,
|
|
22
|
+
max_depth: max_depth
|
|
23
|
+
}
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def operator_names
|
|
27
|
+
@operator_names ||= operators.map(&:op_name)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def codeset_only?
|
|
31
|
+
return @codeset_only if defined?(@codeset_only)
|
|
32
|
+
|
|
33
|
+
@codeset_only = operators.reject do |op|
|
|
34
|
+
# We skip Unions because they don't count against codesets
|
|
35
|
+
op.op_name == "union"
|
|
36
|
+
end.map(&:class).map(&:basic_type).all? { |type| type == :selection }
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def max_depth
|
|
40
|
+
operators.reverse.each.with_object({}) do |op, counts|
|
|
41
|
+
counts[op] = if op.all_upstreams.empty?
|
|
42
|
+
1
|
|
43
|
+
else
|
|
44
|
+
counts.values_at(*op.all_upstreams).max + 1
|
|
45
|
+
end
|
|
46
|
+
end.values.max
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def num_selection_nodes
|
|
50
|
+
operators.select { |op| op.upstreams.empty? }.length
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../operators/vocabulary"
|
|
4
|
+
|
|
5
|
+
module ConceptQL
|
|
6
|
+
module Vocabularies
|
|
7
|
+
module Behaviors
|
|
8
|
+
module Costish
|
|
9
|
+
def query(db)
|
|
10
|
+
costs = db[:procedure_cost].where(column_to_search => values).select(:procedure_occurrence_id)
|
|
11
|
+
db[:procedure_occurrence].where(procedure_occurrence_id: costs)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def table
|
|
15
|
+
:procedure_occurrence
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def column_to_search
|
|
19
|
+
vocab_entry.id == "revenue_code" ? :revenue_code_source_value : :disease_class_source_value
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ConceptQL
|
|
4
|
+
module Vocabularies
|
|
5
|
+
module Behaviors
|
|
6
|
+
module Gdmish
|
|
7
|
+
def exclusion_clause(_db)
|
|
8
|
+
return {} unless select_all?
|
|
9
|
+
|
|
10
|
+
{ clinical_code_concept_id: 0 }
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def additional_columns(_db)
|
|
14
|
+
[ Sequel.cast_string(domain.to_s).as(:criterion_domain) ]
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ConceptQL
|
|
4
|
+
module Vocabularies
|
|
5
|
+
module Behaviors
|
|
6
|
+
module Omopish
|
|
7
|
+
# Overrides Operator#tables
|
|
8
|
+
def tables
|
|
9
|
+
domains
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def vocabulary_condition
|
|
13
|
+
{ dm.source_vocabulary_id(domain) => vocabulary_id.to_i }
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def where_clause(_db)
|
|
17
|
+
conds = vocabulary_condition
|
|
18
|
+
conds[dm.source_value_column(domain)] = arguments unless select_all?
|
|
19
|
+
conds
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def query_cols
|
|
23
|
+
dm.table_columns(domain)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def translated_vocabulary_id
|
|
27
|
+
vocab_entry.omopv4_id
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "csv"
|
|
4
|
+
require_relative "entry"
|
|
5
|
+
|
|
6
|
+
module ConceptQL
|
|
7
|
+
module Vocabularies
|
|
8
|
+
class DynamicVocabularies
|
|
9
|
+
# lexicon: the Lexicon whose vocabularies become operators, normally a
|
|
10
|
+
# ConceptQL::Database's own (Database#operators passes it). Without one,
|
|
11
|
+
# only the CSV vocabularies are used.
|
|
12
|
+
def initialize(lexicon = nil)
|
|
13
|
+
@lexicon = lexicon
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Vocabulary operators for one data model, as name => operator class.
|
|
17
|
+
# Fresh classes on every call.
|
|
18
|
+
def operators(data_model)
|
|
19
|
+
all_vocabs.each_with_object({}) do |(name, entry), h|
|
|
20
|
+
# A CSV row marked requires_lexicon (CPT4_HCPCS) supplies metadata
|
|
21
|
+
# only. Its operator exists only where the lexicon has the vocabulary.
|
|
22
|
+
next if entry.requires_lexicon? && !entry.from_lexicon?
|
|
23
|
+
|
|
24
|
+
klass = entry.dup.get_klasses[data_model]
|
|
25
|
+
h[name] = klass if klass
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def all_vocabs
|
|
30
|
+
@all_vocabs ||= each_vocab.each_with_object({}) do |row, h|
|
|
31
|
+
entry = Entry.new(row.to_hash.compact)
|
|
32
|
+
h[entry.id] ||= entry
|
|
33
|
+
h[entry.id] = h[entry.id].merge(entry)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
attr_reader :lexicon
|
|
40
|
+
|
|
41
|
+
def each_vocab
|
|
42
|
+
@each_vocab ||= get_all_vocabs
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def get_all_vocabs
|
|
46
|
+
vocabs = [ ConceptQL.vocabularies_file_path,
|
|
47
|
+
ConceptQL.custom_vocabularies_file_path ].select(&:exist?).map do |path|
|
|
48
|
+
CSV.foreach(path, headers: true, header_converters: :symbol).to_a
|
|
49
|
+
end.inject(:+).each do |v|
|
|
50
|
+
v[:from_csv] = true
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
lexicon_vocabularies + vocabs
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# A Database accepts any object as its lexicon (opts[:lexicon]); one that
|
|
57
|
+
# keeps no vocabulary list contributes no vocabulary operators.
|
|
58
|
+
def lexicon_vocabularies
|
|
59
|
+
lexicon.respond_to?(:vocabularies) ? lexicon.vocabularies : []
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "vocabulary_class_factory"
|
|
4
|
+
|
|
5
|
+
module ConceptQL
|
|
6
|
+
module Vocabularies
|
|
7
|
+
class Entry
|
|
8
|
+
attr_reader :hash
|
|
9
|
+
|
|
10
|
+
METHODS = %i[
|
|
11
|
+
id
|
|
12
|
+
omopv4_vocabulary_id
|
|
13
|
+
omopv5_vocabulary_id
|
|
14
|
+
domain
|
|
15
|
+
vocabulary_short_name
|
|
16
|
+
vocabulary_long_name
|
|
17
|
+
format_regexp
|
|
18
|
+
].freeze
|
|
19
|
+
|
|
20
|
+
METHODS.each do |meth|
|
|
21
|
+
define_method(meth) do
|
|
22
|
+
hash[meth]
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
METADATA_METHODS = METHODS + %i[
|
|
27
|
+
preferred_name
|
|
28
|
+
predominant_domains
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
COST_RELATED_VOCABS = [
|
|
32
|
+
"Revenue Code",
|
|
33
|
+
"DRG"
|
|
34
|
+
].freeze
|
|
35
|
+
|
|
36
|
+
def initialize(hash)
|
|
37
|
+
@hash = hash
|
|
38
|
+
@hash[:omopv5_vocabulary_id] ||= @hash[:id]
|
|
39
|
+
@hash[:aliases_arr] ||= []
|
|
40
|
+
@hash[:aliases_arr] |= (@hash[:aliases] || "").split(";")
|
|
41
|
+
@hash[:id] = @hash[:id].to_s.downcase
|
|
42
|
+
@hash[:aliases_arr] << @hash[:id] if @hash[:id] =~ /\s/
|
|
43
|
+
@hash[:id] = translate_id(@hash[:id].gsub(/\W+/, "_"))
|
|
44
|
+
@hash[:aliases_arr] -= [ @hash[:id] ]
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def translate_id(id)
|
|
48
|
+
{
|
|
49
|
+
"icd10" => "icd10who"
|
|
50
|
+
}[id] || id
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def merge(other_entry)
|
|
54
|
+
self.class.new(hash.merge(other_entry.hash))
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def belongs_in_omopv4_plus?
|
|
58
|
+
@belongs_in_omopv4_plus ||= (!from_lexicon? || from_csv?) \
|
|
59
|
+
&& has_domain? \
|
|
60
|
+
&& visible?
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def to_hash
|
|
64
|
+
METADATA_METHODS.to_h do |meth|
|
|
65
|
+
[ meth, send(meth) ]
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def short_name
|
|
70
|
+
vocabulary_short_name || vocabulary_long_name
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def long_name
|
|
74
|
+
vocabulary_long_name || vocabulary_short_name
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def preferred_name
|
|
78
|
+
omopv5_id || id
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def aliases
|
|
82
|
+
@hash[:aliases_arr]
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def predominant_domains
|
|
86
|
+
Array(domain || :condition_occurrence).flatten
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def from_lexicon?
|
|
90
|
+
hash[:from_lexicon].present?
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def from_csv?
|
|
94
|
+
hash[:from_csv].present?
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def requires_lexicon?
|
|
98
|
+
hash[:requires_lexicon].present?
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def has_domain?
|
|
102
|
+
hash[:domain].present?
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def hidden?
|
|
106
|
+
hash[:hidden].present?
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def is_labish?
|
|
110
|
+
hash[:domain] =~ /observation|measurement/i
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def is_drugish?
|
|
114
|
+
hash[:domain] =~ /drug_exposure/i
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def is_costish?
|
|
118
|
+
belongs_in_omopv4_plus? && COST_RELATED_VOCABS.include?(omopv5_vocabulary_id)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def visible?
|
|
122
|
+
!hidden?
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def omopv5_id
|
|
126
|
+
omopv5_vocabulary_id || id
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def omopv4_id
|
|
130
|
+
omopv4_vocabulary_id
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def get_klasses
|
|
134
|
+
VocabularyClassFactory.new(self).get_klasses
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../behaviors/drugish"
|
|
4
|
+
require_relative "../behaviors/labish"
|
|
5
|
+
require_relative "behaviors/gdmish"
|
|
6
|
+
require_relative "behaviors/omopish"
|
|
7
|
+
require_relative "behaviors/costish"
|
|
8
|
+
require_relative "../operators/vocabulary"
|
|
9
|
+
|
|
10
|
+
module ConceptQL
|
|
11
|
+
module Vocabularies
|
|
12
|
+
class VocabularyClassFactory
|
|
13
|
+
attr_reader :entry
|
|
14
|
+
|
|
15
|
+
def initialize(entry)
|
|
16
|
+
@entry = entry
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def get_klasses
|
|
20
|
+
{
|
|
21
|
+
gdm: get_gdm_klass,
|
|
22
|
+
gdm_wide: get_gdm_klass,
|
|
23
|
+
omopv4_plus: get_omopv4_plus_klass
|
|
24
|
+
}.compact
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def get_gdm_klass
|
|
28
|
+
get_klass do
|
|
29
|
+
include Behaviors::Gdmish
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def get_omopv4_plus_klass
|
|
34
|
+
return nil unless entry.belongs_in_omopv4_plus?
|
|
35
|
+
|
|
36
|
+
get_klass do
|
|
37
|
+
include Behaviors::Omopish
|
|
38
|
+
|
|
39
|
+
include Behaviors::Costish if entry.is_costish?
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def get_klass(&block)
|
|
44
|
+
ventry = entry
|
|
45
|
+
Class.new(ConceptQL::Operators::Vocabulary) do |klass|
|
|
46
|
+
@entry = ventry
|
|
47
|
+
|
|
48
|
+
class << self
|
|
49
|
+
attr_reader :entry
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
preferred_name entry.preferred_name
|
|
53
|
+
argument :codes, type: :codelist
|
|
54
|
+
aliases entry.aliases
|
|
55
|
+
predominant_domains entry.predominant_domains
|
|
56
|
+
short_name entry.short_name
|
|
57
|
+
long_name entry.long_name
|
|
58
|
+
conceptql_spec_id "vocabulary"
|
|
59
|
+
|
|
60
|
+
klass.class_eval(&block)
|
|
61
|
+
|
|
62
|
+
def self.inspect
|
|
63
|
+
name
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def self.to_s
|
|
67
|
+
name
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def self.name
|
|
71
|
+
"ConceptQL::Operator::#{entry.id}"
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
include ConceptQL::Behaviors::Labish if entry.is_labish?
|
|
75
|
+
|
|
76
|
+
include ConceptQL::Behaviors::Drugish if entry.is_drugish?
|
|
77
|
+
|
|
78
|
+
def vocab_entry
|
|
79
|
+
self.class.entry
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ConceptQL
|
|
4
|
+
module Window
|
|
5
|
+
# Base class for classes apply scope windows to incoming Datasets
|
|
6
|
+
class Base
|
|
7
|
+
attr_reader :opts
|
|
8
|
+
|
|
9
|
+
def initialize(opts = {})
|
|
10
|
+
@opts = opts
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def call(_op, _ds, _options = {})
|
|
14
|
+
raise NotImplementedError
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def event_start_date_column
|
|
18
|
+
Sequel[opts[:event_start_date_column].to_sym]
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def event_end_date_column
|
|
22
|
+
Sequel[opts[:event_end_date_column].to_sym]
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base"
|
|
4
|
+
|
|
5
|
+
module ConceptQL
|
|
6
|
+
module Window
|
|
7
|
+
# Provides a scope window that uses a date range literal
|
|
8
|
+
class DateRange < Base
|
|
9
|
+
def call(operator, dset, options = {})
|
|
10
|
+
return dset if options[:timeless]
|
|
11
|
+
|
|
12
|
+
rdbms = operator.rdbms
|
|
13
|
+
dset.where(start_check(rdbms)).where(end_check(rdbms)).from_self
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def start_check(rdbms)
|
|
17
|
+
rdbms.cast_date(opts[:start_date]) <= event_start_date_column
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def end_check(rdbms)
|
|
21
|
+
event_end_date_column <= rdbms.cast_date(opts[:end_date])
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|