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.
Files changed (242) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +226 -117
  3. data/LICENSE +21 -0
  4. data/README.md +35 -83
  5. data/bin/conceptql +2 -1
  6. data/bin/conceptql_utils +6 -0
  7. data/config/multiple_vocabularies.csv +4 -0
  8. data/config/provenance.yml +489 -0
  9. data/config/race.yml +50 -0
  10. data/config/vocabularies.csv +87 -0
  11. data/lib/conceptql/behaviors/code_lister.rb +23 -0
  12. data/lib/conceptql/behaviors/drugish.rb +17 -0
  13. data/lib/conceptql/behaviors/labish.rb +19 -0
  14. data/lib/conceptql/behaviors/metadatable.rb +226 -73
  15. data/lib/conceptql/behaviors/provenanceable.rb +26 -0
  16. data/lib/conceptql/behaviors/single_event.rb +11 -0
  17. data/lib/conceptql/behaviors/timeless.rb +11 -0
  18. data/lib/conceptql/behaviors/utilizable.rb +206 -0
  19. data/lib/conceptql/behaviors/windowable.rb +27 -0
  20. data/lib/conceptql/cli.rb +117 -108
  21. data/lib/conceptql/columnizer.rb +75 -0
  22. data/lib/conceptql/data_model/base.rb +462 -0
  23. data/lib/conceptql/data_model/gdm.rb +155 -0
  24. data/lib/conceptql/data_model/gdm_wide.rb +42 -0
  25. data/lib/conceptql/data_model/omopv4_plus.rb +12 -0
  26. data/lib/conceptql/data_model.rb +30 -0
  27. data/lib/conceptql/database.rb +214 -0
  28. data/lib/conceptql/date_adjuster.rb +66 -18
  29. data/lib/conceptql/diagram.rb +243 -0
  30. data/lib/conceptql/lexicon/lexicon_gdm.rb +47 -0
  31. data/lib/conceptql/lexicon/lexicon_no_db.rb +49 -0
  32. data/lib/conceptql/lexicon/lexicon_ohdsi.rb +63 -0
  33. data/lib/conceptql/lexicon/lexicon_strategy.rb +116 -0
  34. data/lib/conceptql/lexicon.rb +111 -0
  35. data/lib/conceptql/nodifier.rb +65 -24
  36. data/lib/conceptql/operators/after.rb +34 -14
  37. data/lib/conceptql/operators/any_overlap.rb +8 -6
  38. data/lib/conceptql/operators/before.rb +34 -10
  39. data/lib/conceptql/operators/binary_operator_operator.rb +66 -23
  40. data/lib/conceptql/operators/casting_operator.rb +51 -36
  41. data/lib/conceptql/operators/co_reported.rb +111 -0
  42. data/lib/conceptql/operators/concurrent_within.rb +69 -0
  43. data/lib/conceptql/operators/contains.rb +12 -8
  44. data/lib/conceptql/operators/count.rb +17 -9
  45. data/lib/conceptql/operators/date_range.rb +48 -14
  46. data/lib/conceptql/operators/death.rb +14 -4
  47. data/lib/conceptql/operators/dedup.rb +31 -0
  48. data/lib/conceptql/operators/during.rb +7 -12
  49. data/lib/conceptql/operators/episode.rb +146 -0
  50. data/lib/conceptql/operators/equal.rb +9 -3
  51. data/lib/conceptql/operators/ethnicity.rb +49 -0
  52. data/lib/conceptql/operators/except.rb +19 -12
  53. data/lib/conceptql/operators/filter.rb +23 -8
  54. data/lib/conceptql/operators/first.rb +16 -3
  55. data/lib/conceptql/operators/from.rb +52 -4
  56. data/lib/conceptql/operators/gender.rb +34 -11
  57. data/lib/conceptql/operators/hospice.rb +19 -0
  58. data/lib/conceptql/operators/hospitalization.rb +19 -0
  59. data/lib/conceptql/operators/information_periods.rb +36 -0
  60. data/lib/conceptql/operators/invalid.rb +35 -0
  61. data/lib/conceptql/operators/last.rb +16 -3
  62. data/lib/conceptql/operators/match.rb +91 -0
  63. data/lib/conceptql/operators/multiple_vocabularies.rb +172 -0
  64. data/lib/conceptql/operators/numeric.rb +65 -23
  65. data/lib/conceptql/operators/numeric_filter.rb +68 -0
  66. data/lib/conceptql/operators/occurrence.rb +153 -32
  67. data/lib/conceptql/operators/one_in_two_out.rb +237 -38
  68. data/lib/conceptql/operators/operator.rb +624 -178
  69. data/lib/conceptql/operators/pass_thru.rb +17 -3
  70. data/lib/conceptql/operators/person.rb +18 -6
  71. data/lib/conceptql/operators/person_filter.rb +9 -3
  72. data/lib/conceptql/operators/place_of_service_filter.rb +43 -0
  73. data/lib/conceptql/operators/provenance.rb +55 -0
  74. data/lib/conceptql/operators/provider_filter.rb +62 -0
  75. data/lib/conceptql/operators/race.rb +55 -7
  76. data/lib/conceptql/operators/read.rb +168 -0
  77. data/lib/conceptql/operators/recall.rb +49 -14
  78. data/lib/conceptql/operators/snf.rb +19 -0
  79. data/lib/conceptql/operators/sum.rb +29 -12
  80. data/lib/conceptql/operators/temporal_operator.rb +108 -16
  81. data/lib/conceptql/operators/time_window.rb +20 -32
  82. data/lib/conceptql/operators/trim_date.rb +71 -0
  83. data/lib/conceptql/operators/trim_date_end.rb +28 -40
  84. data/lib/conceptql/operators/trim_date_start.rb +28 -41
  85. data/lib/conceptql/operators/union.rb +67 -6
  86. data/lib/conceptql/operators/vocabulary.rb +151 -0
  87. data/lib/conceptql/paths.rb +41 -0
  88. data/lib/conceptql/query.rb +191 -26
  89. data/lib/conceptql/query_modifiers/gdm/admission_date_query_modifier.rb +46 -0
  90. data/lib/conceptql/query_modifiers/gdm/drug_query_modifier.rb +54 -0
  91. data/lib/conceptql/query_modifiers/gdm/lab_query_modifier.rb +57 -0
  92. data/lib/conceptql/query_modifiers/gdm/pos_query_modifier.rb +46 -0
  93. data/lib/conceptql/query_modifiers/gdm/provenance_query_modifier.rb +45 -0
  94. data/lib/conceptql/query_modifiers/gdm/provider_query_modifier.rb +53 -0
  95. data/lib/conceptql/query_modifiers/gdm_wide/admission_date_query_modifier.rb +42 -0
  96. data/lib/conceptql/query_modifiers/gdm_wide/drug_query_modifier.rb +52 -0
  97. data/lib/conceptql/query_modifiers/gdm_wide/lab_query_modifier.rb +55 -0
  98. data/lib/conceptql/query_modifiers/gdm_wide/pos_query_modifier.rb +37 -0
  99. data/lib/conceptql/query_modifiers/gdm_wide/provenance_query_modifier.rb +38 -0
  100. data/lib/conceptql/query_modifiers/omopv4_plus/admission_date_query_modifier.rb +45 -0
  101. data/lib/conceptql/query_modifiers/omopv4_plus/drug_query_modifier.rb +80 -0
  102. data/lib/conceptql/query_modifiers/omopv4_plus/pos_query_modifier.rb +47 -0
  103. data/lib/conceptql/query_modifiers/omopv4_plus/provenance_query_modifier.rb +45 -0
  104. data/lib/conceptql/query_modifiers/omopv4_plus/provider_query_modifier.rb +26 -0
  105. data/lib/conceptql/query_modifiers/query_modifier.rb +20 -0
  106. data/lib/conceptql/rdbms/duckdb.rb +25 -0
  107. data/lib/conceptql/rdbms/generic.rb +120 -0
  108. data/lib/conceptql/rdbms/postgres.rb +51 -0
  109. data/lib/conceptql/rdbms/spark.rb +22 -0
  110. data/lib/conceptql/rdbms.rb +22 -0
  111. data/lib/conceptql/scope.rb +481 -31
  112. data/lib/conceptql/sql_formatters/formatter.rb +25 -0
  113. data/lib/conceptql/sql_formatters.rb +22 -0
  114. data/lib/conceptql/utils.rb +113 -0
  115. data/lib/conceptql/utils_cli.rb +17 -0
  116. data/lib/conceptql/version.rb +3 -1
  117. data/lib/conceptql/visitors/metadata_extractor.rb +54 -0
  118. data/lib/conceptql/vocabularies/behaviors/costish.rb +24 -0
  119. data/lib/conceptql/vocabularies/behaviors/gdmish.rb +19 -0
  120. data/lib/conceptql/vocabularies/behaviors/omopish.rb +32 -0
  121. data/lib/conceptql/vocabularies/dynamic_vocabularies.rb +63 -0
  122. data/lib/conceptql/vocabularies/entry.rb +138 -0
  123. data/lib/conceptql/vocabularies/vocabulary_class_factory.rb +85 -0
  124. data/lib/conceptql/window/base.rb +26 -0
  125. data/lib/conceptql/window/date_range.rb +25 -0
  126. data/lib/conceptql/window/table.rb +116 -0
  127. data/lib/conceptql/window.rb +72 -0
  128. data/lib/conceptql.rb +65 -2
  129. data/lib/sequel/extensions/pg_ctas_explain.rb +22 -0
  130. data/lib/sequel/extensions/pg_vacuum_table.rb +23 -0
  131. data/schemas/gdm.yml +882 -0
  132. data/schemas/gdm_wide.yml +757 -0
  133. data/schemas/ohdsi_vocabs.yml +143 -0
  134. data/schemas/omopv4_plus.yml +729 -0
  135. data/schemas/omopv4_plus_more_ignorables.tsv +13 -0
  136. metadata +267 -207
  137. data/.gitignore +0 -23
  138. data/Gemfile +0 -4
  139. data/Guardfile +0 -56
  140. data/LICENSE.txt +0 -22
  141. data/Rakefile +0 -1
  142. data/conceptql.gemspec +0 -32
  143. data/doc/ConceptQL Specification (alpha).pdf +0 -0
  144. data/doc/diagram_0.png +0 -0
  145. data/doc/implementation_notes.md +0 -39
  146. data/doc/spec.md +0 -1344
  147. data/lib/conceptql/behaviors/debuggable.rb +0 -70
  148. data/lib/conceptql/behaviors/dottable.rb +0 -103
  149. data/lib/conceptql/behaviors/preppable.rb +0 -20
  150. data/lib/conceptql/converter.rb +0 -65
  151. data/lib/conceptql/debugger.rb +0 -48
  152. data/lib/conceptql/fake_grapher.rb +0 -20
  153. data/lib/conceptql/graph.rb +0 -52
  154. data/lib/conceptql/graph_nodifier.rb +0 -203
  155. data/lib/conceptql/logger.rb +0 -10
  156. data/lib/conceptql/operators/complement.rb +0 -38
  157. data/lib/conceptql/operators/concept.rb +0 -70
  158. data/lib/conceptql/operators/condition_type.rb +0 -71
  159. data/lib/conceptql/operators/cpt.rb +0 -25
  160. data/lib/conceptql/operators/drug_type_concept.rb +0 -21
  161. data/lib/conceptql/operators/from_seer_visits.rb +0 -23
  162. data/lib/conceptql/operators/hcpcs.rb +0 -25
  163. data/lib/conceptql/operators/icd10.rb +0 -28
  164. data/lib/conceptql/operators/icd9.rb +0 -28
  165. data/lib/conceptql/operators/icd9_procedure.rb +0 -25
  166. data/lib/conceptql/operators/intersect.rb +0 -34
  167. data/lib/conceptql/operators/loinc.rb +0 -25
  168. data/lib/conceptql/operators/medcode.rb +0 -28
  169. data/lib/conceptql/operators/medcode_procedure.rb +0 -27
  170. data/lib/conceptql/operators/ndc.rb +0 -29
  171. data/lib/conceptql/operators/observation_by_enttype.rb +0 -29
  172. data/lib/conceptql/operators/observation_period.rb +0 -30
  173. data/lib/conceptql/operators/overlapped_by.rb +0 -23
  174. data/lib/conceptql/operators/overlaps.rb +0 -19
  175. data/lib/conceptql/operators/place_of_service_code.rb +0 -24
  176. data/lib/conceptql/operators/procedure_occurrence.rb +0 -25
  177. data/lib/conceptql/operators/prodcode.rb +0 -29
  178. data/lib/conceptql/operators/rxnorm.rb +0 -24
  179. data/lib/conceptql/operators/snomed.rb +0 -24
  180. data/lib/conceptql/operators/snomed_condition.rb +0 -26
  181. data/lib/conceptql/operators/source_vocabulary_operator.rb +0 -58
  182. data/lib/conceptql/operators/standard_vocabulary_operator.rb +0 -45
  183. data/lib/conceptql/operators/started_by.rb +0 -22
  184. data/lib/conceptql/operators/to_seer_visits.rb +0 -24
  185. data/lib/conceptql/operators/visit.rb +0 -15
  186. data/lib/conceptql/operators/visit_occurrence.rb +0 -30
  187. data/lib/conceptql/tree.rb +0 -53
  188. data/lib/conceptql/utils/temp_table.rb +0 -72
  189. data/spec/conceptql/behaviors/dottable_spec.rb +0 -99
  190. data/spec/conceptql/converter_spec.rb +0 -51
  191. data/spec/conceptql/date_adjuster_spec.rb +0 -68
  192. data/spec/conceptql/operators/after_spec.rb +0 -16
  193. data/spec/conceptql/operators/before_spec.rb +0 -16
  194. data/spec/conceptql/operators/casting_operator_spec.rb +0 -68
  195. data/spec/conceptql/operators/complement_spec.rb +0 -15
  196. data/spec/conceptql/operators/concept_spec.rb +0 -40
  197. data/spec/conceptql/operators/condition_type_spec.rb +0 -128
  198. data/spec/conceptql/operators/contains_spec.rb +0 -19
  199. data/spec/conceptql/operators/cpt_spec.rb +0 -29
  200. data/spec/conceptql/operators/date_range_spec.rb +0 -33
  201. data/spec/conceptql/operators/death_spec.rb +0 -10
  202. data/spec/conceptql/operators/during_spec.rb +0 -30
  203. data/spec/conceptql/operators/except_spec.rb +0 -15
  204. data/spec/conceptql/operators/first_spec.rb +0 -35
  205. data/spec/conceptql/operators/from_spec.rb +0 -13
  206. data/spec/conceptql/operators/gender_spec.rb +0 -27
  207. data/spec/conceptql/operators/hcpcs_spec.rb +0 -29
  208. data/spec/conceptql/operators/icd10_spec.rb +0 -34
  209. data/spec/conceptql/operators/icd9_procedure_spec.rb +0 -29
  210. data/spec/conceptql/operators/icd9_spec.rb +0 -34
  211. data/spec/conceptql/operators/intersect_spec.rb +0 -28
  212. data/spec/conceptql/operators/last_spec.rb +0 -36
  213. data/spec/conceptql/operators/loinc_spec.rb +0 -29
  214. data/spec/conceptql/operators/medcode_procedure_spec.rb +0 -34
  215. data/spec/conceptql/operators/medcode_spec.rb +0 -34
  216. data/spec/conceptql/operators/observation_period_spec.rb +0 -10
  217. data/spec/conceptql/operators/occurrence_spec.rb +0 -87
  218. data/spec/conceptql/operators/overlapped_by_spec.rb +0 -32
  219. data/spec/conceptql/operators/overlaps_spec.rb +0 -21
  220. data/spec/conceptql/operators/person_filter_spec.rb +0 -15
  221. data/spec/conceptql/operators/person_spec.rb +0 -10
  222. data/spec/conceptql/operators/place_of_service_code_spec.rb +0 -24
  223. data/spec/conceptql/operators/procedure_occurrence_spec.rb +0 -10
  224. data/spec/conceptql/operators/prodcode_spec.rb +0 -35
  225. data/spec/conceptql/operators/query_double.rb +0 -20
  226. data/spec/conceptql/operators/query_double_spec.rb +0 -7
  227. data/spec/conceptql/operators/race_spec.rb +0 -21
  228. data/spec/conceptql/operators/rxnorm_spec.rb +0 -29
  229. data/spec/conceptql/operators/snomed_spec.rb +0 -29
  230. data/spec/conceptql/operators/source_vocabulary_operator_spec.rb +0 -35
  231. data/spec/conceptql/operators/standard_vocabulary_operator_spec.rb +0 -35
  232. data/spec/conceptql/operators/started_by_spec.rb +0 -22
  233. data/spec/conceptql/operators/temporal_operator_spec.rb +0 -51
  234. data/spec/conceptql/operators/time_window_spec.rb +0 -77
  235. data/spec/conceptql/operators/union_spec.rb +0 -21
  236. data/spec/conceptql/operators/visit_occurrence_spec.rb +0 -10
  237. data/spec/conceptql/query_spec.rb +0 -22
  238. data/spec/conceptql/tree_spec.rb +0 -50
  239. data/spec/doubles/stream_for_casting_double.rb +0 -9
  240. data/spec/doubles/stream_for_occurrence_double.rb +0 -25
  241. data/spec/doubles/stream_for_temporal_double.rb +0 -6
  242. 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
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ConceptQL
2
- VERSION = "0.2.0"
4
+ VERSION = "0.4.0"
3
5
  end
@@ -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