malawi_hiv_program_reports 1.0.21 → 1.0.23

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.
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: []