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,62 +1,261 @@
1
- require_relative 'operator'
2
- require_relative 'visit_occurrence'
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "operator"
4
+ require_relative "../date_adjuster"
5
+ require_relative "../behaviors/provenanceable"
6
+ require_relative "../behaviors/single_event"
3
7
 
4
8
  module ConceptQL
5
9
  module Operators
6
10
  class OneInTwoOut < Operator
7
- desc <<-EOF
8
- Represents a common pattern in research algorithms: searching for a condition
9
- that appears either two times in an outpatient setting with a 30-day gap or once
10
- in an inpatient setting
11
- EOF
11
+ include ConceptQL::Behaviors::SingleEvent
12
+
13
+ # GDM only: omopv4_plus has no provenance vocabularies (conceptql-myp).
14
+ register __FILE__, :gdm, :gdm_wide
15
+
16
+ desc "Identifies an event that appears either once in an inpatient setting or twice within a specified interval in an outpatient setting."
17
+
12
18
  allows_one_upstream
13
- category %w(Temporal Relative)
19
+ validate_one_upstream
20
+ validate_no_arguments
21
+ category "Filter Single Stream"
22
+ basic_type :temporal
14
23
 
15
- def types
16
- [:visit_occurrence]
17
- end
24
+ option :inpatient_length_of_stay, type: :integer, min: 0, default: 0,
25
+ desc: "Minimum length of inpatient stay (in days) required for inpatient event to be valid", label: "Inpatient Length of Stay (Days)"
26
+ option :outpatient_minimum_gap, type: :string, default: "30d",
27
+ instructions: 'Enter a numeric value and specify "d", "m", or "y" for "days", "months", or "years".'
28
+ option :outpatient_maximum_gap, type: :string, default: "365d",
29
+ instructions: 'Enter a numeric value and specify "d", "m", or "y" for "days", "months", or "years".'
30
+ option :outpatient_event_to_return, type: :string, options: [ "Initial Event", "Confirming Event" ],
31
+ default: "Initial Event", desc: "Which event to pass downstream"
32
+
33
+ validate_option DateAdjuster::VALID_INPUT, :outpatient_minimum_gap, :outpatient_maximum_gap
34
+
35
+ default_query_columns
36
+
37
+ include ConceptQL::Provenanceable
38
+
39
+ require_column :admission_date
40
+ require_column :discharge_date
41
+
42
+ # Each event is copied once per tag, by a cross join with a constant
43
+ # table, so the upstream is read only once. The copies sort by date
44
+ # and, at equal dates, by tag: a lower gap bound, then an event, then
45
+ # an upper gap bound. Running sums over that order then count, at each
46
+ # bound, the events on or after the lower bound and on or before the
47
+ # upper bound, which is the old self-join's condition.
48
+ LOWER = 0
49
+ EVENT = 1
50
+ UPPER = 2
51
+ INPATIENT = 3
52
+
53
+ attr_reader :db
18
54
 
19
55
  def query(db)
20
- inpatient = select_it(visit_query(db).where(place_of_service_concept_id: 8717), :visit_occurrence).from_self
21
- outpatient = select_it(visit_query(db).exclude(place_of_service_concept_id: 8717), :visit_occurrence).from_self
22
-
23
- gap = options[:gap] || 30
24
- valid_outpatient_people = outpatient
25
- .group_by(:person_id)
26
- .select(:person_id)
27
- .select_append(Sequel.expr(Sequel.function(:max, :start_date) - Sequel.function(:min, :end_date)).as(:date_diff))
56
+ @db = db
57
+ first_valid_event.from_self
58
+ end
59
+
60
+ private
61
+
62
+ def initial_mode?
63
+ options[:outpatient_event_to_return] == "Initial Event"
64
+ end
65
+
66
+ def min_gap
67
+ (options[:outpatient_minimum_gap] || "30d").to_s
68
+ end
69
+
70
+ def max_gap
71
+ options[:outpatient_maximum_gap].to_s
72
+ end
73
+
74
+ # In the old self-join, a bare START or END resolved to the subquery's
75
+ # table. In Initial Event mode that is the confirming event, so the
76
+ # "gap" compares the confirming event with itself. It filters which
77
+ # events can confirm and bounds nothing.
78
+ def keyword_gap?(gap)
79
+ initial_mode? && %w[start end].include?(gap.downcase)
80
+ end
81
+
82
+ def adjusted(gap)
83
+ DateAdjuster.new(self, gap).adjust(:start_date)
84
+ end
85
+
86
+ # The earliest date a partner event may have. NULL when the bound is
87
+ # NULL, as the old comparison then failed.
88
+ def lower_bound
89
+ return Sequel[:start_date] if min_gap.empty? || keyword_gap?(min_gap)
90
+
91
+ bound = adjusted(min_gap)
92
+ Sequel.case([ [ Sequel.expr(bound) > :start_date, bound ], [ Sequel.expr(bound) <= :start_date, :start_date ] ], nil)
93
+ end
94
+
95
+ # The latest date a partner event may have, or nil for no limit.
96
+ def upper_bound
97
+ return if max_gap.empty? || keyword_gap?(max_gap)
98
+
99
+ adjusted(max_gap)
100
+ end
101
+
102
+ def partner_filter
103
+ conditions = []
104
+ conditions << (Sequel.expr(:start_date) >= adjusted(min_gap)) if keyword_gap?(min_gap)
105
+ conditions << (Sequel.expr(:start_date) <= adjusted(max_gap)) if keyword_gap?(max_gap)
106
+ return true if conditions.empty?
107
+
108
+ Sequel.&(*conditions)
109
+ end
110
+
111
+ def inpatient_condition
112
+ condition = build_where_from_codes(db, [ "inpatient" ])
113
+ return condition if options[:inpatient_length_of_stay].nil? || options[:inpatient_length_of_stay].to_i.zero?
114
+
115
+ Sequel.&(condition, rdbms.days_between(:admission_date, :discharge_date) > options[:inpatient_length_of_stay].to_i)
116
+ end
117
+
118
+ # The old self-join compared these columns with = and <>, so a NULL in
119
+ # any of them kept an event from pairing.
120
+ def outpatient_condition
121
+ not_null = (matching_columns + [ :criterion_id ]).map { |c| Sequel.~(c => nil) }
122
+ Sequel.&(build_where_from_codes(db, %w[carrier_claim outpatient]), *not_null)
123
+ end
124
+
125
+ def flag(condition)
126
+ Sequel.case([ [ condition, 1 ] ], 0)
127
+ end
128
+
129
+ def condition_events
130
+ db[stream.evaluate(db)]
131
+ .where(criterion_domain: "condition_occurrence")
132
+ .where(Sequel.|(inpatient_condition, outpatient_condition))
133
+ .select(*query_cols)
134
+ .select_append(flag(inpatient_condition).as(:is_inpatient), flag(outpatient_condition).as(:is_outpatient))
135
+ .select_append(lower_bound.as(:lower_bound))
136
+ .select_append(flag(partner_filter).as(:can_partner))
137
+ .then { |ds| initial_mode? ? ds.select_append(event_key) : ds }
138
+ .from_self
139
+ # The upper bound gets its own SELECT. DuckDB compares 1 month as
140
+ # equal to 30 days, so its optimizer merges date + 30 days and
141
+ # date + 1 month in one SELECT list into one expression (bead
142
+ # conceptql-a4j, seed conceptql-3wy). sequel-duckdb is to emit month
143
+ # intervals in a form that avoids this; the split stays as a second
144
+ # guard.
145
+ .select_append(Sequel.as(upper_bound || Sequel.cast(nil, Date), :upper_bound))
28
146
  .from_self
29
- .where{ date_diff >= gap}
147
+ end
30
148
 
31
- relevant_outpatient = outpatient.where(person_id: valid_outpatient_people.select(:person_id))
32
- earliest(db, inpatient.union(relevant_outpatient))
149
+ # Tells an event's copies apart from other events' copies. Only Initial
150
+ # Event mode needs it, to bring its two bounds' counts together.
151
+ def event_key
152
+ Sequel.function(:row_number).over(partition: matching_columns, order: %i[start_date criterion_id]).as(:event_key)
33
153
  end
34
154
 
35
- private
36
- def visit_query(db)
37
- VisitOccurrence.new(FakeOperator.new(stream.evaluate(db).from_self, stream.types)).query(db)
155
+ def tags
156
+ [ LOWER, EVENT, UPPER, INPATIENT ]
157
+ .map { |tag| db.select(Sequel.as(tag, :tag)) }
158
+ .inject { |all, one| all.union(one, all: true, from_self: false) }
38
159
  end
39
160
 
40
- def earliest(db, query)
41
- db[:earliest]
42
- .with(:earliest,
43
- query.select_append { |o| o.row_number(:over, partition: :person_id, order: [Sequel.asc(:start_date), :criterion_type, :criterion_id]){}.as(:rn) })
44
- .where(rn: 1)
161
+ def bounds_valid
162
+ valid = Sequel.~(lower_bound: nil)
163
+ return valid unless upper_bound
164
+
165
+ Sequel.&(valid, Sequel.~(upper_bound: nil), Sequel.expr(:lower_bound) <= :upper_bound)
166
+ end
167
+
168
+ def copies
169
+ bound_tags = upper_bound ? [ LOWER, UPPER ] : [ LOWER ]
170
+ condition_events
171
+ .cross_join(tags, table_alias: :tags)
172
+ .where(Sequel.|(
173
+ Sequel.&({ tag: INPATIENT }, { is_inpatient: 1 }),
174
+ Sequel.&({ tag: EVENT }, { is_outpatient: 1 }, Sequel.~(start_date: nil)),
175
+ Sequel.&({ tag: bound_tags }, { is_outpatient: 1 }, bounds_valid)
176
+ ))
177
+ .select_all(:t1)
178
+ .select_append(:tag)
179
+ .select_append(Sequel.case({ LOWER => :lower_bound, UPPER => :upper_bound }, :start_date, :tag).as(:sweep_date))
180
+ .select_append(weight.as(:weight))
45
181
  .from_self
46
182
  end
47
183
 
48
- class FakeOperator < Operator
49
- attr :types
50
- def initialize(query, types)
51
- @query = query
52
- @types = types
184
+ # Initial Event mode counts events at its bounds. Confirming Event mode
185
+ # counts the bound intervals that are open at each event.
186
+ def weight
187
+ if initial_mode?
188
+ Sequel.case([ [ Sequel.&({ tag: EVENT }, { can_partner: 1 }), 1 ] ], 0)
189
+ else
190
+ Sequel.case({ LOWER => 1, UPPER => -1 }, 0, :tag)
53
191
  end
192
+ end
54
193
 
55
- def query(db)
56
- @query
194
+ def running_sum(partition)
195
+ Sequel.function(:sum, :weight).over(partition: partition, order: %i[sweep_date tag])
196
+ end
197
+
198
+ def total_sum(partition)
199
+ Sequel.function(:sum, :weight).over(partition: partition)
200
+ end
201
+
202
+ # Counts from events with a different criterion_id only: the old
203
+ # self-join excluded pairs that shared one.
204
+ def counted_copies
205
+ same = matching_columns + [ :criterion_id ]
206
+ ds = copies.select_append((running_sum(matching_columns) - running_sum(same)).as(:partners))
207
+ if initial_mode? && !upper_bound
208
+ ds = ds.select_append((total_sum(matching_columns) - total_sum(same)).as(:all_partners))
57
209
  end
210
+ ds.from_self
211
+ end
212
+
213
+ def valid_condition
214
+ return Sequel.&({ tag: EVENT }, Sequel.expr(:partners) >= 1) unless initial_mode?
215
+
216
+ Sequel.&({ tag: EVENT }, Sequel.expr(:upper_partners) - :lower_partners >= 1)
217
+ end
218
+
219
+ def bound_partners(tag)
220
+ Sequel.function(:max, Sequel.case([ [ { tag: tag }, :partners ] ], nil))
221
+ .over(partition: matching_columns + [ :event_key ])
222
+ end
223
+
224
+ def judged_copies
225
+ return counted_copies unless initial_mode?
226
+
227
+ upper = upper_bound ? bound_partners(UPPER) : Sequel.function(:max, :all_partners).over(partition: matching_columns + [ :event_key ])
228
+ counted_copies
229
+ .select_append(bound_partners(LOWER).as(:lower_partners), upper.as(:upper_partners))
230
+ .from_self
231
+ end
232
+
233
+ def output_column(column)
234
+ case column
235
+ when :start_date then Sequel.case([ [ { tag: INPATIENT }, :admission_date ] ], :start_date).as(:start_date)
236
+ when :end_date then Sequel.case([ [ { tag: INPATIENT }, :discharge_date ] ], :end_date).as(:end_date)
237
+ else column
238
+ end
239
+ end
240
+
241
+ def all_valid_events
242
+ judged_copies
243
+ .where(Sequel.|({ tag: INPATIENT }, valid_condition))
244
+ .select(*dynamic_columns.map { |column| output_column(column) })
245
+ .from_self
246
+ end
247
+
248
+ def first_valid_event
249
+ all_valid_events
250
+ .select_append do |o|
251
+ o.row_number.function.over(partition: matching_columns,
252
+ order: [
253
+ :start_date, :criterion_id
254
+ ]).as(:rn)
255
+ end
256
+ .from_self
257
+ .where(rn: 1)
58
258
  end
59
259
  end
60
260
  end
61
261
  end
62
-