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
@@ -1,58 +0,0 @@
1
- require_relative 'operator'
2
-
3
- module ConceptQL
4
- module Operators
5
- # A SourceVocabularyOperator is a superclass for a operator that represents a criterion whose column stores information associated with a source vocabulary.
6
- #
7
- # If that seems confusing, then think of ICD-9 or NDC criteria. That type of criterion takes a set of values that are mapped via the source_to_concept_map table into a standard vocabulary.
8
- #
9
- # This kind of criterion can have some interesting issues when we are searching for rows that match. Let's take ICD-9 286.53 for example. That code maps to concept_id 0, so if we try to pull all conditions that match just the concept_id of 0 we'll pull all condtions that have no match in the SNOMED standard vocabulary. That's not what we want! So we need to search the condition_source_value field for matches on this code instead.
10
- #
11
- # But there's another, more complicated problem. Say we're looking for ICD-9 289.81. OMOP maps this to concept_id 432585. OMOP also maps 20 other conditions, 6 of which are other ICD-9 codes, to this same concept_id. So if we look for ICD-9s that have a non-zero condition_condept_id, we might pull up conditions that match on concept_id, but aren't the same exact code as the one we're looking for.
12
- #
13
- # My coworker came up with a nice, gneralized query that checks for matching concept_ids and matching source_code values. This class encapsulates that query.
14
- #
15
- # Subclasses must provide the following methods:
16
- # * table
17
- # * The CDM table name where the criterion will fetch its rows
18
- # * e.g. for ICD-9, this would be condition_occurrence
19
- # * concept_column
20
- # * Name of the column in the table that stores a concept_id related to the criterion
21
- # * e.g. for ICD-9, this would be condition_concept_id
22
- # * source_column
23
- # * Name of the column in the table that stores the "raw" value related to the criterion
24
- # * e.g. for ICD-9, this would be condition_source_value
25
- # * vocabulary_id
26
- # * The vocabulary ID of the source vocabulary for the criterion
27
- # * e.g. for ICD-9, a value of 2 (for ICD-9-CM)
28
- class SourceVocabularyOperator < Operator
29
- category 'Source Vocabulary'
30
- category 'Code Lists'
31
-
32
- def query(db)
33
- db.from(table_name)
34
- .join(:vocabulary__source_to_concept_map___scm, scm__target_concept_id: table_concept_column)
35
- .where(Sequel.expr(scm__source_code: values, scm__source_vocabulary_id: vocabulary_id).&(Sequel.expr(scm__source_code: table_source_column)))
36
- end
37
-
38
- def type
39
- table
40
- end
41
-
42
- private
43
-
44
- def table_name
45
- @table_name ||= make_table_name(table)
46
- end
47
-
48
- def table_concept_column
49
- "tab__#{concept_column}".to_sym
50
- end
51
-
52
- def table_source_column
53
- "tab__#{source_column}".to_sym
54
- end
55
- end
56
- end
57
- end
58
-
@@ -1,45 +0,0 @@
1
- require_relative 'operator'
2
-
3
- module ConceptQL
4
- module Operators
5
- # A StandardVocabularyOperator is a superclass for a operator that represents a criterion whose column stores information associated with a standard vocabulary.
6
- #
7
- # If that seems confusing, then think of CPT or SNOMED criteria. That type of criterion takes a set of values that live in the OMOP concept table.
8
- #
9
- # My coworker came up with a nice, gneralized query that checks for matching concept_ids and matching source_code values. This class encapsulates that query.
10
- #
11
- # Subclasses must provide the following methods:
12
- # * table
13
- # * The CDM table name where the criterion will fetch its rows
14
- # * e.g. for CPT, this would be procedure_occurrence
15
- # * concept_column
16
- # * Name of the column in the table that stores a concept_id related to the criterion
17
- # * e.g. for CPT, this would be procedure_concept_id
18
- # * vocabulary_id
19
- # * The vocabulary ID of the source vocabulary for the criterion
20
- # * e.g. for CPT, a value of 4 (for CPT-4)
21
- class StandardVocabularyOperator < Operator
22
- category 'Standard Vocabulary'
23
- category 'Code Lists'
24
- def query(db)
25
- db.from(table_name)
26
- .join(:vocabulary__concept___c, c__concept_id: table_concept_column)
27
- .where(c__concept_code: values, c__vocabulary_id: vocabulary_id)
28
- end
29
-
30
- def type
31
- table
32
- end
33
- private
34
-
35
- def table_name
36
- @table_name ||= make_table_name(table)
37
- end
38
-
39
- def table_concept_column
40
- "tab__#{concept_column}".to_sym
41
- end
42
- end
43
- end
44
- end
45
-
@@ -1,22 +0,0 @@
1
- require_relative 'temporal_operator'
2
-
3
- module ConceptQL
4
- module Operators
5
- class StartedBy < TemporalOperator
6
- desc <<-EOF
7
- If LHR has the same start date as RHR, but LHR's end_date falls on or after end_date of RHR, LHR is passed through.
8
- L----Y----L
9
- R-------R
10
- L--N--L
11
- EOF
12
- def where_clause
13
- [ { l__start_date: :r__start_date } ] + \
14
- if inclusive?
15
- [ Proc.new { l__end_date >= r__end_date } ]
16
- else
17
- [ Proc.new { l__end_date > r__end_date } ]
18
- end
19
- end
20
- end
21
- end
22
- end
@@ -1,24 +0,0 @@
1
- require_relative 'operator'
2
-
3
- module ConceptQL
4
- module Operators
5
- class ToSeerVisits < Operator
6
- def type
7
- :visit_occurrence
8
- end
9
-
10
- def query(db)
11
- query = options.map do |k, v|
12
- next if v.nil?
13
- db[:observation]
14
- .where(observation_source_value: k.to_s.upcase, value_as_string: v)
15
- .select(:visit_occurrence_id)
16
- .from_self
17
- end.compact.inject { |q, i| i.intersect(q) }
18
- db[:visit_occurrence].where(visit_occurrence_id: query)
19
- end
20
- end
21
- end
22
- end
23
-
24
-
@@ -1,15 +0,0 @@
1
- require_relative 'operator'
2
-
3
- module ConceptQL
4
- module Operators
5
- class Visit < Operator
6
- desc 'Generates all visit_occurrence records, or, if fed a stream, fetches all visit_occurrence records for the people represented in the incoming result set.'
7
- types :visit_occurrence
8
- allows_one_upstream
9
-
10
- def types
11
- [:visit_occurrence]
12
- end
13
- end
14
- end
15
- end
@@ -1,30 +0,0 @@
1
- require_relative 'casting_operator'
2
-
3
- module ConceptQL
4
- module Operators
5
- class VisitOccurrence < CastingOperator
6
- desc 'Returns all visits in the database, or if given a upstream, converts all results to the set of visit_occurrences related to those results.'
7
- allows_one_upstream
8
- types :visit_occurrence
9
-
10
- def my_type
11
- :visit_occurrence
12
- end
13
-
14
- def i_point_at
15
- [ :person ]
16
- end
17
-
18
- def these_point_at_me
19
- %i[
20
- condition_occurrence
21
- drug_cost
22
- drug_exposure
23
- observation
24
- procedure_cost
25
- procedure_occurrence
26
- ]
27
- end
28
- end
29
- end
30
- end
@@ -1,53 +0,0 @@
1
- require_relative 'nodifier'
2
- require_relative 'converter'
3
- require_relative 'scope'
4
- require 'facets/hash/deep_rekey'
5
- require 'facets/array/recurse'
6
-
7
- module ConceptQL
8
- # Tree is used to walk through a ConceptQL statement, instantiate
9
- # all operators, and then provide access to the root operator
10
- class Tree
11
- attr :nodifier, :behavior, :defined, :opts, :temp_tables, :scope
12
- def initialize(opts = {})
13
- @nodifier = opts.fetch(:nodifier, Nodifier.new)
14
- @behavior = opts.fetch(:behavior, nil)
15
- @defined = {}
16
- @temp_tables = {}
17
- @opts = {}
18
- @scope = opts.fetch(:scope, Scope.new)
19
- end
20
-
21
- def root(query)
22
- @root ||= start_traverse(query.statement)
23
- end
24
-
25
- private
26
-
27
- def start_traverse(stmt)
28
- case stmt
29
- when Hash
30
- traverse(converter.convert(stmt))
31
- when Array
32
- traverse(stmt)
33
- end
34
- end
35
-
36
- def traverse(stmt)
37
- stmt.recurse(Array, Hash) do |arr_or_hash|
38
- if arr_or_hash.is_a?(Array)
39
- type = arr_or_hash.shift
40
- obj = nodifier.create(scope, type, *arr_or_hash)
41
- obj.extend(behavior) if behavior
42
- obj
43
- else
44
- arr_or_hash
45
- end
46
- end
47
- end
48
-
49
- def converter
50
- @converter ||= Converter.new
51
- end
52
- end
53
- end
@@ -1,72 +0,0 @@
1
- require 'zlib'
2
-
3
- module ConceptQL
4
- # TempTable coordinates the creation of any temporary tables a statement
5
- # might need.
6
- #
7
- # Currently, temp tables are used to share a set of results when a Recall
8
- # operator is present in a statement.
9
- #
10
- # It also provides an API to generate the SQL that Sequel would use to create
11
- # and populate the temp table
12
- class TempTable
13
- attr :operator
14
- def initialize(operator, db)
15
- @operator = operator
16
- fake_it!(db)
17
- end
18
-
19
- def build(db)
20
- @built ||= build_it(db)
21
- end
22
-
23
- def sql(db)
24
- # Sequel doesn't (currently) provide an API for getting the SQL for the
25
- # creation of tables, so we're calling one of the private methods
26
- sql = db[db.send(:create_table_as_sql, table_name, operator.evaluate(db), temp: true)].sql
27
- ["-- #{operator.label}", sql].join("\n")
28
- end
29
-
30
- def from(db)
31
- db[table_name]
32
- end
33
-
34
- private
35
-
36
- def table_name
37
- namify(operator.label)
38
- end
39
-
40
- def build_it(db)
41
- db.create_table!(table_name, as: operator.evaluate(db), temp: true)
42
- true
43
- end
44
-
45
- def namify(name)
46
- digest = Zlib.crc32 name
47
- ('_' + digest.to_s).to_sym
48
- end
49
-
50
- # Creates the temp table, but populates it with a single fake row of null
51
- # values. Some operations, such as generating the SQL for a statement
52
- # Want the temp tables to exist, but we don't _really_ need the actual
53
- # results in the temp table since they might take a long time to calculate.
54
- def fake_it!(db)
55
- db.create_table!(table_name, temp: true, as: fake_row(db))
56
- end
57
-
58
- def fake_row(db)
59
- db
60
- .select(Sequel.cast(nil, Bignum).as(:person_id))
61
- .select_append(Sequel.cast(nil, Bignum).as(:criterion_id))
62
- .select_append(Sequel.cast(nil, String).as(:criterion_type))
63
- .select_append(Sequel.cast(nil, Date).as(:start_date))
64
- .select_append(Sequel.cast(nil, Date).as(:end_date))
65
- .select_append(Sequel.cast(nil, Bignum).as(:value_as_number))
66
- .select_append(Sequel.cast(nil, String).as(:value_as_string))
67
- .select_append(Sequel.cast(nil, Bignum).as(:value_as_concept_id))
68
- .select_append(Sequel.cast(nil, String).as(:units_source_value))
69
- .select_append(Sequel.cast(nil, String).as(:source_value))
70
- end
71
- end
72
- end
@@ -1,99 +0,0 @@
1
- require 'spec_helper'
2
- require 'conceptql/operators/operator'
3
- require 'conceptql/behaviors/dottable'
4
-
5
- class OperatorDouble < ConceptQL::Operators::Operator
6
- include ConceptQL::Behaviors::Dottable
7
-
8
- def initialize(*args)
9
- super
10
- @types = []
11
- end
12
- end
13
-
14
- describe ConceptQL::Behaviors::Dottable do
15
- before do
16
- @obj = OperatorDouble.new
17
- end
18
-
19
- describe '#display_name' do
20
- it 'should show just the name if no upstreams or arguments' do
21
- @obj.values = []
22
- expect(@obj.display_name).to match(/Operator Double( \d+)?/)
23
- end
24
-
25
- it 'should show name and args' do
26
- @obj.values = [5, 10]
27
- expect(@obj.display_name).to match(/Operator Double( \d+)?: 5, 10/)
28
- end
29
-
30
- it 'should not include upstreams' do
31
- @obj.values = [::ConceptQL::Operators::Operator.new]
32
- expect(@obj.display_name).to match(/Operator Double( \d+)?/)
33
- end
34
- end
35
-
36
- describe '#operator_name' do
37
- it 'should show just the name and digit if no upstreams' do
38
- @obj.values = [::ConceptQL::Operators::Operator.new]
39
- expect(@obj.operator_name).to match(/^operator_double_\d+$/)
40
- end
41
-
42
- it 'should not show args' do
43
- @obj.values = [5, 10]
44
- expect(@obj.operator_name).to match(/^operator_double_\d+$/)
45
- end
46
-
47
- it 'should not include upstreams' do
48
- @obj.values = [::ConceptQL::Operators::Operator.new]
49
- expect(@obj.operator_name).to match(/^operator_double_\d+$/)
50
- end
51
- end
52
-
53
- describe '#graph_it' do
54
- it 'should add itself as a operator if no upstreams' do
55
- @obj.values = []
56
- mock_graph = double("graph")
57
- mock_operator = double("operator")
58
- expect(mock_graph).to receive(:add_nodes).with(@obj.operator_name).and_return(mock_operator)
59
- expect(mock_operator).to receive(:[]=).with(:label, @obj.display_name).and_return(nil)
60
- expect(mock_operator).to receive(:[]=).with(:color, 'black').and_return(nil)
61
- @obj.graph_it(mock_graph, Sequel.mock)
62
- end
63
-
64
- it 'should add its upstreams, then link itself as a operator if upstreams' do
65
- class MockUpstream < ConceptQL::Operators::Operator
66
- include ConceptQL::Behaviors::Dottable
67
-
68
- attr_accessor :mock
69
- def graph_it(graph, db)
70
- mock.graph_it(graph, db)
71
- end
72
-
73
- def types
74
- mock.types
75
- end
76
-
77
- def link_to(mock_graph, mock_operator, db)
78
- mock.link_to(mock_graph, mock_operator, db)
79
- end
80
- end
81
-
82
- mock_operator = double("operator")
83
- expect(mock_operator).to receive(:[]=).with(:label, @obj.display_name).and_return(nil)
84
- expect(mock_operator).to receive(:[]=).with(:color, 'black').and_return(nil)
85
-
86
- mock_graph = double("graph")
87
- expect(mock_graph).to receive(:add_nodes).with(@obj.operator_name).and_return(mock_operator)
88
-
89
- mock_upstream = MockUpstream.new
90
- mock_upstream.mock = double("upstream")
91
- expect(mock_upstream.mock).to receive(:graph_it).with(mock_graph, :db).and_return(:upstream_operator)
92
- expect(mock_upstream.mock).to receive(:link_to).with(mock_graph, mock_operator, :db).and_return(nil)
93
-
94
- @obj.values = mock_upstream
95
-
96
- @obj.graph_it(mock_graph, :db)
97
- end
98
- end
99
- end
@@ -1,51 +0,0 @@
1
- require 'spec_helper'
2
- require 'conceptql/converter'
3
-
4
- describe ConceptQL::Converter do
5
- describe '#convert' do
6
- it 'convert {icd9: 412} to [:icd9, "412"]' do
7
- statement = { icd9: '412' }
8
- expect(ConceptQL::Converter.new.convert(statement)).to eq([:icd9, '412'])
9
- end
10
-
11
- it 'convert {icd9: ["412", "200"]} to [:icd9, "412"]' do
12
- statement = { icd9: %w(412 200) }
13
- expect(ConceptQL::Converter.new.convert(statement)).to eq([:icd9, '412', '200'])
14
- end
15
-
16
- it 'convert { first: {icd9: ["412", "200"]}} to [:first, [:icd9, "412"] ]' do
17
- statement = {
18
- first: {
19
- icd9: %w(412 200)
20
- }
21
- }
22
- expect(ConceptQL::Converter.new.convert(statement)).to eq([:first, [:icd9, '412', '200'] ])
23
- end
24
-
25
- it 'convert operators in options to list-syntax' do
26
- statement = {
27
- after: {
28
- left: {
29
- icd9: '412'
30
- },
31
- right: {
32
- icd9: '200'
33
-
34
- }
35
- }
36
- }
37
- expect(ConceptQL::Converter.new.convert(statement)).to eq([:after, { left: [:icd9, '412'], right: [:icd9, '200'] }])
38
- end
39
-
40
- it 'converts operators with an array of upstreams' do
41
- statement = {
42
- intersect: [
43
- { icd9: '412' },
44
- { condition_type: :inpatient_header }
45
- ]
46
- }
47
- expect(ConceptQL::Converter.new.convert(statement)).to eq([:intersect, [ :icd9, '412' ], [ :condition_type, :inpatient_header ] ])
48
- end
49
- end
50
- end
51
-
@@ -1,68 +0,0 @@
1
- require 'spec_helper'
2
- require 'conceptql/date_adjuster'
3
-
4
- describe ConceptQL::DateAdjuster do
5
- describe '#adjustments' do
6
- it 'returns nothing for input of ""' do
7
- expect(ConceptQL::DateAdjuster.new('').adjustments).to eq([])
8
- end
9
-
10
- it 'returns nothing for input of "0"' do
11
- expect(ConceptQL::DateAdjuster.new('').adjustments).to eq([])
12
- end
13
-
14
- it 'returns nothing for input of nil' do
15
- expect(ConceptQL::DateAdjuster.new(nil).adjustments).to eq([])
16
- end
17
-
18
- it 'returns single day for input of "1"' do
19
- expect(ConceptQL::DateAdjuster.new('1').adjustments).to eq([[:days, 1]])
20
- end
21
-
22
- it 'returns 20 days for input of "20"' do
23
- expect(ConceptQL::DateAdjuster.new('20').adjustments).to eq([[:days, 20]])
24
- end
25
-
26
- it 'returns single day for input of "d"' do
27
- expect(ConceptQL::DateAdjuster.new('d').adjustments).to eq([[:days, 1]])
28
- end
29
-
30
- it 'returns single month for input of "m"' do
31
- expect(ConceptQL::DateAdjuster.new('m').adjustments).to eq([[:months, 1]])
32
- end
33
-
34
- it 'returns single year for input of "y"' do
35
- expect(ConceptQL::DateAdjuster.new('y').adjustments).to eq([[:years, 1]])
36
- end
37
-
38
- it 'returns 2 days for input of "2d"' do
39
- expect(ConceptQL::DateAdjuster.new('2d').adjustments).to eq([[:days, 2]])
40
- end
41
-
42
- it 'returns 2 months for input of "2m"' do
43
- expect(ConceptQL::DateAdjuster.new('2m').adjustments).to eq([[:months, 2]])
44
- end
45
-
46
- it 'returns 2 years for input of "2y"' do
47
- expect(ConceptQL::DateAdjuster.new('2y').adjustments).to eq([[:years, 2]])
48
- end
49
-
50
- it 'returns negative single day for input of "-d"' do
51
- expect(ConceptQL::DateAdjuster.new('-d').adjustments).to eq([[:days, -1]])
52
- end
53
-
54
- it 'returns negative single day, positive single month for "-dm"' do
55
- expect(ConceptQL::DateAdjuster.new('-dm').adjustments).to eq([[:days, -1], [:months, 1]])
56
- end
57
-
58
- it 'returns negative 2 days for "-2d"' do
59
- expect(ConceptQL::DateAdjuster.new('-2d').adjustments).to eq([[:days, -2]])
60
- end
61
-
62
- it 'returns positive 2 days for "2d"' do
63
- expect(ConceptQL::DateAdjuster.new('2d').adjustments).to eq([[:days, 2]])
64
- end
65
- end
66
- end
67
-
68
-
@@ -1,16 +0,0 @@
1
- require 'spec_helper'
2
- require 'conceptql/operators/after'
3
- require_double('stream_for_temporal')
4
-
5
- describe ConceptQL::Operators::After do
6
- it_behaves_like(:temporal_operator)
7
-
8
- subject do
9
- described_class.new(left: StreamForTemporalDouble.new, right: StreamForTemporalDouble.new)
10
- end
11
-
12
- it 'should use proper where clause' do
13
- expect(subject.query(Sequel.mock).sql).to match('l.start_date > r.end_date')
14
- end
15
- end
16
-
@@ -1,16 +0,0 @@
1
- require 'spec_helper'
2
- require 'conceptql/operators/before'
3
- require_double('stream_for_temporal')
4
-
5
- describe ConceptQL::Operators::Before do
6
- it_behaves_like(:temporal_operator)
7
-
8
- subject do
9
- described_class.new(left: StreamForTemporalDouble.new, right: StreamForTemporalDouble.new)
10
- end
11
-
12
- it 'should use proper where clause' do
13
- expect(subject.query(Sequel.mock).sql).to match('l.end_date < r.start_date')
14
- end
15
- end
16
-
@@ -1,68 +0,0 @@
1
- require 'spec_helper'
2
- require 'conceptql/operators/casting_operator'
3
- require_double('stream_for_casting')
4
-
5
- describe ConceptQL::Operators::CastingOperator do
6
- it_behaves_like(:evaluator)
7
-
8
- class CastingDouble < ConceptQL::Operators::CastingOperator
9
- def my_type
10
- :my_type
11
- end
12
-
13
- def i_point_at
14
- [ :i_point1, :i_point2 ]
15
- end
16
-
17
- def these_point_at_me
18
- [ :at_me1, :at_me2 ]
19
- end
20
- end
21
-
22
- describe CastingDouble do
23
- it_behaves_like(:casting_operator)
24
- end
25
-
26
- describe '#query' do
27
- it 'uses person_ids when an uncastable type is encountered' do
28
- stream = StreamForCastingDouble.new
29
- stream.types = [:uncastable]
30
- sql = CastingDouble.new(stream).query(Sequel.mock).sql
31
- expect(sql).to match('person_id IN')
32
- expect(sql).to match('GROUP BY person_id')
33
- expect(sql).to match('FROM table')
34
- end
35
-
36
- it 'uses person_ids when an uncastable type is included among castable types' do
37
- stream = StreamForCastingDouble.new
38
- stream.types = [:i_point1, :uncastable]
39
- sql = CastingDouble.new(stream).query(Sequel.mock).sql
40
- expect(sql).to match('person_id IN')
41
- expect(sql).to match('GROUP BY person_id')
42
- end
43
-
44
- it 'uses castable types if possible' do
45
- stream = StreamForCastingDouble.new
46
- stream.types = [:i_point1]
47
- sql = CastingDouble.new(stream).query(Sequel.mock).sql
48
- expect(sql).to match('i_point1_id IN')
49
- expect(sql).to match("criterion_type = 'i_point1'")
50
- end
51
-
52
- it 'uses and unions multiple castable types if possible' do
53
- stream = StreamForCastingDouble.new
54
- stream.types = [:i_point1, :at_me2]
55
- sql = CastingDouble.new(stream).query(Sequel.mock).sql
56
- expect(sql).to match('my_type_id IN')
57
- expect(sql).to match('i_point1_id IN')
58
- expect(sql).to match("criterion_type = 'i_point1'")
59
- end
60
-
61
- it 'returns all rows of a table if passed the argument "true"' do
62
- sql = CastingDouble.new(true).query(Sequel.mock).sql
63
- expect(sql).to match("SELECT * FROM my_type AS tab")
64
- end
65
- end
66
- end
67
-
68
-
@@ -1,15 +0,0 @@
1
- require 'spec_helper'
2
- require 'conceptql/operators/complement'
3
- require_relative 'query_double'
4
-
5
- describe ConceptQL::Operators::Complement do
6
- it_behaves_like(:evaluator)
7
-
8
- it 'generates complement for single criteria' do
9
- double1 = QueryDouble.new(1)
10
- sql = ConceptQL::Operators::Complement.new(double1).query(Sequel.mock).sql
11
- expect(sql).to match("criterion_id IS NOT NULL")
12
- expect(sql).to match("visit_occurrence_id NOT IN")
13
- expect(sql).to match("criterion_type = 'visit_occurrence'")
14
- end
15
- end