malawi_hiv_program_reports 1.0.1 → 1.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (76) hide show
  1. checksums.yaml +4 -4
  2. data/app/services/malawi_hiv_program_reports/README.md +16 -0
  3. data/app/services/malawi_hiv_program_reports/adapters/moh/custom.rb +199 -0
  4. data/app/services/malawi_hiv_program_reports/archiving_candidates.rb +130 -0
  5. data/app/services/malawi_hiv_program_reports/arv_refill_periods.rb +311 -0
  6. data/app/services/malawi_hiv_program_reports/clinic/README.md +5 -0
  7. data/app/services/malawi_hiv_program_reports/clinic/appointments_report.rb +317 -0
  8. data/app/services/malawi_hiv_program_reports/clinic/discrepancy_report.rb +42 -0
  9. data/app/services/malawi_hiv_program_reports/clinic/docs/hypertension_report.md +31 -0
  10. data/app/services/malawi_hiv_program_reports/clinic/drug_dispensations.rb +48 -0
  11. data/app/services/malawi_hiv_program_reports/clinic/external_consultation_clients.rb +69 -0
  12. data/app/services/malawi_hiv_program_reports/clinic/hypertension_report.rb +223 -0
  13. data/app/services/malawi_hiv_program_reports/clinic/ipt_coverage.rb +112 -0
  14. data/app/services/malawi_hiv_program_reports/clinic/ipt_report.rb +69 -0
  15. data/app/services/malawi_hiv_program_reports/clinic/lims_results.rb +55 -0
  16. data/app/services/malawi_hiv_program_reports/clinic/outcome_list.rb +127 -0
  17. data/app/services/malawi_hiv_program_reports/clinic/patients_alive_and_on_treatment.rb +57 -0
  18. data/app/services/malawi_hiv_program_reports/clinic/patients_due_for_viral_load.rb +39 -0
  19. data/app/services/malawi_hiv_program_reports/clinic/patients_on_antiretrovirals.rb +44 -0
  20. data/app/services/malawi_hiv_program_reports/clinic/patients_on_dtg.rb +36 -0
  21. data/app/services/malawi_hiv_program_reports/clinic/patients_on_treatment.rb +42 -0
  22. data/app/services/malawi_hiv_program_reports/clinic/patients_with_outdated_demographics.rb +173 -0
  23. data/app/services/malawi_hiv_program_reports/clinic/pregnant_patients.rb +91 -0
  24. data/app/services/malawi_hiv_program_reports/clinic/regimen_dispensation_data.rb +282 -0
  25. data/app/services/malawi_hiv_program_reports/clinic/regimen_switch.rb +456 -0
  26. data/app/services/malawi_hiv_program_reports/clinic/regimens_and_formulations.rb +182 -0
  27. data/app/services/malawi_hiv_program_reports/clinic/regimens_by_weight_and_gender.rb +108 -0
  28. data/app/services/malawi_hiv_program_reports/clinic/retention.rb +246 -0
  29. data/app/services/malawi_hiv_program_reports/clinic/stock_card_report.rb +65 -0
  30. data/app/services/malawi_hiv_program_reports/clinic/tpt_outcome.rb +494 -0
  31. data/app/services/malawi_hiv_program_reports/clinic/tx_rtt.rb +169 -0
  32. data/app/services/malawi_hiv_program_reports/clinic/viral_load.rb +292 -0
  33. data/app/services/malawi_hiv_program_reports/clinic/viral_load_disaggregated.rb +97 -0
  34. data/app/services/malawi_hiv_program_reports/clinic/viral_load_results.rb +175 -0
  35. data/app/services/malawi_hiv_program_reports/clinic/visits_report.rb +113 -0
  36. data/app/services/malawi_hiv_program_reports/clinic/vl_collection.rb +48 -0
  37. data/app/services/malawi_hiv_program_reports/cohort/outcomes.rb +338 -0
  38. data/app/services/malawi_hiv_program_reports/cohort/regimens.rb +69 -0
  39. data/app/services/malawi_hiv_program_reports/cohort/side_effects.rb +141 -0
  40. data/app/services/malawi_hiv_program_reports/cohort/tpt.rb +172 -0
  41. data/app/services/malawi_hiv_program_reports/moh/cohort.rb +278 -0
  42. data/app/services/malawi_hiv_program_reports/moh/cohort_builder.rb +2337 -0
  43. data/app/services/malawi_hiv_program_reports/moh/cohort_disaggregated.rb +608 -0
  44. data/app/services/malawi_hiv_program_reports/moh/cohort_disaggregated_additions.rb +208 -0
  45. data/app/services/malawi_hiv_program_reports/moh/cohort_disaggregated_builder.rb +526 -0
  46. data/app/services/malawi_hiv_program_reports/moh/cohort_struct.rb +219 -0
  47. data/app/services/malawi_hiv_program_reports/moh/cohort_survival_analysis.rb +203 -0
  48. data/app/services/malawi_hiv_program_reports/moh/moh_tpt.rb +223 -0
  49. data/app/services/malawi_hiv_program_reports/moh/tpt_newly_initiated.rb +235 -0
  50. data/app/services/malawi_hiv_program_reports/pepfar/defaulter_list.rb +25 -0
  51. data/app/services/malawi_hiv_program_reports/pepfar/maternal_status.rb +29 -0
  52. data/app/services/malawi_hiv_program_reports/pepfar/patient_start_vl.rb +45 -0
  53. data/app/services/malawi_hiv_program_reports/pepfar/regimen_switch.rb +479 -0
  54. data/app/services/malawi_hiv_program_reports/pepfar/sc_arvdisp.rb +174 -0
  55. data/app/services/malawi_hiv_program_reports/pepfar/sc_curr.rb +98 -0
  56. data/app/services/malawi_hiv_program_reports/pepfar/tb_prev.rb +163 -0
  57. data/app/services/malawi_hiv_program_reports/pepfar/tb_prev2.rb +222 -0
  58. data/app/services/malawi_hiv_program_reports/pepfar/tb_prev3.rb +421 -0
  59. data/app/services/malawi_hiv_program_reports/pepfar/tpt_status.rb +181 -0
  60. data/app/services/malawi_hiv_program_reports/pepfar/tx_ml.rb +181 -0
  61. data/app/services/malawi_hiv_program_reports/pepfar/tx_new.rb +205 -0
  62. data/app/services/malawi_hiv_program_reports/pepfar/tx_rtt.rb +288 -0
  63. data/app/services/malawi_hiv_program_reports/pepfar/tx_tb.rb +283 -0
  64. data/app/services/malawi_hiv_program_reports/pepfar/utils.rb +141 -0
  65. data/app/services/malawi_hiv_program_reports/pepfar/viral_load_coverage.rb +414 -0
  66. data/app/services/malawi_hiv_program_reports/pepfar/viral_load_coverage2.rb +433 -0
  67. data/app/services/malawi_hiv_program_reports/report_map.rb +56 -0
  68. data/app/services/malawi_hiv_program_reports/utils/README.md +8 -0
  69. data/app/services/malawi_hiv_program_reports/utils/common_sql_query_utils.rb +60 -0
  70. data/app/services/malawi_hiv_program_reports/utils/concurrency_utils.rb +53 -0
  71. data/app/services/malawi_hiv_program_reports/utils/docs/common_sql_query_utils.md +53 -0
  72. data/app/services/malawi_hiv_program_reports/utils/model_utils.rb +66 -0
  73. data/app/services/malawi_hiv_program_reports/utils/parameter_utils.rb +32 -0
  74. data/app/services/malawi_hiv_program_reports/utils/time_utils.rb +52 -0
  75. data/lib/malawi_hiv_program_reports/version.rb +1 -1
  76. metadata +74 -1
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Report for Showing all HIV Viral Load Tests Done Or Sample Collected per the specified period
4
+ module MalawiHivProgramReports
5
+ module Clinic
6
+ class VlCollection
7
+ include MalawiHivProgramReports::Utils::CommonSqlQueryUtils
8
+ def initialize(start_date:, end_date:, **kwargs)
9
+ @start_date = start_date.to_date.beginning_of_day
10
+ @end_date = end_date.to_date.end_of_day
11
+ @occupation = kwargs[:occupation]
12
+ end
13
+
14
+ def find_report
15
+ data
16
+ end
17
+
18
+ private
19
+
20
+ def data
21
+ ActiveRecord::Base.connection.select_all <<~SQL
22
+ SELECT
23
+ o.person_id AS patient_id,
24
+ pn.given_name,
25
+ pn.family_name,
26
+ p.gender,
27
+ p.birthdate,
28
+ i.identifier AS identifier,
29
+ DATE(ord.start_date) AS order_date
30
+ FROM obs o
31
+ INNER JOIN orders ord ON ord.order_id = o.order_id AND ord.voided = 0
32
+ INNER JOIN person p ON p.person_id = o.person_id AND p.voided = 0
33
+ INNER JOIN person_name pn ON pn.person_id = p.person_id AND pn.voided = 0
34
+ LEFT JOIN patient_identifier i ON i.patient_id = p.person_id AND i.voided = 0 AND i.identifier_type = #{indetifier_type}
35
+ LEFT JOIN (#{current_occupation_query}) AS a ON a.person_id = p.person_id
36
+ WHERE o.concept_id = 9737 -- Test Type
37
+ AND o.value_coded = 856 -- Viral Load
38
+ AND o.obs_datetime BETWEEN '#{@start_date}' AND '#{@end_date}'
39
+ AND o.voided = 0 #{%w[Military Civilian].include?(@occupation) ? 'AND' : ''} #{occupation_filter(occupation: @occupation, field_name: 'value', table_name: 'a', include_clause: false)}
40
+ SQL
41
+ end
42
+
43
+ def indetifier_type
44
+ @indetifier_type ||= ::PatientIdentifierType.find_by_name!(::GlobalPropertyService.use_filing_numbers? ? 'Filing Number' : 'ARV Number').id
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,338 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MalawiHivProgramReports
4
+ module Cohort
5
+ class Outcomes
6
+ include MalawiHivProgramReports::Adapters::Moh::Custom
7
+ include MalawiHivProgramReports::Utils::ModelUtils
8
+ attr_reader :end_date
9
+
10
+ def initialize(end_date:, definition: 'moh', location: nil)
11
+ definition = definition.downcase
12
+ raise ::ArgumentError, "Invalid outcomes definition: #{definition}" unless %w[moh pepfar].include?(definition)
13
+
14
+ @adapter = ActiveRecord::Base.connection.adapter_name.downcase
15
+ @end_date = end_date.to_date
16
+ @definition = definition
17
+ @location = location
18
+ end
19
+
20
+ def update_cummulative_outcomes
21
+ initialize_table
22
+
23
+ # HIC SUNT DRACONIS: The order of the operations below matters,
24
+ # do not change it unless you know what you are doing!!!
25
+ load_patients_who_died
26
+ load_patients_who_stopped_treatment
27
+ load_patients_on_pre_art
28
+ load_patients_without_state
29
+ load_patients_without_drug_orders
30
+ load_patients_on_treatment
31
+ load_defaulters
32
+ end
33
+
34
+ private
35
+
36
+ def arv_drugs_concept_set
37
+ @arv_drugs_concept_set ||= ::ConceptSet.where(set: concept_name('Antiretroviral drugs').concept)
38
+ .select(:concept_id)
39
+ end
40
+
41
+ def drug_order_type
42
+ @drug_order_type ||= order_type('Drug order')
43
+ end
44
+
45
+ def program_states(*names)
46
+ ::ProgramWorkflowState.joins(:program_workflow)
47
+ .joins(:concept)
48
+ .merge(::ProgramWorkflow.where(program: hiv_program))
49
+ .merge(::Concept.joins(:concept_names)
50
+ .merge(::ConceptName.where(name: names)))
51
+ .select(:program_workflow_state_id)
52
+ end
53
+
54
+ def hiv_program
55
+ @hiv_program ||= program('HIV Program')
56
+ end
57
+
58
+ def initialize_table
59
+ ActiveRecord::Base.connection.execute <<~SQL
60
+ DROP TABLE IF EXISTS temp_patient_outcomes
61
+ SQL
62
+
63
+ ActiveRecord::Base.connection.execute <<~SQL
64
+ CREATE TABLE temp_patient_outcomes (
65
+ patient_id INT NOT NULL,
66
+ cum_outcome VARCHAR(120) NOT NULL,
67
+ outcome_date DATE DEFAULT NULL,
68
+ site_id INT NOT NULL,
69
+ PRIMARY KEY (patient_id, site_id)
70
+ )
71
+ SQL
72
+
73
+ ActiveRecord::Base.connection.execute <<~SQL
74
+ CREATE INDEX tpo_outcomes ON temp_patient_outcomes (patient_id, cum_outcome, outcome_date, site_id)
75
+ SQL
76
+ end
77
+
78
+ # Loads all patiens with an outcome of died as of given date
79
+ # into the temp_patient_outcomes table.
80
+ def load_patients_who_died
81
+ date = ActiveRecord::Base.connection.quote(end_date)
82
+
83
+ ActiveRecord::Base.connection.execute <<~SQL
84
+ INSERT INTO temp_patient_outcomes
85
+ SELECT patients.patient_id, 'Patient died', MIN(patient_state.start_date), #{@location}
86
+ FROM temp_earliest_start_date AS patients
87
+ INNER JOIN patient_program
88
+ ON patient_program.patient_id = patients.patient_id
89
+ AND patient_program.program_id = #{hiv_program.program_id}
90
+ AND patient_program.voided = 0 #{site_manager(operator: 'AND', column: 'patient_program.site_id', location: @location)}
91
+ INNER JOIN patient_state
92
+ ON patient_state.patient_program_id = patient_program.patient_program_id
93
+ AND patient_state.state = (#{program_states('Patient died').limit(1).to_sql})
94
+ AND patient_state.start_date < #{interval_manager(date:, value: 1, interval: 'DAY', operator: '+')}
95
+ AND patient_state.voided = 0 #{site_manager(operator: 'AND', column: 'patient_state.site_id', location: @location)}
96
+ WHERE patients.date_enrolled <= #{date} #{site_manager(operator: 'AND', column: 'patients.site_id', location: @location)}
97
+ GROUP BY patients.patient_id
98
+ SQL
99
+ end
100
+
101
+ # Loads all patients with an outcome of transferred out or
102
+ # treatment stopped into temp_patient_outcomes table.
103
+ def load_patients_who_stopped_treatment
104
+ date = ActiveRecord::Base.connection.quote(end_date)
105
+
106
+ ActiveRecord::Base.connection.execute <<~SQL
107
+ INSERT INTO temp_patient_outcomes
108
+ SELECT patients.patient_id,
109
+ (
110
+ SELECT name FROM concept_name
111
+ WHERE concept_id = (
112
+ SELECT concept_id FROM program_workflow_state
113
+ WHERE program_workflow_state_id = patient_state.state
114
+ LIMIT 1
115
+ )
116
+ ) AS cum_outcome,
117
+ patient_state.start_date, #{@location}
118
+ FROM temp_earliest_start_date AS patients
119
+ INNER JOIN patient_program
120
+ ON patient_program.patient_id = patients.patient_id
121
+ AND patient_program.program_id = #{hiv_program.program_id}
122
+ AND patient_program.voided = 0 #{site_manager(operator: 'AND', column: 'patient_program.site_id', location: @location)}
123
+ INNER JOIN patient_state
124
+ ON patient_state.patient_program_id = patient_program.patient_program_id
125
+ AND patient_state.state IN (#{program_states('Patient transferred out', 'Treatment stopped').to_sql})
126
+ AND patient_state.start_date < #{interval_manager(date: end_date, value: 1, interval: 'DAY', operator: '+')}
127
+ AND (patient_state.end_date >= #{date} OR patient_state.end_date IS NULL)
128
+ AND patient_state.voided = 0 #{site_manager(operator: 'AND', column: 'patient_state.site_id', location: @location)}
129
+ INNER JOIN (
130
+ SELECT patient_program_id, MAX(start_date) AS start_date
131
+ FROM patient_state
132
+ WHERE patient_state.start_date < #{interval_manager(date: end_date, value: 1, interval: 'DAY', operator: '+')}
133
+ AND patient_state.voided = 0 #{site_manager(operator: 'AND', column: 'patient_state.site_id', location: @location)}
134
+ GROUP BY patient_program_id
135
+ ) AS max_patient_state
136
+ ON max_patient_state.patient_program_id = patient_state.patient_program_id
137
+ AND max_patient_state.start_date = patient_state.start_date #{site_manager(operator: 'AND', column: 'patient_state.site_id', location: @location)}
138
+ WHERE patients.date_enrolled <= #{date} #{site_manager(operator: 'AND', column: 'patients.site_id', location: @location)}
139
+ AND patients.patient_id NOT IN (SELECT patient_id FROM temp_patient_outcomes #{site_manager(operator: 'WHERE', column: 'site_id', location: @location)} )
140
+ GROUP BY patients.patient_id #{@adapter == 'mysql2' ? '' : ',patient_state.state, patient_state.start_date'}
141
+ SQL
142
+ end
143
+
144
+ # Load all patients on Pre-ART.
145
+ def load_patients_on_pre_art
146
+ date = ActiveRecord::Base.connection.quote(end_date)
147
+
148
+ ActiveRecord::Base.connection.execute <<~SQL
149
+ INSERT INTO temp_patient_outcomes
150
+ SELECT patients.patient_id,
151
+ CASE
152
+ WHEN #{current_defaulter_function('patients.patient_id')} = 1 THEN 'Defaulted'
153
+ ELSE 'Pre-ART (Continue)'
154
+ END AS cum_outcome,
155
+ patient_state.start_date, #{@location}
156
+ FROM temp_earliest_start_date AS patients
157
+ INNER JOIN patient_program
158
+ ON patient_program.patient_id = patients.patient_id
159
+ AND patient_program.program_id = #{hiv_program.program_id}
160
+ AND patient_program.voided = 0 #{site_manager(operator: 'AND', column: 'patient_program.site_id', location: @location)}
161
+ INNER JOIN patient_state
162
+ ON patient_state.patient_program_id = patient_program.patient_program_id
163
+ AND patient_state.state = (#{program_states('Pre-ART (Continue)').limit(1).to_sql})
164
+ AND patient_state.start_date < #{interval_manager(date: end_date, value: 1, interval: 'DAY', operator: '+')}
165
+ AND (patient_state.end_date >= #{date} OR patient_state.end_date IS NULL)
166
+ AND patient_state.voided = 0 #{site_manager(operator: 'AND', column: 'patient_state.site_id', location: @location)}
167
+ INNER JOIN (
168
+ SELECT patient_program_id, MAX(start_date) AS start_date
169
+ FROM patient_state
170
+ WHERE patient_state.start_date < #{interval_manager(date: end_date, value: 1, interval: 'DAY', operator: '+')}
171
+ AND patient_state.voided = 0 #{site_manager(operator: 'AND', column: 'patient_state.site_id', location: @location)}
172
+ GROUP BY patient_program_id
173
+ ) AS max_patient_state
174
+ ON max_patient_state.patient_program_id = patient_state.patient_program_id
175
+ AND max_patient_state.start_date = patient_state.start_date #{site_manager(operator: 'AND', column: 'patient_state.site_id', location: @location)}
176
+ WHERE patients.date_enrolled <= #{date} #{site_manager(operator: 'AND', column: 'patients.site_id', location: @location)}
177
+ AND patients.patient_id NOT IN (SELECT patient_id FROM temp_patient_outcomes #{site_manager(operator: 'WHERE', column: 'site_id', location: @location)} )
178
+ GROUP BY patients.patient_id #{@adapter == 'mysql2' ? '' : ',patient_state.start_date'}
179
+ SQL
180
+ end
181
+
182
+ # Load all patients without a state
183
+ def load_patients_without_state
184
+ date = ActiveRecord::Base.connection.quote(end_date)
185
+
186
+ ActiveRecord::Base.connection.execute <<~SQL
187
+ INSERT INTO temp_patient_outcomes
188
+ SELECT patients.patient_id,
189
+ CASE
190
+ WHEN #{current_defaulter_function('patients.patient_id')} = 1 THEN 'Defaulted'
191
+ ELSE 'Unknown'
192
+ END AS cum_outcome,
193
+ NULL, #{@location} as site_id
194
+ FROM temp_earliest_start_date AS patients
195
+ INNER JOIN patient_program
196
+ ON patient_program.patient_id = patients.patient_id
197
+ AND patient_program.program_id = #{hiv_program.program_id}
198
+ AND patient_program.voided = 0 #{site_manager(operator: 'AND', column: 'patient_program.site_id', location: @location)}
199
+ WHERE patients.date_enrolled <= #{date}
200
+ AND patient_program.patient_program_id NOT IN (
201
+ SELECT patient_program_id
202
+ FROM patient_state
203
+ WHERE start_date < #{interval_manager(date: end_date, value: 1, interval: 'DAY', operator: '+')}
204
+ AND voided = 0 #{site_manager(operator: 'AND', column: 'patient_state.site_id', location: @location)}
205
+ )
206
+ AND patients.patient_id NOT IN (SELECT patient_id FROM temp_patient_outcomes #{site_manager(operator: 'WHERE', column: 'patient_program.site_id', location: @location)} )
207
+ GROUP BY patients.patient_id
208
+ SQL
209
+ end
210
+
211
+ # Load all patients without drug orders or have drug orders
212
+ # without a quantity.
213
+ def load_patients_without_drug_orders
214
+ date = ActiveRecord::Base.connection.quote(end_date)
215
+
216
+ ActiveRecord::Base.connection.execute <<~SQL
217
+ INSERT INTO temp_patient_outcomes
218
+ SELECT patients.patient_id,
219
+ 'Unknown',
220
+ NULL, #{@location}
221
+ FROM temp_earliest_start_date AS patients
222
+ WHERE date_enrolled <= #{date} #{site_manager(operator: 'AND', column: 'patients.site_id', location: @location)}
223
+ AND patient_id NOT IN (SELECT patient_id FROM temp_patient_outcomes #{site_manager(operator: 'WHERE', column: 'site_id', location: @location)} )
224
+ AND patient_id NOT IN (
225
+ SELECT patient_id
226
+ FROM orders
227
+ LEFT JOIN drug_order ON orders.order_id = drug_order.order_id #{site_manager(operator: 'AND', column: 'drug_order.site_id', location: @location)}
228
+ WHERE start_date < #{interval_manager(date: end_date, value: 1, interval: 'DAY', operator: '+')}
229
+ AND quantity > 0 #{site_manager(operator: 'AND', column: 'orders.site_id', location: @location)}
230
+ AND order_type_id = #{drug_order_type.order_type_id}
231
+ AND concept_id IN (#{arv_drugs_concept_set.to_sql})
232
+ )
233
+ SQL
234
+ end
235
+
236
+ # Loads all patients who are on treatment
237
+ def load_patients_on_treatment
238
+ date = ActiveRecord::Base.connection.quote(end_date)
239
+
240
+ ActiveRecord::Base.connection.execute <<~SQL
241
+ INSERT INTO temp_patient_outcomes
242
+ SELECT patients.patient_id, 'On antiretrovirals', patient_state.start_date, #{@location}
243
+ FROM temp_earliest_start_date AS patients
244
+ INNER JOIN patient_program
245
+ ON patient_program.patient_id = patients.patient_id
246
+ AND patient_program.program_id = #{hiv_program.program_id}
247
+ AND patient_program.voided = 0 #{site_manager(operator: 'AND', column: 'patient_program.site_id', location: @location)}
248
+ /* Get patients' `on ARV` states that are before given date */
249
+ INNER JOIN patient_state
250
+ ON patient_state.patient_program_id = patient_program.patient_program_id
251
+ AND patient_state.state = (#{program_states('On antiretrovirals').limit(1).to_sql})
252
+ AND patient_state.start_date < #{interval_manager(date:, value: 1, interval: 'DAY', operator: '+')}
253
+ AND (patient_state.end_date >= #{date} OR patient_state.end_date IS NULL)
254
+ AND patient_state.voided = 0 #{site_manager(operator: 'AND', column: 'patient_state.site_id', location: @location)}
255
+ /* Select only the most recent state out of those retrieved above */
256
+ INNER JOIN (
257
+ SELECT patient_program_id, MAX(start_date) AS start_date
258
+ FROM patient_state
259
+ WHERE patient_state.start_date < #{interval_manager(date:, value: 1, interval: 'DAY', operator: '+')}
260
+ AND patient_state.voided = 0 #{site_manager(operator: 'AND', column: 'patient_state.site_id', location: @location)}
261
+ GROUP BY patient_program_id
262
+ ) AS max_patient_state
263
+ ON max_patient_state.patient_program_id = patient_state.patient_program_id
264
+ AND max_patient_state.start_date = patient_state.start_date #{site_manager(operator: 'AND', column: 'patient_state.site_id', location: @location)}
265
+ /* HACK: Ensure that the states captured above do correspond have corresponding
266
+ ARV dispensations. In other words filter out any `on ARVs` states whose
267
+ dispensation's may have been voided or states that were created manually
268
+ without any drugs being dispensed. */
269
+ INNER JOIN (
270
+ SELECT orders.patient_id, MIN(orders.auto_expire_date) AS auto_expire_date
271
+ FROM orders
272
+ INNER JOIN drug_order ON orders.order_id = drug_order.order_id #{site_manager(operator: 'AND', column: 'drug_order.site_id', location: @location)}
273
+ INNER JOIN (
274
+ SELECT patient_id, MAX(start_date) AS start_date
275
+ FROM orders
276
+ INNER JOIN drug_order ON orders.order_id = drug_order.order_id #{site_manager(operator: 'AND', column: 'drug_order.site_id', location: @location)} AND drug_order.quantity > 0
277
+ WHERE order_type_id = #{drug_order_type.order_type_id}
278
+ AND concept_id IN (#{arv_drugs_concept_set.to_sql})
279
+ AND orders.start_date < #{interval_manager(date:, value: 1, interval: 'DAY', operator: '+')}
280
+ AND voided = 0 #{site_manager(operator: 'AND', column: 'orders.site_id', location: @location)}
281
+ AND patient_id IN (SELECT patient_id FROM temp_earliest_start_date #{site_manager(operator: 'WHERE', column: 'site_id', location: @location)} )
282
+ AND patient_id NOT IN (SELECT patient_id FROM temp_patient_outcomes #{site_manager(operator: 'WHERE', column: 'site_id', location: @location)} )
283
+ GROUP BY patient_id
284
+ ) AS max_drug_orders
285
+ ON max_drug_orders.patient_id = orders.patient_id
286
+ AND max_drug_orders.start_date = orders.start_date #{site_manager(operator: 'AND', column: 'orders.site_id', location: @location)}
287
+ WHERE order_type_id = #{drug_order_type.order_type_id}
288
+ AND concept_id IN (#{arv_drugs_concept_set.to_sql})
289
+ AND orders.start_date < #{interval_manager(date:, value: 1, interval: 'DAY', operator: '+')}
290
+ AND quantity > 0
291
+ AND voided = 0 #{site_manager(operator: 'AND', column: 'orders.site_id', location: @location)}
292
+ AND orders.patient_id IN (SELECT patient_id FROM temp_earliest_start_date #{site_manager(operator: 'WHERE', column: 'site_id', location: @location)})
293
+ AND orders.patient_id NOT IN (SELECT patient_id FROM temp_patient_outcomes #{site_manager(operator: 'WHERE', column: 'site_id', location: @location)})
294
+ GROUP BY orders.patient_id
295
+ ) AS first_order_to_expire
296
+ ON (first_order_to_expire.auto_expire_date >= #{date} OR #{timestampdiff_manager(date1: 'DATE(first_order_to_expire.auto_expire_date)', date2: date, interval: 'DAY')} <= #{@definition == 'pepfar' ? 28 : 56})
297
+ AND first_order_to_expire.patient_id = patient_program.patient_id #{site_manager(operator: 'AND', column: 'patient_program.site_id', location: @location)}
298
+ WHERE patients.date_enrolled <= #{date} #{site_manager(operator: 'AND', column: 'patients.site_id', location: @location)}
299
+ AND patients.patient_id NOT IN (SELECT patient_id FROM temp_patient_outcomes #{site_manager(operator: 'WHERE', column: 'site_id', location: @location)} )
300
+ GROUP BY patients.patient_id #{@adapter == 'mysql2' ? '' : ',patient_state.start_date'}
301
+ SQL
302
+ end
303
+
304
+ # Load defaulters
305
+ def load_defaulters
306
+ date = ActiveRecord::Base.connection.quote(end_date)
307
+
308
+ ActiveRecord::Base.connection.execute <<~SQL
309
+ INSERT INTO temp_patient_outcomes
310
+ SELECT patient_id, #{patient_outcome_function('patient_id')}, NULL, #{@location}
311
+ FROM temp_earliest_start_date
312
+ WHERE date_enrolled <= #{date} #{site_manager(operator: 'AND', column: 'site_id', location: @location)}
313
+ AND patient_id NOT IN (SELECT patient_id FROM temp_patient_outcomes #{site_manager(operator: 'WHERE', column: 'site_id', location: @location)} )
314
+ SQL
315
+ end
316
+
317
+ def current_defaulter_function(sql_column)
318
+ defaulter_function = @definition == 'moh' ? 'current_defaulter' : 'current_pepfar_defaulter'
319
+ quoted_end_date = ActiveRecord::Base.connection.quote(end_date)
320
+ parameters = "#{sql_column}, #{quoted_end_date}, #{@location}"
321
+ function_manager(function: defaulter_function, location: @location, args: parameters)
322
+ rescue StandardError => e
323
+ Rails.logger.error "Error: #{e.message}"
324
+ raise "Invalid outcomes definition: #{@definition}"
325
+ end
326
+
327
+ def patient_outcome_function(sql_column)
328
+ outcome_function = @definition == 'moh' ? 'patient_outcome' : 'pepfar_patient_outcome'
329
+ quoted_end_date = ActiveRecord::Base.connection.quote(end_date)
330
+ parameters = "#{sql_column}, #{quoted_end_date}, #{@location}"
331
+ function_manager(function: outcome_function, location: @location, args: parameters)
332
+ rescue StandardError => e
333
+ Rails.logger.error "Error: #{e.message}"
334
+ raise "Invalid outcomes definition: #{@definition}"
335
+ end
336
+ end
337
+ end
338
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MalawiHivProgramReports
4
+ module Cohort
5
+ class Regimens
6
+ include MalawiHivProgramReports::Utils::ModelUtils
7
+ include MalawiHivProgramReports::Adapters::Moh::Custom
8
+
9
+ def patient_regimens(date, location)
10
+ @location = location
11
+ @adapter = ActiveRecord::Base.connection.adapter_name.downcase
12
+ date = ActiveRecord::Base.connection.quote(date)
13
+
14
+ ActiveRecord::Base.connection.select_all <<~SQL
15
+ SELECT prescriptions.patient_id,
16
+ regimens.name AS regimen_category,
17
+ prescriptions.drugs,
18
+ prescriptions.prescription_date
19
+ FROM (
20
+ SELECT orders.patient_id,
21
+ #{@adapter == 'mysql2' ? 'GROUP_CONCAT(DISTINCT drug_order.drug_inventory_id ORDER BY drug_order.drug_inventory_id ASC) AS drugs,' : "(SELECT STRING_AGG(drug_inventory_id::VARCHAR, ',') FROM (SELECT DISTINCT drug_order.drug_inventory_id FROM drug_order ORDER BY drug_order.drug_inventory_id ASC) AS subquery) AS drugs,"}
22
+ recent_prescription.prescription_date
23
+ FROM temp_patient_outcomes AS outcomes
24
+ INNER JOIN orders
25
+ ON orders.patient_id = outcomes.patient_id
26
+ AND orders.concept_id IN (#{arv_drugs_concept_set.to_sql})
27
+ AND orders.voided = 0 #{site_manager(operator: 'AND', column: 'orders.site_id', location: @location)}
28
+ INNER JOIN drug_order
29
+ ON drug_order.order_id = orders.order_id AND drug_order.quantity > 0 #{site_manager(operator: 'AND', column: 'drug_order.site_id', location: @location)}
30
+ /* Only select drugs prescribed on the last prescription day */
31
+ INNER JOIN (
32
+ SELECT patient_id, DATE(MAX(start_date)) AS prescription_date
33
+ FROM orders
34
+ INNER JOIN drug_order
35
+ ON drug_order.order_id = orders.order_id
36
+ AND drug_order.quantity > 0 #{site_manager(operator: 'AND', column: 'drug_order.site_id', location: @location)}
37
+ WHERE orders.voided = 0 #{site_manager(operator: 'AND', column: 'orders.site_id', location: @location)}
38
+ AND orders.concept_id IN (#{arv_drugs_concept_set.to_sql})
39
+ AND orders.start_date < #{interval_manager(date:, value: 1, interval: 'DAY', operator: '+')}
40
+ AND orders.patient_id IN (
41
+ SELECT patient_id FROM temp_patient_outcomes WHERE cum_outcome = 'On antiretrovirals' #{site_manager(operator: 'AND', column: 'site_id', location: @location)}
42
+ )
43
+ GROUP BY orders.patient_id
44
+ ) AS recent_prescription
45
+ ON recent_prescription.patient_id = orders.patient_id
46
+ AND orders.start_date
47
+ BETWEEN recent_prescription.prescription_date
48
+ AND #{interval_manager(date: 'recent_prescription.prescription_date', value: 1, interval: 'DAY', operator: '+')}
49
+ GROUP BY orders.patient_id #{@adapter == 'mysql2' ? '' : ', recent_prescription.prescription_date'}
50
+ ) AS prescriptions
51
+ LEFT JOIN (
52
+ SELECT #{@adapter == 'mysql2' ? 'GROUP_CONCAT(drug.drug_id ORDER BY drug.drug_id ASC) AS drugs,' : "(SELECT STRING_AGG(drug_id::VARCHAR, ',') FROM (SELECT drug.drug_id FROM drug ORDER BY drug.drug_id ASC) AS subquery) AS drugs,"}
53
+ regimen_name.name AS name
54
+ FROM moh_regimen_combination AS combo
55
+ INNER JOIN moh_regimen_combination_drug AS drug USING (regimen_combination_id)
56
+ INNER JOIN moh_regimen_name AS regimen_name USING (regimen_name_id)
57
+ GROUP BY combo.regimen_combination_id, regimen_name.name#{' '}
58
+ ) AS regimens
59
+ ON regimens.drugs = prescriptions.drugs
60
+ SQL
61
+ end
62
+
63
+ def arv_drugs_concept_set
64
+ @arv_drugs_concept_set ||= ::ConceptSet.where(set: concept_name('Antiretroviral drugs'))
65
+ .select(:concept_id)
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,141 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MalawiHivProgramReports
4
+ module Cohort
5
+ class SideEffects
6
+ include MalawiHivProgramReports::Utils::ModelUtils
7
+ include MalawiHivProgramReports::Adapters::Moh::Custom
8
+
9
+ def update_side_effects(date, location)
10
+ @location = location
11
+ @adapter = ActiveRecord::Base.connection.adapter_name.downcase
12
+ initialize_table
13
+
14
+ load_patients_with_side_effects(date)
15
+ load_patients_without_side_effects(date)
16
+ load_patients_missing_side_effects(date)
17
+ end
18
+
19
+ def initialize_table
20
+ ActiveRecord::Base.connection.execute <<~SQL
21
+ DROP TABLE IF EXISTS temp_patient_side_effects
22
+ SQL
23
+
24
+ ActiveRecord::Base.connection.execute <<~SQL
25
+ CREATE TABLE temp_patient_side_effects (
26
+ patient_id INT PRIMARY KEY,
27
+ has_se VARCHAR(120) NOT NULL,
28
+ site_id INT DEFAULT NULL
29
+ )
30
+ SQL
31
+ ActiveRecord::Base.connection.execute 'CREATE INDEX tpse_site_id ON temp_patient_side_effects(site_id)'
32
+ ActiveRecord::Base.connection.execute 'CREATE INDEX idx_se ON temp_patient_side_effects(has_se)'
33
+ ActiveRecord::Base.connection.execute <<~SQL
34
+ CREATE INDEX idx_side_effects ON temp_patient_side_effects (patient_id, has_se, site_id)
35
+ SQL
36
+ end
37
+
38
+ def load_patients_with_side_effects(date)
39
+ date = ActiveRecord::Base.connection.quote(date)
40
+
41
+ ActiveRecord::Base.connection.execute <<~SQL
42
+ INSERT INTO temp_patient_side_effects(patient_id, has_se, site_id)
43
+ SELECT patients.patient_id, 'Yes', #{@location}
44
+ FROM temp_earliest_start_date AS patients
45
+ INNER JOIN temp_patient_outcomes
46
+ ON temp_patient_outcomes.patient_id = patients.patient_id
47
+ AND LOWER(temp_patient_outcomes.cum_outcome) = LOWER('On antiretrovirals') #{site_manager(operator: 'AND', column: 'temp_patient_outcomes.site_id', location: @location)}
48
+ INNER JOIN obs AS side_effects_group
49
+ ON side_effects_group.person_id = patients.patient_id
50
+ AND side_effects_group.concept_id = #{art_side_effects.concept_id}
51
+ AND side_effects_group.voided = 0 #{site_manager(operator: 'AND', column: 'side_effects_group.site_id', location: @location)}
52
+ /* Limit check to last visit before #{date} */
53
+ INNER JOIN (
54
+ SELECT person_id, MAX(obs_datetime) AS obs_datetime FROM obs
55
+ WHERE concept_id = #{art_side_effects.concept_id}
56
+ /* Side effects on initial visit are treated as contra-indications */
57
+ AND obs_datetime < #{interval_manager(date:, value: 1, interval: 'DAY', operator: '+')}
58
+ AND voided = 0 #{site_manager(operator: 'AND', column: 'obs.site_id', location: @location)}
59
+ AND person_id IN (SELECT patient_id FROM temp_patient_outcomes WHERE LOWER(cum_outcome) = LOWER('On antiretrovirals') #{site_manager(operator: 'AND', column: 'site_id', location: @location)})
60
+ GROUP BY person_id
61
+ ) AS last_visit
62
+ ON last_visit.person_id = side_effects_group.person_id
63
+ AND last_visit.obs_datetime = side_effects_group.obs_datetime
64
+ AND last_visit.obs_datetime >= #{interval_manager(date: 'patients.date_enrolled', value: 1, interval: 'DAY', operator: '+')}
65
+ INNER JOIN obs AS side_effects
66
+ ON side_effects.person_id = patients.patient_id
67
+ AND side_effects_group.obs_id = side_effects.obs_group_id
68
+ AND side_effects.value_coded = #{yes.concept_id}
69
+ AND side_effects.voided = 0 #{site_manager(operator: 'AND', column: 'side_effects.site_id', location: @location)}
70
+ WHERE patients.date_enrolled <= #{date} #{site_manager(operator: 'AND', column: 'patients.site_id', location: @location)}
71
+ GROUP BY patients.patient_id
72
+ SQL
73
+ end
74
+
75
+ def load_patients_without_side_effects(date)
76
+ date = ActiveRecord::Base.connection.quote(date)
77
+
78
+ ActiveRecord::Base.connection.execute <<~SQL
79
+ INSERT INTO temp_patient_side_effects
80
+ SELECT patients.patient_id, 'No', #{@location}
81
+ FROM temp_earliest_start_date AS patients
82
+ INNER JOIN temp_patient_outcomes
83
+ ON temp_patient_outcomes.patient_id = patients.patient_id
84
+ AND LOWER(temp_patient_outcomes.cum_outcome) = LOWER('On antiretrovirals') #{site_manager(operator: 'AND', column: 'temp_patient_outcomes.site_id', location: @location)}
85
+ INNER JOIN obs AS side_effects_group
86
+ ON side_effects_group.person_id = patients.patient_id
87
+ AND side_effects_group.concept_id = #{art_side_effects.concept_id}
88
+ AND side_effects_group.voided = 0 #{site_manager(operator: 'AND', column: 'side_effects_group.site_id', location: @location)}
89
+ /* Limit check to last visit before #{date} */
90
+ INNER JOIN (
91
+ SELECT person_id, MAX(obs_datetime) AS obs_datetime FROM obs
92
+ WHERE concept_id = #{art_side_effects.concept_id}
93
+ /* Side effects on initial visit are treated as contra-indications */
94
+ AND obs_datetime < #{interval_manager(date:, value: 1, interval: 'DAY', operator: '+')}
95
+ AND voided = 0 #{site_manager(operator: 'AND', column: 'site_id', location: @location)}
96
+ AND person_id IN (
97
+ SELECT patient_id FROM temp_patient_outcomes WHERE LOWER(cum_outcome) = LOWER('On antiretrovirals') #{site_manager(operator: 'AND', column: 'site_id', location: @location)}
98
+ )
99
+ GROUP BY person_id
100
+ ) AS last_visit
101
+ ON last_visit.person_id = side_effects_group.person_id
102
+ AND last_visit.obs_datetime = side_effects_group.obs_datetime
103
+ AND last_visit.obs_datetime >= #{interval_manager(date: 'patients.date_enrolled', value: 1, interval: 'DAY', operator: '+')}
104
+ INNER JOIN obs AS side_effects
105
+ ON side_effects.person_id = patients.patient_id
106
+ AND side_effects_group.obs_id = side_effects.obs_group_id
107
+ AND side_effects.value_coded = #{no.concept_id}
108
+ AND side_effects.voided = 0 #{site_manager(operator: 'AND', column: 'side_effects.site_id', location: @location)}
109
+ WHERE patients.date_enrolled <= #{date} #{site_manager(operator: 'AND', column: 'patients.site_id', location: @location)}
110
+ AND patients.patient_id NOT IN (
111
+ SELECT patient_id FROM temp_patient_side_effects WHERE has_se = 'Yes' #{site_manager(operator: 'AND', column: 'site_id', location: @location)}
112
+ )
113
+ GROUP BY patients.patient_id
114
+ SQL
115
+ end
116
+
117
+ def load_patients_missing_side_effects(date)
118
+ date = ActiveRecord::Base.connection.quote(date)
119
+
120
+ ActiveRecord::Base.connection.execute <<~SQL
121
+ INSERT INTO temp_patient_side_effects
122
+ SELECT patient_id, 'Unknown', #{@location} FROM temp_earliest_start_date
123
+ WHERE date_enrolled <= #{date} #{site_manager(operator: 'AND', column: 'site_id', location: @location)}
124
+ AND patient_id NOT IN (SELECT patient_id FROM temp_patient_side_effects #{site_manager(operator: 'WHERE', column: 'site_id', location: @location)})
125
+ SQL
126
+ end
127
+
128
+ def yes
129
+ @yes ||= concept_name('Yes')
130
+ end
131
+
132
+ def no
133
+ @no ||= concept_name('No')
134
+ end
135
+
136
+ def art_side_effects
137
+ @art_side_effects ||= concept_name('Malawi ART Side Effects')
138
+ end
139
+ end
140
+ end
141
+ end