malawi_hiv_program_reports 1.0.21 → 1.0.23

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9694980e1a8ca6e260fc5e92e61931383205f36a696fbdc9764514efca2291f2
4
- data.tar.gz: 3b1c35e90152fca9414bd1d45c9d9e4b953c0278229a0e57f7aaeb249f776fb7
3
+ metadata.gz: 13a308e891673d2c03b82b355cbd1cb30acf9562bd4710e90b975563a7b9a5d0
4
+ data.tar.gz: 5a347a6889a9bf4cedd109e83df9146c21265b38e4aab1999ca1db3dca5f7fd6
5
5
  SHA512:
6
- metadata.gz: 1f360402cf0528ca40b06ed4d72eca644c5d6852685c4fd7aa36a0cee3974608377b501558742f6d4fd7296f4ef7997c39b713072654c936e6d5852e4d49c164
7
- data.tar.gz: 5e594587de91c93eb4dc3f6aaf4923aedd5d56a7d41a2bfbd62c39b88f6a1ede319a2a33be40cecfc4b74ac5f4af9708882592cdaf00a0239751ae369f9c984f
6
+ metadata.gz: f918d797106fafc2c0ea27b5f529dbee2403583be90428e679ec27b5d2bd4bc97a541c5ee369cf7122ec9b52d63be1bb01331833e40a7c7e8ebbc950d612d319
7
+ data.tar.gz: 882a731d6f7f538e3d4cbd81fe9052ac54b15dd5b304b49d27abb097876de56daf9d4e6fcbbe5310fd69fa9003fdb3e01834071e7732afcf292ea56f895e11da
@@ -27,7 +27,7 @@ module MalawiHivProgramReports
27
27
  process_thread
28
28
  end_time = Time.now
29
29
  time_in_minutes = ((end_time - start_time) / 60).round(2)
30
- Rails.logger.info("Cumulative Cohort report took #{time_in_minutes} minutes to generate")
30
+ Rails.logger.info("Cumulative Cohort report took #{time_in_minutes} minutes to generate for these locations: #{locations}")
31
31
  { cohort_time: time_in_minutes }
32
32
  end
33
33
 
@@ -47,7 +47,7 @@ module MalawiHivProgramReports
47
47
  queue = Queue.new
48
48
  locations.each { |loc| queue << loc }
49
49
 
50
- threads = Array.new(10) do
50
+ threads = Array.new(40) do
51
51
  Thread.new do
52
52
  until queue.empty?
53
53
  loc = begin
@@ -59,6 +59,9 @@ module MalawiHivProgramReports
59
59
 
60
60
  ActiveRecord::Base.connection_pool.with_connection do
61
61
  process_data loc
62
+ rescue StandardError => e
63
+ Rails.logger.error("Error processing location #{loc}: #{e.message}")
64
+ save_incomplete_site(location: loc, time_taken: 0)
62
65
  end
63
66
  end
64
67
  end
@@ -104,6 +107,13 @@ module MalawiHivProgramReports
104
107
  SQL
105
108
  end
106
109
 
110
+ def save_incomplete_site(location:, time_taken:)
111
+ ActiveRecord::Base.connection.execute <<~SQL
112
+ INSERT INTO cdr_temp_cohort_status
113
+ VALUES (#{location}, DATE(#{start_date}), DATE(#{end_date}), 'incomplete', #{time_taken})
114
+ SQL
115
+ end
116
+
107
117
  def create_cdr_temp_cohort_status
108
118
  ActiveRecord::Base.connection.execute <<~SQL
109
119
  CREATE TABLE IF NOT EXISTS cdr_temp_cohort_status (
@@ -24,14 +24,13 @@ module MalawiHivProgramReports
24
24
 
25
25
  private
26
26
 
27
- # The main idea here is to come up with cumulative outcomes for patients in cdr_temp_cohort_members
27
+ # The main idea here is to come up with cumulative outcomes for patients in temp_earliest_start_date
28
28
  # 1. load_patients_who_died
29
29
  # 2. load_patients_who_stopped_treatment
30
- # 3. load_patients_on_pre_art
31
- # 4. load_patients_without_state
32
- # 5. load_patients_without_drug_orders
33
- # 6. load_patients_on_treatment
34
- # 7. load_defaulters
30
+ # 3. load_patients_without_drug_orders
31
+ # 4. load_patients_on_treatment
32
+ # 5. load_without_clinical_contact
33
+ # 6. load_defaulters
35
34
 
36
35
  def program_states(*names)
37
36
  ::ProgramWorkflowState.joins(:program_workflow)
@@ -47,22 +46,27 @@ module MalawiHivProgramReports
47
46
  # ===================================
48
47
  # rubocop:disable Metrics/MethodLength
49
48
  def process_data
50
- load_max_drug_orders
51
- load_min_auto_expire_date
52
- load_max_patient_state
53
- load_max_appointment_date
49
+ denormalize
54
50
  # HIC SUNT DRACONIS: The order of the operations below matters,
55
51
  # do not change it unless you know what you are doing!!!
56
52
  load_patients_who_died
57
53
  load_patients_who_stopped_treatment
58
- load_patients_on_pre_art
59
- load_patients_without_state
60
54
  load_patients_without_drug_orders
61
55
  load_patients_on_treatment
62
56
  load_without_clinical_contact
63
57
  load_defaulters
64
58
  end
65
59
 
60
+ def denormalize
61
+ load_max_drug_orders
62
+ load_patient_current_medication
63
+ update_patient_current_medication
64
+ load_min_auto_expire_date
65
+ load_max_patient_state
66
+ load_patient_current_state
67
+ update_patient_current_state
68
+ end
69
+
66
70
  def load_max_drug_orders
67
71
  ActiveRecord::Base.connection.execute <<~SQL
68
72
  INSERT INTO cdr_temp_max_drug_orders PARTITION (p#{location})
@@ -80,34 +84,67 @@ module MalawiHivProgramReports
80
84
  SQL
81
85
  end
82
86
 
83
- def load_min_auto_expire_date
87
+ def load_patient_current_medication
84
88
  ActiveRecord::Base.connection.execute <<~SQL
85
- INSERT INTO cdr_temp_min_auto_expire_date PARTITION (p#{location})
86
- SELECT patient_id, o.site_id, MIN(auto_expire_date) AS auto_expire_date
87
- FROM orders o
88
- INNER JOIN cdr_temp_max_drug_orders USING (patient_id, site_id, start_date)
89
- INNER JOIN drug_order ON drug_order.order_id = o.order_id
90
- AND drug_order.site_id = o.site_id AND drug_order.quantity > 0
91
- AND drug_order.drug_inventory_id IN (#{arv_drug})
92
- WHERE o.order_type_id = 1
93
- AND o.voided = 0 AND o.site_id = #{location}
94
- GROUP BY patient_id, o.site_id
95
- HAVING auto_expire_date IS NOT NULL
96
- ON DUPLICATE KEY UPDATE auto_expire_date = VALUES(auto_expire_date)
89
+ INSERT INTO cdr_temp_current_medication PARTITION (p#{location})
90
+ SELECT mdo.patient_id, mdo.site_id, d.concept_id, do.drug_inventory_id drug_id,
91
+ CASE
92
+ WHEN do.equivalent_daily_dose IS NULL THEN 1
93
+ WHEN do.equivalent_daily_dose = 0 THEN 1
94
+ WHEN do.equivalent_daily_dose REGEXP '^[0-9]+(.[0-9]+)?$' THEN do.equivalent_daily_dose
95
+ ELSE 1
96
+ END daily_dose,
97
+ SUM(do.quantity) quantity,
98
+ DATE(mdo.start_date) start_date, null, null, null, null
99
+ FROM cdr_temp_max_drug_orders mdo
100
+ INNER JOIN orders o ON o.patient_id = mdo.patient_id AND o.site_id = mdo.site_id AND o.order_type_id = 1 AND DATE(o.start_date) = DATE(mdo.start_date) AND o.voided = 0
101
+ INNER JOIN drug_order do ON do.order_id = o.order_id AND do.site_id = o.site_id AND do.quantity > 0 AND do.drug_inventory_id IN (#{arv_drug})
102
+ INNER JOIN drug d ON d.drug_id = do.drug_inventory_id
103
+ WHERE mdo.site_id = #{location}
104
+ GROUP BY mdo.patient_id, do.drug_inventory_id HAVING quantity < 6000
105
+ ON DUPLICATE KEY UPDATE concept_id = VALUES(concept_id), daily_dose = VALUES(daily_dose), quantity=VALUES(quantity), start_date = VALUES(start_date), pill_count = VALUES(pill_count), expiry_date = VALUES(expiry_date), pepfar_defaulter_date = VALUES(pepfar_defaulter_date), moh_defaulter_date = VALUES(moh_defaulter_date)
97
106
  SQL
98
107
  end
99
108
 
100
- def load_max_appointment_date
109
+ def update_patient_current_medication
101
110
  ActiveRecord::Base.connection.execute <<~SQL
102
- INSERT INTO cdr_temp_max_patient_appointment PARTITION (p#{location})
103
- SELECT o.person_id, o.site_id, DATE(MAX(o.value_datetime)) appointment_date
104
- FROM obs o
105
- INNER JOIN encounter e ON e.encounter_id = o.encounter_id AND e.site_id = o.site_id AND e.voided = 0
106
- AND e.program_id = 1 AND e.encounter_datetime < DATE(#{end_date}) + INTERVAL 1 DAY
107
- WHERE o.concept_id = 5096 AND o.voided = 0 AND o.obs_datetime < DATE(#{end_date}) + INTERVAL 1 DAY AND o.site_id = #{location}
108
- GROUP BY o.person_id, o.site_id
109
- HAVING appointment_date IS NOT NULL
110
- ON DUPLICATE KEY UPDATE appointment_date = VALUES(appointment_date)
111
+ INSERT INTO cdr_temp_current_medication PARTITION (p#{location})
112
+ SELECT cm.patient_id, cm.site_id, cm.concept_id, cm.drug_id, cm.daily_dose, cm.quantity, cm.start_date,
113
+ COALESCE(first_ob.quantity, 0) + COALESCE(SUM(second_ob.value_numeric),0) + COALESCE(SUM(third_ob.value_numeric),0) AS pill_count,
114
+ DATE_ADD(DATE_SUB(cm.start_date, INTERVAL 1 DAY), INTERVAL (cm.quantity + COALESCE(first_ob.quantity, 0) + COALESCE(SUM(second_ob.value_numeric),0) + COALESCE(SUM(third_ob.value_numeric),0)) / cm.daily_dose DAY),
115
+ DATE_ADD(DATE_ADD(DATE_SUB(cm.start_date, INTERVAL 1 DAY), INTERVAL (cm.quantity + COALESCE(first_ob.quantity, 0) + COALESCE(SUM(second_ob.value_numeric),0) + COALESCE(SUM(third_ob.value_numeric),0)) / cm.daily_dose DAY), INTERVAL 30 DAY),
116
+ DATE_ADD(DATE_ADD(DATE_SUB(cm.start_date, INTERVAL 1 DAY), INTERVAL (cm.quantity + COALESCE(first_ob.quantity, 0) + COALESCE(SUM(second_ob.value_numeric),0) + COALESCE(SUM(third_ob.value_numeric),0)) / cm.daily_dose DAY), INTERVAL 60 DAY)
117
+ FROM cdr_temp_current_medication cm
118
+ LEFT JOIN (
119
+ SELECT ob.person_id,ob.site_id, cm.drug_id,
120
+ SUM(ob.value_numeric) + SUM(CASE
121
+ WHEN ob.value_text is null then 0
122
+ WHEN ob.value_text REGEXP '^[0-9]+(.[0-9]+)?$' then ob.value_text
123
+ ELSE 0
124
+ END) quantity
125
+ FROM obs ob
126
+ INNER JOIN cdr_temp_current_medication cm ON cm.patient_id = ob.person_id AND cm.site_id = ob.site_id AND cm.start_date = DATE(ob.obs_datetime)
127
+ INNER JOIN orders o ON o.order_id = ob.order_id AND o.site_id = ob.site_id AND o.voided = 0
128
+ INNER JOIN drug_order do ON do.order_id = o.order_id AND do.site_id = o.site_id AND do.drug_inventory_id = cm.drug_id
129
+ WHERE ob.concept_id = 2540 AND ob.voided = 0 AND ob.site_id = #{location}
130
+ GROUP BY ob.person_id, cm.drug_id
131
+ ) first_ob ON first_ob.person_id = cm.patient_id AND first_ob.site_id = cm.site_id AND first_ob.drug_id = cm.drug_id
132
+ LEFT JOIN obs second_ob ON second_ob.person_id = cm.patient_id AND second_ob.site_id = cm.site_id AND second_ob.concept_id = cm.concept_id AND DATE(second_ob.obs_datetime) = cm.start_date AND second_ob.voided = 0
133
+ LEFT JOIN obs third_ob ON third_ob.person_id = cm.patient_id AND third_ob.site_id = cm.site_id AND third_ob.concept_id = 2540 AND third_ob.value_drug = cm.drug_id AND third_ob.voided = 0 AND DATE(third_ob.obs_datetime) = cm.start_date
134
+ WHERE cm.site_id = #{location}
135
+ GROUP BY cm.patient_id, cm.drug_id
136
+ ON DUPLICATE KEY UPDATE pill_count = VALUES(pill_count), expiry_date = VALUES(expiry_date), pepfar_defaulter_date = VALUES(pepfar_defaulter_date), moh_defaulter_date = VALUES(moh_defaulter_date);
137
+ SQL
138
+ end
139
+
140
+ def load_min_auto_expire_date
141
+ ActiveRecord::Base.connection.execute <<~SQL
142
+ INSERT INTO cdr_temp_min_auto_expire_date PARTITION (p#{location})
143
+ SELECT cm.patient_id, cm.site_id, MIN(cm.start_date), MIN(cm.expiry_date), MIN(cm.pepfar_defaulter_date), MIN(cm.moh_defaulter_date)
144
+ FROM cdr_temp_current_medication cm
145
+ WHERE cm.site_id = #{location}
146
+ GROUP BY cm.patient_id
147
+ ON DUPLICATE KEY UPDATE start_date = VALUES(start_date), auto_expire_date = VALUES(auto_expire_date), pepfar_defaulter_date = VALUES(pepfar_defaulter_date), moh_defaulter_date = VALUES(moh_defaulter_date)
111
148
  SQL
112
149
  end
113
150
 
@@ -118,13 +155,43 @@ module MalawiHivProgramReports
118
155
  FROM patient_state ps
119
156
  INNER JOIN patient_program pp ON pp.patient_program_id = ps.patient_program_id AND pp.site_id = ps.site_id AND pp.program_id = 1 AND pp.voided = 0
120
157
  WHERE ps.start_date < DATE(#{end_date}) + INTERVAL 1 DAY
121
- AND ps.voided = 0 AND ps.site_id = #{location}
158
+ AND ps.voided = 0 AND ps.site_id = #{location} AND pp.patient_id IN (SELECT patient_id FROM temp_earliest_start_date WHERE site_id = #{location})
122
159
  GROUP BY pp.patient_id, pp.site_id
123
160
  HAVING start_date IS NOT NULL
124
161
  ON DUPLICATE KEY UPDATE start_date = VALUES(start_date)
125
162
  SQL
126
163
  end
127
164
 
165
+ def load_patient_current_state
166
+ ActiveRecord::Base.connection.execute <<~SQL
167
+ INSERT INTO cdr_temp_current_state PARTITION (p#{location})
168
+ SELECT mps.patient_id, mps.site_id, cn.name AS cum_outcome, ps.start_date as outcome_date, ps.state, count(DISTINCT(ps.state)) outcomes, MAX(ps.patient_state_id) patient_state_id
169
+ FROM cdr_temp_max_patient_state mps
170
+ INNER JOIN patient_program pp ON pp.patient_id = mps.patient_id AND pp.site_id = mps.site_id AND pp.program_id = 1 AND pp.voided = 0
171
+ INNER JOIN patient_state ps ON ps.patient_program_id = pp.patient_program_id AND ps.site_id = pp.site_id AND ps.start_date = mps.start_date AND ps.voided = 0
172
+ AND (ps.end_date IS NULL OR ps.end_date > DATE(#{end_date}))
173
+ INNER JOIN program_workflow_state pws ON pws.program_workflow_state_id = ps.state AND pws.retired = 0
174
+ INNER JOIN concept_name cn ON cn.concept_id = pws.concept_id AND cn.concept_name_type = 'FULLY_SPECIFIED' AND cn.voided = 0
175
+ LEFT JOIN patient_state ps2 ON ps.patient_program_id = ps2.patient_program_id AND ps2.site_id = ps.site_id AND ps.start_date = ps2.start_date AND ps.date_created < ps2.date_created AND ps2.voided = 0
176
+ WHERE ps2.patient_program_id IS NULL AND ps.voided = 0 AND mps.site_id = #{location}
177
+ GROUP BY mps.patient_id
178
+ ON DUPLICATE KEY UPDATE cum_outcome = VALUES(cum_outcome), outcome_date = VALUES(outcome_date), state = VALUES(state), outcomes = VALUES(outcomes)
179
+ SQL
180
+ end
181
+
182
+ def update_patient_current_state
183
+ ActiveRecord::Base.connection.execute <<~SQL
184
+ INSERT INTO cdr_temp_current_state
185
+ SELECT cs.patient_id, cs.site_id, cn.name as cum_outcome, ps.start_date as outcome_date, ps.state, 1, cs.patient_state_id
186
+ FROM patient_state ps
187
+ INNER JOIN cdr_temp_current_state cs ON cs.patient_state_id = ps.patient_state_id AND cs.site_id = ps.site_id
188
+ INNER JOIN program_workflow_state pws ON pws.program_workflow_state_id = ps.state AND pws.retired = 0
189
+ INNER JOIN concept_name cn ON cn.concept_id = pws.concept_id AND cn.concept_name_type = 'FULLY_SPECIFIED' AND cn.voided = 0
190
+ WHERE ps.voided = 0 AND cs.outcomes > 1 AND cs.site_id = #{location}
191
+ ON DUPLICATE KEY UPDATE cum_outcome = VALUES(cum_outcome), outcome_date = VALUES(outcome_date), state = VALUES(state), outcomes = VALUES(outcomes), patient_state_id = VALUES(patient_state_id)
192
+ SQL
193
+ end
194
+
128
195
  # Loads all patiens with an outcome of died as of given date
129
196
  # into the temp_patient_outcomes table.
130
197
  def load_patients_who_died
@@ -160,98 +227,20 @@ module MalawiHivProgramReports
160
227
  ActiveRecord::Base.connection.execute <<~SQL
161
228
  INSERT INTO cdr_temp_patient_outcomes PARTITION (p#{location})
162
229
  SELECT patients.patient_id,
163
- (
164
- SELECT name FROM concept_name
165
- WHERE concept_id = (
166
- SELECT concept_id FROM program_workflow_state
167
- WHERE program_workflow_state_id = patient_state.state
168
- LIMIT 1
169
- )
170
- ) AS cum_outcome,
171
- patient_state.start_date, patients.site_id, 2
172
- FROM cdr_temp_cohort_members AS patients
173
- INNER JOIN patient_program
174
- ON patient_program.patient_id = patients.patient_id
175
- AND patient_program.site_id = patients.site_id
176
- AND patient_program.program_id = 1
177
- AND patient_program.voided = 0
178
- INNER JOIN patient_state
179
- ON patient_state.patient_program_id = patient_program.patient_program_id
180
- AND patient_state.site_id = patient_program.site_id
181
- AND patient_state.state IN (#{program_states('Patient transferred out', 'Treatment stopped').to_sql})
182
- AND patient_state.start_date < DATE(#{end_date}) + INTERVAL 1 DAY
183
- AND (patient_state.end_date >= #{end_date} OR patient_state.end_date IS NULL)
184
- AND patient_state.voided = 0
185
- INNER JOIN cdr_temp_max_patient_state AS max_patient_state
186
- ON max_patient_state.patient_id = patient_program.patient_id
187
- AND max_patient_state.site_id = patient_state.site_id
188
- AND max_patient_state.start_date = patient_state.start_date
189
- WHERE patients.date_enrolled <= #{end_date} AND patients.site_id = #{location}
190
- AND (patients.patient_id, patients.site_id) NOT IN (SELECT patient_id, site_id FROM cdr_temp_patient_outcomes PARTITION (p#{location}) WHERE step = 1)
191
- GROUP BY patients.patient_id, patients.site_id
192
- ON DUPLICATE KEY UPDATE cum_outcome = VALUES(cum_outcome), outcome_date = VALUES(outcome_date), step = VALUES(step)
193
- SQL
194
- end
195
-
196
- # Load all patients on Pre-ART.
197
- def load_patients_on_pre_art
198
- ActiveRecord::Base.connection.execute <<~SQL
199
- INSERT INTO cdr_temp_patient_outcomes PARTITION (p#{location})
200
- SELECT patients.patient_id,
201
- CASE
202
- WHEN #{current_defaulter_function('patients.patient_id', 'patients.site_id')} = 1 THEN 'Defaulted'
203
- ELSE 'Pre-ART (Continue)'
204
- END AS cum_outcome,
205
- patient_state.start_date, patients.site_id, 3
206
- FROM cdr_temp_cohort_members AS patients
207
- INNER JOIN patient_program
208
- ON patient_program.patient_id = patients.patient_id
209
- AND patient_program.site_id = patients.site_id
210
- AND patient_program.program_id = 1
211
- AND patient_program.voided = 0
212
- INNER JOIN patient_state
213
- ON patient_state.patient_program_id = patient_program.patient_program_id
214
- AND patient_state.site_id = patient_program.site_id
215
- AND patient_state.state = (#{program_states('Pre-ART (Continue)').limit(1).to_sql})
216
- AND patient_state.start_date < DATE(#{end_date}) + INTERVAL 1 DAY
217
- AND (patient_state.end_date >= #{end_date} OR patient_state.end_date IS NULL)
218
- AND patient_state.voided = 0
219
- INNER JOIN cdr_temp_max_patient_state AS max_patient_state
220
- ON max_patient_state.patient_id = patient_program.patient_id
221
- AND max_patient_state.site_id = patient_state.site_id
222
- AND max_patient_state.start_date = patient_state.start_date
223
- WHERE patients.date_enrolled <= #{end_date} AND patients.site_id = #{location}
224
- AND (patients.patient_id, patients.site_id) NOT IN (SELECT patient_id, site_id FROM cdr_temp_patient_outcomes PARTITION (p#{location}) WHERE step IN (1, 2))
225
- GROUP BY patients.patient_id, patients.site_id
226
- ON DUPLICATE KEY UPDATE cum_outcome = VALUES(cum_outcome), outcome_date = VALUES(outcome_date), step = VALUES(step)
227
- SQL
228
- end
229
-
230
- # Load all patients without a state
231
- def load_patients_without_state
232
- ActiveRecord::Base.connection.execute <<~SQL
233
- INSERT INTO cdr_temp_patient_outcomes PARTITION (p#{location})
234
- SELECT patients.patient_id,
235
- CASE
236
- WHEN #{current_defaulter_function('patients.patient_id', 'patients.site_id')} = 1 THEN 'Defaulted'
237
- ELSE 'Unknown'
238
- END AS cum_outcome,
239
- NULL, patients.site_id, 4
240
- FROM cdr_temp_cohort_members AS patients
241
- INNER JOIN patient_program
242
- ON patient_program.patient_id = patients.patient_id
243
- AND patient_program.site_id = patients.site_id
244
- AND patient_program.program_id = 1
245
- AND patient_program.voided = 0
246
- WHERE patients.date_enrolled <= #{end_date} AND patients.site_id = #{location}
247
- AND (patient_program.patient_program_id, patient_program.site_id) NOT IN (
248
- SELECT patient_program_id, site_id
249
- FROM patient_state
250
- WHERE start_date < DATE(#{end_date}) + INTERVAL 1 DAY AND voided = 0
251
- )
252
- AND (patients.patient_id, patients.site_id) NOT IN (SELECT patient_id, site_id FROM cdr_temp_patient_outcomes PARTITION (p#{location}) WHERE step IN (1, 2, 3))
253
- GROUP BY patients.patient_id, patients.site_id
254
- HAVING cum_outcome = 'Defaulted'
230
+ patients.cum_outcome,
231
+ patients.outcome_date, patients.site_id, 2
232
+ FROM cdr_temp_current_state AS patients
233
+ WHERE (patients.patient_id) NOT IN (SELECT patient_id FROM cdr_temp_patient_outcomes PARTITION (p#{location}) WHERE step = 1)
234
+ AND patients.outcomes = 1
235
+ AND patients.site_id = #{location}
236
+ AND patients.state IN (
237
+ SELECT pws.program_workflow_state_id state
238
+ FROM program_workflow pw
239
+ INNER JOIN program_workflow_state pws ON pws.program_workflow_id = pw.program_workflow_id AND pws.retired = 0
240
+ WHERE pw.program_id = 1 AND pw.retired = 0 AND pws.terminal = 1
241
+ AND pws.program_workflow_state_id IN (2, 3, 6) -- Transferred out,Patient Died, Treatment stopped
242
+ )
243
+ GROUP BY patients.patient_id
255
244
  ON DUPLICATE KEY UPDATE cum_outcome = VALUES(cum_outcome), outcome_date = VALUES(outcome_date), step = VALUES(step)
256
245
  SQL
257
246
  end
@@ -261,9 +250,7 @@ module MalawiHivProgramReports
261
250
  def load_patients_without_drug_orders
262
251
  ActiveRecord::Base.connection.execute <<~SQL
263
252
  INSERT INTO cdr_temp_patient_outcomes PARTITION (p#{location})
264
- SELECT patients.patient_id,
265
- 'Unknown',
266
- NULL, patients.site_id, 5
253
+ SELECT patients.patient_id, 'Unknown', NULL, patients.site_id, 3
267
254
  FROM cdr_temp_cohort_members AS patients
268
255
  WHERE date_enrolled <= #{end_date} AND site_id = #{location}
269
256
  AND (patient_id, site_id) NOT IN (SELECT patient_id, site_id FROM cdr_temp_patient_outcomes PARTITION (p#{location}) WHERE step IN (1, 2, 3, 4))
@@ -276,37 +263,12 @@ module MalawiHivProgramReports
276
263
  def load_patients_on_treatment
277
264
  ActiveRecord::Base.connection.execute <<~SQL
278
265
  INSERT INTO cdr_temp_patient_outcomes PARTITION (p#{location})
279
- SELECT patients.patient_id, 'On antiretrovirals', patient_state.start_date, patients.site_id, 6
280
- FROM cdr_temp_cohort_members AS patients
281
- INNER JOIN patient_program
282
- ON patient_program.patient_id = patients.patient_id
283
- AND patient_program.site_id = patients.site_id
284
- AND patient_program.program_id = 1
285
- AND patient_program.voided = 0
286
- /* Get patients' `on ARV` states that are before given date */
287
- INNER JOIN patient_state
288
- ON patient_state.patient_program_id = patient_program.patient_program_id
289
- AND patient_state.site_id = patient_program.site_id
290
- AND patient_state.state = 7 -- ON ART
291
- AND patient_state.start_date < DATE(#{end_date}) + INTERVAL 1 DAY
292
- AND (patient_state.end_date >= #{end_date} OR patient_state.end_date IS NULL)
293
- AND patient_state.voided = 0
294
- /* Select only the most recent state out of those retrieved above */
295
- INNER JOIN cdr_temp_max_patient_state AS max_patient_state
296
- ON max_patient_state.patient_id = patient_program.patient_id
297
- AND max_patient_state.site_id = patient_state.site_id
298
- AND max_patient_state.start_date = patient_state.start_date
299
- /* HACK: Ensure that the states captured above do correspond have corresponding
300
- ARV dispensations. In other words filter out any `on ARVs` states whose
301
- dispensation's may have been voided or states that were created manually
302
- without any drugs being dispensed. */
303
- INNER JOIN cdr_temp_min_auto_expire_date AS first_order_to_expire
304
- ON first_order_to_expire.patient_id = patient_program.patient_id
305
- AND first_order_to_expire.site_id = patient_program.site_id
306
- AND (first_order_to_expire.auto_expire_date >= #{end_date} OR TIMESTAMPDIFF(DAY,first_order_to_expire.auto_expire_date, #{end_date}) <= #{@definition == 'pepfar' ? 28 : 56})
307
- WHERE patients.date_enrolled <= #{end_date} AND patients.site_id = #{location}
308
- AND (patients.patient_id, patients.site_id) NOT IN (SELECT patient_id, site_id FROM cdr_temp_patient_outcomes PARTITION (p#{location}) WHERE step IN (1, 2, 3, 4, 5))
309
- GROUP BY patients.patient_id, patients.site_id
266
+ SELECT patients.patient_id, 'On antiretrovirals', COALESCE(cs.outcome_date, patients.start_date), patients.site_id, 4
267
+ FROM cdr_temp_min_auto_expire_date AS patients
268
+ LEFT JOIN cdr_temp_current_state AS cs ON cs.patient_id = patients.patient_id AND cs.site_id = patients.site_id
269
+ WHERE patients.#{@definition == 'pepfar' ? 'pepfar_defaulter_date' : 'moh_defaulter_date'} > #{end_date}
270
+ AND (patients.patient_id) NOT IN (SELECT patient_id FROM cdr_temp_patient_outcomes PARTITION (p#{location}) WHERE step IN (1, 2, 3))
271
+ AND patients.site_id = #{location}
310
272
  ON DUPLICATE KEY UPDATE cum_outcome = VALUES(cum_outcome), outcome_date = VALUES(outcome_date), step = VALUES(step)
311
273
  SQL
312
274
  end
@@ -314,38 +276,12 @@ module MalawiHivProgramReports
314
276
  def load_without_clinical_contact
315
277
  ActiveRecord::Base.connection.execute <<~SQL
316
278
  INSERT INTO cdr_temp_patient_outcomes PARTITION (p#{location})
317
- SELECT patients.patient_id, 'Defaulted', null, patients.site_id, 7
318
- FROM cdr_temp_cohort_members AS patients
319
- INNER JOIN patient_program
320
- ON patient_program.patient_id = patients.patient_id
321
- AND patient_program.site_id = patients.site_id
322
- AND patient_program.program_id = 1
323
- AND patient_program.voided = 0
324
- /* Get patients' `on ARV` states that are before given date */
325
- INNER JOIN patient_state
326
- ON patient_state.patient_program_id = patient_program.patient_program_id
327
- AND patient_state.site_id = patient_program.site_id
328
- AND patient_state.state = 7 -- On ART
329
- AND patient_state.start_date < DATE(#{end_date}) + INTERVAL 1 DAY
330
- AND (patient_state.end_date >= #{end_date} OR patient_state.end_date IS NULL)
331
- AND patient_state.voided = 0
332
- /* Select only the most recent state out of those retrieved above */
333
- INNER JOIN cdr_temp_max_patient_state AS max_patient_state
334
- ON max_patient_state.patient_id = patient_program.patient_id
335
- AND max_patient_state.site_id = patient_state.site_id
336
- AND max_patient_state.start_date = patient_state.start_date
337
- INNER JOIN cdr_temp_max_patient_appointment app ON app.patient_id = patients.patient_id
338
- AND app.site_id = patients.site_id AND app.appointment_date < #{end_date}
339
- INNER JOIN cdr_temp_min_auto_expire_date AS first_order_to_expire
340
- ON first_order_to_expire.patient_id = patient_program.patient_id
341
- AND first_order_to_expire.site_id = patient_program.site_id
342
- AND TIMESTAMPDIFF(DAY,app.appointment_date, first_order_to_expire.auto_expire_date) >= 0
343
- AND TIMESTAMPDIFF(DAY,app.appointment_date, first_order_to_expire.auto_expire_date) <= 5
344
- AND first_order_to_expire.auto_expire_date < #{end_date}
345
- AND TIMESTAMPDIFF(DAY,first_order_to_expire.auto_expire_date, #{end_date}) >= 365
346
- WHERE patients.date_enrolled <= #{end_date} AND patients.site_id = #{location}
347
- AND (patients.patient_id, patients.site_id) NOT IN (SELECT patient_id, site_id FROM cdr_temp_patient_outcomes PARTITION (p#{location}) WHERE step IN (1, 2, 3, 4, 5, 6))
348
- GROUP BY patients.patient_id, patients.site_id
279
+ SELECT patients.patient_id, 'Defaulted', patients.#{@definition == 'pepfar' ? 'pepfar_defaulter_date' : 'moh_defaulter_date'}, patients.site_id, 5
280
+ FROM cdr_temp_current_medication AS patients
281
+ LEFT JOIN cdr_temp_current_state AS cs ON cs.patient_id = patients.patient_id AND cs.site_id = patients.site_id#{' '}
282
+ WHERE patients.#{@definition == 'pepfar' ? 'pepfar_defaulter_date' : 'moh_defaulter_date'} <= #{end_date}
283
+ AND (patients.patient_id) NOT IN (SELECT patient_id FROM cdr_temp_patient_outcomes PARTITION (p#{location}) WHERE step IN (1, 2, 3, 4))
284
+ AND patients.site_id = #{location}
349
285
  ON DUPLICATE KEY UPDATE cum_outcome = VALUES(cum_outcome), outcome_date = VALUES(outcome_date), step = VALUES(step)
350
286
  SQL
351
287
  end
@@ -354,10 +290,10 @@ module MalawiHivProgramReports
354
290
  def load_defaulters
355
291
  ActiveRecord::Base.connection.execute <<~SQL
356
292
  INSERT INTO cdr_temp_patient_outcomes PARTITION (p#{location})
357
- SELECT patient_id, #{patient_outcome_function('patient_id', 'site_id')}, NULL, site_id, 8
293
+ SELECT patient_id, #{patient_outcome_function('patient_id', 'site_id')}, NULL, site_id, 6
358
294
  FROM cdr_temp_cohort_members
359
295
  WHERE date_enrolled <= #{end_date} AND site_id = #{location}
360
- AND (patient_id, site_id) NOT IN (SELECT patient_id, site_id FROM cdr_temp_patient_outcomes PARTITION (p#{location}) WHERE step IN (1, 2, 3, 4, 5, 6, 7))
296
+ AND (patient_id, site_id) NOT IN (SELECT patient_id, site_id FROM cdr_temp_patient_outcomes PARTITION (p#{location}) WHERE step IN (1, 2, 3, 4, 5))
361
297
  ON DUPLICATE KEY UPDATE cum_outcome = VALUES(cum_outcome), outcome_date = VALUES(outcome_date), step = VALUES(step)
362
298
  SQL
363
299
  end
@@ -367,15 +303,6 @@ module MalawiHivProgramReports
367
303
  # ===================================
368
304
  # Function Management Region
369
305
  # ===================================
370
-
371
- def current_defaulter_function(sql_column, site_id)
372
- case definition
373
- when 'moh' then "current_defaulter(#{sql_column}, #{end_date}, #{site_id})"
374
- when 'pepfar' then "current_pepfar_defaulter(#{sql_column}, #{end_date}, #{site_id})"
375
- else raise "Invalid outcomes definition: #{definition}" # Should never happen but you never know!
376
- end
377
- end
378
-
379
306
  def patient_outcome_function(sql_column, site_id)
380
307
  case definition
381
308
  when 'moh' then "patient_outcome(#{sql_column}, #{end_date}, #{site_id})"
@@ -391,8 +318,11 @@ module MalawiHivProgramReports
391
318
  create_outcome_table unless check_if_table_exists('cdr_temp_patient_outcomes')
392
319
  create_tmp_max_drug_orders_table unless check_if_table_exists('cdr_temp_max_drug_orders')
393
320
  create_tmp_min_auto_expire_date unless check_if_table_exists('cdr_temp_min_auto_expire_date')
321
+ drop_tmp_min_auto_expirte_date unless count_table_columns('cdr_temp_min_auto_expire_date') == 6
394
322
  create_cdr_temp_max_patient_state unless check_if_table_exists('cdr_temp_max_patient_state')
395
- create_max_patient_appointment_date unless check_if_table_exists('cdr_temp_max_patient_appointment')
323
+ create_temp_current_state unless check_if_table_exists('cdr_temp_current_state')
324
+ drop_temp_current_state unless count_table_columns('cdr_temp_current_state') == 7
325
+ create_temp_current_medication unless check_if_table_exists('cdr_temp_current_medication')
396
326
  end
397
327
 
398
328
  def check_if_table_exists(table_name)
@@ -405,6 +335,16 @@ module MalawiHivProgramReports
405
335
  result['count'].to_i.positive?
406
336
  end
407
337
 
338
+ def count_table_columns(table_name)
339
+ result = ActiveRecord::Base.connection.select_one <<~SQL
340
+ SELECT COUNT(*) AS count
341
+ FROM INFORMATION_SCHEMA.COLUMNS
342
+ WHERE table_schema = DATABASE()
343
+ AND table_name = '#{table_name}'
344
+ SQL
345
+ result['count'].to_i
346
+ end
347
+
408
348
  def create_outcome_table
409
349
  ActiveRecord::Base.connection.execute <<~SQL
410
350
  CREATE TABLE IF NOT EXISTS cdr_temp_patient_outcomes (
@@ -456,7 +396,10 @@ module MalawiHivProgramReports
456
396
  CREATE TABLE IF NOT EXISTS cdr_temp_min_auto_expire_date (
457
397
  patient_id INT NOT NULL,
458
398
  site_id INT NOT NULL,
399
+ start_date DATE DEFAULT NULL,
459
400
  auto_expire_date DATE DEFAULT NULL,
401
+ pepfar_defaulter_date DATE DEFAULT NULL,
402
+ moh_defaulter_date DATE DEFAULT NULL,
460
403
  PRIMARY KEY (patient_id, site_id)
461
404
  ) ENGINE=InnoDB DEFAULT CHARSET=utf8
462
405
  PARTITION BY LIST(site_id) (#{partition_by_site})
@@ -466,7 +409,13 @@ module MalawiHivProgramReports
466
409
 
467
410
  def create_min_auto_expire_date_indexes
468
411
  ActiveRecord::Base.connection.execute <<~SQL
469
- CREATE INDEX idx_min_auto_expire_date ON cdr_temp_min_auto_expire_date (auto_expire_date)
412
+ CREATE INDEX idx_min_auto_expire_date ON temp_min_auto_expire_date (auto_expire_date)
413
+ SQL
414
+ ActiveRecord::Base.connection.execute <<~SQL
415
+ CREATE INDEX idx_min_pepfar ON temp_min_auto_expire_date (pepfar_defaulter_date)
416
+ SQL
417
+ ActiveRecord::Base.connection.execute <<~SQL
418
+ CREATE INDEX idx_min_moh ON temp_min_auto_expire_date (moh_defaulter_date)
470
419
  SQL
471
420
  end
472
421
 
@@ -489,25 +438,84 @@ module MalawiHivProgramReports
489
438
  SQL
490
439
  end
491
440
 
492
- def create_max_patient_appointment_date
441
+ def create_temp_current_state
493
442
  ActiveRecord::Base.connection.execute <<~SQL
494
- CREATE TABLE IF NOT EXISTS cdr_temp_max_patient_appointment (
443
+ CREATE TABLE IF NOT EXISTS cdr_temp_current_state(
495
444
  patient_id INT NOT NULL,
496
445
  site_id INT NOT NULL,
497
- appointment_date DATE NOT NULL,
498
- PRIMARY KEY (patient_id, site_id)
446
+ cum_outcome VARCHAR(120) NOT NULL,
447
+ outcome_date DATE DEFAULT NULL,
448
+ state INT NOT NULL,
449
+ outcomes INT NOT NULL,
450
+ patient_state_id INT NOT NULL,
451
+ PRIMARY KEY(patient_id, site_id)
452
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8
453
+ PARTITION BY LIST(site_id) (#{partition_by_site})
454
+ SQL
455
+ create_current_state_index
456
+ end
457
+
458
+ def create_current_state_index
459
+ ActiveRecord::Base.connection.execute <<~SQL
460
+ CREATE INDEX idx_state_name ON cdr_temp_current_state (cum_outcome)
461
+ SQL
462
+ ActiveRecord::Base.connection.execute <<~SQL
463
+ CREATE INDEX idx_state_id ON cdr_temp_current_state (state)
464
+ SQL
465
+ ActiveRecord::Base.connection.execute <<~SQL
466
+ CREATE INDEX idx_state_count ON cdr_temp_current_state (outcomes)
467
+ SQL
468
+ end
469
+
470
+ def create_temp_current_medication
471
+ ActiveRecord::Base.connection.execute <<~SQL
472
+ CREATE TABLE IF NOT EXISTS cdr_temp_current_medication(
473
+ patient_id INT NOT NULL,
474
+ site_id INT NOT NULL,
475
+ concept_id INT NOT NULL,
476
+ drug_id INT NOT NULL,
477
+ daily_dose DECIMAL(32,2) NOT NULL,
478
+ quantity DECIMAL(32,2) NOT NULL,
479
+ start_date DATE NOT NULL,
480
+ pill_count DECIMAL(32,2) NULL,
481
+ expiry_date DATE NULL,
482
+ pepfar_defaulter_date DATE NULL,
483
+ moh_defaulter_date DATE NULL,
484
+ PRIMARY KEY(patient_id, site_id, drug_id)
499
485
  ) ENGINE=InnoDB DEFAULT CHARSET=utf8
500
486
  PARTITION BY LIST(site_id) (#{partition_by_site})
501
487
  SQL
502
- create_max_patient_appointment_date_indexes
488
+ craete_tmp_current_med_index
503
489
  end
504
490
 
505
- def create_max_patient_appointment_date_indexes
491
+ def craete_tmp_current_med_index
492
+ ActiveRecord::Base.connection.execute <<~SQL
493
+ CREATE INDEX idx_cm_concept ON cdr_temp_current_medication (concept_id)
494
+ SQL
495
+ ActiveRecord::Base.connection.execute <<~SQL
496
+ CREATE INDEX idx_cm_drug ON cdr_temp_current_medication (drug_id)
497
+ SQL
498
+ ActiveRecord::Base.connection.execute <<~SQL
499
+ CREATE INDEX idx_cm_date ON cdr_temp_current_medication (start_date)
500
+ SQL
501
+ ActiveRecord::Base.connection.execute <<~SQL
502
+ CREATE INDEX idx_cm_pepfar ON cdr_temp_current_medication (pepfar_defaulter_date)
503
+ SQL
506
504
  ActiveRecord::Base.connection.execute <<~SQL
507
- CREATE INDEX idx_max_patient_appointment_date ON cdr_temp_max_patient_appointment (appointment_date)
505
+ CREATE INDEX idx_cm_moh ON cdr_temp_current_medication (moh_defaulter_date)
508
506
  SQL
509
507
  end
510
508
 
509
+ def drop_tmp_min_auto_expirte_date
510
+ ActiveRecord::Base.connection.execute 'DROP TABLE cdr_temp_min_auto_expire_date'
511
+ create_tmp_min_auto_expire_date
512
+ end
513
+
514
+ def drop_temp_current_state
515
+ ActiveRecord::Base.connection.execute 'DROP TABLE cdr_temp_current_state'
516
+ create_temp_current_state
517
+ end
518
+
511
519
  def update_steps
512
520
  ActiveRecord::Base.connection.execute <<~SQL
513
521
  UPDATE cdr_temp_patient_outcomes SET step = 0 WHERE step > 0 AND site_id = #{location}
@@ -525,15 +533,15 @@ module MalawiHivProgramReports
525
533
  ActiveRecord::Base.connection.execute('TRUNCATE cdr_temp_max_drug_orders')
526
534
  ActiveRecord::Base.connection.execute('TRUNCATE cdr_temp_min_auto_expire_date')
527
535
  ActiveRecord::Base.connection.execute('TRUNCATE cdr_temp_max_patient_state')
528
- ActiveRecord::Base.connection.execute('TRUNCATE cdr_temp_max_patient_appointment')
529
- ActiveRecord::Base.connection.execute('TRUNCATE cdr_temp_cohort_status')
536
+ ActiveRecord::Base.connection.execute('TRUNCATE cdr_temp_current_state')
537
+ ActiveRecord::Base.connection.execute('TRUNCATE cdr_temp_current_medication')
530
538
  else
531
539
  ActiveRecord::Base.connection.execute("DELETE FROM cdr_temp_patient_outcomes WHERE site_id = #{location}")
532
540
  ActiveRecord::Base.connection.execute("DELETE FROM cdr_temp_max_drug_orders WHERE site_id = #{location}")
533
541
  ActiveRecord::Base.connection.execute("DELETE FROM cdr_temp_min_auto_expire_date WHERE site_id = #{location}")
534
542
  ActiveRecord::Base.connection.execute("DELETE FROM cdr_temp_max_patient_state WHERE site_id = #{location}")
535
- ActiveRecord::Base.connection.execute("DELETE FROM cdr_temp_max_patient_appointment WHERE site_id = #{location}")
536
- ActiveRecord::Base.connection.execute("DELETE FROM cdr_temp_cohort_status WHERE site_id = #{location}")
543
+ ActiveRecord::Base.connection.execute("DELETE FROM cdr_temp_current_state WHERE site_id = #{location}")
544
+ ActiveRecord::Base.connection.execute("DELETE FROM cdr_temp_current_medication WHERE site_id = #{location}")
537
545
  end
538
546
  end
539
547
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MalawiHivProgramReports
4
- VERSION = '1.0.21'
4
+ VERSION = '1.0.23'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: malawi_hiv_program_reports
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.21
4
+ version: 1.0.23
5
5
  platform: ruby
6
6
  authors:
7
7
  - Roy Chanunkha
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-05-06 00:00:00.000000000 Z
11
+ date: 2024-06-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -174,7 +174,7 @@ licenses:
174
174
  metadata:
175
175
  source_code_uri: https://github.com/EGPAFMalawiHIS/malawi_hiv_program_reports
176
176
  rubygems_mfa_required: 'true'
177
- post_install_message:
177
+ post_install_message:
178
178
  rdoc_options: []
179
179
  require_paths:
180
180
  - lib
@@ -189,8 +189,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
189
189
  - !ruby/object:Gem::Version
190
190
  version: '0'
191
191
  requirements: []
192
- rubygems_version: 3.4.1
193
- signing_key:
192
+ rubygems_version: 3.5.6
193
+ signing_key:
194
194
  specification_version: 4
195
195
  summary: Malawi HIV PROGRAM Reports for Ruby on Rails
196
196
  test_files: []