malawi_hiv_program_reports 1.0.1 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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 +2340 -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 +202 -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,494 @@
1
+ # frozen_string_literal: true
2
+
3
+ ##
4
+ # Family planning, action to take
5
+ # It should pull data for 6 month back e.g when the report is generated for the month of June,
6
+ # the report must pick clients who started TPT in the month of December
7
+ # it must be drillable
8
+ module MalawiHivProgramReports
9
+ module Clinic
10
+ class TptOutcome
11
+ include MalawiHivProgramReports::Utils::CommonSqlQueryUtils
12
+ include MalawiHivProgramReports::Utils::ModelUtils
13
+ include MalawiHivProgramReports::Pepfar::Utils
14
+
15
+ def initialize(start_date:, end_date:, **kwargs)
16
+ @start_date = start_date.to_date
17
+ @end_date = end_date.to_date
18
+ @tb_prev = Pepfar::TbPrev3.new(start_date: @start_date, end_date: @end_date)
19
+ @occupation = kwargs[:occupation]
20
+ end
21
+
22
+ def find_report
23
+ report = init_report
24
+ @param = 'tpt_type'
25
+ load_patients_into_report report, process_tpt_clients
26
+ response = []
27
+ report.each do |key, value|
28
+ response << { age_group: key, tpt_type: '3HP', **value['3HP'] }
29
+ response << { age_group: key, tpt_type: '6H', **value['6H'] }
30
+ end
31
+ response
32
+ end
33
+
34
+ def moh_report(report, clients, start_date, end_date)
35
+ @first_day_of_month = start_date.to_date
36
+ @last_day_of_month = end_date.to_date
37
+ tpt_clients = process_tpt_clients(clients)
38
+ @param = 'gender'
39
+ load_moh_patients_into_report report, tpt_clients
40
+ end
41
+
42
+ private
43
+
44
+ TPT_TYPES = %w[3HP 6H].freeze
45
+
46
+ def init_report
47
+ pepfar_age_groups.each_with_object({}) do |age_group, report|
48
+ next if age_group == 'Unknown'
49
+
50
+ report[age_group] = TPT_TYPES.each_with_object({}) do |tpt_type, tpt_report|
51
+ tpt_report[tpt_type] = {
52
+ started_tpt_new: [],
53
+ started_tpt_prev: [],
54
+ completed_tpt_new: [],
55
+ completed_tpt_prev: [],
56
+ not_completed_tpt: [],
57
+ died: [],
58
+ stopped: [],
59
+ defaulted: [],
60
+ transfer_out: [],
61
+ confirmed_tb: [],
62
+ pregnant: [],
63
+ breast_feeding: [],
64
+ skin_rash: [],
65
+ peripheral_neuropathy: [],
66
+ yellow_eyes: [],
67
+ nausea: [],
68
+ dizziness: []
69
+ }
70
+ end
71
+ end
72
+ end
73
+
74
+ def patient_breast_feeding?(patient_id, last_tpt)
75
+ ::Observation.where(person_id: patient_id, concept_id: breast_feeding_concept_id,
76
+ value_coded: yes_concept_id)
77
+ .where('DATE(obs_datetime) > DATE(?) AND DATE(obs_datetime) < DATE(?) + INTERVAL 1 DAY', last_tpt&.to_date, @end_date.to_date)
78
+ .exists?
79
+ end
80
+
81
+ def patient_skin_rash?(patient_id, last_tpt)
82
+ ::Observation.where(person_id: patient_id, concept_id: drug_induced_concept_id,
83
+ value_coded: skin_rash_concept_id)
84
+ .where('DATE(obs_datetime) > DATE(?) AND DATE(obs_datetime) < DATE(?) + INTERVAL 1 DAY', last_tpt&.to_date, @end_date.to_date)
85
+ .where("value_drug IN (#{tpt_actual_drugs})")
86
+ .exists?
87
+ end
88
+
89
+ def patient_peripheral_neuropathy?(patient_id, last_tpt)
90
+ ::Observation.where(person_id: patient_id, concept_id: drug_induced_concept_id,
91
+ value_coded: peripheral_neuropathy_concept_id)
92
+ .where('DATE(obs_datetime) > DATE(?) AND DATE(obs_datetime) < DATE(?) + INTERVAL 1 DAY', last_tpt&.to_date, @end_date.to_date)
93
+ .where("value_drug IN (#{tpt_actual_drugs})")
94
+ .exists?
95
+ end
96
+
97
+ def patient_pregnant?(patient_id, last_tpt)
98
+ ::Observation.where(person_id: patient_id, concept_id: pregnant_concept_id,
99
+ value_coded: yes_concept_id)
100
+ .where('DATE(obs_datetime) > DATE(?) AND DATE(obs_datetime) < DATE(?) + INTERVAL 1 DAY', last_tpt&.to_date, @end_date.to_date)
101
+ .exists?
102
+ end
103
+
104
+ def patient_on_tb_treatment?(patient_id, last_tpt)
105
+ ::Observation.where(person_id: patient_id, concept_id: tb_treatment_concept_id,
106
+ value_coded: yes_concept_id)
107
+ .where('DATE(obs_datetime) > DATE(?) AND DATE(obs_datetime) < DATE(?) + INTERVAL 1 DAY', last_tpt&.to_date, @end_date.to_date)
108
+ .exists?
109
+ end
110
+
111
+ def patient_yellow_eyes?(patient_id, last_tpt)
112
+ ::Observation.where(person_id: patient_id, concept_id: drug_induced_concept_id,
113
+ value_coded: yellow_eyes_concept_id)
114
+ .where('DATE(obs_datetime) > DATE(?) AND DATE(obs_datetime) < DATE(?) + INTERVAL 1 DAY', last_tpt&.to_date, @end_date.to_date)
115
+ .where("value_drug IN (#{tpt_actual_drugs})")
116
+ .exists?
117
+ end
118
+
119
+ def patient_nausea?(patient_id, last_tpt)
120
+ ::Observation.where(person_id: patient_id, concept_id: drug_induced_concept_id,
121
+ value_coded: nausea_concept_id)
122
+ .where('DATE(obs_datetime) > DATE(?) AND DATE(obs_datetime) < DATE(?) + INTERVAL 1 DAY', last_tpt&.to_date, @end_date.to_date)
123
+ .where("value_drug IN (#{tpt_actual_drugs})")
124
+ .exists?
125
+ end
126
+
127
+ def patient_dizziness?(patient_id, last_tpt)
128
+ ::Observation.where(person_id: patient_id, concept_id: drug_induced_concept_id,
129
+ value_coded: dizziness_concept_id)
130
+ .where('DATE(obs_datetime) > DATE(?) AND DATE(obs_datetime) < DATE(?) + INTERVAL 1 DAY', last_tpt&.to_date, @end_date.to_date)
131
+ .where("value_drug IN (#{tpt_actual_drugs})")
132
+ .exists?
133
+ end
134
+
135
+ def pregnant_concept_id
136
+ @pregnant_concept_id ||= concept_name_to_id('Is patient pregnant?')
137
+ end
138
+
139
+ def tb_treatment_concept_id
140
+ @tb_treatment_concept_id ||= concept_name_to_id('TB treatment')
141
+ end
142
+
143
+ def yes_concept_id
144
+ @yes_concept_id ||= concept_name_to_id('Yes')
145
+ end
146
+
147
+ def breast_feeding_concept_id
148
+ @breast_feeding_concept_id ||= concept_name_to_id('Breast feeding?')
149
+ end
150
+
151
+ def skin_rash_concept_id
152
+ @skin_rash_concept_id ||= concept_name_to_id('Skin rash')
153
+ end
154
+
155
+ def peripheral_neuropathy_concept_id
156
+ @peripheral_neuropathy_concept_id ||= concept_name_to_id('Peripheral neuropathy')
157
+ end
158
+
159
+ def yellow_eyes_concept_id
160
+ @yellow_eyes_concept_id ||= concept_name_to_id('Yellow eyes')
161
+ end
162
+
163
+ def nausea_concept_id
164
+ @nausea_concept_id ||= concept_name_to_id('Nausea')
165
+ end
166
+
167
+ def dizziness_concept_id
168
+ @dizziness_concept_id ||= concept_name_to_id('Dizziness')
169
+ end
170
+
171
+ def drug_induced_concept_id
172
+ @drug_induced_concept_id ||= concept_name_to_id('Drug Induced')
173
+ end
174
+
175
+ def tpt_clients
176
+ ActiveRecord::Base.connection.select_all <<~SQL
177
+ SELECT
178
+ pp.patient_id,
179
+ patient_outcome(p.person_id, DATE('#{@end_date}')) AS outcome,
180
+ p.gender,
181
+ p.birthdate,
182
+ disaggregated_age_group(p.birthdate, DATE('#{@end_date}')) AS age_group,
183
+ DATE(COALESCE(art_start_date_obs.value_datetime, MIN(art_order.start_date))) AS earliest_start_date,
184
+ GROUP_CONCAT(DISTINCT o.concept_id SEPARATOR ',') AS drug_concepts,
185
+ CASE
186
+ WHEN count(DISTINCT o.concept_id) > 1 THEN '3HP'
187
+ WHEN o.concept_id = 10565 THEN '3HP'
188
+ ELSE '6H'
189
+ END AS tpt_type
190
+ FROM patient_program pp
191
+ INNER JOIN patient_state ps ON ps.patient_program_id = pp.patient_program_id AND ps.voided = 0 AND ps.state = 7
192
+ INNER JOIN person p ON p.person_id = pp.patient_id AND p.voided = 0
193
+ INNER JOIN encounter e ON e.patient_id = pp.patient_id
194
+ AND e.encounter_type = 25 /* Treatment */
195
+ AND e.voided = 0
196
+ AND e.program_id = 1 /* HIV PROGRAM */
197
+ INNER JOIN orders o ON o.encounter_id = e.encounter_id
198
+ AND o.order_type_id = #{::OrderType.find_by_name('Drug order').id}
199
+ AND o.voided = 0
200
+ AND o.concept_id IN (#{tpt_drugs.to_sql})
201
+ INNER JOIN drug_order dor ON dor.order_id = o.order_id AND dor.quantity > 0
202
+ INNER JOIN (
203
+ SELECT e.patient_id
204
+ FROM encounter e
205
+ INNER JOIN orders o ON o.encounter_id = e.encounter_id
206
+ AND o.voided = 0
207
+ AND o.order_type_id = #{::OrderType.find_by_name('Drug order').id}
208
+ AND o.concept_id IN (#{tpt_drugs.to_sql})
209
+ AND DATE(o.start_date) BETWEEN #{first_day_of_month} AND DATE(#{last_day_of_month})
210
+ INNER JOIN drug_order dor ON dor.order_id = o.order_id AND dor.quantity > 0
211
+ WHERE e.encounter_type = 25 /* Treatment */
212
+ AND e.voided = 0
213
+ AND e.program_id = 1 /* HIV PROGRAM */
214
+ AND DATE(e.encounter_datetime) BETWEEN #{first_day_of_month} AND DATE(#{last_day_of_month})
215
+ GROUP BY e.patient_id
216
+ ) clients_on_tpt ON clients_on_tpt.patient_id = e.patient_id
217
+ LEFT JOIN encounter AS clinic_registration_encounter
218
+ ON clinic_registration_encounter.encounter_type = (
219
+ SELECT encounter_type_id FROM encounter_type WHERE name = 'HIV CLINIC REGISTRATION' LIMIT 1
220
+ )
221
+ AND clinic_registration_encounter.patient_id = pp.patient_id
222
+ AND clinic_registration_encounter.program_id = pp.program_id
223
+ AND clinic_registration_encounter.encounter_datetime < DATE('#{@end_date}') + INTERVAL 1 DAY
224
+ AND clinic_registration_encounter.voided = 0
225
+ INNER JOIN orders AS art_order
226
+ ON art_order.patient_id = pp.patient_id
227
+ /* AND art_order.encounter_id = prescription_encounter.encounter_id */
228
+ AND art_order.concept_id IN (SELECT concept_id FROM concept_set WHERE concept_set = 1085)
229
+ AND art_order.start_date < DATE('#{@end_date}') + INTERVAL 1 DAY
230
+ AND art_order.order_type_id IN (SELECT order_type_id FROM order_type WHERE name = 'Drug order')
231
+ AND art_order.start_date >= DATE('1901-01-01')
232
+ AND art_order.voided = 0
233
+ LEFT JOIN obs AS art_start_date_obs
234
+ ON art_start_date_obs.concept_id = 2516
235
+ AND art_start_date_obs.person_id = pp.patient_id
236
+ AND art_start_date_obs.voided = 0
237
+ AND art_start_date_obs.obs_datetime < (DATE('#{@end_date}') + INTERVAL 1 DAY)
238
+ AND art_start_date_obs.encounter_id = clinic_registration_encounter.encounter_id
239
+ LEFT JOIN (#{current_occupation_query}) AS a ON a.person_id = pp.patient_id
240
+ WHERE pp.program_id = 1 /* HIV PROGRAM */
241
+ AND pp.patient_id NOT IN (
242
+ /* External consultations */
243
+ SELECT DISTINCT registration_encounter.patient_id
244
+ FROM patient_program
245
+ INNER JOIN program ON program.name = 'HIV PROGRAM'
246
+ INNER JOIN encounter AS registration_encounter
247
+ ON registration_encounter.patient_id = patient_program.patient_id
248
+ AND registration_encounter.program_id = patient_program.program_id
249
+ AND registration_encounter.encounter_datetime < DATE('#{@end_date}') + INTERVAL 1 DAY
250
+ AND registration_encounter.voided = 0
251
+ INNER JOIN (
252
+ SELECT MAX(encounter.encounter_datetime) AS encounter_datetime, encounter.patient_id
253
+ FROM encounter
254
+ INNER JOIN encounter_type
255
+ ON encounter_type.encounter_type_id = encounter.encounter_type
256
+ AND encounter_type.name = 'Registration'
257
+ INNER JOIN program
258
+ ON program.program_id = encounter.program_id
259
+ AND program.name = 'HIV PROGRAM'
260
+ WHERE encounter.encounter_datetime < DATE('#{@end_date}') AND encounter.voided = 0
261
+ GROUP BY encounter.patient_id
262
+ ) AS max_registration_encounter
263
+ ON max_registration_encounter.patient_id = registration_encounter.patient_id
264
+ AND max_registration_encounter.encounter_datetime = registration_encounter.encounter_datetime
265
+ INNER JOIN obs AS patient_type_obs
266
+ ON patient_type_obs.encounter_id = registration_encounter.encounter_id
267
+ AND patient_type_obs.concept_id IN (SELECT concept_id FROM concept_name WHERE name = 'Type of patient' AND voided = 0)
268
+ AND patient_type_obs.value_coded IN (SELECT concept_id FROM concept_name WHERE name IN ('Drug refill', 'External consultation') AND voided = 0)
269
+ AND patient_type_obs.voided = 0
270
+ WHERE patient_program.voided = 0
271
+ )
272
+ AND pp.voided = 0 #{%w[Military Civilian].include?(@occupation) ? 'AND' : ''} #{occupation_filter(occupation: @occupation, field_name: 'value', table_name: 'a', include_clause: false)}
273
+ AND DATE(o.start_date)<= DATE('#{@end_date}')
274
+ GROUP BY pp.patient_id
275
+ SQL
276
+ end
277
+
278
+ def process_tpt_clients(patients = nil)
279
+ clients = []
280
+ (patients || tpt_clients || []).each do |client|
281
+ result = @tb_prev.fetch_individual_report(client['patient_id'])
282
+ next if result.blank?
283
+ next if result['tpt_initiation_date'].to_date < first_day_of_month.to_date
284
+
285
+ client['start_date'] = result['tpt_initiation_date']
286
+ client['last_dispense_date'] = result['last_dispensed_date']
287
+ client['total_pills_taken'] = result['total_pills_taken']
288
+ client['total_days_on_medication'] = result['total_days_on_medication']
289
+ client['tpt_type'] = @tb_prev.patient_on_3hp?(result) ? '3HP' : '6H'
290
+ client['drug_concepts'] = result['drug_concepts']
291
+
292
+ clients << client
293
+ end
294
+ clients
295
+ end
296
+
297
+ # def tpt_clients
298
+ # ActiveRecord::Base.connection.select_all <<~SQL
299
+ # SELECT
300
+ # p.person_id AS patient_id,
301
+ # DATE(min(o.start_date)) AS start_date,
302
+ # DATE(max(o.start_date)) AS last_dispense_date,
303
+ # patient_outcome(p.person_id, DATE('#{@end_date}')) AS outcome,
304
+ # SUM(d.quantity) AS total_pills_taken,
305
+ # SUM(DATEDIFF(o.auto_expire_date, o.start_date)) AS total_days_on_medication,
306
+ # p.gender,
307
+ # p.birthdate,
308
+ # disaggregated_age_group(p.birthdate, DATE('#{@end_date}')) AS age_group,
309
+ # GROUP_CONCAT(DISTINCT o.concept_id SEPARATOR ',') AS drug_concepts,
310
+ # CASE
311
+ # WHEN count(DISTINCT o.concept_id) > 1 THEN '3HP'
312
+ # WHEN o.concept_id = 10565 THEN '3HP'
313
+ # ELSE '6H'
314
+ # END AS tpt_type
315
+ # FROM person p
316
+ # INNER JOIN encounter e
317
+ # ON e.patient_id = p.person_id
318
+ # AND e.encounter_type = #{::EncounterType.find_by_name('Treatment').id}
319
+ # AND e.voided = 0
320
+ # AND e.program_id = #{::Program.find_by_name('HIV PROGRAM').id}
321
+ # AND e.encounter_datetime >= DATE('#{@start_date}') - INTERVAL 6 MONTH
322
+ # AND e.encounter_datetime <= DATE('#{@start_date}')
323
+ # INNER JOIN orders o
324
+ # ON o.encounter_id = e.encounter_id
325
+ # AND o.concept_id IN (#{tpt_drugs.to_sql})
326
+ # AND o.start_date >= DATE('#{@start_date}') - INTERVAL 6 MONTH
327
+ # AND o.start_date <= DATE('#{@end_date}')
328
+ # AND o.voided = 0
329
+ # AND o.order_type_id = #{::OrderType.find_by_name('Drug order').id}
330
+ # INNER JOIN drug_order d ON d.order_id = o.order_id AND d.quantity > 0
331
+ # WHERE p.voided = 0
332
+ # AND p.person_id NOT IN (
333
+ # SELECT p.person_id
334
+ # FROM person p
335
+ # INNER JOIN encounter e
336
+ # ON e.patient_id = p.person_id
337
+ # AND e.encounter_type = #{::EncounterType.find_by_name('Treatment').id}
338
+ # AND e.voided = 0
339
+ # AND e.program_id = #{::Program.find_by_name('HIV PROGRAM').id}
340
+ # INNER JOIN orders o
341
+ # ON o.patient_id = e.patient_id
342
+ # AND o.concept_id IN (#{tpt_drugs.to_sql})
343
+ # AND o.voided = 0
344
+ # AND o.order_type_id = #{::OrderType.find_by_name('Drug order').id}
345
+ # INNER JOIN drug_order d ON d.order_id = o.order_id AND d.quantity > 0
346
+ # WHERE p.voided = 0
347
+ # AND e.encounter_datetime < '#{@start_date - 6.months}'
348
+ # AND e.encounter_datetime >= '#{@start_date - 15.months}'
349
+ # )
350
+ # AND p.person_id NOT IN (
351
+ # /* External consultations */
352
+ # SELECT DISTINCT registration_encounter.patient_id
353
+ # FROM patient_program
354
+ # INNER JOIN program ON program.name = 'HIV PROGRAM'
355
+ # INNER JOIN encounter AS registration_encounter
356
+ # ON registration_encounter.patient_id = patient_program.patient_id
357
+ # AND registration_encounter.program_id = patient_program.program_id
358
+ # AND registration_encounter.encounter_datetime < DATE('#{@end_date}') + INTERVAL 1 DAY
359
+ # AND registration_encounter.voided = 0
360
+ # INNER JOIN (
361
+ # SELECT MAX(encounter.encounter_datetime) AS encounter_datetime, encounter.patient_id
362
+ # FROM encounter
363
+ # INNER JOIN encounter_type
364
+ # ON encounter_type.encounter_type_id = encounter.encounter_type
365
+ # AND encounter_type.name = 'Registration'
366
+ # INNER JOIN program
367
+ # ON program.program_id = encounter.program_id
368
+ # AND program.name = 'HIV PROGRAM'
369
+ # WHERE encounter.encounter_datetime < DATE('#{@end_date}') AND encounter.voided = 0
370
+ # GROUP BY encounter.patient_id
371
+ # ) AS max_registration_encounter
372
+ # ON max_registration_encounter.patient_id = registration_encounter.patient_id
373
+ # AND max_registration_encounter.encounter_datetime = registration_encounter.encounter_datetime
374
+ # INNER JOIN obs AS patient_type_obs
375
+ # ON patient_type_obs.encounter_id = registration_encounter.encounter_id
376
+ # AND patient_type_obs.concept_id IN (SELECT concept_id FROM concept_name WHERE name = 'Type of patient' AND voided = 0)
377
+ # AND patient_type_obs.value_coded IN (SELECT concept_id FROM concept_name WHERE name IN ('Drug refill', 'External consultation') AND voided = 0)
378
+ # AND patient_type_obs.voided = 0
379
+ # WHERE patient_program.voided = 0
380
+ # )
381
+ # GROUP BY p.person_id
382
+ # SQL
383
+ # end
384
+
385
+ def load_patients_into_report(report, patients)
386
+ patients.each do |patient|
387
+ new_on_art = patient_new_on_art?(patient)
388
+ common_reponse(patient)
389
+ if new_on_art
390
+ report[patient['age_group']][patient[@param]][:started_tpt_new] << @common_response
391
+ else
392
+ report[patient['age_group']][patient[@param]][:started_tpt_prev] << @common_response
393
+ end
394
+
395
+ if patient_completed_tpt?(patient, patient['tpt_type'])
396
+ report[patient['age_group']][patient[@param]][:completed_tpt_new] << @common_response if new_on_art
397
+ report[patient['age_group']][patient[@param]][:completed_tpt_prev] << @common_response unless new_on_art
398
+ else
399
+ report[patient['age_group']][patient[@param]][:not_completed_tpt] << @common_response
400
+ process_outcomes report, patient
401
+ end
402
+ end
403
+ end
404
+
405
+ def load_moh_patients_into_report(report, patients)
406
+ patients.each do |patient|
407
+ common_reponse(patient)
408
+ report[patient['age_group']][patient[@param]][:started_tpt] << @common_response
409
+ if patient_completed_tpt?(patient, patient['tpt_type'])
410
+ report[patient['age_group']][patient[@param]][:completed_tpt] << @common_response
411
+ else
412
+ report[patient['age_group']][patient[@param]][:not_completed_tpt] << @common_response
413
+ process_outcomes report, patient
414
+ end
415
+ end
416
+ end
417
+
418
+ def process_outcomes(report, patient)
419
+ process_patient_conditions report, patient
420
+ return if @condition
421
+
422
+ case patient['outcome']
423
+ when 'Patient died'
424
+ report[patient['age_group']][patient[@param]][:died] << @common_response
425
+ when 'Patient transferred out'
426
+ report[patient['age_group']][patient[@param]][:transfer_out] << @common_response
427
+ when 'Treatment stopped'
428
+ report[patient['age_group']][patient[@param]][:stopped] << @common_response
429
+ when 'Defaulted'
430
+ report[patient['age_group']][patient[@param]][:defaulted] << @common_response
431
+ else
432
+ process_patient_conditions report, patient
433
+ end
434
+ end
435
+
436
+ def process_patient_conditions(report, patient)
437
+ if patient_on_tb_treatment?(patient['patient_id'], patient['last_dispense_date'])
438
+ report[patient['age_group']][patient[@param]][:confirmed_tb] << @common_response
439
+ elsif patient['gender'] == 'F' && patient_pregnant?(patient['patient_id'], patient['last_dispense_date'])
440
+ report[patient['age_group']][patient[@param]][:pregnant] << @common_response
441
+ @condition = true
442
+ return
443
+ elsif patient['gender'] == 'F' && patient_breast_feeding?(patient['patient_id'], patient['last_dispense_date'])
444
+ report[patient['age_group']][patient[@param]][:breast_feeding] << @common_response
445
+ @condition = true
446
+ return
447
+ end
448
+
449
+ process_malawi_art_conditions report, patient
450
+ end
451
+
452
+ def process_malawi_art_conditions(report, patient)
453
+ %i[skin_rash nausea peripheral_neuropathy dizziness yellow_eyes].each do |condition|
454
+ method_name = :"patient_#{condition}?"
455
+ next unless send(method_name, patient['patient_id'], patient['last_dispense_date'])
456
+
457
+ report[patient['age_group']][patient[@param]][condition] << @common_response
458
+ @condition = true
459
+ end
460
+ end
461
+
462
+ def patient_new_on_art?(patient)
463
+ init_date = patient['earliest_start_date'].to_date
464
+ start_date = patient['start_date'].to_date
465
+
466
+ init_date + 6.months > start_date
467
+ end
468
+
469
+ def common_reponse(patient)
470
+ @common_response = if @param == 'tpt_type'
471
+ { patient_id: patient['patient_id'], gender: patient['gender'] }
472
+ else
473
+ patient['patient_id']
474
+ end
475
+ end
476
+
477
+ def tpt_drugs
478
+ ::ConceptName.where(name: ['INH', 'Isoniazid/Rifapentine', 'Rifapentine']).select(:concept_id)
479
+ end
480
+
481
+ def tpt_actual_drugs
482
+ @tpt_actual_drugs ||= ::Drug.where(concept_id: tpt_drugs.map(&:concept_id)).select(:drug_id).to_sql
483
+ end
484
+
485
+ def first_day_of_month
486
+ @first_day_of_month ||= ActiveRecord::Base.connection.quote((@start_date - 6.month).beginning_of_month)
487
+ end
488
+
489
+ def last_day_of_month
490
+ @last_day_of_month ||= ActiveRecord::Base.connection.quote((@start_date - 6.month).end_of_month)
491
+ end
492
+ end
493
+ end
494
+ end
@@ -0,0 +1,169 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This is MOH client returned to care report
4
+ module MalawiHivProgramReports
5
+ module Clinic
6
+ class TxRtt
7
+ include MalawiHivProgramReports::Utils::CommonSqlQueryUtils
8
+
9
+ def initialize(start_date:, end_date:, **kwargs)
10
+ @start_date = ActiveRecord::Base.connection.quote(start_date)
11
+ @end_date = ActiveRecord::Base.connection.quote(end_date)
12
+ @occupation = kwargs[:occupation]
13
+ end
14
+
15
+ def find_report
16
+ tx_rtt.each_with_object({}) do |patient, report|
17
+ age_group = report[patient['age_group']] || { 'M' => [], 'F' => [], 'Unknown' => [] }
18
+ age_group[patient['gender']&.first&.upcase || 'Unknown'] << { patient_id: patient['patient_id'],
19
+ months: patient['months'] }
20
+
21
+ report[patient['age_group']] = age_group
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def tx_rtt
28
+ ActiveRecord::Base.connection.select_all <<~SQL
29
+ SELECT patient_program.patient_id,
30
+ disaggregated_age_group(person.birthdate, #{@end_date}) AS age_group,
31
+ person.gender,
32
+ IF(
33
+ patient_state_at_start_of_quarter.state = 6, 'Treatment stopped',
34
+ IF(
35
+ patient_state_at_start_of_quarter.state = 12, 'Defaulted',
36
+ patient_outcome(patient_program.patient_id, (DATE(#{@start_date}) - INTERVAL 1 DAY) )
37
+ )
38
+ ) AS initial_outcome,
39
+ IF(
40
+ patient_state_at_start_of_quarter.state = 6,
41
+ patient_state_at_start_of_quarter.start_date,
42
+ IF(
43
+ patient_state_at_start_of_quarter.state = 12,
44
+ patient_state_at_start_of_quarter.start_date,
45
+ current_defaulter_date(patient_program.patient_id, (DATE(#{@start_date}) - INTERVAL 1 DAY) ))) AS initial_outcome_date,
46
+ IF(
47
+ patients_with_orders_at_end_of_quarter.patient_id IS NOT NULL, 'On antiretrovirals',
48
+ IF(
49
+ current_defaulter(patient_program.patient_id, #{@end_date}) = 0,
50
+ 'On antiretrovirals','Defaulted'
51
+ )
52
+ ) AS final_outcome,
53
+ TIMESTAMPDIFF(MONTH, IF(
54
+ patient_state_at_start_of_quarter.state = 6,
55
+ patient_state_at_start_of_quarter.start_date,
56
+ IF(
57
+ patient_state_at_start_of_quarter.state = 12,
58
+ patient_state_at_start_of_quarter.start_date,
59
+ current_defaulter_date(
60
+ patient_program.patient_id,
61
+ (DATE(#{@start_date}) - INTERVAL 1 DAY)
62
+ )
63
+ )
64
+ ), patients_who_received_art_in_quarter.start_date) AS months
65
+ FROM patient_program
66
+ INNER JOIN person ON person.person_id = patient_program.patient_id
67
+ /* Select patients that were on treatment before start of reporting period */
68
+ INNER JOIN patient_state AS patient_ever_on_treatment
69
+ ON patient_ever_on_treatment.patient_program_id = patient_program.patient_program_id
70
+ AND patient_ever_on_treatment.state = 7
71
+ AND patient_ever_on_treatment.start_date < DATE(#{@start_date})
72
+ AND patient_ever_on_treatment.voided = 0
73
+ /* Get patient's state at the start of the quarter. */
74
+ INNER JOIN (
75
+ SELECT patient_program_id, MAX(patient_state.date_created) AS date_created
76
+ FROM patient_state
77
+ INNER JOIN patient_program USING (patient_program_id)
78
+ WHERE patient_state.voided = 0
79
+ AND patient_program.voided = 0
80
+ AND patient_program.program_id = 1
81
+ AND patient_state.start_date < DATE(#{@start_date}) + INTERVAL 1 DAY
82
+ GROUP BY patient_program_id
83
+ ) AS date_of_last_patient_state_before_quarter
84
+ ON date_of_last_patient_state_before_quarter.patient_program_id = patient_program.patient_program_id
85
+ LEFT JOIN patient_state AS patient_state_at_start_of_quarter
86
+ ON patient_state_at_start_of_quarter.patient_program_id = date_of_last_patient_state_before_quarter.patient_program_id
87
+ AND patient_state_at_start_of_quarter.date_created = date_of_last_patient_state_before_quarter.date_created
88
+ AND patient_state_at_start_of_quarter.state IN (6, 12) /* 2: TO, 6: Tx Stopped, 12: Defaulted */
89
+ /* Select patients who received ART within the reporting period. */
90
+ INNER JOIN (
91
+ SELECT DISTINCT encounter.patient_id, orders.start_date
92
+ FROM encounter
93
+ INNER JOIN orders
94
+ ON orders.encounter_id = encounter.encounter_id
95
+ AND DATE(orders.start_date) BETWEEN DATE(#{@start_date}) AND DATE(#{@end_date})
96
+ AND orders.voided = 0
97
+ INNER JOIN drug_order
98
+ ON drug_order.order_id = orders.order_id
99
+ AND drug_order.quantity > 0
100
+ AND drug_order.drug_inventory_id IN (SELECT DISTINCT drug_id FROM arv_drug)
101
+ WHERE encounter.voided = 0
102
+ AND encounter.program_id = 1
103
+ AND DATE(encounter.encounter_datetime) BETWEEN DATE(#{@start_date}) AND DATE(#{@end_date})
104
+ ) AS patients_who_received_art_in_quarter
105
+ ON patients_who_received_art_in_quarter.patient_id = patient_program.patient_id
106
+ /* Ensure that patients are on ART at the end of the quarter */
107
+ INNER JOIN (
108
+ SELECT patient_program_id, MAX(patient_state.date_created) AS date_created
109
+ FROM patient_state
110
+ INNER JOIN patient_program USING (patient_program_id)
111
+ WHERE patient_state.voided = 0
112
+ AND patient_program.voided = 0
113
+ AND patient_program.program_id = 1
114
+ AND patient_state.start_date < DATE(#{@end_date}) + INTERVAL 1 DAY
115
+ GROUP BY patient_program_id
116
+ ) AS date_of_last_patient_state_in_quarter
117
+ ON date_of_last_patient_state_in_quarter.patient_program_id = patient_program.patient_program_id
118
+
119
+ /*Not sure why Walter had this section in but I believe its not neccessary*/
120
+ /*INNER JOIN patient_state AS patient_state_at_end_of_quarter
121
+ ON patient_state_at_end_of_quarter.patient_program_id = patient_program.patient_program_id
122
+ AND patient_state_at_end_of_quarter.date_created = date_of_last_patient_state_before_quarter.date_created
123
+ AND patient_state_at_end_of_quarter.state = 7*/
124
+
125
+ /* Select patient who had orders in the last 30 days of the reporting period.
126
+ This is to be used as a quick filter for patients who are definitely
127
+ 'On ART' by the end of the reporting period. The rest will be filtered by
128
+ the current_defaulter function. */
129
+ LEFT JOIN (
130
+ SELECT DISTINCT encounter.patient_id
131
+ FROM encounter
132
+ INNER JOIN orders
133
+ ON orders.encounter_id = encounter.encounter_id
134
+ AND orders.voided = 0
135
+ AND DATE(orders.start_date) BETWEEN DATE(#{@start_date}) AND DATE(#{@end_date})
136
+ AND orders.auto_expire_date >= (DATE(#{@end_date}) - INTERVAL 60 DAY)
137
+ INNER JOIN drug_order
138
+ ON drug_order.order_id = orders.order_id
139
+ AND drug_order.quantity > 0
140
+ AND drug_order.drug_inventory_id IN (SELECT DISTINCT drug_id FROM arv_drug)
141
+ WHERE encounter.program_id = 1
142
+ AND DATE(encounter.encounter_datetime) BETWEEN DATE(#{@start_date}) AND DATE(#{@end_date})
143
+ AND encounter.voided = 0
144
+ ) AS patients_with_orders_at_end_of_quarter
145
+ ON patients_with_orders_at_end_of_quarter.patient_id = patient_program.patient_id
146
+ LEFT JOIN (#{current_occupation_query}) AS a ON a.person_id = patient_program.patient_id
147
+ WHERE patient_program.program_id = 1
148
+ /* Ensure that the patients retrieved, did not receive ART within 28 days
149
+ before the start of the reporting period */
150
+ AND patient_program.patient_id NOT IN (
151
+ SELECT DISTINCT orders.patient_id
152
+ FROM orders
153
+ INNER JOIN drug_order USING (order_id)
154
+ INNER JOIN arv_drug ON arv_drug.drug_id = drug_inventory_id
155
+ INNER JOIN patient_program
156
+ ON patient_program.patient_id = orders.patient_id
157
+ AND patient_program.program_id = 1
158
+ WHERE ((DATE(orders.start_date) BETWEEN (DATE(#{@start_date}) - INTERVAL 60 DAY) AND DATE(#{@start_date}))
159
+ OR (DATE(orders.auto_expire_date )BETWEEN (DATE(#{@start_date}) - INTERVAL 60 DAY) AND DATE(#{@start_date})))
160
+ AND orders.voided = 0
161
+ ) #{%w[Military Civilian].include?(@occupation) ? 'AND' : ''} #{occupation_filter(occupation: @occupation, field_name: 'value', table_name: 'a', include_clause: false)}
162
+ GROUP BY patient_program.patient_id
163
+ HAVING initial_outcome IN ('Defaulted', 'Treatment stopped')
164
+ AND final_outcome = 'On antiretrovirals';
165
+ SQL
166
+ end
167
+ end
168
+ end
169
+ end